mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
(IDEA-327207) Settings Transfer: new plugin mapping customization
GitOrigin-RevId: 093663162362ffc887bb3a8896b789285f90cd2f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c04e4a4748
commit
496d040682
83
platform/platform-impl/resources/pluginData/general.json
Normal file
83
platform/platform-impl/resources/pluginData/general.json
Normal file
@@ -0,0 +1,83 @@
|
||||
[
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Ideolog",
|
||||
"ideaId": "com.intellij.ideolog",
|
||||
"disabled": false,
|
||||
"vsCodeName": "Log File Highlighter",
|
||||
"builtIn": false,
|
||||
"vsCodeId": "emilast.LogFileHighlighter"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "IdeaVIM",
|
||||
"ideaId": "IdeaVIM",
|
||||
"disabled": false,
|
||||
"vsCodeName": "Vim",
|
||||
"builtIn": false,
|
||||
"vsCodeId": "vscodevim.vim"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "NodeJS support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "[Deprecated] Node Debug",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "ms-vscode.node-debug2"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Git",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Git History",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "donjayamanne.githistory"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Git",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "GitLens — Git supercharged",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "eamodio.gitlens"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Git",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Git Blame",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "waderyan.gitblame"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Git",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Git Graph",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "mhutchie.git-graph"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Web support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Live Server",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "ritwickdey.LiveServer"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Docker",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Docker",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "ms-azuretools.vscode-docker"
|
||||
}
|
||||
]
|
||||
11
platform/platform-impl/resources/pluginData/pc.json
Normal file
11
platform/platform-impl/resources/pluginData/pc.json
Normal file
@@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"vsCodeId": "ms-python.python",
|
||||
"vsCodeName": "Python",
|
||||
"ideaId": "com.intellij.python",
|
||||
"ideaName": "Python",
|
||||
"disabled": false,
|
||||
"builtIn": true,
|
||||
"bundled": false
|
||||
}
|
||||
]
|
||||
146
platform/platform-impl/resources/pluginData/rm.json
Normal file
146
platform/platform-impl/resources/pluginData/rm.json
Normal file
@@ -0,0 +1,146 @@
|
||||
[
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Ruby support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Ruby LSP",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "Shopify.ruby-lsp"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Ruby support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Ruby Solargraph",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "castwide.solargraph"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Ruby Debugger",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "VSCode rdbg Ruby Debugger",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "KoichiSasada.vscode-rdbg"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Rubocop support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "ruby-rubocop",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "misogi.ruby-rubocop"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Ruby Sorbet support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Ruby Sorbet",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "sorbet.sorbet-vscode-extension"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Ruby Test Runner",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Ruby Test Explorer",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "connorshea.vscode-ruby-test-adapter"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Ruby support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Endwise",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "kaiwood.endwise"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "ERB support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "erb",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "CraigMaslowski.erb"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "RSpec support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Rails Run Specs",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "noku.rails-run-spec-vscode"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Rails support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Rails",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "bung87.rails"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Rails Routes",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Rails Routes",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "aki77.rails-routes"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "ERB support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "ERB Foramtter/Beautify",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "aliariff.vscode-erb-beautify"
|
||||
},
|
||||
{
|
||||
"bundled": true,
|
||||
"ideaName": "HAML support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Better HAML",
|
||||
"builtIn": false,
|
||||
"vsCodeId": "karunamurti.haml"
|
||||
},
|
||||
{
|
||||
"bundled": true,
|
||||
"ideaName": "Slim support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Slim",
|
||||
"builtIn": false,
|
||||
"vsCodeId": "sianglim.slim"
|
||||
},
|
||||
{
|
||||
"bundled": true,
|
||||
"ideaName": "Liquid support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "Shopify Liquid",
|
||||
"builtIn": false,
|
||||
"vsCodeId": "Shopify.theme-check-vscode"
|
||||
},
|
||||
{
|
||||
"bundled": false,
|
||||
"ideaName": "Gemfile support",
|
||||
"ideaId": null,
|
||||
"disabled": false,
|
||||
"vsCodeName": "vscode-gemfile",
|
||||
"builtIn": true,
|
||||
"vsCodeId": "bung87.vscode-gemfile"
|
||||
}
|
||||
]
|
||||
@@ -61,11 +61,10 @@ enum class TransferableIdeFeatureId {
|
||||
Monokai,
|
||||
NuGet,
|
||||
Prettier,
|
||||
Python,
|
||||
ReSharper,
|
||||
RunConfigurations,
|
||||
Rust,
|
||||
Scala,
|
||||
Rust,
|
||||
Solarized,
|
||||
SpellChecker,
|
||||
TeamCity,
|
||||
|
||||
@@ -50,7 +50,6 @@ object KnownPlugins {
|
||||
|
||||
// Plugins
|
||||
|
||||
val Python = PluginFeature(TransferableIdeFeatureId.Python, "com.intellij.python", "Pythonid")
|
||||
val XAMLStyler: PluginFeature = PluginFeature(TransferableIdeFeatureId.XamlStyler, "xamlstyler.rider", "XAML Styler")
|
||||
val Ideolog: PluginFeature = PluginFeature(TransferableIdeFeatureId.Ideolog, "com.intellij.ideolog", "Ideolog (logging)")
|
||||
val IdeaVim: PluginFeature = PluginFeature(TransferableIdeFeatureId.IdeaVim, "IdeaVIM", "IdeaVIM")
|
||||
@@ -69,7 +68,3 @@ object KnownPlugins {
|
||||
val DummyBuiltInFeature: BuiltInFeature = BuiltInFeature(TransferableIdeFeatureId.DummyBuiltInFeature, "")
|
||||
val DummyPlugin: PluginFeature = PluginFeature(TransferableIdeFeatureId.DummyPlugin, "", "")
|
||||
}
|
||||
|
||||
object KnownBuiltInFeatures {
|
||||
val Python = BuiltInFeature(TransferableIdeFeatureId.Python, "Python")
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ object TransferSettingsCollector : CounterUsagesCollector() {
|
||||
|
||||
if (settings.preferences.plugins) {
|
||||
for (plugin in settings.plugins) {
|
||||
featureImported.log(plugin.transferableId, ide)
|
||||
featureImported.log(plugin.transferableId ?: TransferableIdeFeatureId.DummyBuiltInFeature, ide)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,7 @@ object TransferSettingsCollector : CounterUsagesCollector() {
|
||||
logger.runAndLogException {
|
||||
val ide = ideVersion.transferableId
|
||||
for (plugin in settings.plugins) {
|
||||
featureDetected.log(ide, plugin.transferableId)
|
||||
featureDetected.log(ide, plugin.transferableId ?: TransferableIdeFeatureId.DummyBuiltInFeature)
|
||||
}
|
||||
recentProjectsDetected.log(ide, settings.recentProjects.size)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import com.intellij.openapi.util.NlsSafe
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
abstract class FeatureInfo(
|
||||
val transferableId: TransferableIdeFeatureId,
|
||||
@Deprecated("Not used anymore")
|
||||
val transferableId: TransferableIdeFeatureId?,
|
||||
@NlsSafe val name: String,
|
||||
@Nls val hint: String? = null,
|
||||
val isHidden: Boolean = false
|
||||
@@ -16,14 +17,14 @@ abstract class FeatureInfo(
|
||||
}
|
||||
|
||||
class BuiltInFeature(
|
||||
transferableId: TransferableIdeFeatureId,
|
||||
transferableId: TransferableIdeFeatureId?,
|
||||
@NlsSafe name: String,
|
||||
@Nls hint: String? = null,
|
||||
isHidden: Boolean = false
|
||||
) : FeatureInfo(transferableId, name, hint, isHidden)
|
||||
|
||||
class PluginFeature(
|
||||
transferableId: TransferableIdeFeatureId,
|
||||
transferableId: TransferableIdeFeatureId?,
|
||||
val pluginId: String,
|
||||
@NlsSafe name: String,
|
||||
@Nls hint: String? = null,
|
||||
|
||||
@@ -2,8 +2,17 @@
|
||||
package com.intellij.ide.customize.transferSettings.providers.vscode.mappings
|
||||
|
||||
import com.intellij.ide.customize.transferSettings.db.KnownPlugins
|
||||
import com.intellij.ide.customize.transferSettings.models.BuiltInFeature
|
||||
import com.intellij.ide.customize.transferSettings.models.FeatureInfo
|
||||
import com.intellij.ide.customize.transferSettings.models.PluginFeature
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.diagnostic.runAndLogException
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.util.PlatformUtils
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
|
||||
/**
|
||||
* Allows to register plugins of third-party products for importing from VSCode.
|
||||
@@ -28,7 +37,6 @@ open class VSCodePluginMappingBase(private val map: Map<String, FeatureInfo>) :
|
||||
private val commonPluginMap = mapOf(
|
||||
// Plugins
|
||||
"emilast.logfilehighlighter" to KnownPlugins.Ideolog,
|
||||
"ms-python.python" to KnownPlugins.Python,
|
||||
"xinyayang0506.log-analysis" to KnownPlugins.Ideolog,
|
||||
"vscodevim.vim" to KnownPlugins.IdeaVim,
|
||||
"msveden.teamcity-checker" to KnownPlugins.TeamCity,
|
||||
@@ -96,7 +104,57 @@ val DotNetFeatures = mapOf(
|
||||
"icsharpcode.ilspy-vscode" to KnownPlugins.DotNetDecompiler,
|
||||
)
|
||||
|
||||
class CommonPluginMapping : VSCodePluginMappingBase(commonPluginMap)
|
||||
@Serializable
|
||||
private data class FeatureData(
|
||||
val vsCodeId: String,
|
||||
val vsCodeName: String,
|
||||
val ideaId: String?,
|
||||
val ideaName: String,
|
||||
val builtIn: Boolean,
|
||||
val bundled: Boolean,
|
||||
val disabled: Boolean
|
||||
)
|
||||
|
||||
private val logger = logger<CommonPluginMapping>()
|
||||
|
||||
internal class CommonPluginMapping : VSCodePluginMapping {
|
||||
|
||||
private fun getResourceMappings(): List<String> = when {
|
||||
PlatformUtils.isPyCharm() -> listOf("pc.json", "general.json")
|
||||
PlatformUtils.isRubyMine() -> listOf("rm.json", "general.json")
|
||||
else -> listOf("general.json")
|
||||
}
|
||||
|
||||
val allPlugins by lazy {
|
||||
val resourceNames = getResourceMappings()
|
||||
val result = mutableMapOf<String, FeatureInfo>()
|
||||
for (resourceName in resourceNames) {
|
||||
logger.runAndLogException {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
val features = this.javaClass.classLoader.getResourceAsStream("pluginData/$resourceName").use { file ->
|
||||
Json.decodeFromStream<List<FeatureData>>(file)
|
||||
}
|
||||
for (data in features) {
|
||||
val isBundled = data.bundled || data.builtIn
|
||||
val feature =
|
||||
if (isBundled) BuiltInFeature(null, data.ideaName)
|
||||
else {
|
||||
if (data.ideaId == null) {
|
||||
logger.error("Cannot determine IntelliJ plugin id for feature $data.")
|
||||
continue
|
||||
}
|
||||
PluginFeature(null, data.ideaId, data.ideaName)
|
||||
}
|
||||
result[data.vsCodeId.lowercase()] = feature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
override fun mapPlugin(pluginId: String) = allPlugins[pluginId.lowercase()]
|
||||
}
|
||||
|
||||
object PluginsMappings {
|
||||
|
||||
|
||||
@@ -4,15 +4,12 @@ package com.intellij.ide.startup.importSettings.transfer
|
||||
import com.intellij.ide.customize.transferSettings.DefaultTransferSettingsConfiguration
|
||||
import com.intellij.ide.customize.transferSettings.TransferSettingsDataProvider
|
||||
import com.intellij.ide.customize.transferSettings.controllers.TransferSettingsListener
|
||||
import com.intellij.ide.customize.transferSettings.models.FeatureInfo
|
||||
import com.intellij.ide.customize.transferSettings.models.IdeVersion
|
||||
import com.intellij.ide.customize.transferSettings.models.PluginFeature
|
||||
import com.intellij.ide.customize.transferSettings.models.Settings
|
||||
import com.intellij.ide.customize.transferSettings.models.SettingsPreferencesKind
|
||||
import com.intellij.ide.customize.transferSettings.providers.PluginInstallationState
|
||||
import com.intellij.ide.customize.transferSettings.providers.TransferSettingsPerformContext
|
||||
import com.intellij.ide.customize.transferSettings.providers.vscode.VSCodeTransferSettingsProvider
|
||||
import com.intellij.ide.plugins.marketplace.MarketplaceRequests
|
||||
import com.intellij.ide.startup.importSettings.ImportSettingsBundle
|
||||
import com.intellij.ide.startup.importSettings.data.BaseSetting
|
||||
import com.intellij.ide.startup.importSettings.data.DataForSave
|
||||
@@ -29,7 +26,6 @@ import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.diagnostic.runAndLogException
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.rd.util.withSyncIOBackgroundContext
|
||||
import com.intellij.util.containers.nullize
|
||||
@@ -64,36 +60,11 @@ class SettingTransferService : ExternalService {
|
||||
shouldDisplayFailedVersions = false
|
||||
)
|
||||
|
||||
data class ThirdPartyProductSettings(
|
||||
val originalSettings: Settings,
|
||||
val compatibleFeatures: Deferred<List<FeatureInfo>>
|
||||
) {
|
||||
val laf
|
||||
get() = originalSettings.laf
|
||||
val keymap
|
||||
get() = originalSettings.keymap
|
||||
val recentProjects
|
||||
get() = originalSettings.recentProjects
|
||||
|
||||
/**
|
||||
* If this is unable to get the compatible plugins "quickly", then it just returns all the plugins.
|
||||
*/
|
||||
fun getCompatiblePluginsQuickly(): List<FeatureInfo> =
|
||||
@Suppress("RAW_RUN_BLOCKING")
|
||||
runBlocking {
|
||||
logger.runAndLogException {
|
||||
withTimeout(1.seconds) {
|
||||
compatibleFeatures.await()
|
||||
}
|
||||
} ?: originalSettings.plugins
|
||||
}
|
||||
}
|
||||
|
||||
data class ThirdPartyProductInfo(
|
||||
val product: IdeVersion,
|
||||
val settings: Deferred<ThirdPartyProductSettings>
|
||||
val settings: Deferred<Settings>
|
||||
) {
|
||||
fun getSettingsQuickly(): ThirdPartyProductSettings? =
|
||||
fun getSettingsQuickly(): Settings? =
|
||||
@Suppress("RAW_RUN_BLOCKING")
|
||||
runBlocking {
|
||||
logger.runAndLogException {
|
||||
@@ -123,27 +94,10 @@ class SettingTransferService : ExternalService {
|
||||
return versions
|
||||
}
|
||||
|
||||
private suspend fun CoroutineScope.loadIdeVersionSettingsAsync(version: IdeVersion): ThirdPartyProductSettings {
|
||||
val settings = version.settingsCache
|
||||
val pluginById = settings.plugins.filterIsInstance<PluginFeature>().map { it.pluginId to it }.toMap()
|
||||
val pluginIds = pluginById.keys.map { PluginId.getId(it) }.toSet()
|
||||
val actualFeatures = async {
|
||||
val compatiblePluginIds =
|
||||
logger.runAndLogException {
|
||||
withSyncIOBackgroundContext {
|
||||
val compatiblePlugins = MarketplaceRequests.getLastCompatiblePluginUpdate(pluginIds)
|
||||
compatiblePlugins.mapNotNull { it.pluginId.toString() }
|
||||
}
|
||||
} ?: pluginIds
|
||||
settings.plugins.filter {
|
||||
when (it) {
|
||||
is PluginFeature -> compatiblePluginIds.contains(it.pluginId)
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
private suspend fun CoroutineScope.loadIdeVersionSettingsAsync(version: IdeVersion): Settings =
|
||||
withSyncIOBackgroundContext {
|
||||
version.settingsCache
|
||||
}
|
||||
return ThirdPartyProductSettings(settings, actualFeatures)
|
||||
}
|
||||
|
||||
override suspend fun warmUp() {
|
||||
coroutineScope {
|
||||
@@ -186,7 +140,7 @@ class SettingTransferService : ExternalService {
|
||||
buildList {
|
||||
settings.laf?.let(TransferableSetting::uiTheme)?.let(::add)
|
||||
settings.keymap?.let(TransferableSetting::keymap)?.let(::add)
|
||||
settings.getCompatiblePluginsQuickly().nullize()?.let(TransferableSetting::plugins)?.let(::add)
|
||||
settings.plugins.nullize()?.let(TransferableSetting::plugins)?.let(::add)
|
||||
settings.recentProjects.nullize()?.let { TransferableSetting.recentProjects() }?.let(::add)
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.customization
|
||||
|
||||
import com.intellij.ide.customize.transferSettings.db.KnownBuiltInFeatures
|
||||
import com.intellij.ide.customize.transferSettings.providers.vscode.mappings.VSCodePluginMappingBase
|
||||
|
||||
class PythonPluginMapping : VSCodePluginMappingBase(mapOf("ms-python.python" to KnownBuiltInFeatures.Python))
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
<renameHandler implementation="com.intellij.platform.renameProject.RenameProjectHandler"/>
|
||||
<renameHandler implementation="com.intellij.platform.renameProject.ProjectFolderRenameHandler"/>
|
||||
<transferSettings.vscode.pluginMapping order="before CommonPluginMapping"
|
||||
implementation="com.intellij.pycharm.community.customization.PythonPluginMapping"/>
|
||||
</extensions>
|
||||
|
||||
<actions>
|
||||
|
||||
Reference in New Issue
Block a user