mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
replace complex eslint config with biome
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useMantineTheme } from "@mantine/core"
|
||||
import { useMobile } from "hooks/useMobile"
|
||||
|
||||
export const useActionButton = () => {
|
||||
const theme = useMantineTheme()
|
||||
const mobile = useMobile(theme.breakpoints.xl)
|
||||
const spacing = mobile ? 14 : 0
|
||||
return { mobile, spacing }
|
||||
}
|
||||
import { useMantineTheme } from "@mantine/core"
|
||||
import { useMobile } from "hooks/useMobile"
|
||||
|
||||
export const useActionButton = () => {
|
||||
const theme = useMantineTheme()
|
||||
const mobile = useMobile(theme.breakpoints.xl)
|
||||
const spacing = mobile ? 14 : 0
|
||||
return { mobile, spacing }
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import { t } from "@lingui/macro"
|
||||
import { useAppSelector } from "app/store"
|
||||
|
||||
interface Step {
|
||||
label: string
|
||||
done: boolean
|
||||
}
|
||||
|
||||
export const useAppLoading = () => {
|
||||
const profile = useAppSelector(state => state.user.profile)
|
||||
const settings = useAppSelector(state => state.user.settings)
|
||||
const rootCategory = useAppSelector(state => state.tree.rootCategory)
|
||||
const tags = useAppSelector(state => state.user.tags)
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
label: t`Loading settings...`,
|
||||
done: !!settings,
|
||||
},
|
||||
{
|
||||
label: t`Loading profile...`,
|
||||
done: !!profile,
|
||||
},
|
||||
{
|
||||
label: t`Loading subscriptions...`,
|
||||
done: !!rootCategory,
|
||||
},
|
||||
{
|
||||
label: t`Loading tags...`,
|
||||
done: !!tags,
|
||||
},
|
||||
]
|
||||
|
||||
const loading = steps.some(s => !s.done)
|
||||
const loadingPercentage = Math.round((100.0 * steps.filter(s => s.done).length) / steps.length)
|
||||
const loadingStepLabel = steps.find(s => !s.done)?.label
|
||||
|
||||
return { steps, loading, loadingPercentage, loadingStepLabel }
|
||||
}
|
||||
import { t } from "@lingui/macro"
|
||||
import { useAppSelector } from "app/store"
|
||||
|
||||
interface Step {
|
||||
label: string
|
||||
done: boolean
|
||||
}
|
||||
|
||||
export const useAppLoading = () => {
|
||||
const profile = useAppSelector(state => state.user.profile)
|
||||
const settings = useAppSelector(state => state.user.settings)
|
||||
const rootCategory = useAppSelector(state => state.tree.rootCategory)
|
||||
const tags = useAppSelector(state => state.user.tags)
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
label: t`Loading settings...`,
|
||||
done: !!settings,
|
||||
},
|
||||
{
|
||||
label: t`Loading profile...`,
|
||||
done: !!profile,
|
||||
},
|
||||
{
|
||||
label: t`Loading subscriptions...`,
|
||||
done: !!rootCategory,
|
||||
},
|
||||
{
|
||||
label: t`Loading tags...`,
|
||||
done: !!tags,
|
||||
},
|
||||
]
|
||||
|
||||
const loading = steps.some(s => !s.done)
|
||||
const loadingPercentage = Math.round((100.0 * steps.filter(s => s.done).length) / steps.length)
|
||||
const loadingStepLabel = steps.find(s => !s.done)?.label
|
||||
|
||||
return { steps, loading, loadingPercentage, loadingStepLabel }
|
||||
}
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export const useBrowserExtension = () => {
|
||||
// the extension will set the "browser-extension-installed" attribute on the root element
|
||||
const [browserExtensionVersion, setBrowserExtensionVersion] = useState(
|
||||
document.documentElement.getAttribute("browser-extension-installed")
|
||||
)
|
||||
|
||||
// monitor the attribute on the root element as it may change after the page was loaded
|
||||
useEffect(() => {
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.type === "attributes") {
|
||||
const element = mutation.target as Element
|
||||
const version = element.getAttribute("browser-extension-installed")
|
||||
if (version) setBrowserExtensionVersion(version)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
// when not in an iframe, window.parent is a reference to window
|
||||
const isBrowserExtensionPopup = window.parent !== window
|
||||
const isBrowserExtensionInstalled = isBrowserExtensionPopup || !!browserExtensionVersion
|
||||
const isBrowserExtensionInstallable = !isBrowserExtensionPopup
|
||||
|
||||
const w = isBrowserExtensionPopup ? window.parent : window
|
||||
const openSettingsPage = () => w.postMessage("open-settings-page", "*")
|
||||
const openAppInNewTab = () => w.postMessage("open-app-in-new-tab", "*")
|
||||
const openLinkInBackgroundTab = (url: string) => {
|
||||
if (isBrowserExtensionInstalled) {
|
||||
w.postMessage(`open-link-in-background-tab:${url}`, "*")
|
||||
} else {
|
||||
// fallback to ctrl+click simulation
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.rel = "noreferrer"
|
||||
a.dispatchEvent(
|
||||
new MouseEvent("click", {
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
const setBadgeUnreadCount = (count: number | string) => w.postMessage(`set-badge-unread-count:${count}`, "*")
|
||||
|
||||
return {
|
||||
browserExtensionVersion,
|
||||
isBrowserExtensionInstallable,
|
||||
isBrowserExtensionInstalled,
|
||||
isBrowserExtensionPopup,
|
||||
openSettingsPage,
|
||||
openAppInNewTab,
|
||||
openLinkInBackgroundTab,
|
||||
setBadgeUnreadCount,
|
||||
}
|
||||
}
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
export const useBrowserExtension = () => {
|
||||
// the extension will set the "browser-extension-installed" attribute on the root element
|
||||
const [browserExtensionVersion, setBrowserExtensionVersion] = useState(
|
||||
document.documentElement.getAttribute("browser-extension-installed")
|
||||
)
|
||||
|
||||
// monitor the attribute on the root element as it may change after the page was loaded
|
||||
useEffect(() => {
|
||||
const observer = new MutationObserver(mutations => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === "attributes") {
|
||||
const element = mutation.target as Element
|
||||
const version = element.getAttribute("browser-extension-installed")
|
||||
if (version) setBrowserExtensionVersion(version)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
// when not in an iframe, window.parent is a reference to window
|
||||
const isBrowserExtensionPopup = window.parent !== window
|
||||
const isBrowserExtensionInstalled = isBrowserExtensionPopup || !!browserExtensionVersion
|
||||
const isBrowserExtensionInstallable = !isBrowserExtensionPopup
|
||||
|
||||
const w = isBrowserExtensionPopup ? window.parent : window
|
||||
const openSettingsPage = () => w.postMessage("open-settings-page", "*")
|
||||
const openAppInNewTab = () => w.postMessage("open-app-in-new-tab", "*")
|
||||
const openLinkInBackgroundTab = (url: string) => {
|
||||
if (isBrowserExtensionInstalled) {
|
||||
w.postMessage(`open-link-in-background-tab:${url}`, "*")
|
||||
} else {
|
||||
// fallback to ctrl+click simulation
|
||||
const a = document.createElement("a")
|
||||
a.href = url
|
||||
a.rel = "noreferrer"
|
||||
a.dispatchEvent(
|
||||
new MouseEvent("click", {
|
||||
ctrlKey: true,
|
||||
metaKey: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
const setBadgeUnreadCount = (count: number | string) => w.postMessage(`set-badge-unread-count:${count}`, "*")
|
||||
|
||||
return {
|
||||
browserExtensionVersion,
|
||||
isBrowserExtensionInstallable,
|
||||
isBrowserExtensionInstalled,
|
||||
isBrowserExtensionPopup,
|
||||
openSettingsPage,
|
||||
openAppInNewTab,
|
||||
openLinkInBackgroundTab,
|
||||
setBadgeUnreadCount,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// the color scheme to use to render components
|
||||
import { useMantineColorScheme } from "@mantine/core"
|
||||
import { useMediaQuery } from "@mantine/hooks"
|
||||
|
||||
export const useColorScheme = () => {
|
||||
const systemColorScheme = useMediaQuery(
|
||||
"(prefers-color-scheme: dark)",
|
||||
// passing undefined will use window.matchMedia(query) as default value
|
||||
undefined,
|
||||
{
|
||||
// get initial value synchronously and not in useEffect to avoid flash of light theme
|
||||
getInitialValueInEffect: false,
|
||||
}
|
||||
)
|
||||
? "dark"
|
||||
: "light"
|
||||
|
||||
const { colorScheme } = useMantineColorScheme()
|
||||
return colorScheme === "auto" ? systemColorScheme : colorScheme
|
||||
}
|
||||
// the color scheme to use to render components
|
||||
import { useMantineColorScheme } from "@mantine/core"
|
||||
import { useMediaQuery } from "@mantine/hooks"
|
||||
|
||||
export const useColorScheme = () => {
|
||||
const systemColorScheme = useMediaQuery(
|
||||
"(prefers-color-scheme: dark)",
|
||||
// passing undefined will use window.matchMedia(query) as default value
|
||||
undefined,
|
||||
{
|
||||
// get initial value synchronously and not in useEffect to avoid flash of light theme
|
||||
getInitialValueInEffect: false,
|
||||
}
|
||||
)
|
||||
? "dark"
|
||||
: "light"
|
||||
|
||||
const { colorScheme } = useMantineColorScheme()
|
||||
return colorScheme === "auto" ? systemColorScheme : colorScheme
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useMediaQuery } from "@mantine/hooks"
|
||||
import { Constants } from "app/constants"
|
||||
|
||||
export const useMobile = (breakpoint: string | number = Constants.layout.mobileBreakpoint) => {
|
||||
const bp = typeof breakpoint === "number" ? `${breakpoint}px` : breakpoint
|
||||
return !useMediaQuery(`(min-width: ${bp})`, undefined, {
|
||||
getInitialValueInEffect: false,
|
||||
})
|
||||
}
|
||||
import { useMediaQuery } from "@mantine/hooks"
|
||||
import { Constants } from "app/constants"
|
||||
|
||||
export const useMobile = (breakpoint: string | number = Constants.layout.mobileBreakpoint) => {
|
||||
const bp = typeof breakpoint === "number" ? `${breakpoint}px` : breakpoint
|
||||
return !useMediaQuery(`(min-width: ${bp})`, undefined, {
|
||||
getInitialValueInEffect: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import mousetrap, { type ExtendedKeyboardEvent } from "mousetrap"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
type Callback = (e: ExtendedKeyboardEvent, combo: string) => void
|
||||
|
||||
export const useMousetrap = (key: string | string[], callback: Callback) => {
|
||||
// use a ref to avoid unbinding/rebinding every time the callback changes
|
||||
const callbackRef = useRef(callback)
|
||||
callbackRef.current = callback
|
||||
|
||||
useEffect(() => {
|
||||
mousetrap.bind(key, (event, combo) => {
|
||||
callbackRef.current(event, combo)
|
||||
|
||||
// prevent default behavior
|
||||
return false
|
||||
})
|
||||
return () => {
|
||||
mousetrap.unbind(key)
|
||||
}
|
||||
}, [key])
|
||||
}
|
||||
import mousetrap, { type ExtendedKeyboardEvent } from "mousetrap"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
type Callback = (e: ExtendedKeyboardEvent, combo: string) => void
|
||||
|
||||
export const useMousetrap = (key: string | string[], callback: Callback) => {
|
||||
// use a ref to avoid unbinding/rebinding every time the callback changes
|
||||
const callbackRef = useRef(callback)
|
||||
callbackRef.current = callback
|
||||
|
||||
useEffect(() => {
|
||||
mousetrap.bind(key, (event, combo) => {
|
||||
callbackRef.current(event, combo)
|
||||
|
||||
// prevent default behavior
|
||||
return false
|
||||
})
|
||||
return () => {
|
||||
mousetrap.unbind(key)
|
||||
}
|
||||
}, [key])
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type ViewMode } from "app/types"
|
||||
import useLocalStorage from "use-local-storage"
|
||||
|
||||
export function useViewMode() {
|
||||
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("view-mode", "detailed")
|
||||
return { viewMode, setViewMode }
|
||||
}
|
||||
import type { ViewMode } from "app/types"
|
||||
import useLocalStorage from "use-local-storage"
|
||||
|
||||
export function useViewMode() {
|
||||
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("view-mode", "detailed")
|
||||
return { viewMode, setViewMode }
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
import { setWebSocketConnected } from "app/server/slice"
|
||||
import { type AppDispatch, useAppDispatch, useAppSelector } from "app/store"
|
||||
import { incrementUnreadCount } from "app/tree/slice"
|
||||
import { useEffect } from "react"
|
||||
import WebsocketHeartbeatJs from "websocket-heartbeat-js"
|
||||
|
||||
const handleMessage = (dispatch: AppDispatch, message: string) => {
|
||||
const parts = message.split(":")
|
||||
const type = parts[0]
|
||||
if (type === "new-feed-entries") {
|
||||
dispatch(
|
||||
incrementUnreadCount({
|
||||
feedId: +parts[1],
|
||||
amount: +parts[2],
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const useWebSocket = () => {
|
||||
const websocketEnabled = useAppSelector(state => state.server.serverInfos?.websocketEnabled)
|
||||
const websocketPingInterval = useAppSelector(state => state.server.serverInfos?.websocketPingInterval)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
let ws: WebsocketHeartbeatJs | undefined
|
||||
|
||||
if (websocketEnabled && websocketPingInterval) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss"
|
||||
const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}${currentUrl.pathname}ws`
|
||||
|
||||
ws = new WebsocketHeartbeatJs({
|
||||
url: wsUrl,
|
||||
pingMsg: "ping",
|
||||
pingTimeout: websocketPingInterval,
|
||||
})
|
||||
ws.onopen = () => dispatch(setWebSocketConnected(true))
|
||||
ws.onclose = () => dispatch(setWebSocketConnected(false))
|
||||
ws.onmessage = event => {
|
||||
if (typeof event.data === "string") {
|
||||
handleMessage(dispatch, event.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => ws?.close()
|
||||
}, [dispatch, websocketEnabled, websocketPingInterval])
|
||||
}
|
||||
import { setWebSocketConnected } from "app/server/slice"
|
||||
import { type AppDispatch, useAppDispatch, useAppSelector } from "app/store"
|
||||
import { incrementUnreadCount } from "app/tree/slice"
|
||||
import { useEffect } from "react"
|
||||
import WebsocketHeartbeatJs from "websocket-heartbeat-js"
|
||||
|
||||
const handleMessage = (dispatch: AppDispatch, message: string) => {
|
||||
const parts = message.split(":")
|
||||
const type = parts[0]
|
||||
if (type === "new-feed-entries") {
|
||||
dispatch(
|
||||
incrementUnreadCount({
|
||||
feedId: +parts[1],
|
||||
amount: +parts[2],
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const useWebSocket = () => {
|
||||
const websocketEnabled = useAppSelector(state => state.server.serverInfos?.websocketEnabled)
|
||||
const websocketPingInterval = useAppSelector(state => state.server.serverInfos?.websocketPingInterval)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
let ws: WebsocketHeartbeatJs | undefined
|
||||
|
||||
if (websocketEnabled && websocketPingInterval) {
|
||||
const currentUrl = new URL(window.location.href)
|
||||
const wsProtocol = currentUrl.protocol === "http:" ? "ws" : "wss"
|
||||
const wsUrl = `${wsProtocol}://${currentUrl.hostname}:${currentUrl.port}${currentUrl.pathname}ws`
|
||||
|
||||
ws = new WebsocketHeartbeatJs({
|
||||
url: wsUrl,
|
||||
pingMsg: "ping",
|
||||
pingTimeout: websocketPingInterval,
|
||||
})
|
||||
ws.onopen = () => dispatch(setWebSocketConnected(true))
|
||||
ws.onclose = () => dispatch(setWebSocketConnected(false))
|
||||
ws.onmessage = event => {
|
||||
if (typeof event.data === "string") {
|
||||
handleMessage(dispatch, event.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => ws?.close()
|
||||
}, [dispatch, websocketEnabled, websocketPingInterval])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user