add popu-up menu
This commit is contained in:
parent
2161fe11a2
commit
ad79ac1746
|
@ -199,6 +199,7 @@ export function receiveChatMessage(
|
|||
text: string,
|
||||
country: string,
|
||||
channel: number,
|
||||
user: number,
|
||||
isPing: boolean,
|
||||
): Action {
|
||||
return {
|
||||
|
@ -207,6 +208,7 @@ export function receiveChatMessage(
|
|||
text,
|
||||
country,
|
||||
channel,
|
||||
user,
|
||||
isPing,
|
||||
};
|
||||
}
|
||||
|
@ -705,6 +707,35 @@ export function showCanvasSelectionModal(): Action {
|
|||
return showModal('CANVAS_SELECTION');
|
||||
}
|
||||
|
||||
export function showContextMenu(
|
||||
menuType: string,
|
||||
xPos: number,
|
||||
yPos: number,
|
||||
args: Object,
|
||||
): Action {
|
||||
return {
|
||||
type: 'SHOW_CONTEXT_MENU',
|
||||
menuType,
|
||||
xPos,
|
||||
yPos,
|
||||
args,
|
||||
};
|
||||
}
|
||||
|
||||
export function setChatInputMessage(message: string): Action {
|
||||
return {
|
||||
type: 'SET_CHAT_INPUT_MSG',
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function addToChatInputMessage(message: string): Action {
|
||||
return {
|
||||
type: 'ADD_CHAT_INPUT_MSG',
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function showChatModal(forceModal: boolean = false): Action {
|
||||
if (window.innerWidth > 604 && !forceModal) { return toggleChatBox(); }
|
||||
return showModal('CHAT');
|
||||
|
@ -723,6 +754,12 @@ export function hideModal(): Action {
|
|||
};
|
||||
}
|
||||
|
||||
export function hideContextMenu(): Action {
|
||||
return {
|
||||
type: 'HIDE_CONTEXT_MENU',
|
||||
};
|
||||
}
|
||||
|
||||
export function reloadUrl(): Action {
|
||||
return {
|
||||
type: 'RELOAD_URL',
|
||||
|
|
|
@ -65,11 +65,14 @@ export type Action =
|
|||
text: string,
|
||||
country: string,
|
||||
channel: number,
|
||||
user: number,
|
||||
isPing: boolean,
|
||||
}
|
||||
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
|
||||
| { type: 'SET_CHAT_CHANNEL', channelId: number }
|
||||
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
||||
| { type: 'SET_CHAT_INPUT_MSG', message: string }
|
||||
| { type: 'ADD_CHAT_INPUT_MSG', message: string }
|
||||
| { type: 'RECEIVE_ME',
|
||||
name: string,
|
||||
waitSeconds: number,
|
||||
|
@ -90,7 +93,14 @@ export type Action =
|
|||
| { type: 'SET_MAILREG', mailreg: boolean }
|
||||
| { type: 'REM_FROM_MESSAGES', message: string }
|
||||
| { type: 'SHOW_MODAL', modalType: string }
|
||||
| { type: 'SHOW_CONTEXT_MENU',
|
||||
menuType: string,
|
||||
xPos: number,
|
||||
yPos: number,
|
||||
args: Object,
|
||||
}
|
||||
| { type: 'HIDE_MODAL' }
|
||||
| { type: 'HIDE_CONTEXT_MENU' }
|
||||
| { type: 'RELOAD_URL' }
|
||||
| { type: 'SET_HISTORICAL_TIME', date: string, time: string }
|
||||
| { type: 'ON_VIEW_FINISH_CHANGE' };
|
||||
|
|
|
@ -52,9 +52,22 @@ function init() {
|
|||
ProtocolClient.on('setWsName', (name) => {
|
||||
nameRegExp = new RegExp(`(^|\\s+)(@${name})(\\s+|$)`, 'g');
|
||||
});
|
||||
ProtocolClient.on('chatMessage', (name, text, country, channelId) => {
|
||||
ProtocolClient.on('chatMessage', (
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channelId,
|
||||
userId,
|
||||
) => {
|
||||
const isPing = (nameRegExp && text.match(nameRegExp));
|
||||
store.dispatch(receiveChatMessage(name, text, country, channelId, isPing));
|
||||
store.dispatch(receiveChatMessage(
|
||||
name,
|
||||
text,
|
||||
country,
|
||||
channelId,
|
||||
userId,
|
||||
isPing,
|
||||
));
|
||||
});
|
||||
ProtocolClient.on('changedMe', () => {
|
||||
store.dispatch(fetchMe());
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
showUserAreaModal,
|
||||
setChatChannel,
|
||||
fetchChatMessages,
|
||||
setChatInputMessage,
|
||||
} from '../actions';
|
||||
import ProtocolClient from '../socket/ProtocolClient';
|
||||
import { saveSelection, restoreSelection } from '../utils/storeSelection';
|
||||
|
@ -32,13 +33,13 @@ const Chat = ({
|
|||
chatChannel,
|
||||
ownName,
|
||||
open,
|
||||
inputMessage,
|
||||
setInputMessage,
|
||||
setChannel,
|
||||
fetchMessages,
|
||||
fetching,
|
||||
}) => {
|
||||
const listRef = useRef();
|
||||
const inputRef = useRef();
|
||||
const [inputMessage, setInputMessage] = useState('');
|
||||
const [selection, setSelection] = useState(null);
|
||||
const [nameRegExp, setNameRegExp] = useState(null);
|
||||
|
||||
|
@ -56,13 +57,15 @@ const Chat = ({
|
|||
stayScrolled();
|
||||
}, [channelMessages.length]);
|
||||
|
||||
/*
|
||||
* TODO this removes focus from chat box, fix this
|
||||
*
|
||||
useEffect(() => {
|
||||
// TODO this removes focus from chat box, fix this
|
||||
return;
|
||||
if (channelMessages.length === MAX_CHAT_MESSAGES) {
|
||||
restoreSelection(selection);
|
||||
}
|
||||
}, [channelMessages]);
|
||||
*/
|
||||
|
||||
useEffect(() => {
|
||||
const regExp = (ownName)
|
||||
|
@ -71,16 +74,6 @@ const Chat = ({
|
|||
setNameRegExp(regExp);
|
||||
}, [ownName]);
|
||||
|
||||
function padToInputMessage(txt) {
|
||||
const lastChar = inputMessage.substr(-1);
|
||||
const pad = (lastChar && lastChar !== ' ');
|
||||
let newMsg = inputMessage;
|
||||
if (pad) newMsg += ' ';
|
||||
newMsg += txt;
|
||||
setInputMessage(newMsg);
|
||||
inputRef.current.focus();
|
||||
}
|
||||
|
||||
function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const msg = inputMessage.trim();
|
||||
|
@ -123,7 +116,7 @@ const Chat = ({
|
|||
name="info"
|
||||
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
|
||||
country="xx"
|
||||
insertText={(txt) => padToInputMessage(txt)}
|
||||
uid={0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -133,7 +126,7 @@ const Chat = ({
|
|||
name={message[0]}
|
||||
msgArray={splitChatMessage(message[1], nameRegExp)}
|
||||
country={message[2]}
|
||||
insertText={(txt) => padToInputMessage(txt)}
|
||||
uid={message[3]}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
@ -148,7 +141,7 @@ const Chat = ({
|
|||
style={{ flexGrow: 1, minWidth: 40 }}
|
||||
value={inputMessage}
|
||||
onChange={(e) => setInputMessage(e.target.value)}
|
||||
ref={inputRef}
|
||||
id="chatmsginput"
|
||||
maxLength="200"
|
||||
type="text"
|
||||
placeholder="Chat here"
|
||||
|
@ -198,11 +191,17 @@ const Chat = ({
|
|||
function mapStateToProps(state: State) {
|
||||
const { name } = state.user;
|
||||
const { chatChannel } = state.gui;
|
||||
const { channels, messages, fetching } = state.chat;
|
||||
const {
|
||||
channels,
|
||||
messages,
|
||||
fetching,
|
||||
inputMessage,
|
||||
} = state.chat;
|
||||
return {
|
||||
channels,
|
||||
messages,
|
||||
fetching,
|
||||
inputMessage,
|
||||
chatChannel,
|
||||
ownName: name,
|
||||
};
|
||||
|
@ -219,6 +218,9 @@ function mapDispatchToProps(dispatch) {
|
|||
fetchMessages(channelId) {
|
||||
dispatch(fetchChatMessages(channelId));
|
||||
},
|
||||
setInputMessage(message) {
|
||||
dispatch(setChatInputMessage(message));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { showContextMenu } from '../actions';
|
||||
import { colorFromText, setBrightness } from '../core/utils';
|
||||
|
||||
|
||||
function ChatMessage({
|
||||
name,
|
||||
msgArray,
|
||||
uid,
|
||||
country,
|
||||
insertText,
|
||||
msgArray,
|
||||
openUserContextMenu,
|
||||
darkMode,
|
||||
}) {
|
||||
if (!name || !msgArray) {
|
||||
|
@ -54,8 +56,17 @@ function ChatMessage({
|
|||
}}
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
onClick={() => {
|
||||
insertText(`@${name} `);
|
||||
onClick={(event) => {
|
||||
const {
|
||||
clientX,
|
||||
clientY,
|
||||
} = event;
|
||||
openUserContextMenu(
|
||||
clientX,
|
||||
clientY,
|
||||
uid,
|
||||
name,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
|
@ -103,4 +114,15 @@ function mapStateToProps(state: State) {
|
|||
return { darkMode };
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ChatMessage);
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
openUserContextMenu(xPos, yPos, uid, name) {
|
||||
dispatch(showContextMenu('USER', xPos, yPos, {
|
||||
uid,
|
||||
name,
|
||||
}));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMessage);
|
||||
|
|
|
@ -14,11 +14,28 @@ import PalselButton from './PalselButton';
|
|||
import Palette from './Palette';
|
||||
import HistorySelect from './HistorySelect';
|
||||
import Mobile3DControls from './Mobile3DControls';
|
||||
import UserContextMenu from './UserContextMenu';
|
||||
|
||||
|
||||
const UI = ({ isHistoricalView, is3D, isOnMobile }) => {
|
||||
const CONTEXT_MENUS = {
|
||||
USER: <UserContextMenu />,
|
||||
/* other context menus */
|
||||
};
|
||||
|
||||
const UI = ({
|
||||
isHistoricalView,
|
||||
is3D,
|
||||
isOnMobile,
|
||||
menuOpen,
|
||||
menuType,
|
||||
}) => {
|
||||
if (isHistoricalView) {
|
||||
return <HistorySelect />;
|
||||
return (
|
||||
<div>
|
||||
<HistorySelect />
|
||||
{(menuOpen && menuType) ? CONTEXT_MENUS[menuType] : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
|
@ -28,6 +45,7 @@ const UI = ({ isHistoricalView, is3D, isOnMobile }) => {
|
|||
{(is3D && isOnMobile) ? <Mobile3DControls /> : null}
|
||||
<CoolDownBox />
|
||||
<NotifyBox />
|
||||
{(menuOpen && menuType) ? CONTEXT_MENUS[menuType] : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -40,10 +58,16 @@ function mapStateToProps(state: State) {
|
|||
const {
|
||||
isOnMobile,
|
||||
} = state.user;
|
||||
const {
|
||||
menuOpen,
|
||||
menuType,
|
||||
} = state.contextMenu;
|
||||
return {
|
||||
isHistoricalView,
|
||||
is3D,
|
||||
isOnMobile,
|
||||
menuOpen,
|
||||
menuType,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
120
src/components/UserContextMenu.jsx
Normal file
120
src/components/UserContextMenu.jsx
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useRef, useEffect,
|
||||
} from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
hideContextMenu,
|
||||
addToChatInputMessage,
|
||||
setChatInputMessage,
|
||||
} from '../actions';
|
||||
import type { State } from '../reducers';
|
||||
|
||||
|
||||
const UserContextMenu = ({
|
||||
xPos,
|
||||
yPos,
|
||||
uid,
|
||||
name,
|
||||
setInput,
|
||||
addToInput,
|
||||
close,
|
||||
}) => {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event) {
|
||||
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.addEventListener('touchstart', handleClickOutside);
|
||||
};
|
||||
}, [wrapperRef]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="contextmenu"
|
||||
style={{
|
||||
left: xPos,
|
||||
top: yPos,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ borderBottom: 'thin solid' }}
|
||||
>
|
||||
Block
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setInput('loool');
|
||||
}}
|
||||
style={{ borderBottom: 'thin solid' }}
|
||||
>
|
||||
DM
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
addToInput(`@${name} `);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Ping
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const {
|
||||
xPos,
|
||||
yPos,
|
||||
args,
|
||||
} = state.contextMenu;
|
||||
const {
|
||||
name,
|
||||
uid,
|
||||
} = args;
|
||||
return {
|
||||
xPos,
|
||||
yPos,
|
||||
name,
|
||||
uid,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
addToInput(text) {
|
||||
dispatch(addToChatInputMessage(text));
|
||||
const input = document.getElementById('chatmsginput');
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
},
|
||||
setInput(text) {
|
||||
dispatch(setChatInputMessage(text));
|
||||
},
|
||||
close() {
|
||||
dispatch(hideContextMenu());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserContextMenu);
|
|
@ -68,6 +68,7 @@ class ChatMessageBuffer {
|
|||
name,
|
||||
message,
|
||||
flag,
|
||||
uid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +81,7 @@ class ChatMessageBuffer {
|
|||
as: 'user',
|
||||
foreignKey: 'uid',
|
||||
attributes: [
|
||||
'id',
|
||||
'name',
|
||||
'flag',
|
||||
],
|
||||
|
@ -101,11 +103,13 @@ class ChatMessageBuffer {
|
|||
message,
|
||||
'user.name': name,
|
||||
'user.flag': flag,
|
||||
'user.id': uid,
|
||||
} = messagesModel[i];
|
||||
messages.push([
|
||||
name,
|
||||
message,
|
||||
flag,
|
||||
uid,
|
||||
]);
|
||||
}
|
||||
return messages;
|
||||
|
|
|
@ -5,6 +5,7 @@ import { MAX_CHAT_MESSAGES } from '../core/constants';
|
|||
import type { Action } from '../actions/types';
|
||||
|
||||
export type ChatState = {
|
||||
inputMessage: string,
|
||||
// [[cid, name], [cid2, name2],...]
|
||||
channels: Array,
|
||||
// { cid: [message1,message2,message2,...]}
|
||||
|
@ -14,6 +15,7 @@ export type ChatState = {
|
|||
}
|
||||
|
||||
const initialState: ChatState = {
|
||||
inputMessage: '',
|
||||
channels: [],
|
||||
messages: {},
|
||||
fetching: false,
|
||||
|
@ -39,9 +41,33 @@ export default function chat(
|
|||
};
|
||||
}
|
||||
|
||||
case 'SET_CHAT_INPUT_MSG': {
|
||||
const { message } = action;
|
||||
return {
|
||||
...state,
|
||||
inputMessage: message,
|
||||
};
|
||||
}
|
||||
|
||||
case 'ADD_CHAT_INPUT_MSG': {
|
||||
const { message } = action;
|
||||
let { inputMessage } = state;
|
||||
const lastChar = inputMessage.substr(-1);
|
||||
const pad = (lastChar && lastChar !== ' ');
|
||||
if (pad) {
|
||||
inputMessage += ' ';
|
||||
}
|
||||
inputMessage += message;
|
||||
|
||||
return {
|
||||
...state,
|
||||
inputMessage,
|
||||
};
|
||||
}
|
||||
|
||||
case 'RECEIVE_CHAT_MESSAGE': {
|
||||
const {
|
||||
name, text, country, channel,
|
||||
name, text, country, channel, user,
|
||||
} = action;
|
||||
if (!state.messages[channel]) {
|
||||
return state;
|
||||
|
@ -50,7 +76,7 @@ export default function chat(
|
|||
...state.messages,
|
||||
[channel]: [
|
||||
...state.messages[channel],
|
||||
[name, text, country],
|
||||
[name, text, country, user],
|
||||
],
|
||||
};
|
||||
if (messages[channel].length > MAX_CHAT_MESSAGES) {
|
||||
|
|
57
src/reducers/contextMenu.js
Normal file
57
src/reducers/contextMenu.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* https://stackoverflow.com/questions/35623656/how-can-i-display-a-modal-dialog-in-redux-that-performs-asynchronous-actions/35641680#35641680
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Action } from '../actions/types';
|
||||
|
||||
export type ContextMenuState = {
|
||||
menuOpen: boolean,
|
||||
menuType: ?string,
|
||||
xPos: number,
|
||||
yPos: number,
|
||||
args: Object,
|
||||
};
|
||||
|
||||
const initialState: ContextMenuState = {
|
||||
menuOpen: false,
|
||||
menuType: null,
|
||||
xPos: 0,
|
||||
yPos: 0,
|
||||
args: {},
|
||||
};
|
||||
|
||||
|
||||
export default function contextMenu(
|
||||
state: ModalState = initialState,
|
||||
action: Action,
|
||||
): ContextMenuState {
|
||||
switch (action.type) {
|
||||
case 'SHOW_CONTEXT_MENU': {
|
||||
const {
|
||||
menuType, xPos, yPos, args,
|
||||
} = action;
|
||||
return {
|
||||
...state,
|
||||
menuType,
|
||||
xPos,
|
||||
yPos,
|
||||
menuOpen: true,
|
||||
args: {
|
||||
...args,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'HIDE_CONTEXT_MENU': {
|
||||
return {
|
||||
...state,
|
||||
menuOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import gui from './gui';
|
|||
import modal from './modal';
|
||||
import user from './user';
|
||||
import chat from './chat';
|
||||
import contextMenu from './contextMenu';
|
||||
|
||||
import type { AudioState } from './audio';
|
||||
import type { CanvasState } from './canvas';
|
||||
|
@ -15,6 +16,7 @@ import type { GUIState } from './gui';
|
|||
import type { ModalState } from './modal';
|
||||
import type { UserState } from './user';
|
||||
import type { ChatState } from './chat';
|
||||
import type { ContextMenuState } from './contextMenu';
|
||||
|
||||
export type State = {
|
||||
audio: AudioState,
|
||||
|
@ -23,12 +25,19 @@ export type State = {
|
|||
modal: ModalState,
|
||||
user: UserState,
|
||||
chat: ChatState,
|
||||
contextMenu: ContextMenuState,
|
||||
};
|
||||
|
||||
const config = {
|
||||
key: 'primary',
|
||||
storage: localForage,
|
||||
blacklist: ['user', 'canvas', 'modal', 'chat'],
|
||||
blacklist: [
|
||||
'user',
|
||||
'canvas',
|
||||
'modal',
|
||||
'chat',
|
||||
'contextMenu',
|
||||
],
|
||||
};
|
||||
|
||||
export default persistCombineReducers(config, {
|
||||
|
@ -38,4 +47,5 @@ export default persistCombineReducers(config, {
|
|||
modal,
|
||||
user,
|
||||
chat,
|
||||
contextMenu,
|
||||
});
|
||||
|
|
|
@ -24,8 +24,6 @@ export default function modal(
|
|||
action: Action,
|
||||
): ModalState {
|
||||
switch (action.type) {
|
||||
// clear hover when placing a pixel
|
||||
// fixes a bug with iPad
|
||||
case 'SHOW_MODAL': {
|
||||
const { modalType } = action;
|
||||
const chatOpen = (modalType === 'CHAT') ? false : state.chatOpen;
|
||||
|
|
|
@ -169,10 +169,10 @@ class ProtocolClient extends EventEmitter {
|
|||
const data = JSON.parse(message);
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
if (data.length === 4) {
|
||||
if (data.length === 5) {
|
||||
// Ordinary array: Chat message
|
||||
const [name, text, country, channelId] = data;
|
||||
this.emit('chatMessage', name, text, country, channelId);
|
||||
const [name, text, country, channelId, userId] = data;
|
||||
this.emit('chatMessage', name, text, country, channelId, userId);
|
||||
}
|
||||
} else {
|
||||
// string = name
|
||||
|
|
|
@ -169,7 +169,7 @@ class SocketServer extends WebSocketEvents {
|
|||
id: number,
|
||||
country: string,
|
||||
) {
|
||||
const text = JSON.stringify([name, message, country, channelId]);
|
||||
const text = JSON.stringify([name, message, country, channelId, id]);
|
||||
this.wss.clients.forEach((ws) => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
if (chatProvider.userHasChannelAccess(ws.user, channelId)) {
|
||||
|
|
|
@ -124,6 +124,26 @@ tr:nth-child(even) {
|
|||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.contextmenu {
|
||||
position: absolute;
|
||||
width: 90px;
|
||||
font-size: 12px;
|
||||
z-index: 10;
|
||||
background-color: rgba(226, 226, 226);
|
||||
border: solid black;
|
||||
border-width: thin;
|
||||
}
|
||||
|
||||
.contextmenu > div {
|
||||
border-width: thin;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.contextmenu > div:hover {
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbox.show {
|
||||
height: 200px;
|
||||
width: 350px;
|
||||
|
|
Loading…
Reference in New Issue
Block a user