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:
Pieter Vander Vennet 2023-09-15 01:53:50 +02:00
parent 2bbf966d22
commit b4f65bf2f7
3 changed files with 137 additions and 95 deletions

View 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}

View file

@ -1,52 +1,56 @@
import Combine from "./Base/Combine" import Combine from "./Base/Combine"
import {FixedUiElement} from "./Base/FixedUiElement" import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement" import BaseUIElement from "./BaseUIElement"
import Title from "./Base/Title" import Title from "./Base/Title"
import Table from "./Base/Table" import Table from "./Base/Table"
import {RenderingSpecification, SpecialVisualization, SpecialVisualizationState,} from "./SpecialVisualization" import {
import {HistogramViz} from "./Popup/HistogramViz" RenderingSpecification,
import {MinimapViz} from "./Popup/MinimapViz" SpecialVisualization,
import {ShareLinkViz} from "./Popup/ShareLinkViz" SpecialVisualizationState,
import {UploadToOsmViz} from "./Popup/UploadToOsmViz" } from "./SpecialVisualization"
import {MultiApplyViz} from "./Popup/MultiApplyViz" import { HistogramViz } from "./Popup/HistogramViz"
import {AddNoteCommentViz} from "./Popup/AddNoteCommentViz" import { MinimapViz } from "./Popup/MinimapViz"
import {PlantNetDetectionViz} from "./Popup/PlantNetDetectionViz" 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 TagApplyButton from "./Popup/TagApplyButton"
import {CloseNoteButton} from "./Popup/CloseNoteButton" import { CloseNoteButton } from "./Popup/CloseNoteButton"
import {MapillaryLinkVis} from "./Popup/MapillaryLinkVis" import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
import {Store, Stores, UIEventSource} from "../Logic/UIEventSource" import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"
import AllTagsPanel from "./Popup/AllTagsPanel.svelte" import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
import {ImageCarousel} from "./Image/ImageCarousel" import { ImageCarousel } from "./Image/ImageCarousel"
import {ImageUploadFlow} from "./Image/ImageUploadFlow" import { ImageUploadFlow } from "./Image/ImageUploadFlow"
import {VariableUiElement} from "./Base/VariableUIElement" import { VariableUiElement } from "./Base/VariableUIElement"
import {Utils} from "../Utils" import { Utils } from "../Utils"
import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata" import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
import {Translation} from "./i18n/Translation" import { Translation } from "./i18n/Translation"
import Translations from "./i18n/Translations" import Translations from "./i18n/Translations"
import ReviewForm from "./Reviews/ReviewForm" import ReviewForm from "./Reviews/ReviewForm"
import ReviewElement from "./Reviews/ReviewElement" import ReviewElement from "./Reviews/ReviewElement"
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"
import {SubtleButton} from "./Base/SubtleButton" import { SubtleButton } from "./Base/SubtleButton"
import Svg from "../Svg" import Svg from "../Svg"
import NoteCommentElement from "./Popup/NoteCommentElement" import NoteCommentElement from "./Popup/NoteCommentElement"
import ImgurUploader from "../Logic/ImageProviders/ImgurUploader" import ImgurUploader from "../Logic/ImageProviders/ImgurUploader"
import FileSelectorButton from "./Input/FileSelectorButton" import FileSelectorButton from "./Input/FileSelectorButton"
import {LoginToggle} from "./Popup/LoginButton" import { LoginToggle } from "./Popup/LoginButton"
import Toggle from "./Input/Toggle" import Toggle from "./Input/Toggle"
import {SubstitutedTranslation} from "./SubstitutedTranslation" import { SubstitutedTranslation } from "./SubstitutedTranslation"
import List from "./Base/List" import List from "./Base/List"
import StatisticsPanel from "./BigComponents/StatisticsPanel" import StatisticsPanel from "./BigComponents/StatisticsPanel"
import AutoApplyButton from "./Popup/AutoApplyButton" import AutoApplyButton from "./Popup/AutoApplyButton"
import {LanguageElement} from "./Popup/LanguageElement" import { LanguageElement } from "./Popup/LanguageElement"
import FeatureReviews from "../Logic/Web/MangroveReviews" import FeatureReviews from "../Logic/Web/MangroveReviews"
import Maproulette from "../Logic/Maproulette" import Maproulette from "../Logic/Maproulette"
import SvelteUIElement from "./Base/SvelteUIElement" 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 QuestionViz from "./Popup/QuestionViz"
import {Feature, Point} from "geojson" import { Feature, Point } from "geojson"
import {GeoOperations} from "../Logic/GeoOperations" import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/CreateNewNote.svelte" import CreateNewNote from "./Popup/CreateNewNote.svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./BigComponents/UserProfile.svelte" import UserProfile from "./BigComponents/UserProfile.svelte"
@ -54,27 +58,32 @@ import LanguagePicker from "./LanguagePicker"
import Link from "./Base/Link" import Link from "./Base/Link"
import LayerConfig from "../Models/ThemeConfig/LayerConfig" import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import NearbyImages, {NearbyImageOptions, P4CPicture, SelectOneNearbyImage,} from "./Popup/NearbyImages" import NearbyImages, {
import {Tag} from "../Logic/Tags/Tag" NearbyImageOptions,
P4CPicture,
SelectOneNearbyImage,
} from "./Popup/NearbyImages"
import { Tag } from "../Logic/Tags/Tag"
import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction" import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction"
import {And} from "../Logic/Tags/And" import { And } from "../Logic/Tags/And"
import {SaveButton} from "./Popup/SaveButton" import { SaveButton } from "./Popup/SaveButton"
import Lazy from "./Base/Lazy" import Lazy from "./Base/Lazy"
import {CheckBox} from "./Input/Checkboxes" import { CheckBox } from "./Input/Checkboxes"
import Slider from "./Input/Slider" import Slider from "./Input/Slider"
import {OsmTags, WayId} from "../Models/OsmFeature" import { OsmTags, WayId } from "../Models/OsmFeature"
import MoveWizard from "./Popup/MoveWizard" import MoveWizard from "./Popup/MoveWizard"
import SplitRoadWizard from "./Popup/SplitRoadWizard" import SplitRoadWizard from "./Popup/SplitRoadWizard"
import {ExportAsGpxViz} from "./Popup/ExportAsGpxViz" import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.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 WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"
import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
import {OpenJosm} from "./BigComponents/OpenJosm" import { OpenJosm } from "./BigComponents/OpenJosm"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" 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 NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -173,7 +182,7 @@ class NearbyImageVis implements SpecialVisualization {
towardsCenter, towardsCenter,
new Combine([ new Combine([
new VariableUiElement( new VariableUiElement(
radius.GetValue().map((radius) => t.withinRadius.Subs({radius})) radius.GetValue().map((radius) => t.withinRadius.Subs({ radius }))
), ),
radius, radius,
]).SetClass("flex justify-between"), ]).SetClass("flex justify-between"),
@ -386,24 +395,24 @@ export default class SpecialVisualizations {
viz.docs, viz.docs,
viz.args.length > 0 viz.args.length > 0
? new Table( ? new Table(
["name", "default", "description"], ["name", "default", "description"],
viz.args.map((arg) => { viz.args.map((arg) => {
let defaultArg = arg.defaultValue ?? "_undefined_" let defaultArg = arg.defaultValue ?? "_undefined_"
if (defaultArg == "") { if (defaultArg == "") {
defaultArg = "_empty string_" defaultArg = "_empty string_"
} }
return [arg.name, defaultArg, arg.doc] return [arg.name, defaultArg, arg.doc]
}) })
) )
: undefined, : undefined,
new Title("Example usage of " + viz.funcName, 4), new Title("Example usage of " + viz.funcName, 4),
new FixedUiElement( new FixedUiElement(
viz.example ?? viz.example ??
"`{" + "`{" +
viz.funcName + viz.funcName +
"(" + "(" +
viz.args.map((arg) => arg.defaultValue).join(",") + viz.args.map((arg) => arg.defaultValue).join(",") +
")}`" ")}`"
).SetClass("literal-code"), ).SetClass("literal-code"),
]) ])
} }
@ -462,14 +471,14 @@ export default class SpecialVisualizations {
s.structuredExamples === undefined s.structuredExamples === undefined
? [] ? []
: s.structuredExamples().map((e) => { : s.structuredExamples().map((e) => {
return s.constr( return s.constr(
state, state,
new UIEventSource<Record<string, string>>(e.feature.properties), new UIEventSource<Record<string, string>>(e.feature.properties),
e.args, e.args,
e.feature, e.feature,
undefined undefined
) )
}) })
return new Combine([new Title(s.funcName), s.docs, ...examples]) return new Combine([new Title(s.funcName), s.docs, ...examples])
} }
@ -484,7 +493,7 @@ export default class SpecialVisualizations {
let [lon, lat] = GeoOperations.centerpointCoordinates(feature) let [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(AddNewPoint, { return new SvelteUIElement(AddNewPoint, {
state, state,
coordinate: {lon, lat}, coordinate: { lon, lat },
}) })
}, },
}, },
@ -603,7 +612,7 @@ export default class SpecialVisualizations {
feature: Feature feature: Feature
): BaseUIElement { ): BaseUIElement {
const [lon, lat] = GeoOperations.centerpointCoordinates(feature) const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
return new SvelteUIElement(CreateNewNote, {state, coordinate: {lon, lat}}) return new SvelteUIElement(CreateNewNote, { state, coordinate: { lon, lat } })
}, },
}, },
new CloseNoteButton(), new CloseNoteButton(),
@ -680,7 +689,7 @@ export default class SpecialVisualizations {
docs: "Prints all key-value pairs of the object - used for debugging", docs: "Prints all key-value pairs of the object - used for debugging",
args: [], args: [],
constr: (state, tags: UIEventSource<any>) => constr: (state, tags: UIEventSource<any>) =>
new SvelteUIElement(AllTagsPanel, {tags, state}), new SvelteUIElement(AllTagsPanel, { tags, state }),
}, },
{ {
funcName: "image_carousel", funcName: "image_carousel",
@ -1229,23 +1238,7 @@ export default class SpecialVisualizations {
}, },
], ],
constr(__, tags, args) { constr(__, tags, args) {
return new VariableUiElement( return new SvelteUIElement(SendEmail, { args, tags })
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,
})
})
)
}, },
}, },
{ {
@ -1319,7 +1312,7 @@ export default class SpecialVisualizations {
], ],
constr(state, featureTags, args) { constr(state, featureTags, args) {
const [key, tr] = args const [key, tr] = args
const translation = new Translation({"*": tr}) const translation = new Translation({ "*": tr })
return new VariableUiElement( return new VariableUiElement(
featureTags.map((tags) => { featureTags.map((tags) => {
const properties: object[] = JSON.parse(tags[key]) const properties: object[] = JSON.parse(tags[key])
@ -1340,29 +1333,46 @@ export default class SpecialVisualizations {
{ {
funcName: "fediverse_link", funcName: "fediverse_link",
docs: "Converts a fediverse username or link into a clickable link", docs: "Converts a fediverse username or link into a clickable link",
args: [{ args: [
name: "key", {
doc: "The attribute-name containing the link", name: "key",
required: true doc: "The attribute-name containing the link",
}], required: true,
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>, argument: string[], feature: Feature, layer: LayerConfig): BaseUIElement { },
],
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const key = argument[0] const key = argument[0]
const validator = new FediverseValidator() const validator = new FediverseValidator()
return new VariableUiElement(tagSource.map(tags => tags[key]).map(fediAccount => { return new VariableUiElement(
fediAccount = validator.reformat(fediAccount) tagSource
const [_, username, host] = fediAccount.match(FediverseValidator.usernameAtServer) .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)) specialVisualizations.push(new AutoApplyButton(specialVisualizations))
const invalid = specialVisualizations const invalid = specialVisualizations
.map((sp, i) => ({sp, i})) .map((sp, i) => ({ sp, i }))
.filter((sp) => sp.sp.funcName === undefined) .filter((sp) => sp.sp.funcName === undefined)
if (invalid.length > 0) { if (invalid.length > 0) {
throw ( throw (

View file

@ -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}ghi", {def: 'XYZ'}) // => "abcXYZghi"
* Utils.SubstituteKeys("abc{def}{def}ghi", {def: 'XYZ'}) // => "abcXYZXYZghi" * Utils.SubstituteKeys("abc{def}{def}ghi", {def: 'XYZ'}) // => "abcXYZXYZghi"
* Utils.SubstituteKeys("abc{def}ghi", {def: '{XYZ}'}) // => "abc{XYZ}ghi" * 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 txt
* @param tags * @param tags
@ -456,7 +457,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
if (txt === undefined) { if (txt === undefined) {
return undefined return undefined
} }
const regex = /(.*?){([^}]*)}(.*)/ const regex = /(.*?){([^}]*)}(.*)/s
let match = txt.match(regex) let match = txt.match(regex)