forked from MapComplete/MapComplete
		
	Fix: substituteKeys works correctly if newline is in the text, fix 'send email to report broken'-button by porting it to svelte
This commit is contained in:
		
							parent
							
								
									2bbf966d22
								
							
						
					
					
						commit
						b4f65bf2f7
					
				
					 3 changed files with 137 additions and 95 deletions
				
			
		
							
								
								
									
										31
									
								
								src/UI/Popup/SendEmail.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/UI/Popup/SendEmail.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| <script lang="ts"> | ||||
|    | ||||
|   import type { OsmTags } from "../../Models/OsmFeature"; | ||||
|   import Svg from "../../Svg"; | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte"; | ||||
|   import { Utils } from "../../Utils"; | ||||
| 
 | ||||
|   export let tags: Store<OsmTags> | ||||
|   export let args: string[] | ||||
| 
 | ||||
|   let [to, subject, body, button_text] = args.map(a => Utils.SubstituteKeys(a, $tags)) | ||||
|   let url = "mailto:" + | ||||
|     to + | ||||
|     "?subject=" + | ||||
|     encodeURIComponent(subject) + | ||||
|     "&body=" + | ||||
|     encodeURIComponent(body) | ||||
|   $: console.log(url) | ||||
|   console.log(">>> args", args) | ||||
| </script> | ||||
| <a class="button flex items-center w-full" href={url}> | ||||
|   <ToSvelte construct={Svg.envelope_svg().SetClass("w-8 h-8 mr-4 shrink-0")}/> | ||||
|   {button_text} | ||||
| </a> | ||||
| SEND EMAIL to {to}  | ||||
| <br/> | ||||
| subject: {subject} | ||||
| <br/> | ||||
| body: {body} | ||||
| <br/> | ||||
| {button_text} | ||||
|  | @ -1,52 +1,56 @@ | |||
| import Combine from "./Base/Combine" | ||||
| import {FixedUiElement} from "./Base/FixedUiElement" | ||||
| import { FixedUiElement } from "./Base/FixedUiElement" | ||||
| import BaseUIElement from "./BaseUIElement" | ||||
| import Title from "./Base/Title" | ||||
| import Table from "./Base/Table" | ||||
| import {RenderingSpecification, SpecialVisualization, SpecialVisualizationState,} from "./SpecialVisualization" | ||||
| import {HistogramViz} from "./Popup/HistogramViz" | ||||
| import {MinimapViz} from "./Popup/MinimapViz" | ||||
| import {ShareLinkViz} from "./Popup/ShareLinkViz" | ||||
| import {UploadToOsmViz} from "./Popup/UploadToOsmViz" | ||||
| import {MultiApplyViz} from "./Popup/MultiApplyViz" | ||||
| import {AddNoteCommentViz} from "./Popup/AddNoteCommentViz" | ||||
| import {PlantNetDetectionViz} from "./Popup/PlantNetDetectionViz" | ||||
| import { | ||||
|     RenderingSpecification, | ||||
|     SpecialVisualization, | ||||
|     SpecialVisualizationState, | ||||
| } from "./SpecialVisualization" | ||||
| import { HistogramViz } from "./Popup/HistogramViz" | ||||
| import { MinimapViz } from "./Popup/MinimapViz" | ||||
| import { ShareLinkViz } from "./Popup/ShareLinkViz" | ||||
| import { UploadToOsmViz } from "./Popup/UploadToOsmViz" | ||||
| import { MultiApplyViz } from "./Popup/MultiApplyViz" | ||||
| import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" | ||||
| import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" | ||||
| import TagApplyButton from "./Popup/TagApplyButton" | ||||
| import {CloseNoteButton} from "./Popup/CloseNoteButton" | ||||
| import {MapillaryLinkVis} from "./Popup/MapillaryLinkVis" | ||||
| import {Store, Stores, UIEventSource} from "../Logic/UIEventSource" | ||||
| import { CloseNoteButton } from "./Popup/CloseNoteButton" | ||||
| import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" | ||||
| import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" | ||||
| import AllTagsPanel from "./Popup/AllTagsPanel.svelte" | ||||
| import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" | ||||
| import {ImageCarousel} from "./Image/ImageCarousel" | ||||
| import {ImageUploadFlow} from "./Image/ImageUploadFlow" | ||||
| import {VariableUiElement} from "./Base/VariableUIElement" | ||||
| import {Utils} from "../Utils" | ||||
| import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata" | ||||
| import {Translation} from "./i18n/Translation" | ||||
| import { ImageCarousel } from "./Image/ImageCarousel" | ||||
| import { ImageUploadFlow } from "./Image/ImageUploadFlow" | ||||
| import { VariableUiElement } from "./Base/VariableUIElement" | ||||
| import { Utils } from "../Utils" | ||||
| import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" | ||||
| import { Translation } from "./i18n/Translation" | ||||
| import Translations from "./i18n/Translations" | ||||
| import ReviewForm from "./Reviews/ReviewForm" | ||||
| import ReviewElement from "./Reviews/ReviewElement" | ||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | ||||
| import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" | ||||
| import {SubtleButton} from "./Base/SubtleButton" | ||||
| import { SubtleButton } from "./Base/SubtleButton" | ||||
| import Svg from "../Svg" | ||||
| import NoteCommentElement from "./Popup/NoteCommentElement" | ||||
| import ImgurUploader from "../Logic/ImageProviders/ImgurUploader" | ||||
| import FileSelectorButton from "./Input/FileSelectorButton" | ||||
| import {LoginToggle} from "./Popup/LoginButton" | ||||
| import { LoginToggle } from "./Popup/LoginButton" | ||||
| import Toggle from "./Input/Toggle" | ||||
| import {SubstitutedTranslation} from "./SubstitutedTranslation" | ||||
| import { SubstitutedTranslation } from "./SubstitutedTranslation" | ||||
| import List from "./Base/List" | ||||
| import StatisticsPanel from "./BigComponents/StatisticsPanel" | ||||
| import AutoApplyButton from "./Popup/AutoApplyButton" | ||||
| import {LanguageElement} from "./Popup/LanguageElement" | ||||
| import { LanguageElement } from "./Popup/LanguageElement" | ||||
| import FeatureReviews from "../Logic/Web/MangroveReviews" | ||||
| import Maproulette from "../Logic/Maproulette" | ||||
| import SvelteUIElement from "./Base/SvelteUIElement" | ||||
| import {BBoxFeatureSourceForLayer} from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | ||||
| import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | ||||
| import QuestionViz from "./Popup/QuestionViz" | ||||
| import {Feature, Point} from "geojson" | ||||
| import {GeoOperations} from "../Logic/GeoOperations" | ||||
| import { Feature, Point } from "geojson" | ||||
| import { GeoOperations } from "../Logic/GeoOperations" | ||||
| import CreateNewNote from "./Popup/CreateNewNote.svelte" | ||||
| import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" | ||||
| import UserProfile from "./BigComponents/UserProfile.svelte" | ||||
|  | @ -54,27 +58,32 @@ import LanguagePicker from "./LanguagePicker" | |||
| import Link from "./Base/Link" | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||
| import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" | ||||
| import NearbyImages, {NearbyImageOptions, P4CPicture, SelectOneNearbyImage,} from "./Popup/NearbyImages" | ||||
| import {Tag} from "../Logic/Tags/Tag" | ||||
| import NearbyImages, { | ||||
|     NearbyImageOptions, | ||||
|     P4CPicture, | ||||
|     SelectOneNearbyImage, | ||||
| } from "./Popup/NearbyImages" | ||||
| import { Tag } from "../Logic/Tags/Tag" | ||||
| import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction" | ||||
| import {And} from "../Logic/Tags/And" | ||||
| import {SaveButton} from "./Popup/SaveButton" | ||||
| import { And } from "../Logic/Tags/And" | ||||
| import { SaveButton } from "./Popup/SaveButton" | ||||
| import Lazy from "./Base/Lazy" | ||||
| import {CheckBox} from "./Input/Checkboxes" | ||||
| import { CheckBox } from "./Input/Checkboxes" | ||||
| import Slider from "./Input/Slider" | ||||
| import {OsmTags, WayId} from "../Models/OsmFeature" | ||||
| import { OsmTags, WayId } from "../Models/OsmFeature" | ||||
| import MoveWizard from "./Popup/MoveWizard" | ||||
| import SplitRoadWizard from "./Popup/SplitRoadWizard" | ||||
| import {ExportAsGpxViz} from "./Popup/ExportAsGpxViz" | ||||
| import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" | ||||
| import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" | ||||
| import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" | ||||
| import {PointImportButtonViz} from "./Popup/ImportButtons/PointImportButtonViz" | ||||
| import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz" | ||||
| import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" | ||||
| import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" | ||||
| import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" | ||||
| import {OpenJosm} from "./BigComponents/OpenJosm" | ||||
| import { OpenJosm } from "./BigComponents/OpenJosm" | ||||
| import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" | ||||
| import FediverseValidator from "./InputElement/Validators/FediverseValidator"; | ||||
| import FediverseValidator from "./InputElement/Validators/FediverseValidator" | ||||
| import SendEmail from "./Popup/SendEmail.svelte" | ||||
| 
 | ||||
