TagRenderingConfig: fix 'leftovers' of multi-answer freeform, generateDocs now generates Markdown instead of a BaseUIElement, add 'postfixDistinguished'-option for 'charge'-key
This commit is contained in:
parent
53ef1b947d
commit
3a69157d10
4 changed files with 138 additions and 92 deletions
|
@ -867,6 +867,7 @@
|
||||||
},
|
},
|
||||||
"website",
|
"website",
|
||||||
{
|
{
|
||||||
|
"id": "charge_cost_rewritten",
|
||||||
"rewrite": {
|
"rewrite": {
|
||||||
"sourceString": [
|
"sourceString": [
|
||||||
"{product_key}",
|
"{product_key}",
|
||||||
|
@ -902,7 +903,9 @@
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"renderings": {
|
"renderings": [
|
||||||
|
{
|
||||||
|
"id": "charge_{product_key}",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "How much does a {product_name} cost?",
|
"en": "How much does a {product_name} cost?",
|
||||||
"ca": "Quant costa {product_name}?",
|
"ca": "Quant costa {product_name}?",
|
||||||
|
@ -914,22 +917,24 @@
|
||||||
"pt": "Quanto custa {product_name}?"
|
"pt": "Quanto custa {product_name}?"
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"en": "{product_name} costs {charge:{product_key}}",
|
"en": "{product_name} costs {charge}",
|
||||||
"ca": "{product_name} costa {charge:{product_key}}",
|
"ca": "{product_name} costa {charge}",
|
||||||
"de": "{product_name} kostet {charge:{product_key}}",
|
"de": "{product_name} kostet {charge}",
|
||||||
"cs": "{product_name} {charge:{product_key}}",
|
"cs": "{product_name} {charge}",
|
||||||
"nl": "{product_name} kost {charge:{product_key}}",
|
"nl": "{product_name} kost {charge}",
|
||||||
"pt_BR": "{product_name} custa {charge:{product_key}}",
|
"pt_BR": "{product_name} custa {charge}",
|
||||||
"es": "{product_name} cuesta {charge:{product_key}}",
|
"es": "{product_name} cuesta {charge}",
|
||||||
"pt": "{product_name} custa {charge:{product_key}}"
|
"pt": "{product_name} custa {charge}"
|
||||||
},
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "charge:{product_key}",
|
"key": "charge",
|
||||||
"type": "currency"
|
"type": "currency",
|
||||||
|
"inline": true,
|
||||||
|
"postfixDistinguished": "{product_key}"
|
||||||
},
|
},
|
||||||
"condition": "vending~.*{product_key}.*",
|
"condition": "vending~.*{product_key}.*"
|
||||||
"id": "charge_{product_key}"
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "operational_status",
|
"id": "operational_status",
|
||||||
|
|
|
@ -264,7 +264,22 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
|
||||||
* ifunset: The question will be considered answered if any value is set for the key
|
* ifunset: The question will be considered answered if any value is set for the key
|
||||||
* group: expert
|
* group: expert
|
||||||
*/
|
*/
|
||||||
invalidValues?: TagConfigJson
|
invalidValues?: TagConfigJson,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* question: If this key shared and distinguished by a postfix, what is the postfix?
|
||||||
|
* This option is used specifically for `charge`, where the cost is indicated with `/item`.
|
||||||
|
*
|
||||||
|
* For example, a vending machine might sell `bicycle_tube`.
|
||||||
|
* Setting this value to `bicycle_tube`, then answering this question will set `charge= €XX/bicycle_tube`.
|
||||||
|
* If charge did already contain another value, e.g. `charge= €YY/some_item; €ZZ/other_item`, then `€XX/bicycle_tube`will be added.
|
||||||
|
* Note: those values are sorted alphabetically
|
||||||
|
* Note: no need to add the `/`
|
||||||
|
*
|
||||||
|
* ifunset: Don't distinguish by postfix
|
||||||
|
* group: expert
|
||||||
|
*/
|
||||||
|
postfixDistinguished?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,13 +5,8 @@ import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils"
|
||||||
import { And } from "../../Logic/Tags/And"
|
import { And } from "../../Logic/Tags/And"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { Tag } from "../../Logic/Tags/Tag"
|
import { Tag } from "../../Logic/Tags/Tag"
|
||||||
import BaseUIElement from "../../UI/BaseUIElement"
|
|
||||||
import Combine from "../../UI/Base/Combine"
|
|
||||||
import Title from "../../UI/Base/Title"
|
|
||||||
import Link from "../../UI/Base/Link"
|
import Link from "../../UI/Base/Link"
|
||||||
import List from "../../UI/Base/List"
|
|
||||||
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
|
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
|
||||||
import { FixedUiElement } from "../../UI/Base/FixedUiElement"
|
|
||||||
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
|
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
|
||||||
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
|
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
|
||||||
import { RegexTag } from "../../Logic/Tags/RegexTag"
|
import { RegexTag } from "../../Logic/Tags/RegexTag"
|
||||||
|
@ -19,9 +14,7 @@ import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import NameSuggestionIndex from "../../Logic/Web/NameSuggestionIndex"
|
import NameSuggestionIndex from "../../Logic/Web/NameSuggestionIndex"
|
||||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
|
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
||||||
export interface Icon {
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Mapping {
|
export interface Mapping {
|
||||||
readonly if: UploadableTag
|
readonly if: UploadableTag
|
||||||
|
@ -72,6 +65,7 @@ export default class TagRenderingConfig {
|
||||||
readonly addExtraTags: UploadableTag[]
|
readonly addExtraTags: UploadableTag[]
|
||||||
readonly inline: boolean
|
readonly inline: boolean
|
||||||
readonly default?: string
|
readonly default?: string
|
||||||
|
readonly postfixDistinguished?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly multiAnswer: boolean
|
public readonly multiAnswer: boolean
|
||||||
|
@ -201,7 +195,8 @@ export default class TagRenderingConfig {
|
||||||
TagUtils.ParseUploadableTag(tg, `${context}.extratag[${i}]`)
|
TagUtils.ParseUploadableTag(tg, `${context}.extratag[${i}]`)
|
||||||
) ?? [],
|
) ?? [],
|
||||||
inline: json.freeform.inline ?? false,
|
inline: json.freeform.inline ?? false,
|
||||||
default: json.freeform.default
|
default: json.freeform.default,
|
||||||
|
postfixDistinguished: json.freeform.postfixDistinguished?.trim()
|
||||||
}
|
}
|
||||||
if (json.freeform["extraTags"] !== undefined) {
|
if (json.freeform["extraTags"] !== undefined) {
|
||||||
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
|
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
|
||||||
|
@ -218,6 +213,14 @@ export default class TagRenderingConfig {
|
||||||
throw `If you use a freeform key 'questions', the ID must be 'questions' too to trigger the special behaviour. The current id is '${this.id}' (at ${context})`
|
throw `If you use a freeform key 'questions', the ID must be 'questions' too to trigger the special behaviour. The current id is '${this.id}' (at ${context})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.freeform.postfixDistinguished) {
|
||||||
|
if (this.multiAnswer) {
|
||||||
|
throw "At " + context + ": a postfixDistinguished-value cannot be used with a multiAnswer"
|
||||||
|
}
|
||||||
|
if (this.freeform.postfixDistinguished.startsWith("/")) {
|
||||||
|
throw "At " + context + ": a postfixDistinguished-value should not start with `/`. This will be inserted automatically"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// freeform.type is validated in Validation.ts so that we don't need ValidatedTextFields here
|
// freeform.type is validated in Validation.ts so that we don't need ValidatedTextFields here
|
||||||
if (this.freeform.addExtraTags) {
|
if (this.freeform.addExtraTags) {
|
||||||
|
@ -466,9 +469,9 @@ export default class TagRenderingConfig {
|
||||||
// A flag to check that the freeform key isn't matched multiple times
|
// A flag to check that the freeform key isn't matched multiple times
|
||||||
// If it is undefined, it is "used" already, or at least we don't have to check for it anymore
|
// If it is undefined, it is "used" already, or at least we don't have to check for it anymore
|
||||||
const freeformKeyDefined = this.freeform?.key !== undefined
|
const freeformKeyDefined = this.freeform?.key !== undefined
|
||||||
const usedFreeformValues = new Set<string>()
|
|
||||||
// We run over all the mappings first, to check if the mapping matches
|
// We run over all the mappings first, to check if the mapping matches
|
||||||
const applicableMappings: {
|
const applicableMappings: {
|
||||||
|
if?: TagsFilter
|
||||||
then: TypedTranslation<Record<string, string>>
|
then: TypedTranslation<Record<string, string>>
|
||||||
img?: string
|
img?: string
|
||||||
}[] = Utils.NoNull(
|
}[] = Utils.NoNull(
|
||||||
|
@ -477,23 +480,23 @@ export default class TagRenderingConfig {
|
||||||
return mapping
|
return mapping
|
||||||
}
|
}
|
||||||
if (TagUtils.MatchesMultiAnswer(mapping.if, tags)) {
|
if (TagUtils.MatchesMultiAnswer(mapping.if, tags)) {
|
||||||
if (freeformKeyDefined && mapping.if.isUsableAsAnswer()) {
|
|
||||||
// THe freeform key is defined: what value does it use though?
|
|
||||||
// We mark the value to see if we have any leftovers
|
|
||||||
const value = mapping.if
|
|
||||||
.asChange({})
|
|
||||||
.find((kv) => kv.k === this.freeform.key).v
|
|
||||||
usedFreeformValues.add(value)
|
|
||||||
}
|
|
||||||
return mapping
|
return mapping
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if (freeformKeyDefined && tags[this.freeform.key] !== undefined) {
|
if (freeformKeyDefined && tags[this.freeform.key] !== undefined) {
|
||||||
|
const usedFreeformValues = new Set<string>(
|
||||||
|
applicableMappings
|
||||||
|
?.flatMap(m => m.if?.usedTags() ?? [])
|
||||||
|
?.filter(kv => kv.key === this.freeform.key)
|
||||||
|
?.map(kv => kv.value)
|
||||||
|
)
|
||||||
|
|
||||||
const freeformValues = tags[this.freeform.key].split(";")
|
const freeformValues = tags[this.freeform.key].split(";")
|
||||||
const leftovers = freeformValues.filter((v) => !usedFreeformValues.has(v))
|
const leftovers = freeformValues.filter((v) => !usedFreeformValues.has(v.trim()))
|
||||||
for (const leftover of leftovers) {
|
for (const leftover of leftovers) {
|
||||||
applicableMappings.push({
|
applicableMappings.push({
|
||||||
then: new TypedTranslation<object>(
|
then: new TypedTranslation<object>(
|
||||||
|
@ -539,6 +542,23 @@ export default class TagRenderingConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.freeform?.key === undefined || tags[this.freeform.key] !== undefined) {
|
if (this.freeform?.key === undefined || tags[this.freeform.key] !== undefined) {
|
||||||
|
const postfix = this.freeform?.postfixDistinguished
|
||||||
|
if (postfix !== undefined) {
|
||||||
|
const allFreeforms = tags[this.freeform.key].split(";").map(s => s.trim())
|
||||||
|
for (const allFreeform of allFreeforms) {
|
||||||
|
if (allFreeform.endsWith(postfix)) {
|
||||||
|
const [v] = allFreeform.split("/")
|
||||||
|
// We found the needed postfix
|
||||||
|
return {
|
||||||
|
then: this.render.PartialSubs({ [this.freeform.key]: v.trim() }),
|
||||||
|
icon: this.renderIcon,
|
||||||
|
iconClass: this.renderIconClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// needed postfix not found
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
return { then: this.render, icon: this.renderIcon, iconClass: this.renderIconClass }
|
return { then: this.render, icon: this.renderIcon, iconClass: this.renderIconClass }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,6 +663,11 @@ export default class TagRenderingConfig {
|
||||||
* const tags = config.constructChangeSpecification("Tu-Fr 05:30-09:30", undefined, undefined, { }}
|
* const tags = config.constructChangeSpecification("Tu-Fr 05:30-09:30", undefined, undefined, { }}
|
||||||
* tags // =>new And([ new Tag("opening_hours", "Tu-Fr 05:30-09:30")])
|
* tags // =>new And([ new Tag("opening_hours", "Tu-Fr 05:30-09:30")])
|
||||||
*
|
*
|
||||||
|
* const config = new TagRenderingConfig({"id": "charge", render: "One tube costs {charge}", freeform: {key: "charge", postfixDistinguished: "bicycle_tube"]}, })
|
||||||
|
* const tags = config.constructChangeSpecification("€5", undefined, undefined, {vending: "books;bicycle_tubes" charge: "€42/book"})
|
||||||
|
* tags // =>new And([ new Tag("charge", "€5/bicycle_tube; €42/book")])
|
||||||
|
*
|
||||||
|
*
|
||||||
* @param freeformValue The freeform value which will be applied as 'freeform.key'. Ignored if 'freeform.key' is not set
|
* @param freeformValue The freeform value which will be applied as 'freeform.key'. Ignored if 'freeform.key' is not set
|
||||||
*
|
*
|
||||||
* @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform
|
* @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform
|
||||||
|
@ -658,6 +683,7 @@ export default class TagRenderingConfig {
|
||||||
if (typeof freeformValue === "string") {
|
if (typeof freeformValue === "string") {
|
||||||
freeformValue = freeformValue?.trim()
|
freeformValue = freeformValue?.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
const validator = Validators.get(<ValidatorType>this.freeform?.type)
|
const validator = Validators.get(<ValidatorType>this.freeform?.type)
|
||||||
if (validator && freeformValue) {
|
if (validator && freeformValue) {
|
||||||
freeformValue = validator.reformat(freeformValue, () => currentProperties["_country"])
|
freeformValue = validator.reformat(freeformValue, () => currentProperties["_country"])
|
||||||
|
@ -665,6 +691,18 @@ export default class TagRenderingConfig {
|
||||||
if (freeformValue === "") {
|
if (freeformValue === "") {
|
||||||
freeformValue = undefined
|
freeformValue = undefined
|
||||||
}
|
}
|
||||||
|
if (this.freeform?.postfixDistinguished && freeformValue !== undefined) {
|
||||||
|
const allValues = currentProperties[this.freeform.key].split(";").map(s => s.trim())
|
||||||
|
const perPostfix: Record<string, string> = {}
|
||||||
|
for (const value of allValues) {
|
||||||
|
const [v, postfix] = value.split("/")
|
||||||
|
perPostfix[postfix.trim()] = v.trim()
|
||||||
|
}
|
||||||
|
perPostfix[this.freeform.postfixDistinguished] = freeformValue
|
||||||
|
const keys = Object.keys(perPostfix)
|
||||||
|
keys.sort()
|
||||||
|
freeformValue = keys.map(k => perPostfix[k] + "/" + k).join("; ")
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
freeformValue === undefined &&
|
freeformValue === undefined &&
|
||||||
singleSelectedMapping === undefined &&
|
singleSelectedMapping === undefined &&
|
||||||
|
@ -740,6 +778,7 @@ export default class TagRenderingConfig {
|
||||||
!someMappingIsShown ||
|
!someMappingIsShown ||
|
||||||
singleSelectedMapping === undefined)
|
singleSelectedMapping === undefined)
|
||||||
if (useFreeform) {
|
if (useFreeform) {
|
||||||
|
|
||||||
return new And([
|
return new And([
|
||||||
new Tag(this.freeform.key, freeformValue),
|
new Tag(this.freeform.key, freeformValue),
|
||||||
...(this.freeform.addExtraTags ?? [])
|
...(this.freeform.addExtraTags ?? [])
|
||||||
|
@ -762,30 +801,23 @@ export default class TagRenderingConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerateDocumentation(): BaseUIElement {
|
GenerateDocumentation(): string {
|
||||||
let withRender: (BaseUIElement | string)[] = []
|
let withRender: string[] = []
|
||||||
if (this.freeform?.key !== undefined) {
|
if (this.freeform?.key !== undefined) {
|
||||||
withRender = [
|
withRender = [
|
||||||
`This rendering asks information about the property `,
|
`This rendering asks information about the property `,
|
||||||
Link.OsmWiki(this.freeform.key),
|
Link.OsmWiki(this.freeform.key).AsMarkdown(),
|
||||||
new Combine([
|
"This is rendered with `" + this.render.txt + "`"
|
||||||
"This is rendered with ",
|
|
||||||
new FixedUiElement(this.render.txt).SetClass("code font-bold")
|
|
||||||
])
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
let mappings: BaseUIElement = undefined
|
let mappings: string = undefined
|
||||||
if (this.mappings !== undefined) {
|
if (this.mappings !== undefined) {
|
||||||
mappings = new List(
|
mappings = MarkdownUtils.list(
|
||||||
[].concat(
|
this.mappings.flatMap((m) => {
|
||||||
...this.mappings.map((m) => {
|
const msgs: (string)[] = [
|
||||||
const msgs: (string | BaseUIElement)[] = [
|
"*" + m.then.txt + "* corresponds with " +
|
||||||
new Combine([
|
|
||||||
new FixedUiElement(m.then.txt).SetClass("font-bold"),
|
|
||||||
" corresponds with ",
|
|
||||||
m.if.asHumanString(true, false, {})
|
m.if.asHumanString(true, false, {})
|
||||||
])
|
|
||||||
]
|
]
|
||||||
if (m.hideInAnswer === true) {
|
if (m.hideInAnswer === true) {
|
||||||
msgs.push("_This option cannot be chosen as answer_")
|
msgs.push("_This option cannot be chosen as answer_")
|
||||||
|
@ -799,43 +831,31 @@ export default class TagRenderingConfig {
|
||||||
return msgs
|
return msgs
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let condition: BaseUIElement = undefined
|
let condition: string = undefined
|
||||||
if (this.condition !== undefined && !this.condition?.matchesProperties({})) {
|
if (this.condition !== undefined && !this.condition?.matchesProperties({})) {
|
||||||
condition = new Combine([
|
const conditionAsLink = (<TagsFilter>this.condition.optimize()).asHumanString(true, false, {})
|
||||||
"This tagrendering is only visible in the popup if the following condition is met:",
|
condition = "This tagrendering is only visible in the popup if the following condition is met: " + conditionAsLink
|
||||||
new FixedUiElement(
|
|
||||||
(<TagsFilter>this.condition.optimize()).asHumanString(true, false, {})
|
|
||||||
).SetClass("code")
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let labels: BaseUIElement = undefined
|
let labels: string = undefined
|
||||||
if (this.labels?.length > 0) {
|
if (this.labels?.length > 0) {
|
||||||
labels = new Combine([
|
labels = [
|
||||||
"This tagrendering has labels ",
|
"This tagrendering has labels ",
|
||||||
...this.labels.map((label) => new FixedUiElement(label).SetClass("code"))
|
...this.labels.map((label) => "`" + label + "`")
|
||||||
]).SetClass("flex")
|
].join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Combine([
|
return [
|
||||||
new Title(this.id, 3),
|
"### this.id",
|
||||||
this.description,
|
this.description,
|
||||||
this.question !== undefined
|
this.question !== undefined ? ("The question is `" + this.question.txt + "`") : "_This tagrendering has no question and is thus read-only_",
|
||||||
? new Combine([
|
withRender.join("\n"),
|
||||||
"The question is ",
|
|
||||||
new FixedUiElement(this.question.txt).SetClass("font-bold bold")
|
|
||||||
])
|
|
||||||
: new FixedUiElement(
|
|
||||||
"This tagrendering has no question and is thus read-only"
|
|
||||||
).SetClass("italic"),
|
|
||||||
new Combine(withRender),
|
|
||||||
mappings,
|
mappings,
|
||||||
condition,
|
condition,
|
||||||
labels
|
labels
|
||||||
]).SetClass("flex flex-col")
|
].join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
public usedTags(): TagsFilter[] {
|
public usedTags(): TagsFilter[] {
|
||||||
|
@ -872,7 +892,7 @@ export class TagRenderingConfigUtils {
|
||||||
const extraMappings = tags
|
const extraMappings = tags
|
||||||
.bindD(tags => {
|
.bindD(tags => {
|
||||||
const country = tags._country
|
const country = tags._country
|
||||||
if(country === undefined){
|
if (country === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const center = GeoOperations.centerpointCoordinates(feature)
|
const center = GeoOperations.centerpointCoordinates(feature)
|
||||||
|
@ -883,7 +903,10 @@ export class TagRenderingConfigUtils {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
const clone: TagRenderingConfig = Object.create(config)
|
const clone: TagRenderingConfig = Object.create(config)
|
||||||
const oldMappingsCloned = clone.mappings?.map(m => ({ ...m, priorityIf: m.priorityIf ?? TagUtils.Tag("id~*") })) ?? []
|
const oldMappingsCloned = clone.mappings?.map(m => ({
|
||||||
|
...m,
|
||||||
|
priorityIf: m.priorityIf ?? TagUtils.Tag("id~*")
|
||||||
|
})) ?? []
|
||||||
clone.mappings = [...oldMappingsCloned, ...extraMappings]
|
clone.mappings = [...oldMappingsCloned, ...extraMappings]
|
||||||
return clone
|
return clone
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,4 +16,7 @@ export default class MarkdownUtils {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static list(strings: string[]): string {
|
||||||
|
return strings.map(item => " - "+item).join("\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue