提交 ff805b3a 编写于 作者: I isidor

Consolidate ModifierKeyEmitter and AlternativeKeyEmitter

fixes #109062
上级 e0a433b8
......@@ -1543,6 +1543,16 @@ export class ModifierKeyEmitter extends Emitter<IModifierKeyStatus> {
return this._keyStatus;
}
// This method is a workaround because we do not get keyboard events while a context menu is shown #109062
resetKeyStatus(): void {
this._keyStatus = {
altKey: false,
shiftKey: false,
ctrlKey: false
};
this.fire(this._keyStatus);
}
static getInstance() {
if (!ModifierKeyEmitter.instance) {
ModifierKeyEmitter.instance = new ModifierKeyEmitter();
......
......@@ -3,13 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
import { createCSSRule, asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom';
import { domEvent } from 'vs/base/browser/event';
import { IAction, Separator } from 'vs/base/common/actions';
import { Emitter } from 'vs/base/common/event';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
......@@ -19,68 +17,9 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
// The alternative key on all platforms is alt. On windows we also support shift as an alternative key #44136
class AlternativeKeyEmitter extends Emitter<boolean> {
private readonly _subscriptions = new DisposableStore();
private _isPressed: boolean = false;
private static instance: AlternativeKeyEmitter;
private _suppressAltKeyUp: boolean = false;
private constructor(contextMenuService: IContextMenuService) {
super();
this._subscriptions.add(domEvent(document.body, 'keydown')(e => {
this.isPressed = e.altKey || ((isWindows || isLinux) && e.shiftKey);
}));
this._subscriptions.add(domEvent(document.body, 'keyup')(e => {
if (this.isPressed) {
if (this._suppressAltKeyUp) {
e.preventDefault();
}
}
this._suppressAltKeyUp = false;
this.isPressed = false;
}));
this._subscriptions.add(domEvent(document.body, 'mouseleave')(e => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'blur')(e => this.isPressed = false));
// Workaround since we do not get any events while a context menu is shown
this._subscriptions.add(contextMenuService.onDidContextMenu(() => this.isPressed = false));
}
get isPressed(): boolean {
return this._isPressed;
}
set isPressed(value: boolean) {
this._isPressed = value;
this.fire(this._isPressed);
}
suppressAltKeyUp() {
// Sometimes the native alt behavior needs to be suppresed since the alt was already used as an alternative key
// Example: windows behavior to toggle tha top level menu #44396
this._suppressAltKeyUp = true;
}
static getInstance(contextMenuService: IContextMenuService) {
if (!AlternativeKeyEmitter.instance) {
AlternativeKeyEmitter.instance = new AlternativeKeyEmitter(contextMenuService);
}
return AlternativeKeyEmitter.instance;
}
dispose() {
super.dispose();
this._subscriptions.dispose();
}
}
export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, contextMenuService: IContextMenuService, isPrimaryGroup?: (group: string) => boolean): IDisposable {
export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable {
const groups = menu.getActions(options);
const useAlternativeActions = AlternativeKeyEmitter.getInstance(contextMenuService).isPressed;
const useAlternativeActions = ModifierKeyEmitter.getInstance().keyStatus.altKey;
fillInActions(groups, target, useAlternativeActions, isPrimaryGroup);
return asDisposable(groups);
}
......@@ -133,16 +72,15 @@ export class MenuEntryActionViewItem extends ActionViewItem {
private _wantsAltCommand: boolean = false;
private readonly _itemClassDispose = this._register(new MutableDisposable());
private readonly _altKey: AlternativeKeyEmitter;
private readonly _altKey: ModifierKeyEmitter;
constructor(
readonly _action: MenuItemAction,
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService,
@IContextMenuService _contextMenuService: IContextMenuService
@INotificationService protected _notificationService: INotificationService
) {
super(undefined, _action, { icon: !!(_action.class || _action.item.icon), label: !_action.class && !_action.item.icon });
this._altKey = AlternativeKeyEmitter.getInstance(_contextMenuService);
this._altKey = ModifierKeyEmitter.getInstance();
}
protected get _commandAction(): IAction {
......@@ -153,10 +91,6 @@ export class MenuEntryActionViewItem extends ActionViewItem {
event.preventDefault();
event.stopPropagation();
if (this._altKey.isPressed) {
this._altKey.suppressAltKeyUp();
}
this.actionRunner.run(this._commandAction, this._context)
.then(undefined, err => this._notificationService.error(err));
}
......@@ -168,7 +102,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
let mouseOver = false;
let alternativeKeyDown = this._altKey.isPressed;
let alternativeKeyDown = this._altKey.keyStatus.altKey;
const updateAltState = () => {
const wantsAltCommand = mouseOver && alternativeKeyDown;
......@@ -182,7 +116,7 @@ export class MenuEntryActionViewItem extends ActionViewItem {
if (this._action.alt) {
this._register(this._altKey.event(value => {
alternativeKeyDown = value;
alternativeKeyDown = value.altKey;
updateAltState();
}));
}
......
......@@ -6,19 +6,16 @@
import { ContextMenuHandler, IContextMenuHandlerOptions } from './contextMenuHandler';
import { IContextViewService, IContextMenuService } from './contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Event, Emitter } from 'vs/base/common/event';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Disposable } from 'vs/base/common/lifecycle';
import { ModifierKeyEmitter } from 'vs/base/browser/dom';
export class ContextMenuService extends Disposable implements IContextMenuService {
declare readonly _serviceBrand: undefined;
private _onDidContextMenu = this._register(new Emitter<void>());
readonly onDidContextMenu: Event<void> = this._onDidContextMenu.event;
private contextMenuHandler: ContextMenuHandler;
constructor(
......@@ -41,6 +38,6 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
showContextMenu(delegate: IContextMenuDelegate): void {
this.contextMenuHandler.showContextMenu(delegate);
this._onDidContextMenu.fire();
ModifierKeyEmitter.getInstance().resetKeyStatus();
}
}
......@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
......@@ -41,5 +40,4 @@ export interface IContextMenuService {
readonly _serviceBrand: undefined;
showContextMenu(delegate: IContextMenuDelegate): void;
onDidContextMenu: Event<void>; // TODO@isidor these event should be removed once we get async context menus
}
......@@ -340,7 +340,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Fill in contributed actions
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(menu, undefined, actions, this.contextMenuService);
const actionsDisposable = createAndFillInContextMenuActions(menu, undefined, actions);
// Show it
this.contextMenuService.showContextMenu({
......
......@@ -347,7 +347,7 @@ export abstract class TitleControl extends Themable {
// Fill in contributed actions
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions);
// Show it
this.contextMenuService.showContextMenu({
......
......@@ -429,7 +429,7 @@ export class TitlebarPart extends Part implements ITitleService {
// Fill in contributed actions
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService);
const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions);
// Show it
this.contextMenuService.showContextMenu({
......
......@@ -364,7 +364,7 @@ export class BulkEditPane extends ViewPane {
private _onContextMenu(e: ITreeContextMenuEvent<any>): void {
const menu = this._menuService.createMenu(MenuId.BulkEditContext, this._contextKeyService);
const actions: IAction[] = [];
const disposable = createAndFillInContextMenuActions(menu, undefined, actions, this._contextMenuService);
const disposable = createAndFillInContextMenuActions(menu, undefined, actions);
this._contextMenuService.showContextMenu({
getActions: () => actions,
......
......@@ -7,14 +7,12 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { IAction } from 'vs/base/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Comment, CommentThread } from 'vs/editor/common/modes';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
export class CommentMenus implements IDisposable {
constructor(
@IMenuService private readonly menuService: IMenuService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
@IMenuService private readonly menuService: IMenuService
) { }
getCommentThreadTitleActions(commentThread: CommentThread, contextKeyService: IContextKeyService): IMenu {
......@@ -40,7 +38,7 @@ export class CommentMenus implements IDisposable {
const secondary: IAction[] = [];
const result = { primary, secondary };
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g));
return menu;
}
......
......@@ -441,7 +441,7 @@ export class CallStackView extends ViewPane {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, g => /^inline/.test(g));
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
......
......@@ -217,7 +217,7 @@ export class VariablesView extends ViewPane {
variable: variable.toDebugProtocolObject()
};
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: context, shouldForwardArgs: false }, actions, this.contextMenuService);
const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: context, shouldForwardArgs: false }, actions);
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => actions,
......
......@@ -527,7 +527,7 @@ export class ExplorerView extends ViewPane {
} else {
arg = roots.length === 1 ? roots[0].resource : {};
}
disposables.add(createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService));
disposables.add(createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions));
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
......
......@@ -379,7 +379,7 @@ export class OpenEditorsView extends ViewPane {
const element = e.element;
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? EditorResourceAccessor.getOriginalUri(element.editor) : {} }, actions, this.contextMenuService);
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? EditorResourceAccessor.getOriginalUri(element.editor) : {} }, actions);
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
......
......@@ -116,7 +116,7 @@ class PropertyHeader extends Disposable {
this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService);
return item;
}
......@@ -969,7 +969,7 @@ export class ModifiedCell extends AbstractCellRenderer {
this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService);
return item;
}
......
......@@ -186,7 +186,7 @@ abstract class AbstractCellRenderer {
const toolbar = new ToolBar(container, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService);
return item;
}
......@@ -1059,7 +1059,7 @@ export class ListTopCellToolbar extends Disposable {
const toolbar = new ToolBar(this.topCellToolbar, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService);
return item;
}
......
......@@ -6,7 +6,6 @@
import * as DOM from 'vs/base/browser/dom';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { MenuItemAction } from 'vs/platform/actions/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { renderCodicons } from 'vs/base/browser/codicons';
......@@ -16,9 +15,8 @@ export class CodiconActionViewItem extends MenuEntryActionViewItem {
readonly _action: MenuItemAction,
keybindingService: IKeybindingService,
notificationService: INotificationService,
contextMenuService: IContextMenuService
) {
super(_action, keybindingService, notificationService, contextMenuService);
super(_action, keybindingService, notificationService);
}
updateLabel(): void {
if (this.options.label && this.label) {
......
......@@ -635,7 +635,7 @@ export class TunnelPanel extends ViewPane {
}
const actions: IAction[] = [];
this._register(createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService));
this._register(createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true }, actions));
this.contextMenuService.showContextMenu({
getAnchor: () => treeEvent.anchor,
......
......@@ -151,7 +151,7 @@ export class SCMRepositoriesViewPane extends ViewPane {
const provider = e.element.provider;
const menus = this.scmViewService.menus.getRepositoryMenus(provider);
const menu = menus.repositoryMenu;
const [actions, disposable] = collectContextMenuActions(menu, this.contextMenuService);
const [actions, disposable] = collectContextMenuActions(menu);
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
......
......@@ -1876,27 +1876,27 @@ export class SCMViewPane extends ViewPane {
const menus = this.scmViewService.menus.getRepositoryMenus(element.provider);
const menu = menus.repositoryMenu;
context = element.provider;
[actions, disposable] = collectContextMenuActions(menu, this.contextMenuService);
[actions, disposable] = collectContextMenuActions(menu);
} else if (isSCMInput(element)) {
// noop
} else if (isSCMResourceGroup(element)) {
const menus = this.scmViewService.menus.getRepositoryMenus(element.provider);
const menu = menus.getResourceGroupMenu(element);
[actions, disposable] = collectContextMenuActions(menu, this.contextMenuService);
[actions, disposable] = collectContextMenuActions(menu);
} else if (ResourceTree.isResourceNode(element)) {
if (element.element) {
const menus = this.scmViewService.menus.getRepositoryMenus(element.element.resourceGroup.provider);
const menu = menus.getResourceMenu(element.element);
[actions, disposable] = collectContextMenuActions(menu, this.contextMenuService);
[actions, disposable] = collectContextMenuActions(menu);
} else {
const menus = this.scmViewService.menus.getRepositoryMenus(element.context.provider);
const menu = menus.getResourceFolderMenu(element.context);
[actions, disposable] = collectContextMenuActions(menu, this.contextMenuService);
[actions, disposable] = collectContextMenuActions(menu);
}
} else {
const menus = this.scmViewService.menus.getRepositoryMenus(element.resourceGroup.provider);
const menu = menus.getResourceMenu(element);
[actions, disposable] = collectContextMenuActions(menu, this.contextMenuService);
[actions, disposable] = collectContextMenuActions(menu);
}
const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources());
......
......@@ -10,7 +10,6 @@ import { IDisposable, Disposable, combinedDisposable, toDisposable } from 'vs/ba
import { Action, IAction } from 'vs/base/common/actions';
import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { equals } from 'vs/base/common/arrays';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { renderCodicons } from 'vs/base/browser/codicons';
import { ICommandService } from 'vs/platform/commands/common/commands';
......@@ -75,10 +74,10 @@ export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: Acti
}, g => /^inline/.test(g));
}
export function collectContextMenuActions(menu: IMenu, contextMenuService: IContextMenuService): [IAction[], IDisposable] {
export function collectContextMenuActions(menu: IMenu): [IAction[], IDisposable] {
const primary: IAction[] = [];
const actions: IAction[] = [];
const disposable = createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, { primary, secondary: actions }, contextMenuService, g => /^inline/.test(g));
const disposable = createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, { primary, secondary: actions }, g => /^inline/.test(g));
return [actions, disposable];
}
......
......@@ -780,7 +780,7 @@ export class SearchView extends ViewPane {
e.browserEvent.stopPropagation();
const actions: IAction[] = [];
const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService);
const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true }, actions);
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
......
......@@ -1159,7 +1159,6 @@ class TimelinePaneCommands extends Disposable {
@IConfigurationService private readonly configurationService: IConfigurationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
) {
super();
......@@ -1233,7 +1232,7 @@ class TimelinePaneCommands extends Disposable {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g));
menu.dispose();
scoped.dispose();
......
......@@ -1014,8 +1014,7 @@ class TreeMenus extends Disposable implements IDisposable {
constructor(
private id: string,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IMenuService private readonly menuService: IMenuService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
@IMenuService private readonly menuService: IMenuService
) {
super();
}
......@@ -1037,7 +1036,7 @@ class TreeMenus extends Disposable implements IDisposable {
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g));
menu.dispose();
contextKeyService.dispose();
......
......@@ -10,7 +10,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { getZoomFactor } from 'vs/base/browser/browser';
import { unmnemonicLabel } from 'vs/base/common/labels';
import { Event, Emitter } from 'vs/base/common/event';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IContextMenuDelegate, IContextMenuEvent } from 'vs/base/browser/contextmenu';
import { once } from 'vs/base/common/functional';
......@@ -31,8 +30,6 @@ export class ContextMenuService extends Disposable implements IContextMenuServic
declare readonly _serviceBrand: undefined;
get onDidContextMenu(): Event<void> { return this.impl.onDidContextMenu; }
private impl: IContextMenuService;
constructor(
......@@ -66,9 +63,6 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
declare readonly _serviceBrand: undefined;
private _onDidContextMenu = this._register(new Emitter<void>());
readonly onDidContextMenu: Event<void> = this._onDidContextMenu.event;
constructor(
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
......@@ -85,7 +79,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
delegate.onHide(false);
}
this._onDidContextMenu.fire();
dom.ModifierKeyEmitter.getInstance().resetKeyStatus();
});
const menu = this.createMenu(delegate, actions, onHide);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册