| class NearbyImageVis implements SpecialVisualization { | ||||
|     // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | ||||
|  | @ -173,7 +182,7 @@ class NearbyImageVis implements SpecialVisualization { | |||
|                 towardsCenter, | ||||
|                 new Combine([ | ||||
|                     new VariableUiElement( | ||||
|                         radius.GetValue().map((radius) => t.withinRadius.Subs({radius})) | ||||
|                         radius.GetValue().map((radius) => t.withinRadius.Subs({ radius })) | ||||
|                     ), | ||||
|                     radius, | ||||
|                 ]).SetClass("flex justify-between"), | ||||
|  | @ -386,24 +395,24 @@ export default class SpecialVisualizations { | |||
|             viz.docs, | ||||
|             viz.args.length > 0 | ||||
|                 ? new Table( | ||||
|                     ["name", "default", "description"], | ||||
|                     viz.args.map((arg) => { | ||||
|                         let defaultArg = arg.defaultValue ?? "_undefined_" | ||||
|                         if (defaultArg == "") { | ||||
|                             defaultArg = "_empty string_" | ||||
|                         } | ||||
|                         return [arg.name, defaultArg, arg.doc] | ||||
|                     }) | ||||
|                 ) | ||||
|                       ["name", "default", "description"], | ||||
|                       viz.args.map((arg) => { | ||||
|                           let defaultArg = arg.defaultValue ?? "_undefined_" | ||||
|                           if (defaultArg == "") { | ||||
|                               defaultArg = "_empty string_" | ||||
|                           } | ||||
|                           return [arg.name, defaultArg, arg.doc] | ||||
|                       }) | ||||
|                   ) | ||||
|                 : undefined, | ||||
|             new Title("Example usage of " + viz.funcName, 4), | ||||
|             new FixedUiElement( | ||||
|                 viz.example ?? | ||||
|                 "`{" + | ||||
|                 viz.funcName + | ||||
|                 "(" + | ||||
|                 viz.args.map((arg) => arg.defaultValue).join(",") + | ||||
|                 ")}`" | ||||
|                     "`{" + | ||||
|                         viz.funcName + | ||||
|                         "(" + | ||||
|                         viz.args.map((arg) => arg.defaultValue).join(",") + | ||||
|                         ")}`" | ||||
|             ).SetClass("literal-code"), | ||||
|         ]) | ||||
|     } | ||||
|  | @ -462,14 +471,14 @@ export default class SpecialVisualizations { | |||
|             s.structuredExamples === undefined | ||||
|                 ? [] | ||||
|                 : s.structuredExamples().map((e) => { | ||||
|                     return s.constr( | ||||
|                         state, | ||||
|                         new UIEventSource<Record<string, string>>(e.feature.properties), | ||||
|                         e.args, | ||||
|                         e.feature, | ||||
|                         undefined | ||||
|                     ) | ||||
|                 }) | ||||
|                       return s.constr( | ||||
|                           state, | ||||
|                           new UIEventSource<Record<string, string>>(e.feature.properties), | ||||
|                           e.args, | ||||
|                           e.feature, | ||||
|                           undefined | ||||
|                       ) | ||||
|                   }) | ||||
|         return new Combine([new Title(s.funcName), s.docs, ...examples]) | ||||
|     } | ||||
| 
 | ||||
|  | @ -484,7 +493,7 @@ export default class SpecialVisualizations { | |||
|                     let [lon, lat] = GeoOperations.centerpointCoordinates(feature) | ||||
|                     return new SvelteUIElement(AddNewPoint, { | ||||
|                         state, | ||||
|                         coordinate: {lon, lat}, | ||||
|                         coordinate: { lon, lat }, | ||||
|                     }) | ||||
|                 }, | ||||
|             }, | ||||
|  | @ -603,7 +612,7 @@ export default class SpecialVisualizations { | |||
|                     feature: Feature | ||||
|                 ): BaseUIElement { | ||||
|                     const [lon, lat] = GeoOperations.centerpointCoordinates(feature) | ||||
|                     return new SvelteUIElement(CreateNewNote, {state, coordinate: {lon, lat}}) | ||||
|                     return new SvelteUIElement(CreateNewNote, { state, coordinate: { lon, lat } }) | ||||
|                 }, | ||||
|             }, | ||||
|             new CloseNoteButton(), | ||||
|  | @ -680,7 +689,7 @@ export default class SpecialVisualizations { | |||
|                 docs: "Prints all key-value pairs of the object - used for debugging", | ||||
|                 args: [], | ||||
|                 constr: (state, tags: UIEventSource<any>) => | ||||
|                     new SvelteUIElement(AllTagsPanel, {tags, state}), | ||||
|                     new SvelteUIElement(AllTagsPanel, { tags, state }), | ||||
|             }, | ||||
|             { | ||||
|                 funcName: "image_carousel", | ||||
|  | @ -1229,23 +1238,7 @@ export default class SpecialVisualizations { | |||
|                     }, | ||||
|                 ], | ||||
|                 constr(__, 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, | ||||
|                             }) | ||||
|                         }) | ||||
|                     ) | ||||
|                     return new SvelteUIElement(SendEmail, { args, tags }) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|  | @ -1319,7 +1312,7 @@ export default class SpecialVisualizations { | |||
|                 ], | ||||
|                 constr(state, featureTags, args) { | ||||
|                     const [key, tr] = args | ||||
|                     const translation = new Translation({"*": tr}) | ||||
|                     const translation = new Translation({ "*": tr }) | ||||
|                     return new VariableUiElement( | ||||
|                         featureTags.map((tags) => { | ||||
|                             const properties: object[] = JSON.parse(tags[key]) | ||||
|  | @ -1340,29 +1333,46 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "fediverse_link", | ||||
|                 docs: "Converts a fediverse username or link into a clickable link", | ||||
|                 args: [{ | ||||
|                     name: "key", | ||||
|                     doc: "The attribute-name containing the link", | ||||
|                     required: true | ||||
|                 }], | ||||
|                 constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, argument: string[], feature: Feature, layer: LayerConfig): BaseUIElement { | ||||
|                 args: [ | ||||
|                     { | ||||
|                         name: "key", | ||||
|                         doc: "The attribute-name containing the link", | ||||
|                         required: true, | ||||
|                     }, | ||||
|                 ], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|                     argument: string[], | ||||
|                     feature: Feature, | ||||
|                     layer: LayerConfig | ||||
|                 ): BaseUIElement { | ||||
|                     const key = argument[0] | ||||
|                     const validator = new FediverseValidator() | ||||
|                     return new VariableUiElement(tagSource.map(tags => tags[key]).map(fediAccount => { | ||||
|                             fediAccount = validator.reformat(fediAccount) | ||||
|                             const [_, username, host] = fediAccount.match(FediverseValidator.usernameAtServer) | ||||
|                     return new VariableUiElement( | ||||
|                         tagSource | ||||
|                             .map((tags) => tags[key]) | ||||
|                             .map((fediAccount) => { | ||||
|                                 fediAccount = validator.reformat(fediAccount) | ||||
|                                 const [_, username, host] = fediAccount.match( | ||||
|                                     FediverseValidator.usernameAtServer | ||||
|                                 ) | ||||
| 
 | ||||
|                             return new Link(fediAccount, "https://" + host + "/@" + username, true) | ||||
|                         } | ||||
|                     )) | ||||
|                 } | ||||
|             } | ||||
|                                 return new Link( | ||||
|                                     fediAccount, | ||||
|                                     "https://" + host + "/@" + username, | ||||
|                                     true | ||||
|                                 ) | ||||
|                             }) | ||||
|                     ) | ||||
|                 }, | ||||
|             }, | ||||
|         ] | ||||
| 
 | ||||
