linter / simpler webextension pack
This commit is contained in:
parent
47787c0b0b
commit
1471aae892
@ -8,6 +8,12 @@ module.exports = {
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended",
|
||||
],
|
||||
settings: {
|
||||
"react": {
|
||||
"version": "16.9.6",
|
||||
},
|
||||
|
||||
},
|
||||
rules: {
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"prefer-const": ["warn", { destructuring: "all" }],
|
||||
|
8
Makefile
8
Makefile
@ -44,11 +44,7 @@ yarn-install:
|
||||
|
||||
.PHONY: webextensions
|
||||
webextensions: rollup
|
||||
rm -rf dist/wx
|
||||
mkdir dist/wx
|
||||
cp webextension/manifest.json dist/wx/
|
||||
cp -r webextension/static/* dist/wx/
|
||||
cp -r dist/webextension/* dist/wx/
|
||||
./webextension/pack.sh
|
||||
|
||||
.PHONY: i18n
|
||||
i18n: yarn-install
|
||||
@ -86,4 +82,4 @@ rollup: tsc
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
./node_modules/.bin/eslint 'src/**/*'
|
||||
./node_modules/.bin/eslint 'src/**/*' --ext '.js,.ts,.tsx'
|
||||
|
@ -31,7 +31,6 @@ import {
|
||||
WireFee,
|
||||
ExchangeUpdateReason,
|
||||
ExchangeUpdatedEventRecord,
|
||||
CoinStatus,
|
||||
} from "../types/dbTypes";
|
||||
import { canonicalizeBaseUrl } from "../util/helpers";
|
||||
import * as Amounts from "../util/amounts";
|
||||
@ -81,7 +80,7 @@ async function setExchangeError(
|
||||
err: OperationError,
|
||||
): Promise<void> {
|
||||
console.log(`last error for exchange ${baseUrl}:`, err);
|
||||
const mut = (exchange: ExchangeRecord) => {
|
||||
const mut = (exchange: ExchangeRecord): ExchangeRecord => {
|
||||
exchange.lastError = err;
|
||||
return exchange;
|
||||
};
|
||||
@ -284,7 +283,7 @@ async function updateExchangeWithKeys(
|
||||
async function updateExchangeFinalize(
|
||||
ws: InternalWalletState,
|
||||
exchangeBaseUrl: string,
|
||||
) {
|
||||
): Promise<void> {
|
||||
const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
|
||||
if (!exchange) {
|
||||
return;
|
||||
@ -316,7 +315,7 @@ async function updateExchangeFinalize(
|
||||
async function updateExchangeWithTermsOfService(
|
||||
ws: InternalWalletState,
|
||||
exchangeBaseUrl: string,
|
||||
) {
|
||||
): Promise<void> {
|
||||
const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
|
||||
if (!exchange) {
|
||||
return;
|
||||
@ -357,7 +356,7 @@ export async function acceptExchangeTermsOfService(
|
||||
ws: InternalWalletState,
|
||||
exchangeBaseUrl: string,
|
||||
etag: string | undefined,
|
||||
) {
|
||||
): Promise<void> {
|
||||
await ws.db.runWithWriteTransaction([Stores.exchanges], async (tx) => {
|
||||
const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
|
||||
if (!r) {
|
||||
@ -377,7 +376,7 @@ export async function acceptExchangeTermsOfService(
|
||||
async function updateExchangeWithWireInfo(
|
||||
ws: InternalWalletState,
|
||||
exchangeBaseUrl: string,
|
||||
) {
|
||||
): Promise<void> {
|
||||
const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
|
||||
if (!exchange) {
|
||||
return;
|
||||
@ -461,7 +460,7 @@ export async function updateExchangeFromUrl(
|
||||
baseUrl: string,
|
||||
forceNow = false,
|
||||
): Promise<ExchangeRecord> {
|
||||
const onOpErr = (e: OperationError) => setExchangeError(ws, baseUrl, e);
|
||||
const onOpErr = (e: OperationError): Promise<void> => setExchangeError(ws, baseUrl, e);
|
||||
return await guardOperationException(
|
||||
() => updateExchangeFromUrlImpl(ws, baseUrl, forceNow),
|
||||
onOpErr,
|
||||
|
@ -20,11 +20,9 @@
|
||||
import { InternalWalletState } from "./state";
|
||||
import {
|
||||
Stores,
|
||||
TipRecord,
|
||||
ProposalStatus,
|
||||
ProposalRecord,
|
||||
PlanchetRecord,
|
||||
CoinRecord,
|
||||
} from "../types/dbTypes";
|
||||
import { Amounts } from "../util/amounts";
|
||||
import { AmountJson } from "../util/amounts";
|
||||
@ -40,14 +38,14 @@ import {
|
||||
VerboseRefreshDetails,
|
||||
} from "../types/history";
|
||||
import { assertUnreachable } from "../util/assertUnreachable";
|
||||
import { TransactionHandle, Store } from "../util/query";
|
||||
import { TransactionHandle } from "../util/query";
|
||||
import { timestampCmp } from "../util/time";
|
||||
import { summarizeReserveHistory } from "../util/reserveHistoryUtil";
|
||||
|
||||
/**
|
||||
* Create an event ID from the type and the primary key for the event.
|
||||
*/
|
||||
function makeEventId(type: HistoryEventType, ...args: string[]) {
|
||||
function makeEventId(type: HistoryEventType, ...args: string[]): string {
|
||||
return type + ";" + args.map((x) => encodeURIComponent(x)).join(";");
|
||||
}
|
||||
|
||||
@ -72,7 +70,7 @@ async function collectProposalHistory(
|
||||
tx: TransactionHandle,
|
||||
history: HistoryEvent[],
|
||||
historyQuery?: HistoryQuery,
|
||||
) {
|
||||
): Promise<void> {
|
||||
tx.iter(Stores.proposals).forEachAsync(async (proposal) => {
|
||||
const status = proposal.proposalStatus;
|
||||
switch (status) {
|
||||
|
@ -292,7 +292,7 @@ export class TransactionHandle {
|
||||
return requestToPromise(req);
|
||||
}
|
||||
|
||||
mutate<T>(store: Store<T>, key: any, f: (x: T) => T | undefined) {
|
||||
mutate<T>(store: Store<T>, key: any, f: (x: T) => T | undefined): Promise<void> {
|
||||
const req = this.tx.objectStore(store.name).openCursor(key);
|
||||
return applyMutation(req, f);
|
||||
}
|
||||
@ -438,7 +438,7 @@ export function openDatabase(
|
||||
export class Database {
|
||||
constructor(private db: IDBDatabase) {}
|
||||
|
||||
static deleteDatabase(idbFactory: IDBFactory, dbName: string) {
|
||||
static deleteDatabase(idbFactory: IDBFactory, dbName: string): void {
|
||||
idbFactory.deleteDatabase(dbName);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { Duration } from "./time";
|
||||
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2017-2019 Taler Systems S.A.
|
||||
@ -23,6 +21,12 @@ import { Duration } from "./time";
|
||||
* this abstracts over these differences.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { Duration } from "./time";
|
||||
|
||||
|
||||
/**
|
||||
* Cancelable timer.
|
||||
*/
|
||||
@ -33,7 +37,7 @@ export interface TimerHandle {
|
||||
class IntervalHandle {
|
||||
constructor(public h: any) {}
|
||||
|
||||
clear() {
|
||||
clear(): void {
|
||||
clearInterval(this.h);
|
||||
}
|
||||
}
|
||||
@ -41,7 +45,7 @@ class IntervalHandle {
|
||||
class TimeoutHandle {
|
||||
constructor(public h: any) {}
|
||||
|
||||
clear() {
|
||||
clear(): void {
|
||||
clearTimeout(this.h);
|
||||
}
|
||||
}
|
||||
@ -55,7 +59,7 @@ export const performanceNow: () => number = (() => {
|
||||
const t = process.hrtime();
|
||||
return t[0] * 1e9 + t[1];
|
||||
};
|
||||
} else if (typeof "performance" !== "undefined") {
|
||||
} else if (typeof performance !== "undefined") {
|
||||
return () => performance.now();
|
||||
} else {
|
||||
return () => 0;
|
||||
@ -93,7 +97,7 @@ export class TimerGroup {
|
||||
|
||||
private idGen = 1;
|
||||
|
||||
stopCurrentAndFutureTimers() {
|
||||
stopCurrentAndFutureTimers(): void {
|
||||
this.stopped = true;
|
||||
for (const x in this.timerMap) {
|
||||
if (!this.timerMap.hasOwnProperty(x)) {
|
||||
|
@ -78,7 +78,7 @@ function ConfirmAuditor(props: ConfirmAuditorProps): JSX.Element {
|
||||
setAddDone(true);
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
const back = (): void => {
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
@ -86,12 +86,12 @@ function ConfirmAuditor(props: ConfirmAuditorProps): JSX.Element {
|
||||
<div id="main">
|
||||
<p>
|
||||
Do you want to let <strong>{props.auditorPub}</strong> audit the
|
||||
currency "{props.currency}"?
|
||||
currency "{props.currency}"?
|
||||
</p>
|
||||
{addDone ? (
|
||||
<div>
|
||||
Auditor was added! You can also{" "}
|
||||
<a href={chrome.extension.getURL("/src/webex/pages/auditors.html")}>
|
||||
<a href={chrome.extension.getURL("/auditors.html")}>
|
||||
view and edit
|
||||
</a>{" "}
|
||||
auditors.
|
||||
@ -113,7 +113,7 @@ function ConfirmAuditor(props: ConfirmAuditorProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export function makeAddAuditorPage() {
|
||||
export function makeAddAuditorPage(): JSX.Element {
|
||||
const walletPageUrl = new URL(document.location.href);
|
||||
const url = walletPageUrl.searchParams.get("url");
|
||||
if (!url) {
|
||||
|
@ -734,21 +734,18 @@ function WalletDebug(props: any): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<p>Debug tools:</p>
|
||||
<button onClick={openExtensionPage("/src/webex/pages/popup.html")}>
|
||||
<button onClick={openExtensionPage("/popup.html")}>
|
||||
wallet tab
|
||||
</button>
|
||||
<button onClick={openExtensionPage("/src/webex/pages/benchmark.html")}>
|
||||
<button onClick={openExtensionPage("/benchmark.html")}>
|
||||
benchmark
|
||||
</button>
|
||||
<button onClick={openExtensionPage("/src/webex/pages/show-db.html")}>
|
||||
<button onClick={openExtensionPage("/show-db.html")}>
|
||||
show db
|
||||
</button>
|
||||
<button onClick={openExtensionPage("/src/webex/pages/tree.html")}>
|
||||
<button onClick={openExtensionPage("/tree.html")}>
|
||||
show tree
|
||||
</button>
|
||||
<button onClick={openExtensionPage("/src/webex/pages/logs.html")}>
|
||||
show logs
|
||||
</button>
|
||||
<br />
|
||||
<button onClick={confirmReset}>reset</button>
|
||||
<button onClick={reload}>reload chrome extension</button>
|
||||
|
@ -335,9 +335,9 @@ export function ProgressButton(
|
||||
export function PageLink(
|
||||
props: React.PropsWithChildren<{ pageName: string }>,
|
||||
): JSX.Element {
|
||||
const url = chrome.extension.getURL(`/src/webex/pages/${props.pageName}`);
|
||||
const url = chrome.extension.getURL(`/${props.pageName}`);
|
||||
return (
|
||||
<a className="actionLink" href={url} target="_blank">
|
||||
<a className="actionLink" href={url} target="_blank" rel="noopener noreferrer">
|
||||
{props.children}
|
||||
</a>
|
||||
);
|
||||
|
@ -358,7 +358,7 @@ function makeSyncWalletRedirect(
|
||||
oldUrl: string,
|
||||
params?: { [name: string]: string | undefined },
|
||||
): object {
|
||||
const innerUrl = new URL(chrome.extension.getURL("/src/webex/pages/" + url));
|
||||
const innerUrl = new URL(chrome.extension.getURL("/" + url));
|
||||
if (params) {
|
||||
for (const key in params) {
|
||||
const p = params[key];
|
||||
@ -367,10 +367,6 @@ function makeSyncWalletRedirect(
|
||||
}
|
||||
}
|
||||
}
|
||||
const outerUrl = new URL(
|
||||
chrome.extension.getURL("/src/webex/pages/redirect.html"),
|
||||
);
|
||||
outerUrl.searchParams.set("url", innerUrl.href);
|
||||
if (isFirefox()) {
|
||||
// Some platforms don't support the sync redirect (yet), so fall back to
|
||||
// async redirect after a timeout.
|
||||
@ -378,12 +374,12 @@ function makeSyncWalletRedirect(
|
||||
await waitMs(150);
|
||||
const tab = await getTab(tabId);
|
||||
if (tab.url === oldUrl) {
|
||||
chrome.tabs.update(tabId, { url: outerUrl.href });
|
||||
chrome.tabs.update(tabId, { url: innerUrl.href });
|
||||
}
|
||||
};
|
||||
doit();
|
||||
}
|
||||
return { redirectUrl: outerUrl.href };
|
||||
return { redirectUrl: innerUrl.href };
|
||||
}
|
||||
|
||||
async function reinitWallet(): Promise<void> {
|
||||
@ -447,7 +443,7 @@ try {
|
||||
chrome.runtime.onInstalled.addListener((details) => {
|
||||
console.log("onInstalled with reason", details.reason);
|
||||
if (details.reason === "install") {
|
||||
const url = chrome.extension.getURL("/src/webex/pages/welcome.html");
|
||||
const url = chrome.extension.getURL("/welcome.html");
|
||||
chrome.tabs.create({ active: true, url: url });
|
||||
}
|
||||
});
|
||||
|
@ -36,14 +36,14 @@
|
||||
"32": "img/icon.png"
|
||||
},
|
||||
"default_title": "Taler",
|
||||
"default_popup": "pages/popup.html"
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://*/*"],
|
||||
"js": [
|
||||
"js/contentScript.js"
|
||||
"contentScript.js"
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
|
20
webextension/pack.sh
Executable file
20
webextension/pack.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
if [[ ! -e package.json ]]; then
|
||||
echo "Please run this from the root of the repo.">&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
vers_manifest=$(jq -r '.version' webextension/manifest.json)
|
||||
|
||||
rm -rf dist/wx
|
||||
mkdir -p dist/wx
|
||||
cp webextension/manifest.json dist/wx/
|
||||
cp -r webextension/static/* dist/wx/
|
||||
cp -r dist/webextension/* dist/wx/
|
||||
|
||||
cd dist
|
||||
|
||||
zip -r "taler-wallet-${vers_manifest}.zip" wx
|
@ -5,11 +5,11 @@
|
||||
|
||||
<title>Taler Wallet: Add Auditor</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
|
||||
<style>
|
||||
.tree-item {
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Auditors</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="/dist/webextension/background.js"></script>
|
||||
<script src="/background.js"></script>
|
||||
<title>(wallet bg page)</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -3,9 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Benchmarks</title>
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section id="main">
|
||||
|
@ -4,12 +4,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Confirm Contract</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
|
||||
<style>
|
||||
button.accept {
|
||||
|
@ -4,11 +4,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Payback</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
|
@ -2,11 +2,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="popup.css" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/popup.css" />
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -4,11 +4,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Refund Status</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,10 +5,9 @@
|
||||
<title>Taler Wallet: Select Taler Provider</title>
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/pure.css" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
|
@ -4,12 +4,10 @@
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Return Coins to Bank Account</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,10 +5,9 @@
|
||||
<title>Taler Wallet: Received Tip</title>
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,10 +5,9 @@
|
||||
<title>Taler Wallet: Withdraw</title>
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -3,12 +3,10 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Withdraw</title>
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../style/wallet.css" />
|
||||
|
||||
<script src="/dist/webextension/pageEntryPoint.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/wallet.css" />
|
||||
<script src="/pageEntryPoint.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
Loading…
Reference in New Issue
Block a user