Skip to content

Commit 97c6c7b

Browse files
committed
chore: fix husky deprecation and security vulnerabilities
- Remove deprecated shim lines from husky hooks - Add pnpm overrides for glob (>=11.1.0) and js-yaml (>=4.1.1) - Update rimraf to 6.1.2 - Fixes 2 high and 2 moderate vulnerabilities"
1 parent 6736c57 commit 97c6c7b

12 files changed

Lines changed: 85 additions & 52 deletions

File tree

src/components/common/MobileAppBanner/index.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState } from 'react';
22
import { X, Smartphone } from 'lucide-react';
33
import { Button } from '@/components/ui/button';
44
import { useMobilePlatform } from '@/hooks/useIsMobile';
@@ -10,15 +10,11 @@ const BANNER_DISMISSED_KEY = 'typelets_mobile_banner_dismissed';
1010

1111
export function MobileAppBanner() {
1212
const platform = useMobilePlatform();
13-
const [isDismissed, setIsDismissed] = useState(true);
14-
15-
useEffect(() => {
16-
// Check if banner was previously dismissed
13+
// Initialize state based on localStorage to avoid setState in effect
14+
const [isDismissed, setIsDismissed] = useState(() => {
1715
const dismissed = localStorage.getItem(BANNER_DISMISSED_KEY);
18-
if (!dismissed && platform) {
19-
setIsDismissed(false);
20-
}
21-
}, [platform]);
16+
return !!dismissed || !platform;
17+
});
2218

2319
const handleDismiss = () => {
2420
setIsDismissed(true);

src/components/editor/extensions/NoteLinkSuggestionList.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ export const NoteLinkSuggestionList = forwardRef<
5959
);
6060
}, [filteredItems.length]);
6161

62-
// Reset selection when items change
62+
// Reset selection when filtered items change
63+
/* eslint-disable react-hooks/set-state-in-effect -- Legitimate reset on data change */
6364
useEffect(() => {
6465
setSelectedIndex(0);
6566
}, [filteredItems]);
67+
/* eslint-enable react-hooks/set-state-in-effect */
6668

6769
useImperativeHandle(
6870
ref,

src/components/editor/extensions/ResizableImage.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Node, mergeAttributes } from '@tiptap/core';
22
import { ReactNodeViewRenderer } from '@tiptap/react';
33
import { NodeViewWrapper } from '@tiptap/react';
4-
import { useState, useRef, useEffect } from 'react';
4+
import { useState, useEffect, useRef } from 'react';
55
import { X, Maximize2 } from 'lucide-react';
66