|         specialVisualizations.push(new AutoApplyButton(specialVisualizations)) | ||||
| 
 | ||||
|         const invalid = specialVisualizations | ||||
|             .map((sp, i) => ({sp, i})) | ||||
|             .map((sp, i) => ({ sp, i })) | ||||
|             .filter((sp) => sp.sp.funcName === undefined) | ||||
|         if (invalid.length > 0) { | ||||
|             throw ( | ||||
|  |  | |||
|  | @ -442,6 +442,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|      * Utils.SubstituteKeys("abc{def}ghi", {def: 'XYZ'}) // => "abcXYZghi"
 | ||||
|      * Utils.SubstituteKeys("abc{def}{def}ghi", {def: 'XYZ'}) // => "abcXYZXYZghi"
 | ||||
|      * Utils.SubstituteKeys("abc{def}ghi", {def: '{XYZ}'}) // => "abc{XYZ}ghi"
 | ||||
|      * Utils.SubstituteKeys("abc\n\n{def}ghi", {def: '{XYZ}'}) // => "abc\n\n{XYZ}ghi"
 | ||||
|      * | ||||
|      * @param txt | ||||
|      * @param tags | ||||
|  | @ -456,7 +457,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | |||
|         if (txt === undefined) { | ||||
|             return undefined | ||||
|         } | ||||
|         const regex = /(.*?){([^}]*)}(.*)/ | ||||
|         const regex = /(.*?){([^}]*)}(.*)/s | ||||
| 
 | ||||
|         let match = txt.match(regex) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue