forked from MapComplete/MapComplete
		
	Merge branch 'develop'
This commit is contained in:
		
						commit
						d68486ae73
					
				
					 6 changed files with 127 additions and 36 deletions
				
			
		| 
						 | 
					@ -15,7 +15,7 @@ General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_nam
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Instead of using `{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}, one can also write
 | 
					Instead of using `{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}` , one can also write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`{"render":{"special":{"type":"some_special_visualisation","argname":"some_arg","message":{"en":"some other really long message","nl":"een boodschap in een andere taal"},"other_arg_name":"more args"}}}`
 | 
					`{"render":{"special":{"type":"some_special_visualisation","argname":"some_arg","message":{"en":"some other really long message","nl":"een boodschap in een andere taal"},"other_arg_name":"more args"}}}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -387,10 +387,6 @@ export default class MapState extends UserRelatedState {
 | 
				
			||||||
                isDisplayed = QueryParameters.GetBooleanQueryParameter("layer-" + layer.id, layer.shownByDefault, "Wether or not layer " + layer.id + " is shown")
 | 
					                isDisplayed = QueryParameters.GetBooleanQueryParameter("layer-" + layer.id, layer.shownByDefault, "Wether or not layer " + layer.id + " is shown")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            isDisplayed.addCallbackAndRun(_ => {
 | 
					 | 
				
			||||||
            console.log("IsDisplayed?",layer.id, isDisplayed.data, layer.shownByDefault)
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const flayer: FilteredLayer = {
 | 
					            const flayer: FilteredLayer = {
 | 
				
			||||||
                isDisplayed,
 | 
					                isDisplayed,
 | 
				
			||||||
                layerDef: layer,
 | 
					                layerDef: layer,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,11 +133,11 @@ class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | {
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        if (layer === undefined) {
 | 
					                        if (layer === undefined) {
 | 
				
			||||||
                            const candidates = Utils.sortedByLevenshteinDistance(layerName, Array.from(state.sharedLayers.keys()), s => s)
 | 
					                            const candidates = Utils.sortedByLevenshteinDistance(layerName, Array.from(state.sharedLayers.keys()), s => s)
 | 
				
			||||||
                           if(state.sharedLayers.size === 0){
 | 
					                            if (state.sharedLayers.size === 0) {
 | 
				
			||||||
                               warnings.push(ctx + ": BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " + name + ": layer " + layerName + " not found. Maybe you meant on of " + candidates.slice(0, 3).join(", "))
 | 
					                                warnings.push(ctx + ": BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " + name + ": layer " + layerName + " not found. Maybe you meant on of " + candidates.slice(0, 3).join(", "))
 | 
				
			||||||
                           }else{
 | 
					                            } else {
 | 
				
			||||||
                               errors.push(ctx + ": While reusing tagrendering: " + name + ": layer " + layerName + " not found. Maybe you meant on of " + candidates.slice(0, 3).join(", "))
 | 
					                                errors.push(ctx + ": While reusing tagrendering: " + name + ": layer " + layerName + " not found. Maybe you meant on of " + candidates.slice(0, 3).join(", "))
 | 
				
			||||||
                           }
 | 
					                            }
 | 
				
			||||||
                            continue
 | 
					                            continue
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        candidates = Utils.NoNull(layer.tagRenderings.map(tr => tr["id"])).map(id => layerName + "." + id)
 | 
					                        candidates = Utils.NoNull(layer.tagRenderings.map(tr => tr["id"])).map(id => layerName + "." + id)
 | 
				
			||||||
| 
						 | 
					@ -458,14 +458,24 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
 | 
				
			||||||
            for (const argName of argNamesList) {
 | 
					            for (const argName of argNamesList) {
 | 
				
			||||||
                const v = special[argName] ?? ""
 | 
					                const v = special[argName] ?? ""
 | 
				
			||||||
                if (Translations.isProbablyATranslation(v)) {
 | 
					                if (Translations.isProbablyATranslation(v)) {
 | 
				
			||||||
                    args.push(new Translation(v).textFor(ln))
 | 
					                    const txt = new Translation(v).textFor(ln)
 | 
				
			||||||
 | 
					                        .replace(/,/g, "&COMMA")
 | 
				
			||||||
 | 
					                        .replace(/\{/g, "&LBRACE")
 | 
				
			||||||
 | 
					                        .replace(/}/g, "&RBRACE")
 | 
				
			||||||
 | 
					                    ;
 | 
				
			||||||
 | 
					                    args.push(txt)
 | 
				
			||||||
 | 
					                } else if (typeof v === "string") {
 | 
				
			||||||
 | 
					                    const txt = v.replace(/,/g, "&COMMA")
 | 
				
			||||||
 | 
					                        .replace(/\{/g, "&LBRACE")
 | 
				
			||||||
 | 
					                        .replace(/}/g, "&RBRACE")
 | 
				
			||||||
 | 
					                    args.push(txt)
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    args.push(v)
 | 
					                    args.push(v)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const beforeText = before?.textFor(ln) ?? ""
 | 
					            const beforeText = before?.textFor(ln) ?? ""
 | 
				
			||||||
            const afterText = after?.textFor(ln) ?? ""
 | 
					            const afterText = after?.textFor(ln) ?? ""
 | 
				
			||||||
            result[ln] = `${beforeText}{${type}(${args.join(",")})}${afterText}`
 | 
					            result[ln] = `${beforeText}{${type}(${args.map(a => a).join(",")})}${afterText}`
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return result
 | 
					        return result
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,7 +59,8 @@ import {CheckBox} from "./Input/Checkboxes";
 | 
				
			||||||
import Slider from "./Input/Slider";
 | 
					import Slider from "./Input/Slider";
 | 
				
			||||||
import List from "./Base/List";
 | 
					import List from "./Base/List";
 | 
				
			||||||
import StatisticsPanel from "./BigComponents/StatisticsPanel";
 | 
					import StatisticsPanel from "./BigComponents/StatisticsPanel";
 | 
				
			||||||
import { OsmFeature } from "../Models/OsmFeature";
 | 
					import {OsmFeature} from "../Models/OsmFeature";
 | 
				
			||||||
 | 
					import Link from "./Base/Link";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface SpecialVisualization {
 | 
					export interface SpecialVisualization {
 | 
				
			||||||
    funcName: string,
 | 
					    funcName: string,
 | 
				
			||||||
| 
						 | 
					@ -292,7 +293,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
        if (typeof viz === "string") {
 | 
					        if (typeof viz === "string") {
 | 
				
			||||||
            viz = SpecialVisualizations.specialVisualizations.find(sv => sv.funcName === viz)
 | 
					            viz = SpecialVisualizations.specialVisualizations.find(sv => sv.funcName === viz)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if(viz === undefined){
 | 
					        if (viz === undefined) {
 | 
				
			||||||
            return undefined;
 | 
					            return undefined;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return new Combine(
 | 
					        return new Combine(
 | 
				
			||||||
| 
						 | 
					@ -328,7 +329,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                    "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
 | 
					                    "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
 | 
				
			||||||
                    "General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args",
 | 
					                    "General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args",
 | 
				
			||||||
                    new Title("Using expanded syntax", 4),
 | 
					                    new Title("Using expanded syntax", 4),
 | 
				
			||||||
                    `Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}, one can also write`,
 | 
					                    `Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}\`, one can also write`,
 | 
				
			||||||
                    new FixedUiElement(JSON.stringify({
 | 
					                    new FixedUiElement(JSON.stringify({
 | 
				
			||||||
                        render: {
 | 
					                        render: {
 | 
				
			||||||
                            special: {
 | 
					                            special: {
 | 
				
			||||||
| 
						 | 
					@ -341,7 +342,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                                "other_arg_name": "more args"
 | 
					                                "other_arg_name": "more args"
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    })).SetClass("code")
 | 
					                    }, null, "  ")).SetClass("code")
 | 
				
			||||||
                ]).SetClass("flex flex-col"),
 | 
					                ]).SetClass("flex flex-col"),
 | 
				
			||||||
                ...helpTexts
 | 
					                ...helpTexts
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
| 
						 | 
					@ -1106,9 +1107,9 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                    args: [],
 | 
					                    args: [],
 | 
				
			||||||
                    constr(state, tagSource, argument, guistate) {
 | 
					                    constr(state, tagSource, argument, guistate) {
 | 
				
			||||||
                        let parentId = tagSource.data.mr_challengeId;
 | 
					                        let parentId = tagSource.data.mr_challengeId;
 | 
				
			||||||
                        let challenge = Stores.FromPromise(Utils.downloadJsonCached(`https://maproulette.org/api/v2/challenge/${parentId}`,24*60*60*1000));
 | 
					                        let challenge = Stores.FromPromise(Utils.downloadJsonCached(`https://maproulette.org/api/v2/challenge/${parentId}`, 24 * 60 * 60 * 1000));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        let details = new VariableUiElement( challenge.map(challenge => {
 | 
					                        let details = new VariableUiElement(challenge.map(challenge => {
 | 
				
			||||||
                            let listItems: BaseUIElement[] = [];
 | 
					                            let listItems: BaseUIElement[] = [];
 | 
				
			||||||
                            let title: BaseUIElement;
 | 
					                            let title: BaseUIElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1124,7 +1125,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                                listItems.push(new FixedUiElement(challenge.instruction));
 | 
					                                listItems.push(new FixedUiElement(challenge.instruction));
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if(listItems.length === 0) {
 | 
					                            if (listItems.length === 0) {
 | 
				
			||||||
                                return undefined;
 | 
					                                return undefined;
 | 
				
			||||||
                            } else {
 | 
					                            } else {
 | 
				
			||||||
                                return [title, new List(listItems)];
 | 
					                                return [title, new List(listItems)];
 | 
				
			||||||
| 
						 | 
					@ -1138,14 +1139,15 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                    funcName: "statistics",
 | 
					                    funcName: "statistics",
 | 
				
			||||||
                    docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer",
 | 
					                    docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer",
 | 
				
			||||||
                    args: [],
 | 
					                    args: [],
 | 
				
			||||||
                    constr :  (state, tagsSource, args, guiState) => {
 | 
					                    constr: (state, tagsSource, args, guiState) => {
 | 
				
			||||||
                        const elementsInview = new UIEventSource<{ distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]>([]);
 | 
					                        const elementsInview = new UIEventSource<{ distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        function update() {
 | 
					                        function update() {
 | 
				
			||||||
                            const mapCenter = <[number,number]> [state.locationControl.data.lon, state.locationControl.data.lon]
 | 
					                            const mapCenter = <[number, number]>[state.locationControl.data.lon, state.locationControl.data.lon]
 | 
				
			||||||
                            const bbox = state.currentBounds.data
 | 
					                            const bbox = state.currentBounds.data
 | 
				
			||||||
                            const elements = state.featurePipeline.getAllVisibleElementsWithmeta(bbox).map(el => {
 | 
					                            const elements = state.featurePipeline.getAllVisibleElementsWithmeta(bbox).map(el => {
 | 
				
			||||||
                                const distance = GeoOperations.distanceBetween(el.center, mapCenter)
 | 
					                                const distance = GeoOperations.distanceBetween(el.center, mapCenter)
 | 
				
			||||||
                                return {...el, distance }
 | 
					                                return {...el, distance}
 | 
				
			||||||
                            })
 | 
					                            })
 | 
				
			||||||
                            elements.sort((e0, e1) => e0.distance - e1.distance)
 | 
					                            elements.sort((e0, e1) => e0.distance - e1.distance)
 | 
				
			||||||
                            elementsInview.setData(elements)
 | 
					                            elementsInview.setData(elements)
 | 
				
			||||||
| 
						 | 
					@ -1162,6 +1164,44 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                        return new StatisticsPanel(elementsInview, state)
 | 
					                        return new StatisticsPanel(elementsInview, state)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    funcName: "send_email",
 | 
				
			||||||
 | 
					                    docs: "Creates a `mailto`-link where some fields are already set and correctly escaped. The user will be promted to send the email",
 | 
				
			||||||
 | 
					                    args: [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            name: "to",
 | 
				
			||||||
 | 
					                            doc: "Who to send the email to?",
 | 
				
			||||||
 | 
					                            required: true
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            name: "subject",
 | 
				
			||||||
 | 
					                            doc: "The subject of the email",
 | 
				
			||||||
 | 
					                            required: true
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            name: "body",
 | 
				
			||||||
 | 
					                            doc: "The text in the email",
 | 
				
			||||||
 | 
					                            required: true
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            name: "button_text",
 | 
				
			||||||
 | 
					                            doc: "The text shown on the button in the UI",
 | 
				
			||||||
 | 
					                            required: true
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    constr(state, tags, args) {
 | 
				
			||||||
 | 
					                        return new VariableUiElement(tags.map(tags => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            const [to, subject, body, button_text] = args.map(str => Utils.SubstituteKeys(str, tags))
 | 
				
			||||||
 | 
					                            const url = "mailto:" + to + "?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body)
 | 
				
			||||||
 | 
					                            return new SubtleButton(Svg.envelope_svg(), button_text, {
 | 
				
			||||||
 | 
					                                url
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        }))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,11 @@ export class SubstitutedTranslation extends VariableUiElement {
 | 
				
			||||||
                            return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
 | 
					                            return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        const viz = proto.special;
 | 
					                        const viz = proto.special;
 | 
				
			||||||
 | 
					                        if(viz === undefined){
 | 
				
			||||||
 | 
					                            console.error("SPECIALRENDERING UNDEFINED for", tagsSource.data?.id, "THIS IS REALLY WEIRD")
 | 
				
			||||||
 | 
					                            return undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                        try {
 | 
					                        try {
 | 
				
			||||||
                            return viz.func.constr(state, tagsSource, proto.special.args, DefaultGuiState.state)?.SetStyle(proto.special.style);
 | 
					                            return viz.func.constr(state, tagsSource, proto.special.args, DefaultGuiState.state)?.SetStyle(proto.special.style);
 | 
				
			||||||
                        } catch (e) {
 | 
					                        } catch (e) {
 | 
				
			||||||
| 
						 | 
					@ -73,6 +78,17 @@ export class SubstitutedTranslation extends VariableUiElement {
 | 
				
			||||||
        this.SetClass("w-full")
 | 
					        this.SetClass("w-full")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * // Return empty list on empty input
 | 
				
			||||||
 | 
					     * SubstitutedTranslation.ExtractSpecialComponents("") // => ""
 | 
				
			||||||
 | 
					     *  
 | 
				
			||||||
 | 
					     * // Advanced cases with commas, braces and newlines should be handled without problem
 | 
				
			||||||
 | 
					     * const templates = SubstitutedTranslation.ExtractSpecialComponents("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}")
 | 
				
			||||||
 | 
					     * const templ = templates[0]
 | 
				
			||||||
 | 
					     * templ.special.func.funcName // => "send_email"
 | 
				
			||||||
 | 
					     * templ.special.args[0] = "{email}"
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): {
 | 
					    public static ExtractSpecialComponents(template: string, extraMappings: SpecialVisualization[] = []): {
 | 
				
			||||||
        fixed?: string,
 | 
					        fixed?: string,
 | 
				
			||||||
        special?: {
 | 
					        special?: {
 | 
				
			||||||
| 
						 | 
					@ -82,10 +98,14 @@ export class SubstitutedTranslation extends VariableUiElement {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }[] {
 | 
					    }[] {
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        if(template === ""){
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const knownSpecial of extraMappings.concat(SpecialVisualizations.specialVisualizations)) {
 | 
					        for (const knownSpecial of extraMappings.concat(SpecialVisualizations.specialVisualizations)) {
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | 
					            // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | 
				
			||||||
            const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`);
 | 
					            const matched = template.match(new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s"));
 | 
				
			||||||
            if (matched != null) {
 | 
					            if (matched != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // We found a special component that should be brought to live
 | 
					                // We found a special component that should be brought to live
 | 
				
			||||||
| 
						 | 
					@ -97,7 +117,10 @@ export class SubstitutedTranslation extends VariableUiElement {
 | 
				
			||||||
                if (argument.length > 0) {
 | 
					                if (argument.length > 0) {
 | 
				
			||||||
                    const realArgs = argument.split(",").map(str => str.trim()
 | 
					                    const realArgs = argument.split(",").map(str => str.trim()
 | 
				
			||||||
                        .replace(/&LPARENS/g, '(')
 | 
					                        .replace(/&LPARENS/g, '(')
 | 
				
			||||||
                        .replace(/&RPARENS/g, ')'));
 | 
					                        .replace(/&RPARENS/g, ')')
 | 
				
			||||||
 | 
					                        .replace(/&LBRACE/g, '{')
 | 
				
			||||||
 | 
					                        .replace(/&RBRACE/g, '}')
 | 
				
			||||||
 | 
					                        .replace(/&COMMA/g, ','));
 | 
				
			||||||
                    for (let i = 0; i < realArgs.length; i++) {
 | 
					                    for (let i = 0; i < realArgs.length; i++) {
 | 
				
			||||||
                        if (args.length <= i) {
 | 
					                        if (args.length <= i) {
 | 
				
			||||||
                            args.push(realArgs[i]);
 | 
					                            args.push(realArgs[i]);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -557,14 +557,36 @@
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "render": {
 | 
					      "render": {
 | 
				
			||||||
        "en": "<a href='mailto:{email}?subject=Broken bicycle pump&body=Hello,%0D%0A%0D%0AWith this email, I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat={_lat}%26lon={_lon}%26z=18%23{id} is broken.%0D%0A%0D%0A Kind regards'>Report this bicycle pump as broken</a>",
 | 
					        "special": {
 | 
				
			||||||
        "nl": "<a href='mailto:{email}?subject=Kapotte fietspomp&body=Geachte,%0D%0A%0D%0AGraag had ik u gemeld dat een fietspomp defect is. De fietspomp bevindt zich hier: https://mapcomplete.osm.be/cyclofix?lat={_lat}%26lon={_lon}%26z=18%23{id}.%0D%0A%0D%0AMet vriendelijke groeten.'>Rapporteer deze fietspomp als kapot</a>",
 | 
					          "type": "send_email",
 | 
				
			||||||
        "de": "<a href='mailto:{email}?subject=Fahrradpumpe kaputt&body=Hallo,%0D%0A%0D%0AMit dieser E-Mail möchte ich Ihnen mitteilen, dass die Fahrradpumpe, die sich unter https://mapcomplete.osm.be/cyclofix?lat={_lat}%26lon={_lon}%26z=18%23{id} befindet, kaputt ist.'>Melde diese Fahrradpumpe als kaputt</a>",
 | 
					          "to": "{email}",
 | 
				
			||||||
        "da": "<a href='mailto:{email}?subject=Cykelpumpe i stykker&body=Hej,%0D%0A%0D%0D%0Med denne e-mail vil jeg gerne oplyse, at cykelpumpen, der befinder sig på https://mapcomplete.osm.be/cyclofix?lat={_lat}%26lon={_lon}%26z=18%23{id} er i stykker.%0D%0A%0D%0D%0A Med venlig hilsen'>Anmeld denne cykelpumpe som værende i stykker</a>",
 | 
					          "subject": {
 | 
				
			||||||
        "es": "<a href='mailto:{email}?subject=Bomba para bicicletas rota&body=Hola,%0D%0A%0D%0ACon este correo, me gustaría informar de que esta bomba para bicicletas situada en https://mapcomplete.osm.be/cyclofix?lat={_lat}%26lon={_lon}%26z=18%23{id} está rota.%0D%0A%0D%0AUn saludo'>Reportar esta bomba para bicicletas como rota</a>",
 | 
					            "en": "Broken bicycle pump",
 | 
				
			||||||
        "fr": "<a href='mailto:{email}?subject=Pompe à vélo cassée&body=Bonjour,%0D%0A%0D%0ACe mail pour vous informer que la pompe à vélo située à https://mapcomplete.osm.be/cyclofix?lat={_lat}%26lon={_lon}%26z=18%23{id} est cassée.%0D%0A%0D%0ABien à vous.'>Signaler cette pompe à vélo cassée</a>"
 | 
					            "nl": "Kapotte fietspomp",
 | 
				
			||||||
 | 
					            "de": "Fahrradpumpe kaputt",
 | 
				
			||||||
 | 
					            "es": "Bomba para bicicletas rota",
 | 
				
			||||||
 | 
					            "fr": "Pompe à vélo cassée",
 | 
				
			||||||
 | 
					            "da": "Cykelpumpe i stykker"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "body": {
 | 
				
			||||||
 | 
					            "en": "Hello,\n\nWith this email, I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat={_lat}&lon={_lon}&z=18#{id} is broken.\n\n Kind regards",
 | 
				
			||||||
 | 
					            "nl": "Geachte\n\nGraag had ik u gemeld dat een fietspomp defect is. De fietspomp bevindt zich hier: https://mapcomplete.osm.be/cyclofix?lat={_lat}&lon={_lon}&z=18#{id}.\n\nMet vriendelijke groeten.",
 | 
				
			||||||
 | 
					            "de": "Hallo,\n\nMit dieser E-Mail möchte ich Ihnen mitteilen, dass die Fahrradpumpe, die sich unter https://mapcomplete.osm.be/cyclofix?lat={_lat}&lon={_lon}&z=18#{id} befindet, kaputt ist.",
 | 
				
			||||||
 | 
					            "da": "Hej,\n\nMed denne e-mail vil jeg gerne oplyse, at cykelpumpen, der befinder sig på https://mapcomplete.osm.be/cyclofix?lat={_lat}&lon={_lon}&z=18#{id} er i stykker.\n\n Med venlig hilse",
 | 
				
			||||||
 | 
					            "es": "Hola,\n\nCon este correo, me gustaría informar de que esta bomba para bicicletas situada en https://mapcomplete.osm.be/cyclofix?lat={_lat}&lon={_lon}&z=18#{id} está rota.\n\nUn saludo",
 | 
				
			||||||
 | 
					            "fr": "Bonjour,\n\nCe mail pour vous informer que la pompe à vélo située à https://mapcomplete.osm.be/cyclofix?lat={_lat}&lon={_lon}&z=18#{id} est cassée.\n\nBien à vous."
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "button_text": {
 | 
				
			||||||
 | 
					            "en": "Report this bicycle pump as broken",
 | 
				
			||||||
 | 
					            "nl": "Rapporteer deze fietspomp als kapot",
 | 
				
			||||||
 | 
					            "fr": "Signaler cette pompe à vélo cassée",
 | 
				
			||||||
 | 
					            "de": "Melde diese Fahrradpumpe als kaputt",
 | 
				
			||||||
 | 
					            "da": "Anmeld denne cykelpumpe som værende i stykker",
 | 
				
			||||||
 | 
					            "es": "Reportar esta bomba para bicicletas como rota"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "id": "Email maintainer"
 | 
					      "id": "send_email_about_broken_pump"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "question": {
 | 
					      "question": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue