@@ -1,7 +1,7 @@
|
||||
import {useSplitClient} from "@splitsoftware/splitio-react";
|
||||
import {Button, Result} from "antd";
|
||||
import LogRocket from "logrocket";
|
||||
import React, {lazy, Suspense, useEffect} from "react";
|
||||
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Route, Routes} from "react-router-dom";
|
||||
@@ -19,6 +19,7 @@ import {checkUserSession} from "../redux/user/user.actions";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
import "./App.styles.scss";
|
||||
import handleBeta from "../utils/betaHandler";
|
||||
|
||||
const ResetPassword = lazy(() =>
|
||||
import("../pages/reset-password/reset-password.component")
|
||||
@@ -40,14 +41,16 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||
});
|
||||
|
||||
export function App({
|
||||
bodyshop,
|
||||
checkUserSession,
|
||||
currentUser,
|
||||
online,
|
||||
setOnline,
|
||||
}) {
|
||||
export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) {
|
||||
|
||||
const client = useSplitClient().client;
|
||||
const [listenersAdded, setListenersAdded] = useState(false)
|
||||
const {t} = useTranslation();
|
||||
|
||||
// Handle The Beta Switch.
|
||||
useEffect(() => {
|
||||
handleBeta();
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!navigator.onLine) {
|
||||
@@ -60,15 +63,28 @@ export function App({
|
||||
//const b = Grid.useBreakpoint();
|
||||
// console.log("Breakpoints:", b);
|
||||
|
||||
const {t} = useTranslation();
|
||||
|
||||
window.addEventListener("offline", function (e) {
|
||||
const offlineListener = (e) => {
|
||||
setOnline(false);
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("online", function (e) {
|
||||
const onlineListener = (e) => {
|
||||
setOnline(true);
|
||||
});
|
||||
}
|
||||
|
||||
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||
useEffect(() => {
|
||||
if (!listenersAdded) {
|
||||
console.log('Added events for offline and online');
|
||||
window.addEventListener("offline", offlineListener);
|
||||
window.addEventListener("online", onlineListener);
|
||||
setListenersAdded(true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("offline", offlineListener);
|
||||
window.removeEventListener("online", onlineListener);
|
||||
}
|
||||
}, [listenersAdded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser.authorized && bodyshop) {
|
||||
|
||||
@@ -4,20 +4,11 @@ import { Button, notification, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
||||
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FcmHandler from "../../utils/fcm-handler";
|
||||
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
||||
import "./chat-affix.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
chatVisible: selectChatVisible,
|
||||
});
|
||||
|
||||
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -36,35 +27,34 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
console.log("FCM Topic Subscription", r.data);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Error attempting to subscribe to messaging topic: ",
|
||||
error
|
||||
"Error attempting to subscribe to messaging topic: ",
|
||||
error
|
||||
);
|
||||
notification.open({
|
||||
type: "warning",
|
||||
message: t("general.errors.fcm"),
|
||||
btn: (
|
||||
<Space>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await requestForToken();
|
||||
|
||||
SubscribeToTopic();
|
||||
}}
|
||||
>
|
||||
{t("general.actions.tryagain")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const win = window.open(
|
||||
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
||||
"_blank"
|
||||
);
|
||||
win.focus();
|
||||
}}
|
||||
>
|
||||
{t("general.labels.help")}
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await requestForToken();
|
||||
SubscribeToTopic();
|
||||
}}
|
||||
>
|
||||
{t("general.actions.tryagain")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const win = window.open(
|
||||
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
||||
"_blank"
|
||||
);
|
||||
win.focus();
|
||||
}}
|
||||
>
|
||||
{t("general.labels.help")}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -81,16 +71,16 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
payload: (payload && payload.data && payload.data.data) || payload.data,
|
||||
});
|
||||
}
|
||||
let stopMessageListenr, channel;
|
||||
let stopMessageListener, channel;
|
||||
try {
|
||||
stopMessageListenr = onMessage(messaging, handleMessage);
|
||||
stopMessageListener = onMessage(messaging, handleMessage);
|
||||
channel = new BroadcastChannel("imex-sw-messages");
|
||||
channel.addEventListener("message", handleMessage);
|
||||
} catch (error) {
|
||||
console.log("Unable to set event listeners.");
|
||||
}
|
||||
return () => {
|
||||
stopMessageListenr && stopMessageListenr();
|
||||
stopMessageListener && stopMessageListener();
|
||||
channel && channel.removeEventListener("message", handleMessage);
|
||||
};
|
||||
}, [client]);
|
||||
@@ -98,9 +88,10 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
||||
|
||||
return (
|
||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
||||
</div>
|
||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, null)(ChatAffixContainer);
|
||||
|
||||
export default ChatAffixContainer;
|
||||
@@ -12,7 +12,7 @@ import Icon, {
|
||||
FileAddOutlined,
|
||||
FileFilled,
|
||||
HomeFilled,
|
||||
ImportOutlined,
|
||||
ImportOutlined, InfoCircleOutlined,
|
||||
LineChartOutlined,
|
||||
PaperClipOutlined,
|
||||
PhoneOutlined,
|
||||
@@ -25,8 +25,8 @@ import Icon, {
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Layout, Menu} from "antd";
|
||||
import React from "react";
|
||||
import {Layout, Menu, Switch, Tooltip} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {BsKanban} from "react-icons/bs";
|
||||
import {FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar,} from "react-icons/fa";
|
||||
@@ -41,6 +41,7 @@ import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {signOutStart} from "../../redux/user/user.actions";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import {FiLogOut} from "react-icons/fi";
|
||||
import handleBeta, {checkBeta, setBeta} from "../../utils/betaHandler";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -70,9 +71,21 @@ function Header({handleMenuClick, currentUser, bodyshop, selectedHeader, signOut
|
||||
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const [betaSwitch, setBetaSwitch] = useState(false);
|
||||
const {t} = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const isBeta = checkBeta();
|
||||
setBetaSwitch(isBeta );
|
||||
}, []);
|
||||
|
||||
|
||||
const betaSwitchChange = (checked) => {
|
||||
setBeta(checked);
|
||||
setBetaSwitch(checked);
|
||||
handleBeta();
|
||||
}
|
||||
|
||||
const accountingChildren = [
|
||||
{
|
||||
key: 'bills',
|
||||
@@ -451,13 +464,28 @@ function Header({handleMenuClick, currentUser, bodyshop, selectedHeader, signOut
|
||||
|
||||
})),
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
menuItems.push({
|
||||
key: 'beta-switch',
|
||||
style: { marginLeft: 'auto' },
|
||||
label: (
|
||||
<Tooltip title="A faster more modern ImEX Online is ready for you to try! You can switch back at any time.">
|
||||
<InfoCircleOutlined />
|
||||
<span style={{marginRight: 8}}>Try the new ImEX Online</span>
|
||||
<Switch
|
||||
checked={betaSwitch}
|
||||
onChange={betaSwitchChange}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<Layout.Header>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
//theme="light"
|
||||
theme={"dark"}
|
||||
selectedKeys={[selectedHeader]}
|
||||
onClick={handleMenuClick}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {FloatButton, Layout} from "antd";
|
||||
import preval from "preval.macro";
|
||||
import React, {lazy, Suspense, useEffect} from "react";
|
||||
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, Route, Routes} from "react-router-dom";
|
||||
@@ -177,6 +177,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
export function Manage({conflict, bodyshop}) {
|
||||
const {t} = useTranslation();
|
||||
const [chatVisible, setChatVisible] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const widgetId = "IABVNO4scRKY11XBQkNr";
|
||||
@@ -360,7 +362,7 @@ export function Manage({conflict, bodyshop}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChatAffixContainer/>
|
||||
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
|
||||
<Layout className="layout-container">
|
||||
<UpdateAlert/>
|
||||
<HeaderContainer/>
|
||||
|
||||
@@ -42,9 +42,7 @@ if (process.env.NODE_ENV === "development") {
|
||||
export const store = configureStore({
|
||||
reducer: rootReducer,
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
|
||||
serializableCheck: {
|
||||
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
||||
},
|
||||
serializableCheck: false,
|
||||
}).concat(middlewares),
|
||||
// middleware: middlewares,
|
||||
devTools: process.env.NODE_ENV !== 'production',
|
||||
|
||||
32
client/src/utils/betaHandler.js
Normal file
32
client/src/utils/betaHandler.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export const BETA_KEY = 'betaSwitchImex';
|
||||
|
||||
export const checkBeta = () => document.cookie.split('; ').find(row => row.startsWith(BETA_KEY)).split('=')[1] === 'true';
|
||||
|
||||
|
||||
export const setBeta = (value) => {
|
||||
const domain = window.location.hostname.split('.').slice(-2).join('.');
|
||||
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
|
||||
}
|
||||
|
||||
export const handleBeta = () => {
|
||||
// If the current host name does not start with beta or test, then we don't need to do anything.
|
||||
if (window.location.hostname.startsWith('localhost')) {
|
||||
console.log('Not on beta or test, so no need to handle beta.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isBeta = checkBeta();
|
||||
|
||||
const currentHostName = window.location.hostname;
|
||||
|
||||
// Beta is enabled, but the current host name does start with beta.
|
||||
if (isBeta && !currentHostName.startsWith('beta')) {
|
||||
window.location.href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
}
|
||||
|
||||
// Beta is not enabled, but the current host name does start with beta.
|
||||
else if (!isBeta && currentHostName.startsWith('beta')) {
|
||||
window.location.href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
}
|
||||
}
|
||||
export default handleBeta;
|
||||
@@ -1,129 +1,128 @@
|
||||
import { useEffect, useCallback, useReducer } from "react";
|
||||
import {useCallback, useEffect, useReducer, useState} from "react";
|
||||
//Based on https://www.fullstacklabs.co/blog/keyboard-shortcuts-with-react-hooks
|
||||
const blacklistedTargets = []; // ["INPUT", "TEXTAREA"];
|
||||
export const useKeyboardSaveShortcut = (callback) =>
|
||||
useKeyboardShortcut(["Control", "S"], callback, { overrideSystem: true });
|
||||
useKeyboardShortcut(["Control", "S"], callback, {overrideSystem: true});
|
||||
|
||||
const keysReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "set-key-down":
|
||||
const keydownState = { ...state, [action.key]: true };
|
||||
return keydownState;
|
||||
case "set-key-up":
|
||||
const keyUpState = { ...state, [action.key]: false };
|
||||
return keyUpState;
|
||||
case "reset-keys":
|
||||
const resetState = { ...action.data };
|
||||
return resetState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
switch (action.type) {
|
||||
case "set-key-down":
|
||||
const keydownState = {...state, [action.key]: true};
|
||||
return keydownState;
|
||||
case "set-key-up":
|
||||
const keyUpState = {...state, [action.key]: false};
|
||||
return keyUpState;
|
||||
case "reset-keys":
|
||||
const resetState = {...action.data};
|
||||
return resetState;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const useKeyboardShortcut = (shortcutKeys, callback, options) => {
|
||||
if (!Array.isArray(shortcutKeys))
|
||||
throw new Error(
|
||||
"The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings."
|
||||
if (!Array.isArray(shortcutKeys))
|
||||
throw new Error(
|
||||
"The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings."
|
||||
);
|
||||
|
||||
if (!shortcutKeys.length)
|
||||
throw new Error(
|
||||
"The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string."
|
||||
);
|
||||
|
||||
if (!callback || typeof callback !== "function")
|
||||
throw new Error(
|
||||
"The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed."
|
||||
);
|
||||
|
||||
const {overrideSystem} = options || {};
|
||||
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
|
||||
currentKeys[key.toLowerCase()] = false;
|
||||
return currentKeys;
|
||||
}, {});
|
||||
|
||||
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
|
||||
const [listenersAdded, setListenersAdded] = useState(false);
|
||||
|
||||
const keydownListener = useCallback(
|
||||
(assignedKey) => (keydownEvent) => {
|
||||
const loweredKey = assignedKey.toLowerCase();
|
||||
|
||||
if (keydownEvent.repeat) return;
|
||||
if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
|
||||
if (loweredKey !== keydownEvent.key.toLowerCase()) return;
|
||||
if (keys[loweredKey] === undefined) return;
|
||||
|
||||
if (overrideSystem) {
|
||||
keydownEvent.preventDefault();
|
||||
disabledEventPropagation(keydownEvent);
|
||||
}
|
||||
|
||||
setKeys({type: "set-key-down", key: loweredKey});
|
||||
return false;
|
||||
},
|
||||
[keys, overrideSystem]
|
||||
);
|
||||
|
||||
if (!shortcutKeys.length)
|
||||
throw new Error(
|
||||
"The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string."
|
||||
const keyupListener = useCallback(
|
||||
(assignedKey) => (keyupEvent) => {
|
||||
const raisedKey = assignedKey.toLowerCase();
|
||||
|
||||
if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
|
||||
if (keyupEvent.key.toLowerCase() !== raisedKey) return;
|
||||
if (keys[raisedKey] === undefined) return;
|
||||
|
||||
if (overrideSystem) {
|
||||
keyupEvent.preventDefault();
|
||||
disabledEventPropagation(keyupEvent);
|
||||
}
|
||||
|
||||
setKeys({type: "set-key-up", key: raisedKey});
|
||||
return false;
|
||||
},
|
||||
[keys, overrideSystem]
|
||||
);
|
||||
|
||||
if (!callback || typeof callback !== "function")
|
||||
throw new Error(
|
||||
"The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed."
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!Object.values(keys).filter((value) => !value).length) {
|
||||
callback(keys);
|
||||
setKeys({type: "reset-keys", data: initalKeyMapping});
|
||||
} else {
|
||||
setKeys({type: null});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [callback, keys]);
|
||||
|
||||
const { overrideSystem } = options || {};
|
||||
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
|
||||
currentKeys[key.toLowerCase()] = false;
|
||||
return currentKeys;
|
||||
}, {});
|
||||
useEffect(() => {
|
||||
if (!listenersAdded) {
|
||||
console.log('Added events for keyup and keydown');
|
||||
shortcutKeys.forEach((k) => {
|
||||
window.addEventListener("keydown", keydownListener(k));
|
||||
window.addEventListener("keyup", keyupListener(k))
|
||||
});
|
||||
}
|
||||
|
||||
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
|
||||
setListenersAdded(true);
|
||||
|
||||
const keydownListener = useCallback(
|
||||
(assignedKey) => (keydownEvent) => {
|
||||
const loweredKey = assignedKey.toLowerCase();
|
||||
return () => {
|
||||
shortcutKeys.forEach((k) => {
|
||||
window.removeEventListener("keydown", keydownListener(k));
|
||||
window.removeEventListener("keyup", keyupListener(k));
|
||||
});
|
||||
}
|
||||
}, [listenersAdded]);
|
||||
|
||||
if (keydownEvent.repeat) return;
|
||||
if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
|
||||
if (loweredKey !== keydownEvent.key.toLowerCase()) return;
|
||||
if (keys[loweredKey] === undefined) return;
|
||||
|
||||
if (overrideSystem) {
|
||||
keydownEvent.preventDefault();
|
||||
disabledEventPropagation(keydownEvent);
|
||||
}
|
||||
|
||||
setKeys({ type: "set-key-down", key: loweredKey });
|
||||
return false;
|
||||
},
|
||||
[keys, overrideSystem]
|
||||
);
|
||||
|
||||
const keyupListener = useCallback(
|
||||
(assignedKey) => (keyupEvent) => {
|
||||
const raisedKey = assignedKey.toLowerCase();
|
||||
|
||||
if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
|
||||
if (keyupEvent.key.toLowerCase() !== raisedKey) return;
|
||||
if (keys[raisedKey] === undefined) return;
|
||||
|
||||
if (overrideSystem) {
|
||||
keyupEvent.preventDefault();
|
||||
disabledEventPropagation(keyupEvent);
|
||||
}
|
||||
|
||||
setKeys({ type: "set-key-up", key: raisedKey });
|
||||
return false;
|
||||
},
|
||||
[keys, overrideSystem]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!Object.values(keys).filter((value) => !value).length) {
|
||||
callback(keys);
|
||||
setKeys({ type: "reset-keys", data: initalKeyMapping });
|
||||
} else {
|
||||
setKeys({ type: null });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [callback, keys]);
|
||||
|
||||
useEffect(() => {
|
||||
shortcutKeys.forEach((k) =>
|
||||
window.addEventListener("keydown", keydownListener(k))
|
||||
);
|
||||
return () =>
|
||||
shortcutKeys.forEach((k) =>
|
||||
window.removeEventListener("keydown", keydownListener(k))
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
shortcutKeys.forEach((k) =>
|
||||
window.addEventListener("keyup", keyupListener(k))
|
||||
);
|
||||
return () =>
|
||||
shortcutKeys.forEach((k) =>
|
||||
window.removeEventListener("keyup", keyupListener(k))
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default useKeyboardShortcut;
|
||||
|
||||
function disabledEventPropagation(e) {
|
||||
if (e) {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
} else if (window.event) {
|
||||
window.event.cancelBubble = true;
|
||||
if (e) {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
} else if (window.event) {
|
||||
window.event.cancelBubble = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user