mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-23 14:15:13 +00:00
refactor: cleanups and code style improvements (#4788)
This commit is contained in:
parent
db4001dae1
commit
40fbe5ba47
@ -46,7 +46,7 @@ module.exports = (context, options = {}) => {
|
|||||||
decoratorsLegacy
|
decoratorsLegacy
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
let targets = options.targets
|
let { targets } = options
|
||||||
if (modern === true) {
|
if (modern === true) {
|
||||||
targets = { esmodules: true }
|
targets = { esmodules: true }
|
||||||
} else if (targets === undefined) {
|
} else if (targets === undefined) {
|
||||||
|
@ -96,7 +96,7 @@ export default class Builder {
|
|||||||
const context = new BuildContext(this)
|
const context = new BuildContext(this)
|
||||||
|
|
||||||
if (typeof BundleBuilder !== 'function') {
|
if (typeof BundleBuilder !== 'function') {
|
||||||
BundleBuilder = require('@nuxt/webpack').BundleBuilder
|
({ BundleBuilder } = require('@nuxt/webpack'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BundleBuilder(context)
|
return new BundleBuilder(context)
|
||||||
@ -637,7 +637,7 @@ export default class Builder {
|
|||||||
.watch(customPatterns, options)
|
.watch(customPatterns, options)
|
||||||
.on('change', refreshFiles)
|
.on('change', refreshFiles)
|
||||||
|
|
||||||
const rewatchOnRawEvents = this.options.watchers.rewatchOnRawEvents
|
const { rewatchOnRawEvents } = this.options.watchers
|
||||||
if (rewatchOnRawEvents && Array.isArray(rewatchOnRawEvents)) {
|
if (rewatchOnRawEvents && Array.isArray(rewatchOnRawEvents)) {
|
||||||
this.watchers.custom.on('raw', (_event, _path, opts) => {
|
this.watchers.custom.on('raw', (_event, _path, opts) => {
|
||||||
if (rewatchOnRawEvents.includes(_event)) {
|
if (rewatchOnRawEvents.includes(_event)) {
|
||||||
|
@ -13,7 +13,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async run(cmd) {
|
async run(cmd) {
|
||||||
const argv = cmd.argv
|
const { argv } = cmd
|
||||||
await this.startDev(cmd, argv)
|
await this.startDev(cmd, argv)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ export default {
|
|||||||
version: common.version
|
version: common.version
|
||||||
},
|
},
|
||||||
async run(cmd) {
|
async run(cmd) {
|
||||||
const name = cmd._argv[0]
|
const [name] = cmd._argv
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return listCommands()
|
return listCommands()
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,8 @@ export function getNuxtConfig(_options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply default hash to CSP option
|
// Apply default hash to CSP option
|
||||||
const csp = options.render.csp
|
const { csp } = options.render
|
||||||
|
|
||||||
const cspDefaults = {
|
const cspDefaults = {
|
||||||
hashAlgorithm: 'sha256',
|
hashAlgorithm: 'sha256',
|
||||||
allowedSources: undefined,
|
allowedSources: undefined,
|
||||||
@ -297,7 +298,7 @@ export function getNuxtConfig(_options) {
|
|||||||
options.build.optimizeCSS = options.build.extractCSS ? {} : false
|
options.build.optimizeCSS = options.build.extractCSS ? {} : false
|
||||||
}
|
}
|
||||||
|
|
||||||
const loaders = options.build.loaders
|
const { loaders } = options.build
|
||||||
const vueLoader = loaders.vue
|
const vueLoader = loaders.vue
|
||||||
if (vueLoader.productionMode === undefined) {
|
if (vueLoader.productionMode === undefined) {
|
||||||
vueLoader.productionMode = !options.dev
|
vueLoader.productionMode = !options.dev
|
||||||
|
@ -121,13 +121,10 @@ export default class ModuleContainer {
|
|||||||
src = moduleOpts
|
src = moduleOpts
|
||||||
} else if (Array.isArray(moduleOpts)) {
|
} else if (Array.isArray(moduleOpts)) {
|
||||||
// Type 2: Babel style array
|
// Type 2: Babel style array
|
||||||
src = moduleOpts[0]
|
[src, options] = moduleOpts
|
||||||
options = moduleOpts[1]
|
|
||||||
} else if (typeof moduleOpts === 'object') {
|
} else if (typeof moduleOpts === 'object') {
|
||||||
// Type 3: Pure object
|
// Type 3: Pure object
|
||||||
src = moduleOpts.src
|
({ src, options, handler } = moduleOpts)
|
||||||
options = moduleOpts.options
|
|
||||||
handler = moduleOpts.handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve handler
|
// Resolve handler
|
||||||
|
@ -205,7 +205,7 @@ export default class Generator {
|
|||||||
_generate: true,
|
_generate: true,
|
||||||
payload
|
payload
|
||||||
})
|
})
|
||||||
html = res.html
|
;({ html } = res)
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
pageErrors.push({ type: 'handled', route, error: res.error })
|
pageErrors.push({ type: 'handled', route, error: res.error })
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ const detectModernBrowser = ({ socket = {}, headers }) => {
|
|||||||
|
|
||||||
const setModernMode = (req, options) => {
|
const setModernMode = (req, options) => {
|
||||||
const { socket = {} } = req
|
const { socket = {} } = req
|
||||||
const isModernBrowser = socket.isModernBrowser
|
const { isModernBrowser } = socket
|
||||||
if (options.modern === 'server') {
|
if (options.modern === 'server') {
|
||||||
req.modernMode = isModernBrowser
|
req.modernMode = isModernBrowser
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ const defaultPushAssets = (preloadFiles, shouldPush, publicPath, options) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const crossorigin = options.build.crossorigin
|
const { crossorigin } = options.build
|
||||||
const cors = `${crossorigin ? ` crossorigin=${crossorigin};` : ''}`
|
const cors = `${crossorigin ? ` crossorigin=${crossorigin};` : ''}`
|
||||||
const ref = modern ? 'modulepreload' : 'preload'
|
const ref = modern ? 'modulepreload' : 'preload'
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ export default class Server {
|
|||||||
|
|
||||||
// Compression middleware for production
|
// Compression middleware for production
|
||||||
if (!this.options.dev) {
|
if (!this.options.dev) {
|
||||||
const compressor = this.options.render.compressor
|
const { compressor } = this.options.render
|
||||||
if (typeof compressor === 'object') {
|
if (typeof compressor === 'object') {
|
||||||
// If only setting for `compression` are provided, require the module and insert
|
// If only setting for `compression` are provided, require the module and insert
|
||||||
const compression = this.nuxt.resolver.requireModule('compression')
|
const compression = this.nuxt.resolver.requireModule('compression')
|
||||||
|
@ -14,19 +14,19 @@ export const chainFn = function chainFn(base, fn) {
|
|||||||
if (typeof fn !== 'function') {
|
if (typeof fn !== 'function') {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
return function () {
|
return function (...args) {
|
||||||
if (typeof base !== 'function') {
|
if (typeof base !== 'function') {
|
||||||
return fn.apply(this, arguments)
|
return fn.apply(this, args)
|
||||||
}
|
}
|
||||||
let baseResult = base.apply(this, arguments)
|
let baseResult = base.apply(this, args)
|
||||||
// Allow function to mutate the first argument instead of returning the result
|
// Allow function to mutate the first argument instead of returning the result
|
||||||
if (baseResult === undefined) {
|
if (baseResult === undefined) {
|
||||||
baseResult = arguments[0]
|
[baseResult] = args
|
||||||
}
|
}
|
||||||
const fnResult = fn.call(
|
const fnResult = fn.call(
|
||||||
this,
|
this,
|
||||||
baseResult,
|
baseResult,
|
||||||
...Array.prototype.slice.call(arguments, 1)
|
...Array.prototype.slice.call(args, 1)
|
||||||
)
|
)
|
||||||
// Return mutated argument if no result was returned
|
// Return mutated argument if no result was returned
|
||||||
if (fnResult === undefined) {
|
if (fnResult === undefined) {
|
||||||
|
@ -52,12 +52,11 @@ export default class VueRenderer {
|
|||||||
|
|
||||||
renderScripts(context) {
|
renderScripts(context) {
|
||||||
if (this.context.options.modern === 'client') {
|
if (this.context.options.modern === 'client') {
|
||||||
const publicPath = this.context.options.build.publicPath
|
const { publicPath, crossorigin } = this.context.options.build
|
||||||
const scriptPattern = /<script[^>]*?src="([^"]*?)"[^>]*?>[^<]*?<\/script>/g
|
const scriptPattern = /<script[^>]*?src="([^"]*?)"[^>]*?>[^<]*?<\/script>/g
|
||||||
return context.renderScripts().replace(scriptPattern, (scriptTag, jsFile) => {
|
return context.renderScripts().replace(scriptPattern, (scriptTag, jsFile) => {
|
||||||
const legacyJsFile = jsFile.replace(publicPath, '')
|
const legacyJsFile = jsFile.replace(publicPath, '')
|
||||||
const modernJsFile = this.assetsMapping[legacyJsFile]
|
const modernJsFile = this.assetsMapping[legacyJsFile]
|
||||||
const crossorigin = this.context.options.build.crossorigin
|
|
||||||
const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}`
|
const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}`
|
||||||
const moduleTag = modernJsFile
|
const moduleTag = modernJsFile
|
||||||
? scriptTag
|
? scriptTag
|
||||||
@ -94,7 +93,7 @@ export default class VueRenderer {
|
|||||||
|
|
||||||
renderResourceHints(context) {
|
renderResourceHints(context) {
|
||||||
if (this.context.options.modern === 'client') {
|
if (this.context.options.modern === 'client') {
|
||||||
const publicPath = this.context.options.build.publicPath
|
const { publicPath, crossorigin } = this.context.options.build
|
||||||
const linkPattern = /<link[^>]*?href="([^"]*?)"[^>]*?as="script"[^>]*?>/g
|
const linkPattern = /<link[^>]*?href="([^"]*?)"[^>]*?as="script"[^>]*?>/g
|
||||||
return context.renderResourceHints().replace(linkPattern, (linkTag, jsFile) => {
|
return context.renderResourceHints().replace(linkPattern, (linkTag, jsFile) => {
|
||||||
const legacyJsFile = jsFile.replace(publicPath, '')
|
const legacyJsFile = jsFile.replace(publicPath, '')
|
||||||
@ -102,7 +101,6 @@ export default class VueRenderer {
|
|||||||
if (!modernJsFile) {
|
if (!modernJsFile) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
const crossorigin = this.context.options.build.crossorigin
|
|
||||||
const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}`
|
const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}`
|
||||||
return linkTag.replace('rel="preload"', `rel="modulepreload"${cors}`).replace(legacyJsFile, modernJsFile)
|
return linkTag.replace('rel="preload"', `rel="modulepreload"${cors}`).replace(legacyJsFile, modernJsFile)
|
||||||
})
|
})
|
||||||
@ -145,7 +143,7 @@ export default class VueRenderer {
|
|||||||
loadResources(_fs, isMFS = false) {
|
loadResources(_fs, isMFS = false) {
|
||||||
const distPath = path.resolve(this.context.options.buildDir, 'dist', 'server')
|
const distPath = path.resolve(this.context.options.buildDir, 'dist', 'server')
|
||||||
const updated = []
|
const updated = []
|
||||||
const resourceMap = this.resourceMap
|
const { resourceMap } = this
|
||||||
|
|
||||||
const readResource = (fileName, encoding) => {
|
const readResource = (fileName, encoding) => {
|
||||||
try {
|
try {
|
||||||
|
@ -73,10 +73,9 @@ export default class SPAMetaRenderer {
|
|||||||
|
|
||||||
meta.resourceHints = ''
|
meta.resourceHints = ''
|
||||||
|
|
||||||
const clientManifest = this.renderer.context.resources.clientManifest
|
const { clientManifest } = this.renderer.context.resources
|
||||||
|
|
||||||
const shouldPreload = this.options.render.bundleRenderer.shouldPreload
|
const { shouldPreload, shouldPrefetch } = this.options.render.bundleRenderer
|
||||||
const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch
|
|
||||||
|
|
||||||
if (this.options.render.resourceHints && clientManifest) {
|
if (this.options.render.resourceHints && clientManifest) {
|
||||||
const publicPath = clientManifest.publicPath || '/_nuxt/'
|
const publicPath = clientManifest.publicPath || '/_nuxt/'
|
||||||
|
@ -38,7 +38,7 @@ export class WebpackBundler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async build() {
|
async build() {
|
||||||
const options = this.context.options
|
const { options } = this.context
|
||||||
|
|
||||||
const compilersOptions = []
|
const compilersOptions = []
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ export class WebpackBundler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check styleResource existence
|
// Check styleResource existence
|
||||||
const styleResources = this.context.options.build.styleResources
|
const { styleResources } = this.context.options.build
|
||||||
if (styleResources && Object.keys(styleResources).length) {
|
if (styleResources && Object.keys(styleResources).length) {
|
||||||
consola.warn(
|
consola.warn(
|
||||||
'Using styleResources without the nuxt-style-resources-module is not suggested and can lead to severe performance issues.',
|
'Using styleResources without the nuxt-style-resources-module is not suggested and can lead to severe performance issues.',
|
||||||
@ -123,7 +123,7 @@ export class WebpackBundler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async webpackCompile(compiler) {
|
async webpackCompile(compiler) {
|
||||||
const name = compiler.options.name
|
const { name } = compiler.options
|
||||||
const { nuxt, options } = this.context
|
const { nuxt, options } = this.context
|
||||||
|
|
||||||
await nuxt.callHook('build:compile', { name, compiler })
|
await nuxt.callHook('build:compile', { name, compiler })
|
||||||
@ -180,7 +180,7 @@ export class WebpackBundler {
|
|||||||
webpackDev(compiler) {
|
webpackDev(compiler) {
|
||||||
consola.debug('Adding webpack middleware...')
|
consola.debug('Adding webpack middleware...')
|
||||||
|
|
||||||
const name = compiler.options.name
|
const { name } = compiler.options
|
||||||
const { nuxt: { server }, options } = this.context
|
const { nuxt: { server }, options } = this.context
|
||||||
|
|
||||||
// Create webpack dev middleware
|
// Create webpack dev middleware
|
||||||
|
@ -19,7 +19,7 @@ export default class WebpackClientConfig extends WebpackBaseConfig {
|
|||||||
|
|
||||||
getFileName(...args) {
|
getFileName(...args) {
|
||||||
if (this.options.build.analyze) {
|
if (this.options.build.analyze) {
|
||||||
const key = args[0]
|
const [key] = args
|
||||||
if (['app', 'chunk'].includes(key)) {
|
if (['app', 'chunk'].includes(key)) {
|
||||||
return `${this.isModern ? 'modern-' : ''}[name].js`
|
return `${this.isModern ? 'modern-' : ''}[name].js`
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,16 @@ export default class CorsPlugin {
|
|||||||
const ID = `vue-cors-plugin`
|
const ID = `vue-cors-plugin`
|
||||||
compiler.hooks.compilation.tap(ID, (compilation) => {
|
compiler.hooks.compilation.tap(ID, (compilation) => {
|
||||||
compilation.hooks.htmlWebpackPluginAlterAssetTags.tap(ID, (data) => {
|
compilation.hooks.htmlWebpackPluginAlterAssetTags.tap(ID, (data) => {
|
||||||
if (this.crossorigin != null) {
|
if (!this.crossorigin) {
|
||||||
|
return
|
||||||
|
}
|
||||||
[...data.head, ...data.body].forEach((tag) => {
|
[...data.head, ...data.body].forEach((tag) => {
|
||||||
if (tag.tagName === 'script' || tag.tagName === 'link') {
|
if (['script', 'link'].includes(tag.tagName)) {
|
||||||
if (tag.attributes) {
|
if (tag.attributes) {
|
||||||
tag.attributes.crossorigin = this.crossorigin
|
tag.attributes.crossorigin = this.crossorigin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ export default class VueSSRServerPlugin {
|
|||||||
|
|
||||||
onEmit(compiler, 'vue-server-plugin', (compilation, cb) => {
|
onEmit(compiler, 'vue-server-plugin', (compilation, cb) => {
|
||||||
const stats = compilation.getStats().toJson()
|
const stats = compilation.getStats().toJson()
|
||||||
const entryName = Object.keys(stats.entrypoints)[0]
|
const [entryName] = Object.keys(stats.entrypoints)
|
||||||
const entryInfo = stats.entrypoints[entryName]
|
const entryInfo = stats.entrypoints[entryName]
|
||||||
|
|
||||||
if (!entryInfo) {
|
if (!entryInfo) {
|
||||||
@ -29,7 +29,7 @@ export default class VueSSRServerPlugin {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = entryAssets[0]
|
const [entry] = entryAssets
|
||||||
if (!entry || typeof entry !== 'string') {
|
if (!entry || typeof entry !== 'string') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Entry "${entryName}" not found. Did you specify the correct entry option?`
|
`Entry "${entryName}" not found. Did you specify the correct entry option?`
|
||||||
|
@ -108,7 +108,7 @@ export default class PostcssConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadPlugins(config) {
|
loadPlugins(config) {
|
||||||
const plugins = config.plugins
|
const { plugins } = config
|
||||||
if (isPureObject(plugins)) {
|
if (isPureObject(plugins)) {
|
||||||
// Map postcss plugins into instances on object mode once
|
// Map postcss plugins into instances on object mode once
|
||||||
config.plugins = this.sortPlugins(config)
|
config.plugins = this.sortPlugins(config)
|
||||||
|
@ -107,7 +107,8 @@ export default class Package {
|
|||||||
tryRequire(id) {
|
tryRequire(id) {
|
||||||
try {
|
try {
|
||||||
return require(id)
|
return require(id)
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suffixAndVersion() {
|
suffixAndVersion() {
|
||||||
@ -132,7 +133,7 @@ export default class Package {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this.pkg.bin === 'string') {
|
if (typeof this.pkg.bin === 'string') {
|
||||||
const bin = this.pkg.bin
|
const { bin } = this.pkg
|
||||||
this.pkg.bin = {
|
this.pkg.bin = {
|
||||||
[oldPkgName]: bin,
|
[oldPkgName]: bin,
|
||||||
[this.pkg.name]: bin
|
[this.pkg.name]: bin
|
||||||
@ -217,13 +218,16 @@ export default class Package {
|
|||||||
watcher.on('event', (event) => {
|
watcher.on('event', (event) => {
|
||||||
switch (event.code) {
|
switch (event.code) {
|
||||||
// The watcher is (re)starting
|
// The watcher is (re)starting
|
||||||
case 'START': return this.logger.debug('Watching for changes')
|
case 'START':
|
||||||
|
return this.logger.debug('Watching for changes')
|
||||||
|
|
||||||
// Building an individual bundle
|
// Building an individual bundle
|
||||||
case 'BUNDLE_START': return this.logger.debug('Building bundle')
|
case 'BUNDLE_START':
|
||||||
|
return this.logger.debug('Building bundle')
|
||||||
|
|
||||||
// Finished building a bundle
|
// Finished building a bundle
|
||||||
case 'BUNDLE_END': return
|
case 'BUNDLE_END':
|
||||||
|
return
|
||||||
|
|
||||||
// Finished building all bundles
|
// Finished building all bundles
|
||||||
case 'END':
|
case 'END':
|
||||||
@ -231,13 +235,16 @@ export default class Package {
|
|||||||
return this.logger.success('Bundle built')
|
return this.logger.success('Bundle built')
|
||||||
|
|
||||||
// Encountered an error while bundling
|
// Encountered an error while bundling
|
||||||
case 'ERROR': return this.logger.error(event.error)
|
case 'ERROR':
|
||||||
|
return this.logger.error(event.error)
|
||||||
|
|
||||||
// Eencountered an unrecoverable error
|
// Eencountered an unrecoverable error
|
||||||
case 'FATAL': return this.logger.fatal(event.error)
|
case 'FATAL':
|
||||||
|
return this.logger.fatal(event.error)
|
||||||
|
|
||||||
// Unknown event
|
// Unknown event
|
||||||
default: return this.logger.info(JSON.stringify(event))
|
default:
|
||||||
|
return this.logger.info(JSON.stringify(event))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,7 +3,7 @@ import { buildFixture } from '../../utils/build'
|
|||||||
|
|
||||||
describe('missing-pages-dir', () => {
|
describe('missing-pages-dir', () => {
|
||||||
buildFixture('missing-pages-dir', (builder) => {
|
buildFixture('missing-pages-dir', (builder) => {
|
||||||
const options = builder.nuxt.options
|
const { options } = builder.nuxt
|
||||||
expect(consola.warn).toHaveBeenCalledTimes(1)
|
expect(consola.warn).toHaveBeenCalledTimes(1)
|
||||||
expect(consola.warn).toHaveBeenCalledWith(`No \`${options.dir.pages}\` directory found in ${options.srcDir}. Using the default built-in page.`)
|
expect(consola.warn).toHaveBeenCalledWith(`No \`${options.dir.pages}\` directory found in ${options.srcDir}. Using the default built-in page.`)
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,7 @@ const close = async (nuxtInt) => {
|
|||||||
describe.skip.win('cli', () => {
|
describe.skip.win('cli', () => {
|
||||||
test('nuxt dev', async () => {
|
test('nuxt dev', async () => {
|
||||||
let stdout = ''
|
let stdout = ''
|
||||||
const env = process.env
|
const { env } = process
|
||||||
env.PORT = port = await getPort()
|
env.PORT = port = await getPort()
|
||||||
|
|
||||||
const nuxtDev = spawnNuxt('dev', { env })
|
const nuxtDev = spawnNuxt('dev', { env })
|
||||||
@ -52,7 +52,7 @@ describe.skip.win('cli', () => {
|
|||||||
let stdout = ''
|
let stdout = ''
|
||||||
let error
|
let error
|
||||||
|
|
||||||
const env = process.env
|
const { env } = process
|
||||||
env.PORT = port = await getPort()
|
env.PORT = port = await getPort()
|
||||||
|
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user