mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 14:43:46 +01:00
Merge pull request #29899 from overleaf/mj-dark-mode-file-flash
[web] Avoid background color flash when switching files GitOrigin-RevId: e5d2fbb631fd54d195b9cb51b2a9db584d205138
This commit is contained in:
committed by
Copybot
parent
ff5b469b20
commit
7c1a225be4
@@ -1308,49 +1308,49 @@ const defaultUserValues = () => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const THEME_LIST = [
|
const THEME_LIST = [
|
||||||
'cobalt',
|
{ name: 'cobalt', dark: true },
|
||||||
'dracula',
|
{ name: 'dracula', dark: true },
|
||||||
'eclipse',
|
{ name: 'eclipse', dark: false },
|
||||||
'monokai',
|
{ name: 'monokai', dark: true },
|
||||||
'overleaf',
|
{ name: 'overleaf', dark: false },
|
||||||
'overleaf_dark',
|
{ name: 'overleaf_dark', dark: true },
|
||||||
'textmate',
|
{ name: 'textmate', dark: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
const LEGACY_THEME_LIST = [
|
const LEGACY_THEME_LIST = [
|
||||||
'ambiance',
|
{ name: 'ambiance', dark: true },
|
||||||
'chaos',
|
{ name: 'chaos', dark: true },
|
||||||
'chrome',
|
{ name: 'chrome', dark: false },
|
||||||
'clouds',
|
{ name: 'clouds', dark: false },
|
||||||
'clouds_midnight',
|
{ name: 'clouds_midnight', dark: true },
|
||||||
'crimson_editor',
|
{ name: 'crimson_editor', dark: false },
|
||||||
'dawn',
|
{ name: 'dawn', dark: false },
|
||||||
'dreamweaver',
|
{ name: 'dreamweaver', dark: false },
|
||||||
'github',
|
{ name: 'github', dark: false },
|
||||||
'gob',
|
{ name: 'gob', dark: true },
|
||||||
'gruvbox',
|
{ name: 'gruvbox', dark: true },
|
||||||
'idle_fingers',
|
{ name: 'idle_fingers', dark: true },
|
||||||
'iplastic',
|
{ name: 'iplastic', dark: false },
|
||||||
'katzenmilch',
|
{ name: 'katzenmilch', dark: false },
|
||||||
'kr_theme',
|
{ name: 'kr_theme', dark: true },
|
||||||
'kuroir',
|
{ name: 'kuroir', dark: false },
|
||||||
'merbivore',
|
{ name: 'merbivore', dark: true },
|
||||||
'merbivore_soft',
|
{ name: 'merbivore_soft', dark: true },
|
||||||
'mono_industrial',
|
{ name: 'mono_industrial', dark: true },
|
||||||
'nord_dark',
|
{ name: 'nord_dark', dark: true },
|
||||||
'pastel_on_dark',
|
{ name: 'pastel_on_dark', dark: true },
|
||||||
'solarized_dark',
|
{ name: 'solarized_dark', dark: true },
|
||||||
'solarized_light',
|
{ name: 'solarized_light', dark: false },
|
||||||
'sqlserver',
|
{ name: 'sqlserver', dark: false },
|
||||||
'terminal',
|
{ name: 'terminal', dark: true },
|
||||||
'tomorrow',
|
{ name: 'tomorrow', dark: false },
|
||||||
'tomorrow_night',
|
{ name: 'tomorrow_night', dark: true },
|
||||||
'tomorrow_night_blue',
|
{ name: 'tomorrow_night_blue', dark: true },
|
||||||
'tomorrow_night_bright',
|
{ name: 'tomorrow_night_bright', dark: true },
|
||||||
'tomorrow_night_eighties',
|
{ name: 'tomorrow_night_eighties', dark: true },
|
||||||
'twilight',
|
{ name: 'twilight', dark: true },
|
||||||
'vibrant_ink',
|
{ name: 'vibrant_ink', dark: true },
|
||||||
'xcode',
|
{ name: 'xcode', dark: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
const ProjectController = {
|
const ProjectController = {
|
||||||
|
|||||||
+4
-4
@@ -14,8 +14,8 @@ export default function SettingsEditorTheme() {
|
|||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
const editorThemeOptions: Array<Option> =
|
const editorThemeOptions: Array<Option> =
|
||||||
editorThemes?.map(theme => ({
|
editorThemes?.map(theme => ({
|
||||||
value: theme,
|
value: theme.name,
|
||||||
label: theme.replace(/_/g, ' '),
|
label: theme.name.replace(/_/g, ' '),
|
||||||
})) ?? []
|
})) ?? []
|
||||||
|
|
||||||
const dividerOption: Option = {
|
const dividerOption: Option = {
|
||||||
@@ -26,8 +26,8 @@ export default function SettingsEditorTheme() {
|
|||||||
|
|
||||||
const legacyEditorThemeOptions: Array<Option> =
|
const legacyEditorThemeOptions: Array<Option> =
|
||||||
legacyEditorThemes?.map(theme => ({
|
legacyEditorThemes?.map(theme => ({
|
||||||
value: theme,
|
value: theme.name,
|
||||||
label: theme.replace(/_/g, ' ') + ' (Legacy)',
|
label: theme.name.replace(/_/g, ' ') + ' (Legacy)',
|
||||||
})) ?? []
|
})) ?? []
|
||||||
|
|
||||||
return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]
|
return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { FC, useMemo } from 'react'
|
||||||
|
import LoadingSpinner from '@/shared/components/loading-spinner'
|
||||||
|
import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
|
||||||
|
import getMeta from '@/utils/meta'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
export const EditorLoadingPane: FC = () => {
|
||||||
|
const { editorTheme } = useProjectSettingsContext()
|
||||||
|
const isDark = useMemo(() => {
|
||||||
|
const themes = getMeta('ol-editorThemes') || []
|
||||||
|
const legacyThemes = getMeta('ol-legacyEditorThemes') || []
|
||||||
|
const selectedTheme =
|
||||||
|
themes.find(theme => theme.name === editorTheme) ||
|
||||||
|
legacyThemes.find(theme => theme.name === editorTheme)
|
||||||
|
return selectedTheme?.dark ?? false
|
||||||
|
}, [editorTheme])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('loading-panel', { 'loading-panel-dark': isDark })}
|
||||||
|
>
|
||||||
|
<LoadingSpinner />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ import SourceEditor from '@/features/source-editor/components/source-editor'
|
|||||||
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
||||||
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
|
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { LoadingPane } from '@/features/ide-react/components/editor/loading-pane'
|
import { EditorLoadingPane } from '@/features/ide-react/components/editor/editor-loading-pane'
|
||||||
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
||||||
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
|
import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle'
|
||||||
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
|
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
|
||||||
@@ -37,7 +37,7 @@ export const EditorPane: FC = () => {
|
|||||||
className="ide-react-editor-panel"
|
className="ide-react-editor-panel"
|
||||||
>
|
>
|
||||||
<SourceEditor />
|
<SourceEditor />
|
||||||
{isLoading && <LoadingPane />}
|
{isLoading && <EditorLoadingPane />}
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
{showSymbolPalette && (
|
{showSymbolPalette && (
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { FC } from 'react'
|
|
||||||
import LoadingSpinner from '@/shared/components/loading-spinner'
|
|
||||||
|
|
||||||
export const LoadingPane: FC = () => {
|
|
||||||
return (
|
|
||||||
<div className="loading-panel">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LoadingPane } from '@/features/ide-react/components/editor/loading-pane'
|
import { EditorLoadingPane } from '@/features/ide-react/components/editor/editor-loading-pane'
|
||||||
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
|
import { useEditorOpenDocContext } from '@/features/ide-react/context/editor-open-doc-context'
|
||||||
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
|
import { useFileTreeOpenContext } from '@/features/ide-react/context/file-tree-open-context'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
@@ -40,7 +40,7 @@ export const Editor = () => {
|
|||||||
className="ide-redesign-editor-panel"
|
className="ide-redesign-editor-panel"
|
||||||
>
|
>
|
||||||
<SourceEditor />
|
<SourceEditor />
|
||||||
{isLoading && <LoadingPane />}
|
{isLoading && <EditorLoadingPane />}
|
||||||
</Panel>
|
</Panel>
|
||||||
{showSymbolPalette && (
|
{showSymbolPalette && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
+4
-4
@@ -14,8 +14,8 @@ export default function EditorThemeSetting() {
|
|||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
const editorThemeOptions: Array<Option> =
|
const editorThemeOptions: Array<Option> =
|
||||||
editorThemes?.map(theme => ({
|
editorThemes?.map(theme => ({
|
||||||
value: theme,
|
value: theme.name,
|
||||||
label: theme.replace(/_/g, ' '),
|
label: theme.name.replace(/_/g, ' '),
|
||||||
})) ?? []
|
})) ?? []
|
||||||
|
|
||||||
const dividerOption: Option = {
|
const dividerOption: Option = {
|
||||||
@@ -26,8 +26,8 @@ export default function EditorThemeSetting() {
|
|||||||
|
|
||||||
const legacyEditorThemeOptions: Array<Option> =
|
const legacyEditorThemeOptions: Array<Option> =
|
||||||
legacyEditorThemes?.map(theme => ({
|
legacyEditorThemes?.map(theme => ({
|
||||||
value: theme,
|
value: theme.name,
|
||||||
label: theme.replace(/_/g, ' ') + ' (Legacy)',
|
label: theme.name.replace(/_/g, ' ') + ' (Legacy)',
|
||||||
})) ?? []
|
})) ?? []
|
||||||
|
|
||||||
return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]
|
return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]
|
||||||
|
|||||||
@@ -287,7 +287,8 @@ const loadSelectedTheme = async (editorTheme: string) => {
|
|||||||
const themes = getMeta('ol-editorThemes') || []
|
const themes = getMeta('ol-editorThemes') || []
|
||||||
const legacyThemes = getMeta('ol-legacyEditorThemes') || []
|
const legacyThemes = getMeta('ol-legacyEditorThemes') || []
|
||||||
const themeExists =
|
const themeExists =
|
||||||
themes.includes(editorTheme) || legacyThemes.includes(editorTheme)
|
themes.some(theme => theme.name === editorTheme) ||
|
||||||
|
legacyThemes.some(theme => theme.name === editorTheme)
|
||||||
if (!themeExists) {
|
if (!themeExists) {
|
||||||
editorTheme = 'textmate' // fallback to default if the theme is not found
|
editorTheme = 'textmate' // fallback to default if the theme is not found
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export interface Meta {
|
|||||||
'ol-domainCaptureEnabled': boolean | undefined
|
'ol-domainCaptureEnabled': boolean | undefined
|
||||||
'ol-domainCaptureTestURL': string | undefined
|
'ol-domainCaptureTestURL': string | undefined
|
||||||
'ol-dropbox': { error: boolean; registered: boolean }
|
'ol-dropbox': { error: boolean; registered: boolean }
|
||||||
'ol-editorThemes': string[]
|
'ol-editorThemes': { name: string; dark: boolean }[]
|
||||||
'ol-email': string
|
'ol-email': string
|
||||||
'ol-emailAddressLimit': number
|
'ol-emailAddressLimit': number
|
||||||
'ol-error': { name: string } | undefined
|
'ol-error': { name: string } | undefined
|
||||||
@@ -188,7 +188,7 @@ export interface Meta {
|
|||||||
'ol-labsExperiments': ActiveExperiment[] | undefined
|
'ol-labsExperiments': ActiveExperiment[] | undefined
|
||||||
'ol-languages': SpellCheckLanguage[]
|
'ol-languages': SpellCheckLanguage[]
|
||||||
'ol-learnedWords': string[]
|
'ol-learnedWords': string[]
|
||||||
'ol-legacyEditorThemes': string[]
|
'ol-legacyEditorThemes': { name: string; dark: boolean }[]
|
||||||
'ol-licenseQuantity'?: number
|
'ol-licenseQuantity'?: number
|
||||||
'ol-loadingText': string
|
'ol-loadingText': string
|
||||||
'ol-managedGroupSubscriptions': ManagedGroupSubscription[]
|
'ol-managedGroupSubscriptions': ManagedGroupSubscription[]
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
.file-view .no-preview {
|
.file-view .no-preview {
|
||||||
color: var(--content-secondary-themed);
|
color: var(--content-secondary-themed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-view .loading-panel {
|
||||||
|
color: var(--content-secondary-themed);
|
||||||
|
background: var(--bg-primary-themed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-view {
|
.file-view {
|
||||||
|
|||||||
@@ -77,6 +77,17 @@ $editor-toggler-bg-dark-color: color.adjust(
|
|||||||
background-color: var(--bg-tertiary-themed);
|
background-color: var(--bg-tertiary-themed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-panel {
|
||||||
|
background-color: var(--bg-light-primary);
|
||||||
|
color: var(--content-primary);
|
||||||
|
|
||||||
|
// Based on editor theme rather than overall theme
|
||||||
|
&.loading-panel-dark {
|
||||||
|
background-color: var(--bg-dark-primary);
|
||||||
|
color: var(--content-primary-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-alerts {
|
.global-alerts {
|
||||||
|
|||||||
@@ -571,12 +571,16 @@ describe('<EditorLeftMenu />', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('shows editor theme menu correctly', function () {
|
it('shows editor theme menu correctly', function () {
|
||||||
const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
|
const editorThemes = [
|
||||||
|
{ name: 'editortheme-1', dark: false },
|
||||||
|
{ name: 'editortheme-2', dark: false },
|
||||||
|
{ name: 'editortheme-3', dark: false },
|
||||||
|
]
|
||||||
|
|
||||||
const legacyEditorThemes = [
|
const legacyEditorThemes = [
|
||||||
'legacytheme-1',
|
{ name: 'legacytheme-1', dark: false },
|
||||||
'legacytheme-2',
|
{ name: 'legacytheme-2', dark: false },
|
||||||
'legacytheme-3',
|
{ name: 'legacytheme-3', dark: false },
|
||||||
]
|
]
|
||||||
|
|
||||||
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
||||||
|
|||||||
+14
-6
@@ -6,9 +6,17 @@ import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/e
|
|||||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||||
|
|
||||||
describe('<SettingsEditorTheme />', function () {
|
describe('<SettingsEditorTheme />', function () {
|
||||||
const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
|
const editorThemes = [
|
||||||
|
{ name: 'editortheme-1', dark: false },
|
||||||
|
{ name: 'editortheme-2', dark: false },
|
||||||
|
{ name: 'editortheme-3', dark: false },
|
||||||
|
]
|
||||||
|
|
||||||
const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
|
const legacyEditorThemes = [
|
||||||
|
{ name: 'legacytheme-1', dark: false },
|
||||||
|
{ name: 'legacytheme-2', dark: false },
|
||||||
|
{ name: 'legacytheme-3', dark: false },
|
||||||
|
]
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
||||||
@@ -31,15 +39,15 @@ describe('<SettingsEditorTheme />', function () {
|
|||||||
const select = screen.getByLabelText('Editor theme')
|
const select = screen.getByLabelText('Editor theme')
|
||||||
|
|
||||||
for (const theme of editorThemes) {
|
for (const theme of editorThemes) {
|
||||||
const option = within(select).getByText(theme.replace(/_/g, ' '))
|
const option = within(select).getByText(theme.name.replace(/_/g, ' '))
|
||||||
expect(option.getAttribute('value')).to.equal(theme)
|
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const theme of legacyEditorThemes) {
|
for (const theme of legacyEditorThemes) {
|
||||||
const option = within(select).getByText(
|
const option = within(select).getByText(
|
||||||
theme.replace(/_/g, ' ') + ' (Legacy)'
|
theme.name.replace(/_/g, ' ') + ' (Legacy)'
|
||||||
)
|
)
|
||||||
expect(option.getAttribute('value')).to.equal(theme)
|
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -60,8 +60,16 @@ describe('<SettingsModal />', function () {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
|
const editorThemes = [
|
||||||
const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
|
{ name: 'editortheme-1', dark: false },
|
||||||
|
{ name: 'editortheme-2', dark: false },
|
||||||
|
{ name: 'editortheme-3', dark: false },
|
||||||
|
]
|
||||||
|
const legacyEditorThemes = [
|
||||||
|
{ name: 'legacytheme-1', dark: false },
|
||||||
|
{ name: 'legacytheme-2', dark: false },
|
||||||
|
{ name: 'legacytheme-3', dark: false },
|
||||||
|
]
|
||||||
|
|
||||||
const imageNames: ImageName[] = [
|
const imageNames: ImageName[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
+16
-8
@@ -7,8 +7,16 @@ import EditorThemeSetting from '@/features/ide-redesign/components/settings/appe
|
|||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
describe('<EditorThemeSetting />', function () {
|
describe('<EditorThemeSetting />', function () {
|
||||||
const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
|
const editorThemes = [
|
||||||
const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
|
{ name: 'editortheme-1', dark: false },
|
||||||
|
{ name: 'editortheme-2', dark: false },
|
||||||
|
{ name: 'editortheme-3', dark: false },
|
||||||
|
]
|
||||||
|
const legacyEditorThemes = [
|
||||||
|
{ name: 'legacytheme-1', dark: false },
|
||||||
|
{ name: 'legacytheme-2', dark: false },
|
||||||
|
{ name: 'legacytheme-3', dark: false },
|
||||||
|
]
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
||||||
@@ -39,25 +47,25 @@ describe('<EditorThemeSetting />', function () {
|
|||||||
const select = screen.getByLabelText('Editor theme')
|
const select = screen.getByLabelText('Editor theme')
|
||||||
|
|
||||||
for (const theme of editorThemes) {
|
for (const theme of editorThemes) {
|
||||||
const option = within(select).getByText(theme.replace(/_/g, ' '))
|
const option = within(select).getByText(theme.name.replace(/_/g, ' '))
|
||||||
expect(option.getAttribute('value')).to.equal(theme)
|
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||||
await userEvent.selectOptions(select, [option])
|
await userEvent.selectOptions(select, [option])
|
||||||
expect(
|
expect(
|
||||||
saveSettingsMock.callHistory.called(`/user/settings`, {
|
saveSettingsMock.callHistory.called(`/user/settings`, {
|
||||||
body: { editorTheme: theme },
|
body: { editorTheme: theme.name },
|
||||||
})
|
})
|
||||||
).to.be.true
|
).to.be.true
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const theme of legacyEditorThemes) {
|
for (const theme of legacyEditorThemes) {
|
||||||
const option = within(select).getByText(
|
const option = within(select).getByText(
|
||||||
theme.replace(/_/g, ' ') + ' (Legacy)'
|
theme.name.replace(/_/g, ' ') + ' (Legacy)'
|
||||||
)
|
)
|
||||||
expect(option.getAttribute('value')).to.equal(theme)
|
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||||
await userEvent.selectOptions(select, [option])
|
await userEvent.selectOptions(select, [option])
|
||||||
expect(
|
expect(
|
||||||
saveSettingsMock.callHistory.called(`/user/settings`, {
|
saveSettingsMock.callHistory.called(`/user/settings`, {
|
||||||
body: { editorTheme: theme },
|
body: { editorTheme: theme.name },
|
||||||
})
|
})
|
||||||
).to.be.true
|
).to.be.true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user