forked from MapComplete/MapComplete
		
	Themes: add currently open badge
This commit is contained in:
		
							parent
							
								
									56a23deb2d
								
							
						
					
					
						commit
						d9d330e912
					
				
					 7 changed files with 210 additions and 85 deletions
				
			
		| 
						 | 
				
			
			@ -67,6 +67,14 @@
 | 
			
		|||
      ],
 | 
			
		||||
      "metacondition": "__showTimeSensitiveIcons!=no"
 | 
			
		||||
    },
 | 
			
		||||
    {"id":"open_until",
 | 
			
		||||
      "labels": ["defaults","in_favourite"],
 | 
			
		||||
      "#":"Titleicon showing 'open until 17:00'",
 | 
			
		||||
      "icon": {
 | 
			
		||||
        "class": "w-20 mx-1 flex items-center"
 | 
			
		||||
      },
 | 
			
		||||
      "render": "{opening_hours_state()}"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "id": "phonelink",
 | 
			
		||||
      "labels": [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -745,6 +745,10 @@ video {
 | 
			
		|||
  top: 2.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.left-1\/4 {
 | 
			
		||||
  left: 25%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.isolate {
 | 
			
		||||
  isolation: isolate;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1088,6 +1092,10 @@ video {
 | 
			
		|||
  height: 2.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-5 {
 | 
			
		||||
  height: 1.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-48 {
 | 
			
		||||
  height: 12rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1198,6 +1206,14 @@ video {
 | 
			
		|||
  width: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.w-14 {
 | 
			
		||||
  width: 3.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.w-5 {
 | 
			
		||||
  width: 1.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.w-10 {
 | 
			
		||||
  width: 2.5rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1289,8 +1305,8 @@ video {
 | 
			
		|||
          appearance: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-cols-3 {
 | 
			
		||||
  grid-template-columns: repeat(3, minmax(0, 1fr));
 | 
			
		||||
.grid-cols-2 {
 | 
			
		||||
  grid-template-columns: repeat(2, minmax(0, 1fr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid-cols-1 {
 | 
			
		||||
| 
						 | 
				
			
			@ -1453,10 +1469,6 @@ video {
 | 
			
		|||
  justify-self: end;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.justify-self-center {
 | 
			
		||||
  justify-self: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.overflow-auto {
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,6 @@
 | 
			
		|||
  import { ImmutableStore } from "../../Logic/UIEventSource";
 | 
			
		||||
  import { GeoOperations } from "../../Logic/GeoOperations";
 | 
			
		||||
  import Center from "../../assets/svg/Center.svelte";
 | 
			
		||||
  import { Utils } from "../../Utils";
 | 
			
		||||
 | 
			
		||||
  export let feature: Feature;
 | 
			
		||||
  let properties: Record<string, string> = feature.properties;
 | 
			
		||||
| 
						 | 
				
			
			@ -17,48 +16,48 @@
 | 
			
		|||
  const favConfig = favLayer.layerDef;
 | 
			
		||||
  const titleConfig = favConfig.title;
 | 
			
		||||
 | 
			
		||||
  function center(){
 | 
			
		||||
    const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
 | 
			
		||||
  function center() {
 | 
			
		||||
    const [lon, lat] = GeoOperations.centerpointCoordinates(feature);
 | 
			
		||||
    state.mapProperties.location.setData(
 | 
			
		||||
      {lon, lat}
 | 
			
		||||
    )
 | 
			
		||||
    state.guistate.menuIsOpened.setData(false)
 | 
			
		||||
      { lon, lat }
 | 
			
		||||
    );
 | 
			
		||||
    state.guistate.menuIsOpened.setData(false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function select(){
 | 
			
		||||
    state.selectedLayer.setData(favConfig)
 | 
			
		||||
    state.selectedElement.setData(feature)
 | 
			
		||||
    center()
 | 
			
		||||
  function select() {
 | 
			
		||||
    state.selectedLayer.setData(favConfig);
 | 
			
		||||
    state.selectedElement.setData(feature);
 | 
			
		||||
    center();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const coord = GeoOperations.centerpointCoordinates(feature)
 | 
			
		||||
  const distance = state.mapProperties.location.stabilized(500).mapD(({lon, lat}) => {
 | 
			
		||||
    let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]))
 | 
			
		||||
  const coord = GeoOperations.centerpointCoordinates(feature);
 | 
			
		||||
  const distance = state.mapProperties.location.stabilized(500).mapD(({ lon, lat }) => {
 | 
			
		||||
    let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat]));
 | 
			
		||||
 | 
			
		||||
    if(meters < 1000){
 | 
			
		||||
      return meters +"m"
 | 
			
		||||
    if (meters < 1000) {
 | 
			
		||||
      return meters + "m";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    meters = Math.round(meters / 100)
 | 
			
		||||
    const kmStr = ""+meters
 | 
			
		||||
    meters = Math.round(meters / 100);
 | 
			
		||||
    const kmStr = "" + meters;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return kmStr.substring(0, kmStr.length - 1)+"."+kmStr.substring(kmStr.length-1) +"km"
 | 
			
		||||
  })
 | 
			
		||||
  const titleIconBlacklist = ["osmlink","sharelink","favourite_title_icon"]
 | 
			
		||||
    return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km";
 | 
			
		||||
  });
 | 
			
		||||
  const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"];
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded grid grid-cols-3 items-center no-weblate">
 | 
			
		||||
  <button on:click={() => select()} class="cursor-pointer ml-1 m-0 link justify-self-start">
 | 
			
		||||
    <TagRenderingAnswer extraClasses="underline" config={titleConfig} layer={favConfig} selectedElement={feature} {tags} />
 | 
			
		||||
<div class="px-1 my-1 border-2 border-dashed border-gray-300 rounded grid grid-cols-2 items-center no-weblate">
 | 
			
		||||
  <button class="cursor-pointer ml-1 m-0 link justify-self-start" on:click={() => select()}>
 | 
			
		||||
    <TagRenderingAnswer config={titleConfig} extraClasses="underline" layer={favConfig} selectedElement={feature}
 | 
			
		||||
                        {tags} />
 | 
			
		||||
  </button>
 | 
			
		||||
 | 
			
		||||
  {$distance}
 | 
			
		||||
 | 
			
		||||
  <div class="flex items-center justify-self-end title-icons links-as-button gap-x-0.5 p-1 pt-0.5 sm:pt-1">
 | 
			
		||||
    {#each favConfig.titleIcons as titleIconConfig}
 | 
			
		||||
      {#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ...properties, ...state.userRelatedState.preferencesAsTags.data } ) ?? true) && titleIconConfig.IsKnown(properties)}
 | 
			
		||||
      {#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties({ ...properties, ...state.userRelatedState.preferencesAsTags.data }) ?? true) && titleIconConfig.IsKnown(properties)}
 | 
			
		||||
        <div class={titleIconConfig.renderIconClass ?? "flex h-8 w-8 items-center"}>
 | 
			
		||||
          <TagRenderingAnswer
 | 
			
		||||
            config={titleIconConfig}
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +71,11 @@
 | 
			
		|||
      {/if}
 | 
			
		||||
    {/each}
 | 
			
		||||
 | 
			
		||||
    <button on:click={() => center()} class="p-1" ><Center class="w-6 h-6"/></button>
 | 
			
		||||
    <button class="p-1" on:click={() => center()}>
 | 
			
		||||
      <Center class="w-6 h-6" />
 | 
			
		||||
    </button>
 | 
			
		||||
    <div class="w-14">
 | 
			
		||||
      {$distance}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										50
									
								
								src/UI/OpeningHours/NextChangeViz.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/UI/OpeningHours/NextChangeViz.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
<script lang="ts">/**
 | 
			
		||||
 * Simple visualisation which shows when the POI opens/closes next.
 | 
			
		||||
 */
 | 
			
		||||
import type { SpecialVisualizationState } from "../SpecialVisualization";
 | 
			
		||||
import { Store, Stores } from "../../Logic/UIEventSource";
 | 
			
		||||
import { OH } from "./OpeningHours";
 | 
			
		||||
import opening_hours from "opening_hours";
 | 
			
		||||
import Clock from "../../assets/svg/Clock.svelte";
 | 
			
		||||
import { Utils } from "../../Utils";
 | 
			
		||||
import Circle from "../../assets/svg/Circle.svelte";
 | 
			
		||||
import Ring from "../../assets/svg/Ring.svelte";
 | 
			
		||||
import { twMerge } from "tailwind-merge";
 | 
			
		||||
 | 
			
		||||
export let state: SpecialVisualizationState;
 | 
			
		||||
export let tags: Store<Record<string, string>>;
 | 
			
		||||
export let keyToUse: string = "opening_hours";
 | 
			
		||||
export let prefix: string = undefined;
 | 
			
		||||
export let postfix: string = undefined;
 | 
			
		||||
let oh: Store<opening_hours | "error" | undefined> = OH.CreateOhObjectStore(tags, keyToUse, prefix, postfix);
 | 
			
		||||
 | 
			
		||||
let currentState = oh.mapD(oh => typeof oh === "string" ? undefined : oh.getState());
 | 
			
		||||
let tomorrow = new Date();
 | 
			
		||||
tomorrow.setTime(tomorrow.getTime() + 24 * 60 * 60 * 1000);
 | 
			
		||||
let nextChange = oh
 | 
			
		||||
  .mapD(oh => typeof oh === "string" ? undefined : oh.getNextChange(new Date(), tomorrow), [Stores.Chronic(5 * 60 * 1000)])
 | 
			
		||||
  .mapD(date => Utils.TwoDigits(date.getHours()) + ":" + Utils.TwoDigits(date.getMinutes()));
 | 
			
		||||
 | 
			
		||||
let size = nextChange.map(change => change === undefined ? "absolute h-7 w-7" : "absolute h-5 w-5 top-0 left-1/4");
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{#if $currentState !== undefined}
 | 
			
		||||
  <div class="relative h-8 w-8">
 | 
			
		||||
    {#if $currentState === true}
 | 
			
		||||
      <Ring class={$size} color="#0f0" style="z-index: 0" />
 | 
			
		||||
      <Clock class={$size} color="#0f0" style="z-index: 0" />
 | 
			
		||||
    {:else if $currentState === false}
 | 
			
		||||
      <Circle class={$size} color="#f00" style="z-index: 0" />
 | 
			
		||||
      <Clock class={$size} color="#fff" style="z-index: 0" />
 | 
			
		||||
    {/if}
 | 
			
		||||
 | 
			
		||||
    {#if $nextChange !== undefined}
 | 
			
		||||
      <span class="absolute bottom-0 font-bold text-sm" style="z-index: 1; background-color: #ffffff88; margin-top: 3px">
 | 
			
		||||
        {$nextChange}
 | 
			
		||||
      </span>
 | 
			
		||||
    {/if}
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
{/if}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
import { Utils } from "../../Utils"
 | 
			
		||||
import opening_hours from "opening_hours"
 | 
			
		||||
import { Store } from "../../Logic/UIEventSource"
 | 
			
		||||
 | 
			
		||||
export interface OpeningHour {
 | 
			
		||||
    weekday: number // 0 is monday, 1 is tuesday, ...
 | 
			
		||||
| 
						 | 
				
			
			@ -494,10 +495,48 @@ This list will be sorted
 | 
			
		|||
 | 
			
		||||
        return [changeHours, changeHourText]
 | 
			
		||||
    }
 | 
			
		||||
    public static CreateOhObjectStore(
 | 
			
		||||
        tags: Store<Record<string, string>>,
 | 
			
		||||
        key: string = "opening_hours",
 | 
			
		||||
        prefixToIgnore?: string,
 | 
			
		||||
        postfixToIgnore?: string
 | 
			
		||||
    ): Store<opening_hours | undefined | "error"> {
 | 
			
		||||
        prefixToIgnore ??= ""
 | 
			
		||||
        postfixToIgnore ??= ""
 | 
			
		||||
        const country = tags.map((tags) => tags._country)
 | 
			
		||||
        return tags
 | 
			
		||||
            .mapD((tags) => {
 | 
			
		||||
                const value: string = tags[key]
 | 
			
		||||
                if (value === undefined) {
 | 
			
		||||
                    return undefined
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (
 | 
			
		||||
                    (prefixToIgnore || postfixToIgnore) &&
 | 
			
		||||
                    value.startsWith(prefixToIgnore) &&
 | 
			
		||||
                    value.endsWith(postfixToIgnore)
 | 
			
		||||
                ) {
 | 
			
		||||
                    return value
 | 
			
		||||
                        .substring(prefixToIgnore.length, value.length - postfixToIgnore.length)
 | 
			
		||||
                        .trim()
 | 
			
		||||
                }
 | 
			
		||||
                return value
 | 
			
		||||
            })
 | 
			
		||||
            .mapD(
 | 
			
		||||
                (ohtext) => {
 | 
			
		||||
                    try {
 | 
			
		||||
                        return OH.CreateOhObject(<any>tags.data, ohtext, country.data)
 | 
			
		||||
                    } catch (e) {
 | 
			
		||||
                        return "error"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                [country]
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
    public static CreateOhObject(
 | 
			
		||||
        tags: Record<string, string> & { _lat: number; _lon: number; _country?: string },
 | 
			
		||||
        textToParse: string
 | 
			
		||||
        textToParse: string,
 | 
			
		||||
        country?: string
 | 
			
		||||
    ) {
 | 
			
		||||
        // noinspection JSPotentiallyInvalidConstructorUsage
 | 
			
		||||
        return new opening_hours(
 | 
			
		||||
| 
						 | 
				
			
			@ -506,7 +545,7 @@ This list will be sorted
 | 
			
		|||
                lat: tags._lat,
 | 
			
		||||
                lon: tags._lon,
 | 
			
		||||
                address: {
 | 
			
		||||
                    country_code: tags._country?.toLowerCase(),
 | 
			
		||||
                    country_code: country.toLowerCase(),
 | 
			
		||||
                    state: undefined,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,6 @@ import Combine from "../Base/Combine"
 | 
			
		|||
import { FixedUiElement } from "../Base/FixedUiElement"
 | 
			
		||||
import { OH } from "./OpeningHours"
 | 
			
		||||
import Translations from "../i18n/Translations"
 | 
			
		||||
import Constants from "../../Models/Constants"
 | 
			
		||||
import BaseUIElement from "../BaseUIElement"
 | 
			
		||||
import Toggle from "../Input/Toggle"
 | 
			
		||||
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,48 +29,20 @@ export default class OpeningHoursVisualization extends Toggle {
 | 
			
		|||
        prefix = "",
 | 
			
		||||
        postfix = ""
 | 
			
		||||
    ) {
 | 
			
		||||
        const country = tags.map((tags) => tags._country)
 | 
			
		||||
        const openingHoursStore = OH.CreateOhObjectStore(tags, key, prefix, postfix)
 | 
			
		||||
        const ohTable = new VariableUiElement(
 | 
			
		||||
            tags
 | 
			
		||||
                .map((tags) => {
 | 
			
		||||
                    const value: string = tags[key]
 | 
			
		||||
                    if (value === undefined) {
 | 
			
		||||
                        return undefined
 | 
			
		||||
                    }
 | 
			
		||||
                    if (value.startsWith(prefix) && value.endsWith(postfix)) {
 | 
			
		||||
                        return value.substring(prefix.length, value.length - postfix.length).trim()
 | 
			
		||||
                    }
 | 
			
		||||
                    return value
 | 
			
		||||
                }) // This mapping will absorb all other changes to tags in order to prevent regeneration
 | 
			
		||||
                .map(
 | 
			
		||||
                    (ohtext) => {
 | 
			
		||||
                        if (ohtext === undefined) {
 | 
			
		||||
                            return new FixedUiElement(
 | 
			
		||||
                                "No opening hours defined with key " + key
 | 
			
		||||
                            ).SetClass("alert")
 | 
			
		||||
                        }
 | 
			
		||||
                        try {
 | 
			
		||||
                            return OpeningHoursVisualization.CreateFullVisualisation(
 | 
			
		||||
                                OH.CreateOhObject(<any>tags.data, ohtext)
 | 
			
		||||
            openingHoursStore.map((opening_hours_obj) => {
 | 
			
		||||
                if (opening_hours_obj === undefined) {
 | 
			
		||||
                    return new FixedUiElement("No opening hours defined with key " + key).SetClass(
 | 
			
		||||
                        "alert"
 | 
			
		||||
                    )
 | 
			
		||||
                        } catch (e) {
 | 
			
		||||
                            console.warn(e, e.stack)
 | 
			
		||||
                            return new Combine([
 | 
			
		||||
                                Translations.t.general.opening_hours.error_loading,
 | 
			
		||||
                                new Toggle(
 | 
			
		||||
                                    new FixedUiElement(e).SetClass("subtle"),
 | 
			
		||||
                                    undefined,
 | 
			
		||||
                                    state?.osmConnection?.userDetails.map(
 | 
			
		||||
                                        (userdetails) =>
 | 
			
		||||
                                            userdetails.csCount >=
 | 
			
		||||
                                            Constants.userJourney.tagsVisibleAndWikiLinked
 | 
			
		||||
                                    )
 | 
			
		||||
                                ),
 | 
			
		||||
                            ])
 | 
			
		||||
                }
 | 
			
		||||
                    },
 | 
			
		||||
                    [country]
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                if (opening_hours_obj === "error") {
 | 
			
		||||
                    return Translations.t.general.opening_hours.error_loading
 | 
			
		||||
                }
 | 
			
		||||
                return OpeningHoursVisualization.CreateFullVisualisation(opening_hours_obj)
 | 
			
		||||
            })
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        super(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,6 +81,7 @@ import LogoutButton from "./Base/LogoutButton.svelte"
 | 
			
		|||
import OpenJosm from "./Base/OpenJosm.svelte"
 | 
			
		||||
import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"
 | 
			
		||||
import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte"
 | 
			
		||||
import NextChangeViz from "./OpeningHours/NextChangeViz.svelte"
 | 
			
		||||
 | 
			
		||||
class NearbyImageVis implements SpecialVisualization {
 | 
			
		||||
    // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | 
			
		||||
| 
						 | 
				
			
			@ -827,6 +828,46 @@ export default class SpecialVisualizations {
 | 
			
		|||
                    )
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                funcName: "opening_hours_state",
 | 
			
		||||
                docs: "A small element, showing if the POI is currently open and when the next change is",
 | 
			
		||||
                args: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: "key",
 | 
			
		||||
                        defaultValue: "opening_hours",
 | 
			
		||||
                        doc: "The tagkey from which the opening hours are read.",
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: "prefix",
 | 
			
		||||
                        defaultValue: "",
 | 
			
		||||
                        doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: "postfix",
 | 
			
		||||
                        defaultValue: "",
 | 
			
		||||
                        doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
                needsUrls: [],
 | 
			
		||||
                constr(
 | 
			
		||||
                    state: SpecialVisualizationState,
 | 
			
		||||
                    tags: UIEventSource<Record<string, string>>,
 | 
			
		||||
                    args: string[],
 | 
			
		||||
                    feature: Feature,
 | 
			
		||||
                    layer: LayerConfig
 | 
			
		||||
                ): BaseUIElement {
 | 
			
		||||
                    const keyToUse = args[0]
 | 
			
		||||
                    const prefix = args[1]
 | 
			
		||||
                    const postfix = args[2]
 | 
			
		||||
                    return new SvelteUIElement(NextChangeViz, {
 | 
			
		||||
                        state,
 | 
			
		||||
                        keyToUse,
 | 
			
		||||
                        tags,
 | 
			
		||||
                        prefix,
 | 
			
		||||
                        postfix,
 | 
			
		||||
                    })
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                funcName: "canonical",
 | 
			
		||||
                needsUrls: [],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue