Compare commits
16 Commits
main
...
v0.12.2-alpha2
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ac87bef44 | |||
| 4caaa1bc4d | |||
| e46920b6a0 | |||
| 6d4dae59c6 | |||
| 1f3616b056 | |||
| 78a146b1ca | |||
| 8447f9aa7c | |||
| ee768f84bb | |||
| cfd4979a7a | |||
| de5b4e16cd | |||
| 5c696da6e2 | |||
| 2363144bb6 | |||
| 9e2d11d8db | |||
| f2523c778f | |||
| 04a51581fc | |||
| bb40db78e3 |
@@ -17,6 +17,8 @@ describe('Auto Imports', () => {
|
|||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const ContentScriptContext: typeof import('wxt/client')['ContentScriptContext']
|
const ContentScriptContext: typeof import('wxt/client')['ContentScriptContext']
|
||||||
|
const InvalidMatchPattern: typeof import('wxt/sandbox')['InvalidMatchPattern']
|
||||||
|
const MatchPattern: typeof import('wxt/sandbox')['MatchPattern']
|
||||||
const browser: typeof import('wxt/browser')['browser']
|
const browser: typeof import('wxt/browser')['browser']
|
||||||
const builtinDrivers: typeof import('wxt/storage')['builtinDrivers']
|
const builtinDrivers: typeof import('wxt/storage')['builtinDrivers']
|
||||||
const createContentScriptIframe: typeof import('wxt/client')['createContentScriptIframe']
|
const createContentScriptIframe: typeof import('wxt/client')['createContentScriptIframe']
|
||||||
|
|||||||
+4
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "wxt",
|
"name": "wxt",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.12.1",
|
"version": "0.12.2-alpha2",
|
||||||
"description": "Next gen framework for developing web extensions",
|
"description": "Next gen framework for developing web extensions",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=18",
|
||||||
@@ -86,7 +86,9 @@
|
|||||||
"lint": "run-p -c -s lint:*",
|
"lint": "run-p -c -s lint:*",
|
||||||
"lint:eslint": "echo 'ESLint: TODO'",
|
"lint:eslint": "echo 'ESLint: TODO'",
|
||||||
"lint:package": "publint",
|
"lint:package": "publint",
|
||||||
"compile": "tsc --noEmit",
|
"compile": "run-s -c compile:*",
|
||||||
|
"compile:wxt": "tsc --noEmit",
|
||||||
|
"compile:virtual": "tsc --noEmit",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:coverage": "vitest run --coverage",
|
"test:coverage": "vitest run --coverage",
|
||||||
"prepare": "simple-git-hooks",
|
"prepare": "simple-git-hooks",
|
||||||
|
|||||||
+3
-3
@@ -16,18 +16,17 @@ const startTime = Date.now();
|
|||||||
const outDir = 'dist';
|
const outDir = 'dist';
|
||||||
await fs.rm(path.join(outDir, '*'), { recursive: true, force: true });
|
await fs.rm(path.join(outDir, '*'), { recursive: true, force: true });
|
||||||
|
|
||||||
const preset: tsup.Options = {
|
const preset = {
|
||||||
dts: true,
|
dts: true,
|
||||||
silent: true,
|
silent: true,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
external: [
|
external: [
|
||||||
'vite',
|
|
||||||
'virtual:user-unlisted-script',
|
'virtual:user-unlisted-script',
|
||||||
'virtual:user-content-script-isolated-world',
|
'virtual:user-content-script-isolated-world',
|
||||||
'virtual:user-content-script-main-world',
|
'virtual:user-content-script-main-world',
|
||||||
'virtual:user-background',
|
'virtual:user-background',
|
||||||
],
|
],
|
||||||
};
|
} satisfies tsup.Options;
|
||||||
|
|
||||||
function spinnerPMap(configs: tsup.Options[]) {
|
function spinnerPMap(configs: tsup.Options[]) {
|
||||||
let completed = 0;
|
let completed = 0;
|
||||||
@@ -89,6 +88,7 @@ const config: tsup.Options[] = [
|
|||||||
format: ['esm'],
|
format: ['esm'],
|
||||||
splitting: false,
|
splitting: false,
|
||||||
dts: false,
|
dts: false,
|
||||||
|
external: [...preset.external, 'wxt'],
|
||||||
},
|
},
|
||||||
// CJS-only
|
// CJS-only
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,3 +17,65 @@ export interface WxtI18n extends I18n.Static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const browser: AugmentedBrowser = originalBrowser;
|
export const browser: AugmentedBrowser = originalBrowser;
|
||||||
|
|
||||||
|
// re-export all the types from webextension-polyfill
|
||||||
|
// Because webextension-polyfill uses a weird namespace with "import export", there isn't a good way
|
||||||
|
// to get these types without re-listing them.
|
||||||
|
/** @ignore */
|
||||||
|
export type {
|
||||||
|
ActivityLog,
|
||||||
|
Alarms,
|
||||||
|
Bookmarks,
|
||||||
|
Action,
|
||||||
|
BrowserAction,
|
||||||
|
BrowserSettings,
|
||||||
|
BrowsingData,
|
||||||
|
CaptivePortal,
|
||||||
|
Clipboard,
|
||||||
|
Commands,
|
||||||
|
ContentScripts,
|
||||||
|
ContextualIdentities,
|
||||||
|
Cookies,
|
||||||
|
DeclarativeNetRequest,
|
||||||
|
Devtools,
|
||||||
|
Dns,
|
||||||
|
Downloads,
|
||||||
|
Events,
|
||||||
|
Experiments,
|
||||||
|
Extension,
|
||||||
|
ExtensionTypes,
|
||||||
|
Find,
|
||||||
|
GeckoProfiler,
|
||||||
|
History,
|
||||||
|
I18n,
|
||||||
|
Identity,
|
||||||
|
Idle,
|
||||||
|
Management,
|
||||||
|
Manifest,
|
||||||
|
ContextMenus,
|
||||||
|
Menus,
|
||||||
|
NetworkStatus,
|
||||||
|
NormandyAddonStudy,
|
||||||
|
Notifications,
|
||||||
|
Omnibox,
|
||||||
|
PageAction,
|
||||||
|
Permissions,
|
||||||
|
Pkcs11,
|
||||||
|
Privacy,
|
||||||
|
Proxy,
|
||||||
|
Runtime,
|
||||||
|
Scripting,
|
||||||
|
Search,
|
||||||
|
Sessions,
|
||||||
|
SidebarAction,
|
||||||
|
Storage,
|
||||||
|
Tabs,
|
||||||
|
Theme,
|
||||||
|
TopSites,
|
||||||
|
Types,
|
||||||
|
Urlbar,
|
||||||
|
UserScripts,
|
||||||
|
WebNavigation,
|
||||||
|
WebRequest,
|
||||||
|
Windows,
|
||||||
|
} from 'webextension-polyfill';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ContentScriptDefinition } from '~/types';
|
import { ContentScriptDefinition } from '~/types';
|
||||||
import { browser } from '~/browser';
|
import { browser } from '~/browser';
|
||||||
import { logger } from '~/client/utils/logger';
|
import { logger } from '~/sandbox/utils/logger';
|
||||||
import { WxtLocationChangeEvent, getUniqueEventName } from './custom-events';
|
import { WxtLocationChangeEvent, getUniqueEventName } from './custom-events';
|
||||||
import { createLocationWatcher } from './location-watcher';
|
import { createLocationWatcher } from './location-watcher';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import browser from 'webextension-polyfill';
|
import { browser } from '~/browser';
|
||||||
import {
|
import {
|
||||||
ContentScriptAnchoredOptions,
|
ContentScriptAnchoredOptions,
|
||||||
ContentScriptPositioningOptions,
|
ContentScriptPositioningOptions,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createIsolatedElement } from '@webext-core/isolated-element';
|
import { createIsolatedElement } from '@webext-core/isolated-element';
|
||||||
import { browser } from '~/browser';
|
import { browser } from '~/browser';
|
||||||
import { logger } from '~/client/utils/logger';
|
import { logger } from '~/sandbox/utils/logger';
|
||||||
import { ContentScriptContext } from './content-script-context';
|
import { ContentScriptContext } from './content-script-context';
|
||||||
import {
|
import {
|
||||||
ContentScriptAnchoredOptions,
|
ContentScriptAnchoredOptions,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function webextensionPolyfillInlineDeps(): vite.PluginOption {
|
|||||||
return {
|
return {
|
||||||
name: 'wxt:testing-inline-deps',
|
name: 'wxt:testing-inline-deps',
|
||||||
config() {
|
config() {
|
||||||
const wxtModules = ['wxt/browser', 'wxt/client'];
|
const wxtModules = ['wxt/browser'];
|
||||||
return {
|
return {
|
||||||
test: {
|
test: {
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
@@ -150,14 +150,6 @@ async function cloneProject({
|
|||||||
consola.warn('Failed to move _gitignore to .gitignore:', err),
|
consola.warn('Failed to move _gitignore to .gitignore:', err),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Add .npmrc for pnpm
|
|
||||||
if (packageManager === 'pnpm') {
|
|
||||||
await fs.writeFile(
|
|
||||||
path.join(directory, '.npmrc'),
|
|
||||||
'shamefully-hoist=true\n',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
spinner.succeed();
|
spinner.succeed();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
spinner.fail();
|
spinner.fail();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Manifest } from 'webextension-polyfill';
|
import type { Manifest } from '~/browser';
|
||||||
import { BuildOutput, EntrypointGroup, InternalConfig } from '~/types';
|
import { BuildOutput, EntrypointGroup, InternalConfig } from '~/types';
|
||||||
import { findEntrypoints } from './find-entrypoints';
|
import { findEntrypoints } from './find-entrypoints';
|
||||||
import { generateTypesDir } from './generate-wxt-dir';
|
import { generateTypesDir } from './generate-wxt-dir';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Manifest } from 'webextension-polyfill';
|
import type { Manifest } from '~/browser';
|
||||||
import { ContentScriptEntrypoint, InternalConfig } from '~/types';
|
import { ContentScriptEntrypoint, InternalConfig } from '~/types';
|
||||||
import { resolvePerBrowserOption } from './entrypoints';
|
import { resolvePerBrowserOption } from './entrypoints';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Manifest } from 'webextension-polyfill';
|
import type { Manifest } from '~/browser';
|
||||||
import {
|
import {
|
||||||
Entrypoint,
|
Entrypoint,
|
||||||
BackgroundEntrypoint,
|
BackgroundEntrypoint,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import merge from 'lodash.merge';
|
import merge from 'lodash.merge';
|
||||||
import type { Manifest } from 'webextension-polyfill';
|
import type { Manifest } from '~/browser';
|
||||||
import {
|
import {
|
||||||
FsCache,
|
FsCache,
|
||||||
InternalConfig,
|
InternalConfig,
|
||||||
|
|||||||
@@ -6,3 +6,4 @@
|
|||||||
export * from './define-unlisted-script';
|
export * from './define-unlisted-script';
|
||||||
export * from './define-background';
|
export * from './define-background';
|
||||||
export * from './define-content-script';
|
export * from './define-content-script';
|
||||||
|
export * from '@webext-core/match-patterns';
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
function print(method: (...args: any[]) => void, ...args: any[]) {
|
function print(method: (...args: any[]) => void, ...args: any[]) {
|
||||||
if (import.meta.env.MODE === 'production') return;
|
if (import.meta.env.MODE === 'production') return;
|
||||||
|
|
||||||
+1
-1
@@ -8,7 +8,7 @@ import {
|
|||||||
defineDriver,
|
defineDriver,
|
||||||
Storage,
|
Storage,
|
||||||
} from 'unstorage';
|
} from 'unstorage';
|
||||||
import browser, { Storage as BrowserStorage } from 'webextension-polyfill';
|
import { browser, Storage as BrowserStorage } from '~/browser';
|
||||||
|
|
||||||
export interface WebExtensionDriverOptions {
|
export interface WebExtensionDriverOptions {
|
||||||
storageArea: 'sync' | 'local' | 'managed' | 'session';
|
storageArea: 'sync' | 'local' | 'managed' | 'session';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import type { Manifest, Scripting } from 'webextension-polyfill';
|
import type { Manifest, Scripting } from '~/browser';
|
||||||
import { UnimportOptions } from 'unimport';
|
import { UnimportOptions } from 'unimport';
|
||||||
import { LogLevel } from 'consola';
|
import { LogLevel } from 'consola';
|
||||||
import { ContentScriptContext } from '../client/content-scripts/content-script-context';
|
import { ContentScriptContext } from '../client/content-scripts/content-script-context';
|
||||||
|
|||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts
|
||||||
|
declare const __COMMAND__: 'build' | 'serve';
|
||||||
|
declare const __DEV_SERVER_PROTOCOL__: string;
|
||||||
|
declare const __DEV_SERVER_HOSTNAME__: string;
|
||||||
|
declare const __DEV_SERVER_PORT__: string;
|
||||||
|
declare const __MANIFEST_VERSION__: 2 | 3;
|
||||||
|
declare const __ENTRYPOINT__: string;
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from './external';
|
} from './external';
|
||||||
import { UnimportOptions } from 'unimport';
|
import { UnimportOptions } from 'unimport';
|
||||||
import { ResolvedConfig } from 'c12';
|
import { ResolvedConfig } from 'c12';
|
||||||
import type { Manifest } from 'webextension-polyfill';
|
import type { Manifest } from '~/browser';
|
||||||
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
||||||
|
|
||||||
export interface InternalConfig {
|
export interface InternalConfig {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# WXT Virtual Entrypoints
|
||||||
|
|
||||||
|
This folder contains scripts that are either loaded as entrypoints to JS files or included in HTML files, just like a project using WXT might load their own scripts.
|
||||||
|
|
||||||
|
While they are bundled and shipped inside WXT, Vite considers them apart of your project's source code, not WXT's. This means they cannot import 3rd party modules directly, otherwise `pnpm i --shamefully-hoist=false` and Yarn PnP will fail.
|
||||||
|
|
||||||
|
For this reason, the virtual entrypoints get their own TS project to isolate them from the rest of the project. They can only import from `wxt/*` or utils that don't have any imports from node_modules, like the logger.
|
||||||
|
|
||||||
|
When bundling WXT for publishing to NPM, all the `wxt/*` imports are marked as external and resolved when building your application. Other imports are added inline.
|
||||||
|
|
||||||
|
See https://github.com/wxt-dev/wxt/issues/286#issuecomment-1858888390 for more details.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import definition from 'virtual:user-background';
|
import definition from 'virtual:user-background';
|
||||||
import { setupWebSocket } from '../client/utils/setup-web-socket';
|
import { setupWebSocket } from './utils/setup-web-socket';
|
||||||
import { logger } from '../client/utils/logger';
|
import { logger } from '../sandbox/utils/logger';
|
||||||
import browser from 'webextension-polyfill';
|
import { browser } from 'wxt/browser';
|
||||||
import { keepServiceWorkerAlive } from '../client/utils/keep-service-worker-alive';
|
import { keepServiceWorkerAlive } from './utils/keep-service-worker-alive';
|
||||||
import { reloadContentScript } from '../client/utils/reload-content-scripts';
|
import { reloadContentScript } from './utils/reload-content-scripts';
|
||||||
|
|
||||||
if (__COMMAND__ === 'serve') {
|
if (__COMMAND__ === 'serve') {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import definition from 'virtual:user-content-script-isolated-world';
|
import definition from 'virtual:user-content-script-isolated-world';
|
||||||
import { logger } from '~/client/utils/logger';
|
import { logger } from '../sandbox/utils/logger';
|
||||||
import { ContentScriptContext } from '~/client/content-scripts/content-script-context';
|
import { ContentScriptContext } from 'wxt/client';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import definition from 'virtual:user-content-script-main-world';
|
import definition from 'virtual:user-content-script-main-world';
|
||||||
import { logger } from '~/client/utils/logger';
|
import { logger } from '../sandbox/utils/logger';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import { fakeBrowser as mockBrowser } from '~/testing';
|
import { fakeBrowser as mockBrowser } from 'wxt/testing';
|
||||||
|
|
||||||
export default mockBrowser;
|
export default mockBrowser;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
/// <reference types="vite/client" />
|
import { logger } from '../sandbox/utils/logger';
|
||||||
|
import { setupWebSocket } from './utils/setup-web-socket';
|
||||||
import { logger } from '~/client/utils/logger';
|
|
||||||
import { setupWebSocket } from '~/client/utils/setup-web-socket';
|
|
||||||
|
|
||||||
if (__COMMAND__ === 'serve') {
|
if (__COMMAND__ === 'serve') {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["vite/client", "../types/globals.d.ts"],
|
||||||
|
"paths": {
|
||||||
|
"wxt/*": ["../*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import definition from 'virtual:user-unlisted-script';
|
import definition from 'virtual:user-unlisted-script';
|
||||||
import { logger } from '~/client/utils/logger';
|
import { logger } from '../sandbox/utils/logger';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import browser from 'webextension-polyfill';
|
import { browser } from 'wxt/browser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://developer.chrome.com/blog/longer-esw-lifetimes/
|
* https://developer.chrome.com/blog/longer-esw-lifetimes/
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
import browser, { Manifest } from 'webextension-polyfill';
|
import { browser, Manifest } from 'wxt/browser';
|
||||||
import { logger } from './logger';
|
import { logger } from '../../sandbox/utils/logger';
|
||||||
import { MatchPattern } from '@webext-core/match-patterns';
|
import { MatchPattern } from 'wxt/sandbox';
|
||||||
|
|
||||||
export function reloadContentScript(contentScript: Manifest.ContentScript) {
|
export function reloadContentScript(contentScript: Manifest.ContentScript) {
|
||||||
const manifest = browser.runtime.getManifest();
|
const manifest = browser.runtime.getManifest();
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { logger } from './logger';
|
import { logger } from '../../sandbox/utils/logger';
|
||||||
|
|
||||||
interface WebSocketMessage {
|
interface WebSocketMessage {
|
||||||
type: string;
|
type: string;
|
||||||
+4
-17
@@ -1,34 +1,21 @@
|
|||||||
// Types required to make the virtual modules happy.
|
// Types required to make the virtual modules happy.
|
||||||
|
|
||||||
declare module '*?raw' {
|
|
||||||
const content: any;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'virtual:user-background' {
|
declare module 'virtual:user-background' {
|
||||||
const definition: import('~/types').BackgroundDefinition;
|
const definition: import('wxt/types').BackgroundDefinition;
|
||||||
export default definition;
|
export default definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'virtual:user-content-script-isolated-world' {
|
declare module 'virtual:user-content-script-isolated-world' {
|
||||||
const definition: import('~/types').ContentScriptIsolatedWorldDefinition;
|
const definition: import('wxt/types').ContentScriptIsolatedWorldDefinition;
|
||||||
export default definition;
|
export default definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'virtual:user-content-script-main-world' {
|
declare module 'virtual:user-content-script-main-world' {
|
||||||
const definition: import('~/types').ContentScriptMainWorldDefinition;
|
const definition: import('wxt/types').ContentScriptMainWorldDefinition;
|
||||||
export default definition;
|
export default definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'virtual:user-unlisted-script' {
|
declare module 'virtual:user-unlisted-script' {
|
||||||
const definition: import('~/types').UnlistedScriptDefinition;
|
const definition: import('wxt/types').UnlistedScriptDefinition;
|
||||||
export default definition;
|
export default definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Globals defined by the vite-plugins/devServerGlobals.ts and utils/globals.ts
|
|
||||||
declare const __COMMAND__: 'build' | 'serve';
|
|
||||||
declare const __DEV_SERVER_PROTOCOL__: string;
|
|
||||||
declare const __DEV_SERVER_HOSTNAME__: string;
|
|
||||||
declare const __DEV_SERVER_PORT__: string;
|
|
||||||
declare const __MANIFEST_VERSION__: 2 | 3;
|
|
||||||
declare const __ENTRYPOINT__: string;
|
|
||||||
|
|||||||
+1
-1
@@ -5,5 +5,5 @@
|
|||||||
"~/*": ["./src/*"]
|
"~/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["dist", "demo", "e2e/dist", "templates"]
|
"exclude": ["dist", "demo", "e2e/dist", "templates", "src/virtual"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user