|
@@ -1,28 +1,41 @@
|
|
|
|
+<div style="text-align: center;" align="center">
|
|
|
|
+
|
|
## UserUtils
|
|
## UserUtils
|
|
Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more.
|
|
Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more.
|
|
-Contains builtin TypeScript declarations.
|
|
|
|
|
|
+Contains builtin TypeScript declarations. Webpack compatible and supports ESM and CJS.
|
|
|
|
|
|
|
|
+</div>
|
|
<br>
|
|
<br>
|
|
|
|
|
|
## Table of Contents:
|
|
## Table of Contents:
|
|
- [Installation](#installation)
|
|
- [Installation](#installation)
|
|
- [Features](#features)
|
|
- [Features](#features)
|
|
- - [onSelector()](#onselector) - call a listener once a selector is found in the DOM
|
|
|
|
- - [initOnSelector()](#initonselector) - needs to be called once to be able to use `onSelector()`
|
|
|
|
- - [getSelectorMap()](#getselectormap) - returns all currently registered selectors, listeners and options
|
|
|
|
- - [autoPlural()](#autoplural) - automatically pluralize a string
|
|
|
|
- - [clamp()](#clamp) - clamp a number between a min and max value
|
|
|
|
- - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
|
|
|
|
- - [debounce()](#debounce) - call a function only once, after a given amount of time
|
|
|
|
- - [getUnsafeWindow()](#getunsafewindow) - get the unsafeWindow object or fall back to the regular window object
|
|
|
|
- - [insertAfter()](#insertafter) - insert an element as a sibling after another element
|
|
|
|
- - [addParent()](#addparent) - add a parent element around another element
|
|
|
|
- - [addGlobalStyle()](#addglobalstyle) - add a global style to the page
|
|
|
|
- - [preloadImages()](#preloadimages) - preload images into the browser cache for faster loading later on
|
|
|
|
- - [fetchAdvanced()](#fetchadvanced) - wrapper around the fetch API with a timeout option
|
|
|
|
- - [openInNewTab()](#openinnewtab) - open a link in a new tab
|
|
|
|
- - [interceptEvent()](#interceptevent) - conditionally intercepts events registered by `addEventListener()` on any given EventTarget object
|
|
|
|
- - [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
|
|
|
|
|
|
+ - [DOM:](#dom)
|
|
|
|
+ - [onSelector()](#onselector) - call a listener once a selector is found in the DOM
|
|
|
|
+ - [initOnSelector()](#initonselector) - needs to be called once to be able to use `onSelector()`
|
|
|
|
+ - [getSelectorMap()](#getselectormap) - returns all currently registered selectors, listeners and options
|
|
|
|
+ - [getUnsafeWindow()](#getunsafewindow) - get the unsafeWindow object or fall back to the regular window object
|
|
|
|
+ - [insertAfter()](#insertafter) - insert an element as a sibling after another element
|
|
|
|
+ - [addParent()](#addparent) - add a parent element around another element
|
|
|
|
+ - [addGlobalStyle()](#addglobalstyle) - add a global style to the page
|
|
|
|
+ - [preloadImages()](#preloadimages) - preload images into the browser cache for faster loading later on
|
|
|
|
+ - [openInNewTab()](#openinnewtab) - open a link in a new tab
|
|
|
|
+ - [interceptEvent()](#interceptevent) - conditionally intercepts events registered by `addEventListener()` on any given EventTarget object
|
|
|
|
+ - [interceptWindowEvent()](#interceptwindowevent) - conditionally intercepts events registered by `addEventListener()` on the window object
|
|
|
|
+ - [Math:](#math)
|
|
|
|
+ - [clamp()](#clamp) - clamp a number between a min and max value
|
|
|
|
+ - [mapRange()](#maprange) - map a number from one range to the same spot in another range
|
|
|
|
+ - [randRange()](#randrange) - generate a random number between a min and max boundary
|
|
|
|
+ - [Misc:](#misc)
|
|
|
|
+ - [autoPlural()](#autoplural) - automatically pluralize a string
|
|
|
|
+ - [pauseFor()](#pausefor) - pause the execution of a function for a given amount of time
|
|
|
|
+ - [debounce()](#debounce) - call a function only once, after a given amount of time
|
|
|
|
+ - [fetchAdvanced()](#fetchadvanced) - wrapper around the fetch API with a timeout option
|
|
|
|
+ - [Arrays:](#arrays)
|
|
|
|
+ - [randomItem()](#randomitem) - Returns a random item from an array
|
|
|
|
+ - [randomItemIndex()](#randomitemindex) - Returns a tuple of a random item and its index from an array
|
|
|
|
+ - [takeRandomItem()](#takerandomitem) - Returns a random item from an array and mutates it to remove the item
|
|
|
|
+ - [randomizeArray()](#randomizearray) - Returns a copy of the array with its items in a random order
|
|
- [License](#license)
|
|
- [License](#license)
|
|
|
|
|
|
<br><br>
|
|
<br><br>
|
|
@@ -55,6 +68,8 @@ If you like using this library, please consider [supporting development](https:/
|
|
|
|
|
|
## Features:
|
|
## Features:
|
|
|
|
|
|
|
|
+## DOM:
|
|
|
|
+
|
|
### onSelector()
|
|
### onSelector()
|
|
Usage:
|
|
Usage:
|
|
```ts
|
|
```ts
|
|
@@ -181,86 +196,6 @@ const selectorMap = getSelectorMap();
|
|
|
|
|
|
<br>
|
|
<br>
|
|
|
|
|
|
-### autoPlural()
|
|
|
|
-Usage: `autoPlural(str: string, num: number | Array | NodeList): string`
|
|
|
|
-
|
|
|
|
-Automatically pluralizes a string if the given number is not 1.
|
|
|
|
-If an array or NodeList is passed, the length of it will be used.
|
|
|
|
-
|
|
|
|
-<details><summary><b>Example - click to view</b></summary>
|
|
|
|
-
|
|
|
|
-```ts
|
|
|
|
-autoPlural("apple", 0); // "apples"
|
|
|
|
-autoPlural("apple", 1); // "apple"
|
|
|
|
-autoPlural("apple", 2); // "apples"
|
|
|
|
-
|
|
|
|
-autoPlural("apple", [1]); // "apple"
|
|
|
|
-autoPlural("apple", [1, 2]); // "apples"
|
|
|
|
-
|
|
|
|
-const items = [1, 2, 3, 4, "foo", "bar"];
|
|
|
|
-console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 items"
|
|
|
|
-```
|
|
|
|
-
|
|
|
|
-</details>
|
|
|
|
-
|
|
|
|
-<br>
|
|
|
|
-
|
|
|
|
-### clamp()
|
|
|
|
-Usage: `clamp(num: number, min: number, max: number): number`
|
|
|
|
-
|
|
|
|
-Clamps a number between a min and max value.
|
|
|
|
-
|
|
|
|
-<details><summary><b>Example - click to view</b></summary>
|
|
|
|
-
|
|
|
|
-```ts
|
|
|
|
-clamp(5, 0, 10); // 5
|
|
|
|
-clamp(-1, 0, 10); // 0
|
|
|
|
-clamp(7, 0, 10); // 7
|
|
|
|
-clamp(Infinity, 0, 10); // 10
|
|
|
|
-```
|
|
|
|
-
|
|
|
|
-</details>
|
|
|
|
-
|
|
|
|
-<br>
|
|
|
|
-
|
|
|
|
-### pauseFor()
|
|
|
|
-Usage: `pauseFor(ms: number): Promise<void>`
|
|
|
|
-
|
|
|
|
-Pauses async execution for a given amount of time.
|
|
|
|
-
|
|
|
|
-<details><summary><b>Example - click to view</b></summary>
|
|
|
|
-
|
|
|
|
-```ts
|
|
|
|
-async function run() {
|
|
|
|
- console.log("Hello");
|
|
|
|
- await pauseFor(3000); // waits for 3 seconds
|
|
|
|
- console.log("World");
|
|
|
|
-}
|
|
|
|
-```
|
|
|
|
-
|
|
|
|
-</details>
|
|
|
|
-
|
|
|
|
-<br>
|
|
|
|
-
|
|
|
|
-### debounce()
|
|
|
|
-Usage: `debounce(func: Function, timeout?: number): Function`
|
|
|
|
-
|
|
|
|
-Debounces a function, meaning that it will only be called once after a given amount of time.
|
|
|
|
-This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
|
|
|
|
-The timeout will default to 300ms if left undefined.
|
|
|
|
-
|
|
|
|
-<details><summary><b>Example - click to view</b></summary>
|
|
|
|
-
|
|
|
|
-```ts
|
|
|
|
-window.addEventListener("resize", debounce((event) => {
|
|
|
|
- console.log("Window was resized:", event);
|
|
|
|
-}, 500)); // 500ms timeout
|
|
|
|
-```
|
|
|
|
-
|
|
|
|
-</details>
|
|
|
|
-
|
|
|
|
-<br>
|
|
|
|
-
|
|
|
|
### getUnsafeWindow()
|
|
### getUnsafeWindow()
|
|
Usage: `getUnsafeWindow(): Window`
|
|
Usage: `getUnsafeWindow(): Window`
|
|
|
|
|
|
@@ -375,6 +310,191 @@ preloadImages([
|
|
|
|
|
|
<br>
|
|
<br>
|
|
|
|
|
|
|
|
+### openInNewTab()
|
|
|
|
+Usage: `openInNewTab(url: string): void`
|
|
|
|
+
|
|
|
|
+Creates an invisible anchor with a `_blank` target and clicks it.
|
|
|
|
+Contrary to `window.open()`, this has a lesser chance to get blocked by the browser's popup blocker and doesn't open the URL as a new window.
|
|
|
|
+This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.
|
|
|
|
+
|
|
|
|
+⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+document.querySelector("#my-button").addEventListener("click", () => {
|
|
|
|
+ openInNewTab("https://example.org/");
|
|
|
|
+});
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
+### interceptEvent()
|
|
|
|
+Usage: `interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void`
|
|
|
|
+
|
|
|
|
+Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
|
|
+Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not already higher) to ensure the stack trace is preserved.
|
|
|
|
+
|
|
|
|
+⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+interceptEvent(document.body, "click", () => {
|
|
|
|
+ return true; // prevent all click events on the body element
|
|
|
|
+});
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
+### interceptWindowEvent()
|
|
|
|
+Usage: `interceptWindowEvent(eventName: string, predicate: () => boolean): void`
|
|
|
|
+
|
|
|
|
+Intercepts all events dispatched on the `window` object and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
|
|
+This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
|
|
|
|
+
|
|
|
|
+⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+interceptWindowEvent("beforeunload", () => {
|
|
|
|
+ return true; // prevent the pesky "Are you sure you want to leave this page?" popup
|
|
|
|
+});
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br><br>
|
|
|
|
+
|
|
|
|
+## Math:
|
|
|
|
+
|
|
|
|
+### clamp()
|
|
|
|
+Usage: `clamp(num: number, min: number, max: number): number`
|
|
|
|
+
|
|
|
|
+Clamps a number between a min and max value.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+clamp(5, 0, 10); // 5
|
|
|
|
+clamp(-1, 0, 10); // 0
|
|
|
|
+clamp(7, 0, 10); // 7
|
|
|
|
+clamp(Infinity, 0, 10); // 10
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
+### mapRange()
|
|
|
|
+Usage: `mapRange(value: number, range_1_min: number, range_1_max: number, range_2_min: number, range_2_max: number): number`
|
|
|
|
+
|
|
|
|
+Maps a number from one range to the spot it would be in another range.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+mapRange(5, 0, 10, 0, 100); // 50
|
|
|
|
+mapRange(5, 0, 10, 0, 50); // 25
|
|
|
|
+// to calculate a percentage from arbitrary values, use 0 and 100 as the second range:
|
|
|
|
+mapRange(4, 0, 13, 0, 100); // 30.76923076923077
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
+### randRange()
|
|
|
|
+Usages:
|
|
|
|
+```ts
|
|
|
|
+randRange(min: number, max: number): number
|
|
|
|
+randRange(max: number): number
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+Returns a random number between `min` and `max` (inclusive).
|
|
|
|
+If only one argument is passed, it will be used as the `max` value and `min` will be set to 0.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+randRange(0, 10); // 4
|
|
|
|
+randRange(10, 20); // 17
|
|
|
|
+randRange(10); // 7
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br><br>
|
|
|
|
+
|
|
|
|
+## Misc:
|
|
|
|
+
|
|
|
|
+### autoPlural()
|
|
|
|
+Usage: `autoPlural(str: string, num: number | Array | NodeList): string`
|
|
|
|
+
|
|
|
|
+Automatically pluralizes a string if the given number is not 1.
|
|
|
|
+If an array or NodeList is passed, the length of it will be used.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+autoPlural("apple", 0); // "apples"
|
|
|
|
+autoPlural("apple", 1); // "apple"
|
|
|
|
+autoPlural("apple", 2); // "apples"
|
|
|
|
+
|
|
|
|
+autoPlural("apple", [1]); // "apple"
|
|
|
|
+autoPlural("apple", [1, 2]); // "apples"
|
|
|
|
+
|
|
|
|
+const items = [1, 2, 3, 4, "foo", "bar"];
|
|
|
|
+console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 items"
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
+### pauseFor()
|
|
|
|
+Usage: `pauseFor(ms: number): Promise<void>`
|
|
|
|
+
|
|
|
|
+Pauses async execution for a given amount of time.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+async function run() {
|
|
|
|
+ console.log("Hello");
|
|
|
|
+ await pauseFor(3000); // waits for 3 seconds
|
|
|
|
+ console.log("World");
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
+### debounce()
|
|
|
|
+Usage: `debounce(func: Function, timeout?: number): Function`
|
|
|
|
+
|
|
|
|
+Debounces a function, meaning that it will only be called once after a given amount of time.
|
|
|
|
+This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
|
|
|
|
+The timeout will default to 300ms if left undefined.
|
|
|
|
+
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+window.addEventListener("resize", debounce((event) => {
|
|
|
|
+ console.log("Window was resized:", event);
|
|
|
|
+}, 500)); // 500ms timeout
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
### fetchAdvanced()
|
|
### fetchAdvanced()
|
|
Usage:
|
|
Usage:
|
|
```ts
|
|
```ts
|
|
@@ -403,67 +523,82 @@ fetchAdvanced("https://api.example.org/data", {
|
|
|
|
|
|
</details>
|
|
</details>
|
|
|
|
|
|
-<br>
|
|
|
|
|
|
+<br><br>
|
|
|
|
|
|
-### openInNewTab()
|
|
|
|
-Usage: `openInNewTab(url: string): void`
|
|
|
|
-
|
|
|
|
-Creates an invisible anchor with a `_blank` target and clicks it.
|
|
|
|
-Contrary to `window.open()`, this has a lesser chance to get blocked by the browser's popup blocker and doesn't open the URL as a new window.
|
|
|
|
-This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.
|
|
|
|
|
|
+## Arrays:
|
|
|
|
+
|
|
|
|
+### randomItem()
|
|
|
|
+Usage: `randomItem(array: Array): any`
|
|
|
|
|
|
-⚠️ This function needs to be run after the DOM has loaded (when using `@run-at document-end` or after `DOMContentLoaded` has fired).
|
|
|
|
|
|
+Returns a random item from an array.
|
|
|
|
+Returns undefined if the array is empty.
|
|
|
|
|
|
<details><summary><b>Example - click to view</b></summary>
|
|
<details><summary><b>Example - click to view</b></summary>
|
|
|
|
|
|
```ts
|
|
```ts
|
|
-document.querySelector("#my-button").addEventListener("click", () => {
|
|
|
|
- openInNewTab("https://example.org/");
|
|
|
|
-});
|
|
|
|
|
|
+randomItem(["foo", "bar", "baz"]); // "bar"
|
|
|
|
+randomItem([ ]); // undefined
|
|
```
|
|
```
|
|
|
|
|
|
</details>
|
|
</details>
|
|
|
|
|
|
<br>
|
|
<br>
|
|
|
|
|
|
-### interceptEvent()
|
|
|
|
-Usage: `interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void`
|
|
|
|
|
|
+### randomItemIndex()
|
|
|
|
+Usage: `randomItemIndex(array: Array): [item: any, index: number]`
|
|
|
|
|
|
-Intercepts all events dispatched on the `eventObject` and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
|
|
-Calling this function will set the `Error.stackTraceLimit` to 1000 (if it's not already higher) to ensure the stack trace is preserved.
|
|
|
|
-
|
|
|
|
-⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
|
|
|
|
|
|
+Returns a tuple of a random item and its index from an array.
|
|
|
|
+If the array is empty, it will return undefined for both values.
|
|
|
|
|
|
<details><summary><b>Example - click to view</b></summary>
|
|
<details><summary><b>Example - click to view</b></summary>
|
|
|
|
|
|
```ts
|
|
```ts
|
|
-interceptEvent(document.body, "click", () => {
|
|
|
|
- return true; // prevent all click events on the body element
|
|
|
|
-});
|
|
|
|
|
|
+randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
|
|
|
|
+randomItemIndex([ ]); // [undefined, undefined]
|
|
|
|
+// using array destructuring:
|
|
|
|
+const [item, index] = randomItemIndex(["foo", "bar", "baz"]);
|
|
|
|
+// or if you only want the index:
|
|
|
|
+const [, index] = randomItemIndex(["foo", "bar", "baz"]);
|
|
```
|
|
```
|
|
|
|
|
|
</details>
|
|
</details>
|
|
|
|
|
|
<br>
|
|
<br>
|
|
|
|
|
|
-### interceptWindowEvent()
|
|
|
|
-Usage: `interceptWindowEvent(eventName: string, predicate: () => boolean): void`
|
|
|
|
|
|
+### takeRandomItem()
|
|
|
|
+Usage: `takeRandomItem(array: Array): any`
|
|
|
|
|
|
-Intercepts all events dispatched on the `window` object and prevents the listeners from being called as long as the predicate function returns a truthy value.
|
|
|
|
-This is essentially the same as [`interceptEvent()`](#interceptevent), but automatically uses the `unsafeWindow` (or falls back to regular `window`).
|
|
|
|
|
|
+Returns a random item from an array and mutates the array by removing the item.
|
|
|
|
+Returns undefined if the array is empty.
|
|
|
|
|
|
-⚠️ This function should be called as soon as possible (I recommend using `@run-at document-start`), as it will only intercept events that are *attached* after this function is called.
|
|
|
|
|
|
+<details><summary><b>Example - click to view</b></summary>
|
|
|
|
+
|
|
|
|
+```ts
|
|
|
|
+const arr = ["foo", "bar", "baz"];
|
|
|
|
+takeRandomItem(arr); // "bar"
|
|
|
|
+console.log(arr); // ["foo", "baz"]
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+</details>
|
|
|
|
+
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
+### randomizeArray()
|
|
|
|
+Usage: `randomizeArray(array: Array): Array`
|
|
|
|
+
|
|
|
|
+Returns a copy of the array with its items in a random order.
|
|
|
|
+If the array is empty, the originally passed array will be returned.
|
|
|
|
|
|
<details><summary><b>Example - click to view</b></summary>
|
|
<details><summary><b>Example - click to view</b></summary>
|
|
|
|
|
|
```ts
|
|
```ts
|
|
-interceptWindowEvent("beforeunload", () => {
|
|
|
|
- return true; // prevent the pesky "Are you sure you want to leave this page?" popup
|
|
|
|
-});
|
|
|
|
|
|
+randomizeArray([1, 2, 3, 4, 5, 6]); // [3, 1, 5, 2, 4, 6]
|
|
```
|
|
```
|
|
|
|
|
|
</details>
|
|
</details>
|
|
|
|
|
|
|
|
+<br>
|
|
|
|
+
|
|
|
|
|
|
<br><br>
|
|
<br><br>
|
|
|
|
|