Refactoring: automatically generate code files from layer/theme files to avoid using 'Eval'
This commit is contained in:
parent
865b0bc44f
commit
39944a01fb
17 changed files with 269 additions and 31 deletions
1
404.html
1
404.html
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://gc.zgo.at/; img-src *; connect-src 'self' https://www.openstreetmap.org/ https://api.openstreetmap.org/;">
|
||||
<link href="./css/mobile.css" rel="stylesheet"/>
|
||||
<link href="./css/tagrendering.css" rel="stylesheet"/>
|
||||
<link href="./css/index-tailwind-output.css" rel="stylesheet"/>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://gc.zgo.at/; img-src *; connect-src 'self' https://www.openstreetmap.org/ https://api.openstreetmap.org/;">
|
||||
<link href="./css/mobile.css" rel="stylesheet"/>
|
||||
<link href="./css/openinghourstable.css" rel="stylesheet"/>
|
||||
<link href="./css/tagrendering.css" rel="stylesheet"/>
|
||||
|
@ -16,8 +17,6 @@
|
|||
<title>MapComplete</title>
|
||||
<link href="./index.webmanifest" rel="manifest">
|
||||
|
||||
<!-- Mastodon link verification: https://docs.joinmastodon.org/user/profile/#Link%20verification -->
|
||||
<a rel="me" href="https://en.osm.town/@MapComplete" style="display: none">Mastodon</a>
|
||||
<link href="./assets/svg/add.svg" rel="icon" sizes="any" type="image/svg+xml">
|
||||
<meta content="./assets/SocialImage.png" property="og:image">
|
||||
<meta content="MapComplete - editable, thematic maps with OpenStreetMap" property="og:title">
|
||||
|
@ -48,10 +47,12 @@
|
|||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Mastodon link verification: https://docs.joinmastodon.org/user/profile/#Link%20verification -->
|
||||
<a rel="me" href="https://en.osm.town/@MapComplete" class="hidden">Mastodon</a>
|
||||
|
||||
<div id="main"></div>
|
||||
<script type="module" src="./src/all_themes_index.ts"></script>
|
||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous"
|
||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous"
|
||||
integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -761,6 +761,10 @@ video {
|
|||
isolation: auto;
|
||||
}
|
||||
|
||||
.-z-10 {
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
|
@ -1096,10 +1100,6 @@ video {
|
|||
height: 2.75rem;
|
||||
}
|
||||
|
||||
.h-10 {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.h-48 {
|
||||
height: 12rem;
|
||||
}
|
||||
|
@ -1108,6 +1108,10 @@ video {
|
|||
height: 10rem;
|
||||
}
|
||||
|
||||
.h-10 {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.h-80 {
|
||||
height: 20rem;
|
||||
}
|
||||
|
@ -1632,16 +1636,16 @@ video {
|
|||
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-black {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||
|
|
|
@ -12,8 +12,8 @@ mkdir dist/assets 2> /dev/null
|
|||
export NODE_OPTIONS="--max-old-space-size=8192"
|
||||
|
||||
# This script ends every line with '&&' to chain everything. A failure will thus stop the build
|
||||
npm run generate:editor-layer-index &&
|
||||
npm run generate &&
|
||||
# npm run generate:editor-layer-index &&
|
||||
# npm run generate &&
|
||||
npm run generate:layouts
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Utils } from "../src/Utils"
|
|||
import Script from "./Script"
|
||||
import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
|
||||
import { parse as parse_html } from "node-html-parser"
|
||||
import { ExtraFunctions } from "../src/Logic/ExtraFunctions"
|
||||
// This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
|
||||
// It spits out an overview of those to be used to load them
|
||||
|
||||
|
@ -395,10 +396,129 @@ class LayerOverviewUtils extends Script {
|
|||
skippedLayers.length +
|
||||
" layers"
|
||||
)
|
||||
// We always need the calculated tags of 'usersettings', so we export them separately
|
||||
this.extractJavascriptCodeForLayer(
|
||||
state.sharedLayers.get("usersettings"),
|
||||
"./src/Logic/State/UserSettingsMetaTagging.ts"
|
||||
)
|
||||
|
||||
return sharedLayers
|
||||
}
|
||||
|
||||
/**
|
||||
* Given: a fully expanded themeConfigJson
|
||||
*
|
||||
* Will extract a dictionary of the special code and write it into a javascript file which can be imported.
|
||||
* This removes the need for _eval_, allowing for a correct CSP
|
||||
* @param themeFile
|
||||
* @private
|
||||
*/
|
||||
private extractJavascriptCode(themeFile: LayoutConfigJson) {
|
||||
const allCode = [
|
||||
"import {Feature} from 'geojson'",
|
||||
'import { ExtraFuncType } from "../../../Logic/ExtraFunctions";',
|
||||
'import { Utils } from "../../../Utils"',
|
||||
"export class ThemeMetaTagging {",
|
||||
" public static readonly themeName = " + JSON.stringify(themeFile.id),
|
||||
"",
|
||||
]
|
||||
for (const layer of themeFile.layers) {
|
||||
const l = <LayerConfigJson>layer
|
||||
const code = l.calculatedTags ?? []
|
||||
|
||||
allCode.push(
|
||||
" public metaTaggging_for_" +
|
||||
l.id +
|
||||
"(feat: Feature, helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>) {"
|
||||
)
|
||||
allCode.push(" const {" + ExtraFunctions.types.join(", ") + "} = helperFunctions")
|
||||
for (const line of code) {
|
||||
const firstEq = line.indexOf("=")
|
||||
let attributeName = line.substring(0, firstEq).trim()
|
||||
const expression = line.substring(firstEq + 1)
|
||||
const isStrict = attributeName.endsWith(":")
|
||||
if (!isStrict) {
|
||||
allCode.push(
|
||||
" Utils.AddLazyProperty(feat.properties, '" +
|
||||
attributeName +
|
||||
"', () => " +
|
||||
expression +
|
||||
" ) "
|
||||
)
|
||||
} else {
|
||||
attributeName = attributeName.substring(0, attributeName.length - 2).trim()
|
||||
allCode.push(" feat.properties['" + attributeName + "'] = " + expression)
|
||||
}
|
||||
}
|
||||
allCode.push(" }")
|
||||
}
|
||||
|
||||
const targetDir = "./src/assets/generated/metatagging/"
|
||||
if (!existsSync(targetDir)) {
|
||||
mkdirSync(targetDir)
|
||||
}
|
||||
allCode.push("}")
|
||||
|
||||
writeFileSync(targetDir + themeFile.id + ".ts", allCode.join("\n"))
|
||||
}
|
||||
|
||||
private extractJavascriptCodeForLayer(l: LayerConfigJson, targetPath?: string) {
|
||||
let importPath = "../../../"
|
||||
if (targetPath) {
|
||||
const l = targetPath.split("/")
|
||||
if (l.length == 1) {
|
||||
importPath = "./"
|
||||
} else {
|
||||
importPath = ""
|
||||
for (let i = 0; i < l.length - 3; i++) {
|
||||
const _ = l[i]
|
||||
importPath += "../"
|
||||
}
|
||||
}
|
||||
}
|
||||
const allCode = [
|
||||
`import { Utils } from "${importPath}Utils"`,
|
||||
`/** This code is autogenerated - do not edit. Edit ./assets/layers/${l.id}/${l.id}.json instead */`,
|
||||
"export class ThemeMetaTagging {",
|
||||
" public static readonly themeName = " + JSON.stringify(l.id),
|
||||
"",
|
||||
]
|
||||
const code = l.calculatedTags ?? []
|
||||
|
||||
allCode.push(
|
||||
" public metaTaggging_for_" + l.id + "(feat: {properties: Record<string, string>}) {"
|
||||
)
|
||||
for (const line of code) {
|
||||
const firstEq = line.indexOf("=")
|
||||
let attributeName = line.substring(0, firstEq).trim()
|
||||
const expression = line.substring(firstEq + 1)
|
||||
const isStrict = attributeName.endsWith(":")
|
||||
if (!isStrict) {
|
||||
allCode.push(
|
||||
" Utils.AddLazyProperty(feat.properties, '" +
|
||||
attributeName +
|
||||
"', () => " +
|
||||
expression +
|
||||
" ) "
|
||||
)
|
||||
} else {
|
||||
attributeName = attributeName.substring(0, attributeName.length - 2).trim()
|
||||
allCode.push(" feat.properties['" + attributeName + "'] = " + expression)
|
||||
}
|
||||
}
|
||||
allCode.push(" }")
|
||||
allCode.push("}")
|
||||
|
||||
const targetDir = "./src/assets/generated/metatagging/"
|
||||
if (!targetPath) {
|
||||
if (!existsSync(targetDir)) {
|
||||
mkdirSync(targetDir)
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(targetPath ?? targetDir + "layer_" + l.id + ".ts", allCode.join("\n"))
|
||||
}
|
||||
|
||||
private buildThemeIndex(
|
||||
licensePaths: Set<string>,
|
||||
sharedLayers: Map<string, LayerConfigJson>,
|
||||
|
@ -436,6 +556,7 @@ class LayerOverviewUtils extends Script {
|
|||
})
|
||||
|
||||
const skippedThemes: string[] = []
|
||||
|
||||
for (let i = 0; i < themeFiles.length; i++) {
|
||||
const themeInfo = themeFiles[i]
|
||||
const themePath = themeInfo.path
|
||||
|
@ -443,6 +564,7 @@ class LayerOverviewUtils extends Script {
|
|||
|
||||
const targetPath =
|
||||
LayerOverviewUtils.themePath + "/" + themePath.substring(themePath.lastIndexOf("/"))
|
||||
|
||||
const usedLayers = Array.from(
|
||||
LayerOverviewUtils.extractLayerIdsFrom(themeFile, false)
|
||||
).map((id) => LayerOverviewUtils.layerPath + id + ".json")
|
||||
|
@ -504,6 +626,8 @@ class LayerOverviewUtils extends Script {
|
|||
|
||||
this.writeTheme(themeFile)
|
||||
fixed.set(themeFile.id, themeFile)
|
||||
|
||||
this.extractJavascriptCode(themeFile)
|
||||
} catch (e) {
|
||||
console.error("ERROR: could not prepare theme " + themePath + " due to " + e)
|
||||
throw e
|
||||
|
|
|
@ -200,6 +200,26 @@ function asLangSpan(t: Translation, tag = "span"): string {
|
|||
return values.join("\n")
|
||||
}
|
||||
|
||||
let cspCached: string = undefined
|
||||
function generateCsp(): string {
|
||||
if (cspCached !== undefined) {
|
||||
return cspCached
|
||||
}
|
||||
|
||||
const csp = {
|
||||
"default-src": "'self'",
|
||||
"script-src": "'self'",
|
||||
"img-src": "*",
|
||||
"connect-src": "*",
|
||||
}
|
||||
const content = Object.keys(csp)
|
||||
.map((k) => k + ": " + csp[k])
|
||||
.join("; ")
|
||||
|
||||
cspCached = `<meta http-equiv="Content-Security-Policy" content="${content}">`
|
||||
return cspCached
|
||||
}
|
||||
|
||||
async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) {
|
||||
Locale.language.setData(layout.language[0])
|
||||
const targetLanguage = layout.language[0]
|
||||
|
@ -279,6 +299,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr
|
|||
Translations.t.general.poweredByOsm.textFor(targetLanguage)
|
||||
)
|
||||
.replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific)
|
||||
.replace(/<!-- CSP -->/, generateCsp())
|
||||
.replace(
|
||||
/<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s,
|
||||
asLangSpan(layout.shortDescription)
|
||||
|
@ -298,7 +319,12 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr
|
|||
|
||||
async function createIndexFor(theme: LayoutConfig) {
|
||||
const filename = "index_" + theme.id + ".ts"
|
||||
writeFileSync(filename, `import layout from "./src/assets/generated/themes/${theme.id}.json"\n`)
|
||||
|
||||
const imports = [
|
||||
`import layout from "./src/assets/generated/themes/${theme.id}.json"`,
|
||||
`import { ThemeMetaTagging } from "./src/assets/generated/metatagging/${theme.id}"`,
|
||||
]
|
||||
writeFileSync(filename, imports.join("\n") + "\n")
|
||||
|
||||
appendFileSync(filename, codeTemplate)
|
||||
}
|
||||
|
|
|
@ -454,12 +454,16 @@ export class ExtraFunctions {
|
|||
"To enable this feature, add a field `calculatedTags` in the layer object, e.g.:",
|
||||
"````",
|
||||
'"calculatedTags": [',
|
||||
' "_someKey=javascript-expression",',
|
||||
' "_someKey=javascript-expression (lazy execution)",',
|
||||
' "_some_other_key:=javascript expression (strict execution)',
|
||||
' "name=feat.properties.name ?? feat.properties.ref ?? feat.properties.operator",',
|
||||
" \"_distanceCloserThen3Km=distanceTo(feat)( some_lon, some_lat) < 3 ? 'yes' : 'no'\" ",
|
||||
" ]",
|
||||
"````",
|
||||
"",
|
||||
"By using `:=` as separator, the attribute will be calculated as soone as the data is loaded (strict evaluation)",
|
||||
"The default behaviour, using `=` as separator, is lazy loading",
|
||||
"",
|
||||
"The above code will be executed for every feature in the layer. The feature is accessible as `feat` and is an amended geojson object:",
|
||||
|
||||
new List([
|
||||
|
|
|
@ -9,7 +9,6 @@ import { IndexedFeatureSource } from "./FeatureSource/FeatureSource"
|
|||
import OsmObjectDownloader from "./Osm/OsmObjectDownloader"
|
||||
import { Utils } from "../Utils"
|
||||
import { Store, UIEventSource } from "./UIEventSource"
|
||||
import { SpecialVisualizationState } from "../UI/SpecialVisualization"
|
||||
|
||||
/**
|
||||
* Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ...
|
||||
|
@ -19,6 +18,7 @@ import { SpecialVisualizationState } from "../UI/SpecialVisualization"
|
|||
export default class MetaTagging {
|
||||
private static errorPrintCount = 0
|
||||
private static readonly stopErrorOutputAt = 10
|
||||
private static metataggingObject: any = undefined
|
||||
private static retaggingFuncCache = new Map<
|
||||
string,
|
||||
((feature: Feature, propertiesStore: UIEventSource<any>) => void)[]
|
||||
|
@ -77,6 +77,23 @@ export default class MetaTagging {
|
|||
})
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
/**
|
||||
* The 'metaTagging'-object is an object which contains some functions.
|
||||
* Those functions are named `metaTaggging_for_<layer_name>` and are constructed based on the 'calculatedField' for this layer.
|
||||
*
|
||||
* If they are set, those functions will be used instead of parsing them at runtime.
|
||||
*
|
||||
* This means that we can avoid using eval, resulting in faster and safer code (at the cost of more complexity) - at least for official themes.
|
||||
*
|
||||
* Note: this function might appear unused while developing, it is used in the generated `index_<themename>.ts` files.
|
||||
*
|
||||
* @param metatagging
|
||||
*/
|
||||
public static setThemeMetatagging(metatagging: any) {
|
||||
MetaTagging.metataggingObject = metatagging
|
||||
}
|
||||
|
||||
/**
|
||||
* This method (re)calculates all metatags and calculated tags on every given feature.
|
||||
* The given features should be part of the given layer
|
||||
|
@ -298,6 +315,38 @@ export default class MetaTagging {
|
|||
layer: LayerConfig,
|
||||
helpers: Record<ExtraFuncType, (feature: Feature) => Function>
|
||||
): (feature: Feature, tags: UIEventSource<Record<string, any>>) => boolean {
|
||||
if (MetaTagging.metataggingObject) {
|
||||
const funcName = "metaTaggging_for_" + layer.id
|
||||
if (typeof MetaTagging.metataggingObject[funcName] !== "function") {
|
||||
console.log(MetaTagging.metataggingObject)
|
||||
throw (
|
||||
"Error: metatagging-object for this theme does not have an entry at " +
|
||||
funcName +
|
||||
" (or it is not a function)"
|
||||
)
|
||||
}
|
||||
// public metaTaggging_for_walls_and_buildings(feat: Feature, helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>) {
|
||||
//
|
||||
const func: (feat: Feature, helperFunctions: Record<string, any>) => void =
|
||||
MetaTagging.metataggingObject[funcName]
|
||||
return (feature: Feature) => {
|
||||
const tags = feature.properties
|
||||
if (tags === undefined) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
func(feature, helpers)
|
||||
} catch (e) {
|
||||
console.error("Could not calculate calculated tags in exported class: ", e)
|
||||
}
|
||||
return true // Something changed
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Static MetataggingObject for theme is not set; using `new Function` (aka `eval`) to get calculated tags. This might trip up the CSP"
|
||||
)
|
||||
|
||||
const calculatedTags: [string, string, boolean][] = layer.calculatedTags
|
||||
if (calculatedTags === undefined || calculatedTags.length === 0) {
|
||||
return undefined
|
||||
|
|
|
@ -16,6 +16,7 @@ import LinkToWeblate from "../../UI/Base/LinkToWeblate"
|
|||
import FeatureSwitchState from "./FeatureSwitchState"
|
||||
import Constants from "../../Models/Constants"
|
||||
import { QueryParameters } from "../Web/QueryParameters"
|
||||
import { ThemeMetaTagging } from "./UserSettingsMetaTagging"
|
||||
|
||||
/**
|
||||
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
|
||||
|
@ -326,12 +327,15 @@ export default class UserRelatedState {
|
|||
},
|
||||
[translationMode]
|
||||
)
|
||||
|
||||
const usersettingMetaTagging = new ThemeMetaTagging()
|
||||
osmConnection.userDetails.addCallback((userDetails) => {
|
||||
for (const k in userDetails) {
|
||||
amendedPrefs.data["_" + k] = "" + userDetails[k]
|
||||
}
|
||||
|
||||
for (const [name, code, _] of usersettingsConfig.calculatedTags) {
|
||||
usersettingMetaTagging.metaTaggging_for_usersettings({ properties: amendedPrefs.data })
|
||||
/*for (const [name, code, _] of usersettingsConfig.calculatedTags) {
|
||||
try {
|
||||
let result = new Function("feat", "return " + code + ";")({
|
||||
properties: amendedPrefs.data,
|
||||
|
@ -349,7 +353,7 @@ export default class UserRelatedState {
|
|||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "")
|
||||
const isTranslator = translators.contributors.find(
|
||||
|
|
13
src/Logic/State/UserSettingsMetaTagging.ts
Normal file
13
src/Logic/State/UserSettingsMetaTagging.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Utils } from "../../Utils"
|
||||
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
||||
export class ThemeMetaTagging {
|
||||
public static readonly themeName = "usersettings"
|
||||
|
||||
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
|
||||
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
|
||||
}
|
||||
}
|
|
@ -838,6 +838,15 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
}
|
||||
|
||||
const layerConfig = new LayerConfig(json, "validation", true)
|
||||
for (const [attribute, code, isStrict] of layerConfig.calculatedTags ?? []) {
|
||||
try {
|
||||
new Function("feat", "return " + code + ";")
|
||||
} catch (e) {
|
||||
throw `Invalid function definition: the custom javascript is invalid:${e} (at ${context}). The offending javascript code is:\n ${code}`
|
||||
}
|
||||
}
|
||||
|
||||
if (json.source === "special") {
|
||||
if (!Constants.priviliged_layers.find((x) => x == json.id)) {
|
||||
errors.push(
|
||||
|
|
|
@ -204,12 +204,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
}
|
||||
const code = kv.substring(index + 1)
|
||||
|
||||
try {
|
||||
new Function("feat", "return " + code + ";")
|
||||
} catch (e) {
|
||||
throw `Invalid function definition: the custom javascript is invalid:${e} (at ${context}). The offending javascript code is:\n ${code}`
|
||||
}
|
||||
|
||||
this.calculatedTags.push([key, code, isStrict])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1171,7 +1171,7 @@ export default class SpecialVisualizations {
|
|||
new Link(
|
||||
Utils.SubstituteKeys(text, tags),
|
||||
Utils.SubstituteKeys(href, tags),
|
||||
download === undefined,
|
||||
download === undefined && !href.startsWith("#"),
|
||||
Utils.SubstituteKeys(download, tags)
|
||||
).SetClass(classnames)
|
||||
)
|
||||
|
|
|
@ -152,7 +152,8 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
DOMPurify.addHook("afterSanitizeAttributes", function (node) {
|
||||
// set all elements owning target to target=_blank + add noopener noreferrer
|
||||
if ("target" in node) {
|
||||
const target = node.getAttribute("target")
|
||||
if (target) {
|
||||
node.setAttribute("target", "_blank")
|
||||
node.setAttribute("rel", "noopener noreferrer")
|
||||
}
|
||||
|
@ -307,10 +308,12 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds a property to the given object, but the value will _only_ be calculated when it is actually requested
|
||||
* Adds a property to the given object, but the value will _only_ be calculated when it is actually requested.
|
||||
* This calculation will run once
|
||||
* @param object
|
||||
* @param name
|
||||
* @param init
|
||||
* @param whenDone: called when the value is updated. Note that this will be called at most once
|
||||
* @constructor
|
||||
*/
|
||||
public static AddLazyProperty(
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,10 @@ import ThemeViewState from "./src/Models/ThemeViewState"
|
|||
import SvelteUIElement from "./src/UI/Base/SvelteUIElement"
|
||||
import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte"
|
||||
import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig";
|
||||
import MetaTagging from "./src/Logic/MetaTagging";
|
||||
|
||||
|
||||
MetaTagging.setThemeMetatagging(new ThemeMetaTagging())
|
||||
const state = new ThemeViewState(new LayoutConfig(<any> layout))
|
||||
const main = new SvelteUIElement(ThemeViewGUI, { state })
|
||||
main.AttachTo("maindiv")
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport">
|
||||
<!-- CSP // disabled -->
|
||||
<link href="./css/mobile.css" rel="stylesheet"/>
|
||||
<link href="./css/openinghourstable.css" rel="stylesheet"/>
|
||||
<link href="./css/tagrendering.css" rel="stylesheet"/>
|
||||
|
@ -63,7 +64,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="belowmap" class="absolute top-0 left-0" style="z-index: -1;">Below</div>
|
||||
<div id="belowmap" class="absolute top-0 left-0 -z-10">Below</div>
|
||||
|
||||
<script>
|
||||
|
||||
|
|
Loading…
Reference in a new issue