Skip to content

Commit cacfa9d

Browse files
authored
Merge pull request #385 from qa-guru/develop
Develop
2 parents f9983e9 + 65c47ff commit cacfa9d

File tree

8 files changed

+122
-71
lines changed

8 files changed

+122
-71
lines changed

src/app/providers/with-context.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ export const withContext = (component: () => ReactNode) => {
88
return function WithContextComponent() {
99
const { settings } = useSettings();
1010

11-
const theme = createCustomTheme({
12-
theme: settings.theme,
13-
responsiveFontSizes: settings.responsiveFontSizes,
14-
});
11+
const theme = createCustomTheme(settings);
1512

1613
return <ThemeProvider theme={theme}>{component()}</ThemeProvider>;
1714
};

src/cache.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,32 @@ import { initialSettings, themeSettingsTypes } from "theme";
44
import { THEMES } from "theme/constans";
55
import { Maybe, UserRole } from "api/graphql/generated/graphql";
66

7+
// Функция для загрузки настроек из localStorage
8+
const loadSettingsFromStorage = (): themeSettingsTypes => {
9+
try {
10+
const storedSettings = window.localStorage.getItem("settings");
11+
if (storedSettings) {
12+
const parsedSettings = JSON.parse(storedSettings);
13+
// Проверяем, что загруженные настройки валидны
14+
if (parsedSettings && typeof parsedSettings.theme === "string") {
15+
return parsedSettings;
16+
}
17+
}
18+
} catch (error) {
19+
// Silent fail - fallback to initial settings
20+
}
21+
return initialSettings;
22+
};
23+
724
export const userIdVar = makeVar<Maybe<string>>(null);
825
export const userRolesVar = makeVar<Maybe<Maybe<UserRole>[]>>([]);
926
export const commentVar = makeVar<Maybe<string | undefined>>(null);
10-
export const settingsVar = makeVar<themeSettingsTypes>(initialSettings);
27+
28+
// Инициализируем настройки из localStorage
29+
const savedSettings = loadSettingsFromStorage();
30+
export const settingsVar = makeVar<themeSettingsTypes>(savedSettings);
1131
export const lightThemeVar = makeVar<boolean>(
12-
initialSettings.theme === THEMES.LIGHT
32+
savedSettings.theme === THEMES.LIGHT
1333
);
1434

1535
export const cache = new InMemoryCache();

src/features/profile/views/edit-profile/edit-profile.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ const EditProfile: FC<IEditProfile> = ({ user, updateUser }) => {
146146
<StyledInputStack>
147147
<InputText
148148
control={control}
149-
name="linkedIn"
149+
name="linkedin"
150150
placeholder="Cсылка на linkedIn"
151151
label="LinkedIn"
152152
errors={errors}
Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
1-
import { useEffect, useState } from "react";
2-
31
import { themeSettingsTypes } from "theme";
42

53
export const useLocalStorage = (
64
key: string,
75
initialValue: themeSettingsTypes
86
) => {
9-
const [data, setData] = useState(initialValue);
10-
11-
useEffect(() => {
12-
const getData = window.localStorage.getItem(key);
13-
if (getData) {
14-
setData(JSON.parse(getData));
15-
}
16-
}, [key]);
17-
187
const storeData = (updateValue: themeSettingsTypes) => {
19-
setData(updateValue);
20-
window.localStorage.setItem(key, JSON.stringify(updateValue));
8+
try {
9+
window.localStorage.setItem(key, JSON.stringify(updateValue));
10+
} catch (error) {
11+
// Silent fail
12+
}
2113
};
2214

23-
return { data, storeData };
15+
return { storeData };
2416
};

src/shared/lib/mui-tiptap/controls/controlled-bubble-menu.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { ReactNode, useCallback } from "react";
22
import {
3-
Fade,
43
Paper,
54
type PaperProps,
65
Popover,
76
type PopoverProps,
87
type PopoverVirtualElement,
9-
useTheme,
108
} from "@mui/material";
119
import { type Editor, isNodeSelection, posToDOMRect } from "@tiptap/core";
1210
import { makeStyles } from "tss-react/mui";
@@ -77,7 +75,6 @@ export default function ControlledBubbleMenu({
7775
const { classes, cx } = useStyles(undefined, {
7876
props: { classes: overrideClasses },
7977
});
80-
const theme = useTheme();
8178

8279
const defaultAnchorEl = useCallback((): VirtualElement => {
8380
const { ranges } = editor.state.selection;
@@ -119,24 +116,18 @@ export default function ControlledBubbleMenu({
119116
}}
120117
transformOrigin={{ vertical: "top", horizontal: "left" }}
121118
className={cx(controlledBubbleMenuClasses.root, classes.root, className)}
122-
transitionDuration={{
123-
enter: theme.transitions.duration.enteringScreen,
124-
exit: 0,
125-
}}
126119
>
127-
<Fade in={open}>
128-
<Paper
129-
elevation={7}
130-
{...PaperProps}
131-
className={cx(
132-
controlledBubbleMenuClasses.paper,
133-
classes.paper,
134-
PaperProps?.className
135-
)}
136-
>
137-
{children}
138-
</Paper>
139-
</Fade>
120+
<Paper
121+
elevation={7}
122+
{...PaperProps}
123+
className={cx(
124+
controlledBubbleMenuClasses.paper,
125+
classes.paper,
126+
PaperProps?.className
127+
)}
128+
>
129+
{children}
130+
</Paper>
140131
</Popover>
141132
);
142133
}

src/shared/lib/mui-tiptap/controls/table-bubble-menu.tsx

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { findParentNodeClosestToPos, posToDOMRect } from "@tiptap/core";
2-
import { useMemo } from "react";
2+
import { useMemo, useCallback, useEffect, useState } from "react";
33
import { makeStyles } from "tss-react/mui";
44
import type { Except } from "type-fest";
55
import { type PopoverVirtualElement } from "@mui/material";
@@ -13,14 +13,8 @@ import { useRichTextEditorContext } from "../context";
1313
import TableMenuControls, {
1414
type TableMenuControlsProps,
1515
} from "./table-menu-controls";
16-
import useDebouncedFocus from "../hooks/use-debounced-focus";
17-
import DebounceRender, {
18-
type DebounceRenderProps,
19-
} from "../utils/debounce-render";
2016

2117
export type TableBubbleMenuProps = {
22-
disableDebounce?: boolean;
23-
DebounceProps?: Except<DebounceRenderProps, "children">;
2418
labels?: TableMenuControlsProps["labels"];
2519
} & Partial<Except<ControlledBubbleMenuProps, "open" | "editor" | "children">>;
2620

@@ -34,15 +28,12 @@ const useStyles = makeStyles({
3428
}));
3529

3630
export default function TableBubbleMenu({
37-
disableDebounce = false,
38-
DebounceProps,
3931
labels,
4032
...controlledBubbleMenuProps
4133
}: TableBubbleMenuProps) {
4234
const editor = useRichTextEditorContext();
4335
const { classes } = useStyles();
44-
45-
const isEditorFocusedDebounced = useDebouncedFocus({ editor });
36+
const [isManuallyHidden, setIsManuallyHidden] = useState(false);
4637

4738
const bubbleMenuAnchorEl = useMemo(
4839
() =>
@@ -61,7 +52,7 @@ export default function TableBubbleMenu({
6152
nearestTableParent.pos
6253
) as Maybe<HTMLElement | undefined>;
6354

64-
const tableDomNode = wrapperDomNode?.querySelector("admin");
55+
const tableDomNode = wrapperDomNode?.querySelector("table");
6556
if (tableDomNode) {
6657
return tableDomNode.getBoundingClientRect();
6758
}
@@ -77,18 +68,52 @@ export default function TableBubbleMenu({
7768
[editor]
7869
);
7970

71+
const handleClose = useCallback(() => {
72+
setIsManuallyHidden(true);
73+
if (editor) {
74+
editor.commands.blur();
75+
}
76+
}, [editor]);
77+
78+
useEffect(() => {
79+
if (!editor?.isActive("table")) {
80+
setIsManuallyHidden(false);
81+
}
82+
}, [editor?.isActive("table")]);
83+
84+
useEffect(() => {
85+
const handleKeyDown = (event: KeyboardEvent) => {
86+
if (event.key === "Escape" && editor?.isActive("table")) {
87+
handleClose();
88+
}
89+
};
90+
91+
document.addEventListener("keydown", handleKeyDown);
92+
return () => {
93+
document.removeEventListener("keydown", handleKeyDown);
94+
};
95+
}, [editor, handleClose]);
96+
8097
if (!editor?.isEditable) {
8198
return null;
8299
}
83100

84101
const controls = (
85-
<TableMenuControls className={classes.controls} labels={labels} />
102+
<TableMenuControls
103+
className={classes.controls}
104+
labels={labels}
105+
onClose={handleClose}
106+
/>
86107
);
87108

109+
// Логика показа меню - активная таблица и не скрыто вручную
110+
const shouldShowMenu = editor.isActive("table") && !isManuallyHidden;
111+
88112
return (
89113
<ControlledBubbleMenu
90114
editor={editor}
91-
open={isEditorFocusedDebounced && editor.isActive("table")}
115+
open={shouldShowMenu}
116+
onClose={handleClose}
92117
anchorEl={bubbleMenuAnchorEl as PopoverVirtualElement}
93118
placement={{
94119
anchorOrigin: { vertical: "top", horizontal: "left" },
@@ -104,11 +129,7 @@ export default function TableBubbleMenu({
104129
flipPadding={{ top: 35, left: 8, right: 8, bottom: -Infinity }}
105130
{...controlledBubbleMenuProps}
106131
>
107-
{disableDebounce ? (
108-
controls
109-
) : (
110-
<DebounceRender {...DebounceProps}>{controls}</DebounceRender>
111-
)}
132+
{controls}
112133
</ControlledBubbleMenu>
113134
);
114135
}

src/shared/lib/mui-tiptap/controls/table-menu-controls.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import FormatColorFill from "@mui/icons-material/FormatColorFill";
22
import GridOff from "@mui/icons-material/GridOff";
3+
import Close from "@mui/icons-material/Close";
34

45
import MenuDivider from "./menu-divider";
56
import { useRichTextEditorContext } from "../context";
@@ -18,6 +19,7 @@ import LayoutColumnFill from "../icons/layout-column-fill";
1819

1920
export type TableMenuControlsProps = {
2021
className?: string;
22+
onClose?: () => void;
2123

2224
labels?: {
2325
insertColumnBefore?: string;
@@ -38,6 +40,7 @@ export type TableMenuControlsProps = {
3840
export default function TableMenuControls({
3941
className,
4042
labels,
43+
onClose,
4144
}: TableMenuControlsProps) {
4245
const editor = useRichTextEditorContext();
4346
return (
@@ -134,6 +137,21 @@ export default function TableMenuControls({
134137
onClick={() => editor?.chain().focus().deleteTable().run()}
135138
disabled={!editor?.can().deleteTable()}
136139
/>
140+
141+
<MenuDivider />
142+
143+
<MenuButton
144+
tooltipLabel="Close"
145+
IconComponent={Close}
146+
onClick={() => {
147+
if (onClose) {
148+
onClose();
149+
} else {
150+
editor?.commands.blur();
151+
}
152+
}}
153+
disabled={!editor?.isEditable}
154+
/>
137155
</MenuControlsContainer>
138156
);
139157
}

src/theme/index.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createTheme, responsiveFontSizes } from "@mui/material";
1+
import { createTheme, responsiveFontSizes, ThemeOptions } from "@mui/material";
22
import merge from "lodash/merge";
33

44
import components from "./components";
@@ -16,7 +16,7 @@ export const initialSettings: themeSettingsTypes = {
1616
responsiveFontSizes: true,
1717
};
1818

19-
const baseOptions = {
19+
const baseOptions: ThemeOptions = {
2020
typography: {
2121
fontFamily: "'Montserrat', sans-serif",
2222
h1: {
@@ -56,20 +56,32 @@ const baseOptions = {
5656
};
5757

5858
export const createCustomTheme = (settings: themeSettingsTypes) => {
59-
let themeOptions: object = themesOptions[settings.theme];
59+
// Валидируем настройки
60+
if (!settings || typeof settings.theme !== "string") {
61+
settings = { ...initialSettings };
62+
}
63+
64+
let themeOptions: ThemeOptions = themesOptions[
65+
settings.theme
66+
] as unknown as ThemeOptions;
6067

6168
if (!themeOptions) {
62-
themeOptions = themesOptions[THEMES.LIGHT];
69+
themeOptions = themesOptions[THEMES.LIGHT] as unknown as ThemeOptions;
6370
}
6471

65-
const mergedThemeOptions = merge({}, baseOptions, themeOptions);
72+
try {
73+
const mergedThemeOptions = merge({}, baseOptions, themeOptions);
6674

67-
let theme = createTheme(mergedThemeOptions);
75+
let theme = createTheme(mergedThemeOptions);
6876

69-
theme.components = components(theme);
70-
if (settings.responsiveFontSizes) {
71-
theme = responsiveFontSizes(theme);
72-
}
77+
theme.components = components(theme);
78+
if (settings.responsiveFontSizes !== false) {
79+
theme = responsiveFontSizes(theme);
80+
}
7381

74-
return theme;
82+
return theme;
83+
} catch (error) {
84+
// Возвращаем базовую светлую тему в случае ошибки
85+
return createTheme(themesOptions[THEMES.LIGHT] as unknown as ThemeOptions);
86+
}
7587
};

0 commit comments

Comments
 (0)