77
const ImageComponent = (props: {
@@ -19,15 +19,16 @@ const ImageComponent = (props: {
1919
const { node, deleteNode, updateAttributes } = props;
2020
const [showControls, setShowControls] = useState(false);
2121
const [isResizing, setIsResizing] = useState(false);
22+
const [width, setWidth] = useState(node.attrs.width || 'auto');
2223
const imageRef = useRef<HTMLImageElement>(null);
2324
const containerRef = useRef<HTMLDivElement>(null);
2425

25-
const initialWidth = node.attrs.width || 'auto';
26-
const [width, setWidth] = useState(initialWidth);
27-
26+
// Sync width with node attrs
27+
/* eslint-disable react-hooks/set-state-in-effect -- Sync external prop to local state */
2828
useEffect(() => {
2929
setWidth(node.attrs.width || 'auto');
3030
}, [node.attrs.width]);
31+
/* eslint-enable react-hooks/set-state-in-effect */
3132

3233
const handleResize = (e: React.MouseEvent) => {
3334
e.preventDefault();

src/components/editor/extensions/SlashCommands.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
useState,
3-
useEffect,
43
useImperativeHandle,
54
forwardRef,
65
useCallback,
@@ -277,10 +276,6 @@ export const SlashCommandsList = forwardRef<
277276
setSelectedIndex((selectedIndex + 1) % commands.length);
278277
}, [selectedIndex]);
279278

280-
useEffect(() => {
281-
setSelectedIndex(0);
282-
}, []);
283-
284279
useImperativeHandle(
285280
ref,
286281
() => ({

src/components/editor/hooks/useEditorEffects.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export function useEditorEffects({
151151

152152
const editorElement = editor.view.dom as HTMLElement;
153153

154+
/* eslint-disable react-hooks/immutability -- Modifying DOM element style, not React state */
154155
if (zoomLevel === 100) {
155156
// At 100%, use the original font size
156157
editorElement.style.fontSize = baseFontSize;
@@ -161,6 +162,7 @@ export function useEditorEffects({
161162
const newSize = baseSize * scaleFactor;
162163
editorElement.style.fontSize = `${newSize}px`;
163164
}
165+
/* eslint-enable react-hooks/immutability */
164166
} catch {
165167
// Silently ignore zoom level application errors
166168
}

src/components/layout/MainLayout.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,18 @@ export default function MainLayout() {
3434
const [activeTabId, setActiveTabId] = useState<string | null>(null);
3535

3636
// Sync responsive panel states with local states for desktop
37+
/* eslint-disable react-hooks/set-state-in-effect -- Sync external state to local state */
3738
useEffect(() => {
3839
if (!isMobile) {
39-
setFolderSidebarOpen(responsiveFolderPanel.isOpen);
40-
setFilesPanelOpen(responsiveNotesPanel.isOpen);
40+
if (folderSidebarOpen !== responsiveFolderPanel.isOpen) {
41+
setFolderSidebarOpen(responsiveFolderPanel.isOpen);
42+
}
43+
if (filesPanelOpen !== responsiveNotesPanel.isOpen) {
44+
setFilesPanelOpen(responsiveNotesPanel.isOpen);
45+
}
4146
}
42-
}, [isMobile, responsiveFolderPanel.isOpen, responsiveNotesPanel.isOpen]);
47+
}, [isMobile, responsiveFolderPanel.isOpen, responsiveNotesPanel.isOpen, folderSidebarOpen, filesPanelOpen]);
48+
/* eslint-enable react-hooks/set-state-in-effect */
4349

4450
const {
4551
notes,
@@ -280,17 +286,19 @@ export default function MainLayout() {
280286
}, [selectedNote]);
281287

282288
// Update tab properties when note changes
289+
/* eslint-disable react-hooks/set-state-in-effect -- Sync note properties to tab state */
283290
useEffect(() => {
284-
if (!selectedNote?.id) return;
285-
286-
setOpenTabs(tabs =>
287-
tabs.map(tab =>
288-
tab.noteId === selectedNote.id
289-
? { ...tab, title: selectedNote.title || 'Untitled', type: selectedNote.type || 'note', isPublished: selectedNote.isPublished }
290-
: tab
291-
)
292-
);
293-
}, [selectedNote]);
291+
if (selectedNote?.id) {
292+
setOpenTabs(tabs =>
293+
tabs.map(tab =>
294+
tab.noteId === selectedNote.id
295+
? { ...tab, title: selectedNote.title || 'Untitled', type: selectedNote.type || 'note', isPublished: selectedNote.isPublished }
296+
: tab
297+
)
298+
);
299+
}
300+
}, [selectedNote?.id, selectedNote?.title, selectedNote?.type, selectedNote?.isPublished]);
301+
/* eslint-enable react-hooks/set-state-in-effect */
294302

295303
const handlePasswordChange = useCallback(() => {
296304
setSelectedNote(null);

src/components/notes/NotesPanel/NoteCard.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function NoteCard({
7777
}
7878

7979
return text || 'No additional text';
80-
}, [note?.content, note?.hidden]);
80+
}, [note]);
8181

8282
const folder = useMemo(() => {
8383
// First check if note has embedded folder data
@@ -88,7 +88,7 @@ function NoteCard({
8888
// Fallback to looking up in folders array
8989
if (!note?.folderId || !folders || folders.length === 0) return null;
9090
return folders.find((f) => f.id === note.folderId) || null;
91-
}, [note?.folder, note?.folderId, folders]);
91+
}, [note, folders]);
9292

9393
const hasExecutableCode = useMemo(() => {
9494
if (!note?.content) return false;
@@ -99,7 +99,7 @@ function NoteCard({
9999
note.content.includes('class="executable-code-block"') ||
100100
note.content.includes('executableCodeBlock')
101101
);
102-
}, [note?.content]);
102+
}, [note]);
103103

104104
if (!note) {
105105
return null;

src/components/password/ChangeMasterPasswordDialog.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export function ChangeMasterPasswordDialog({
2525
const [isChanging, setIsChanging] = useState(false);
2626
const newPasswordRef = useRef<HTMLInputElement>(null);
2727

28+
// Reset form state when dialog opens
29+
/* eslint-disable react-hooks/set-state-in-effect -- Legitimate form reset on dialog open */
2830
useEffect(() => {
2931
if (open) {
3032
setNewPassword('');
@@ -38,6 +40,7 @@ export function ChangeMasterPasswordDialog({
3840
}, 100);
3941
}
4042
}, [open]);
43+
/* eslint-enable react-hooks/set-state-in-effect */
4144

4245
const handleChange = async () => {
4346
if (!user?.id || isChanging) return;

src/hooks/useIsMobile.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,13 @@ export function useBreakpoint() {
6262
* Checks user agent to distinguish mobile devices from desktop browsers
6363
*/
6464
export function useIsMobileDevice(): boolean {
65-
const [isMobile, setIsMobile] = useState(false);
66-
67-
useEffect(() => {
65+
const [isMobile] = useState(() => {
66+
if (typeof navigator === 'undefined') return false;
6867
const userAgent = navigator.userAgent || navigator.vendor;
69-
const isMobileUA =
70-
/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
71-
userAgent.toLowerCase()
72-
);
73-
setIsMobile(isMobileUA);
74-
}, []);
68+
return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
69+
userAgent.toLowerCase()
70+
);
71+
});
7572

7673
return isMobile;
7774
}
@@ -80,17 +77,17 @@ export function useIsMobileDevice(): boolean {
8077
* Detect specific mobile platform (iOS or Android)
8178
*/
8279
export function useMobilePlatform(): 'ios' | 'android' | null {
83-
const [platform, setPlatform] = useState<'ios' | 'android' | null>(null);
84-
85-
useEffect(() => {
80+
const [platform] = useState<'ios' | 'android' | null>(() => {
81+
if (typeof navigator === 'undefined') return null;
8682
const userAgent = navigator.userAgent || navigator.vendor;
8783

8884
if (/iphone|ipad|ipod/i.test(userAgent.toLowerCase())) {
89-
setPlatform('ios');
85+
return 'ios';
9086
} else if (/android/i.test(userAgent.toLowerCase())) {
91-
setPlatform('android');
87+
return 'android';
9288
}
93-
}, []);
89+
return null;
90+
});
9491

9592
return platform;
9693
}

src/hooks/useMasterPassword.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export function useMasterPassword() {
77
const [needsUnlock, setNeedsUnlock] = useState(false);
88
const [isChecking, setIsChecking] = useState(true);
99

10+
/* eslint-disable react-hooks/set-state-in-effect -- Check encryption status on user change */
1011
useEffect(() => {
1112
if (!user) {
1213
setIsChecking(false);
@@ -26,6 +27,7 @@ export function useMasterPassword() {
2627

2728
checkMasterPassword();
2829
}, [user]);
30+
/* eslint-enable react-hooks/set-state-in-effect */
2931

3032
const handleUnlockSuccess = () => {
3133
setNeedsUnlock(false);

0 commit comments

Comments
 (0)