mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-05 20:41:30 +08:00
feat: open popup url in external browser (#4446)
* feat: open popup url in external browser * fix: allow google auth popup internal * feat: add functionality(including settings) to open links in external browser for webviews * fix: set useragent globally * fix: remove setUserAgent in webview * fix: set Chrome version to newest
This commit is contained in:
parent
308ad9f68f
commit
eaa37fe674
@ -20,6 +20,8 @@ export enum IpcChannel {
|
|||||||
App_InstallUvBinary = 'app:install-uv-binary',
|
App_InstallUvBinary = 'app:install-uv-binary',
|
||||||
App_InstallBunBinary = 'app:install-bun-binary',
|
App_InstallBunBinary = 'app:install-bun-binary',
|
||||||
|
|
||||||
|
Webview_SetOpenLinkExternal = 'webview:set-open-link-external',
|
||||||
|
|
||||||
// Open
|
// Open
|
||||||
Open_Path = 'open:path',
|
Open_Path = 'open:path',
|
||||||
Open_Website = 'open:website',
|
Open_Website = 'open:website',
|
||||||
|
|||||||
@ -75,14 +75,6 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
handleProtocolUrl(url)
|
handleProtocolUrl(url)
|
||||||
})
|
})
|
||||||
|
|
||||||
registerProtocolClient(app)
|
|
||||||
|
|
||||||
// macOS specific: handle protocol when app is already running
|
|
||||||
app.on('open-url', (event, url) => {
|
|
||||||
event.preventDefault()
|
|
||||||
handleProtocolUrl(url)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Listen for second instance
|
// Listen for second instance
|
||||||
app.on('second-instance', (_event, argv) => {
|
app.on('second-instance', (_event, argv) => {
|
||||||
windowService.showMainWindow()
|
windowService.showMainWindow()
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
|||||||
import { searchService } from './services/SearchService'
|
import { searchService } from './services/SearchService'
|
||||||
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
||||||
import { TrayService } from './services/TrayService'
|
import { TrayService } from './services/TrayService'
|
||||||
|
import { setOpenLinkExternal } from './services/WebviewService'
|
||||||
import { windowService } from './services/WindowService'
|
import { windowService } from './services/WindowService'
|
||||||
import { getResourcePath } from './utils'
|
import { getResourcePath } from './utils'
|
||||||
import { decrypt, encrypt } from './utils/aes'
|
import { decrypt, encrypt } from './utils/aes'
|
||||||
@ -342,4 +343,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, async (_, uid: string, url: string) => {
|
ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, async (_, uid: string, url: string) => {
|
||||||
return await searchService.openUrlInSearchWindow(uid, url)
|
return await searchService.openUrlInSearchWindow(uid, url)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// webview
|
||||||
|
ipcMain.handle(IpcChannel.Webview_SetOpenLinkExternal, (_, webviewId: number, isExternal: boolean) =>
|
||||||
|
setOpenLinkExternal(webviewId, isExternal)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/main/services/WebviewService.ts
Normal file
35
src/main/services/WebviewService.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { session, shell, webContents } from 'electron'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init the useragent of the webview session
|
||||||
|
* remove the CherryStudio and Electron from the useragent
|
||||||
|
*/
|
||||||
|
export function initSessionUserAgent() {
|
||||||
|
const wvSession = session.fromPartition('persist:webview')
|
||||||
|
const newChromeVersion = '135.0.7049.96'
|
||||||
|
const originUA = wvSession.getUserAgent()
|
||||||
|
const newUA = originUA
|
||||||
|
.replace(/CherryStudio\/\S+\s/, '')
|
||||||
|
.replace(/Electron\/\S+\s/, '')
|
||||||
|
.replace(/Chrome\/\d+\.\d+\.\d+\.\d+/, `Chrome/${newChromeVersion}`)
|
||||||
|
|
||||||
|
wvSession.setUserAgent(newUA)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebviewService handles the behavior of links opened from webview elements
|
||||||
|
* It controls whether links should be opened within the application or in an external browser
|
||||||
|
*/
|
||||||
|
export function setOpenLinkExternal(webviewId: number, isExternal: boolean) {
|
||||||
|
const webview = webContents.fromId(webviewId)
|
||||||
|
if (!webview) return
|
||||||
|
|
||||||
|
webview.setWindowOpenHandler(({ url }) => {
|
||||||
|
if (isExternal) {
|
||||||
|
shell.openExternal(url)
|
||||||
|
return { action: 'deny' }
|
||||||
|
} else {
|
||||||
|
return { action: 'allow' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ import icon from '../../../build/icon.png?asset'
|
|||||||
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
|
import { titleBarOverlayDark, titleBarOverlayLight } from '../config'
|
||||||
import { locales } from '../utils/locales'
|
import { locales } from '../utils/locales'
|
||||||
import { configManager } from './ConfigManager'
|
import { configManager } from './ConfigManager'
|
||||||
|
import { initSessionUserAgent } from './WebviewService'
|
||||||
|
|
||||||
export class WindowService {
|
export class WindowService {
|
||||||
private static instance: WindowService | null = null
|
private static instance: WindowService | null = null
|
||||||
@ -81,6 +82,9 @@ export class WindowService {
|
|||||||
this.miniWindow = this.createMiniWindow(true)
|
this.miniWindow = this.createMiniWindow(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//init the MinApp webviews' useragent
|
||||||
|
initSessionUserAgent()
|
||||||
|
|
||||||
return this.mainWindow
|
return this.mainWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
src/preload/index.d.ts
vendored
3
src/preload/index.d.ts
vendored
@ -204,6 +204,9 @@ declare global {
|
|||||||
closeSearchWindow: (uid: string) => Promise<string>
|
closeSearchWindow: (uid: string) => Promise<string>
|
||||||
openUrlInSearchWindow: (uid: string, url: string) => Promise<string>
|
openUrlInSearchWindow: (uid: string, url: string) => Promise<string>
|
||||||
}
|
}
|
||||||
|
webview: {
|
||||||
|
setOpenLinkExternal: (webviewId: number, isExternal: boolean) => Promise<void>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,6 +185,10 @@ const api = {
|
|||||||
openSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Open, uid),
|
openSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Open, uid),
|
||||||
closeSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid),
|
closeSearchWindow: (uid: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_Close, uid),
|
||||||
openUrlInSearchWindow: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url)
|
openUrlInSearchWindow: (uid: string, url: string) => ipcRenderer.invoke(IpcChannel.SearchWindow_OpenUrl, uid, url)
|
||||||
|
},
|
||||||
|
webview: {
|
||||||
|
setOpenLinkExternal: (webviewId: number, isExternal: boolean) =>
|
||||||
|
ipcRenderer.invoke(IpcChannel.Webview_SetOpenLinkExternal, webviewId, isExternal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import {
|
|||||||
CodeOutlined,
|
CodeOutlined,
|
||||||
CopyOutlined,
|
CopyOutlined,
|
||||||
ExportOutlined,
|
ExportOutlined,
|
||||||
|
LinkOutlined,
|
||||||
MinusOutlined,
|
MinusOutlined,
|
||||||
PushpinOutlined,
|
PushpinOutlined,
|
||||||
ReloadOutlined
|
ReloadOutlined
|
||||||
@ -14,6 +15,9 @@ import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
|||||||
import { useMinapps } from '@renderer/hooks/useMinapps'
|
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||||
import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
|
import useNavBackgroundColor from '@renderer/hooks/useNavBackgroundColor'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
|
import { useAppDispatch } from '@renderer/store'
|
||||||
|
import { setMinappsOpenLinkExternal } from '@renderer/store/settings'
|
||||||
import { MinAppType } from '@renderer/types'
|
import { MinAppType } from '@renderer/types'
|
||||||
import { delay } from '@renderer/utils'
|
import { delay } from '@renderer/utils'
|
||||||
import { Avatar, Drawer, Tooltip } from 'antd'
|
import { Avatar, Drawer, Tooltip } from 'antd'
|
||||||
@ -40,6 +44,7 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
const { pinned, updatePinnedMinapps } = useMinapps()
|
const { pinned, updatePinnedMinapps } = useMinapps()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const backgroundColor = useNavBackgroundColor()
|
const backgroundColor = useNavBackgroundColor()
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
/** control the drawer open or close */
|
/** control the drawer open or close */
|
||||||
const [isPopupShow, setIsPopupShow] = useState(true)
|
const [isPopupShow, setIsPopupShow] = useState(true)
|
||||||
@ -57,6 +62,8 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
const webviewRefs = useRef<Map<string, WebviewTag | null>>(new Map())
|
const webviewRefs = useRef<Map<string, WebviewTag | null>>(new Map())
|
||||||
/** indicate whether the webview has loaded */
|
/** indicate whether the webview has loaded */
|
||||||
const webviewLoadedRefs = useRef<Map<string, boolean>>(new Map())
|
const webviewLoadedRefs = useRef<Map<string, boolean>>(new Map())
|
||||||
|
/** whether the minapps open link external is enabled */
|
||||||
|
const { minappsOpenLinkExternal } = useSettings()
|
||||||
|
|
||||||
const isInDevelopment = process.env.NODE_ENV === 'development'
|
const isInDevelopment = process.env.NODE_ENV === 'development'
|
||||||
|
|
||||||
@ -107,9 +114,14 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
webviewLoadedRefs.current.forEach((_, appid) => {
|
webviewLoadedRefs.current.forEach((_, appid) => {
|
||||||
if (!webviewRefs.current.has(appid)) {
|
if (!webviewRefs.current.has(appid)) {
|
||||||
webviewLoadedRefs.current.delete(appid)
|
webviewLoadedRefs.current.delete(appid)
|
||||||
|
} else if (appid === currentMinappId) {
|
||||||
|
const webviewId = webviewRefs.current.get(appid)?.getWebContentsId()
|
||||||
|
if (webviewId) {
|
||||||
|
window.api.webview.setOpenLinkExternal(webviewId, minappsOpenLinkExternal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [currentMinappId])
|
}, [currentMinappId, minappsOpenLinkExternal])
|
||||||
|
|
||||||
/** only the keepalive minapp can be minimized */
|
/** only the keepalive minapp can be minimized */
|
||||||
const canMinimize = !(openedOneOffMinapp && openedOneOffMinapp.id == currentMinappId)
|
const canMinimize = !(openedOneOffMinapp && openedOneOffMinapp.id == currentMinappId)
|
||||||
@ -175,6 +187,10 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
/** the callback function to set the webviews loaded indicator */
|
/** the callback function to set the webviews loaded indicator */
|
||||||
const handleWebviewLoaded = (appid: string) => {
|
const handleWebviewLoaded = (appid: string) => {
|
||||||
webviewLoadedRefs.current.set(appid, true)
|
webviewLoadedRefs.current.set(appid, true)
|
||||||
|
const webviewId = webviewRefs.current.get(appid)?.getWebContentsId()
|
||||||
|
if (webviewId) {
|
||||||
|
window.api.webview.setOpenLinkExternal(webviewId, minappsOpenLinkExternal)
|
||||||
|
}
|
||||||
if (appid == currentMinappId) {
|
if (appid == currentMinappId) {
|
||||||
setTimeout(() => setIsReady(true), 200)
|
setTimeout(() => setIsReady(true), 200)
|
||||||
}
|
}
|
||||||
@ -220,6 +236,11 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
updatePinnedMinapps(newPinned)
|
updatePinnedMinapps(newPinned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** set the open external status */
|
||||||
|
const handleToggleOpenExternal = () => {
|
||||||
|
dispatch(setMinappsOpenLinkExternal(!minappsOpenLinkExternal))
|
||||||
|
}
|
||||||
|
|
||||||
/** Title bar of the popup */
|
/** Title bar of the popup */
|
||||||
const Title = ({ appInfo, url }: { appInfo: AppInfo | null; url: string | null }) => {
|
const Title = ({ appInfo, url }: { appInfo: AppInfo | null; url: string | null }) => {
|
||||||
if (!appInfo) return null
|
if (!appInfo) return null
|
||||||
@ -238,7 +259,7 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TitleContainer style={{ backgroundColor: backgroundColor, justifyContent: 'space-between' }}>
|
<TitleContainer style={{ backgroundColor: backgroundColor }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<TitleTextTooltip>
|
<TitleTextTooltip>
|
||||||
@ -256,6 +277,14 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
}}>
|
}}>
|
||||||
<TitleText onContextMenu={(e) => handleCopyUrl(e, url ?? appInfo.url)}>{appInfo.name}</TitleText>
|
<TitleText onContextMenu={(e) => handleCopyUrl(e, url ?? appInfo.url)}>{appInfo.name}</TitleText>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{appInfo.canOpenExternalLink && (
|
||||||
|
<Tooltip title={t('minapp.popup.openExternal')} mouseEnterDelay={0.8} placement="bottom">
|
||||||
|
<Button onClick={() => handleOpenLink(url ?? appInfo.url)}>
|
||||||
|
<ExportOutlined />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Spacer />
|
||||||
<ButtonsGroup className={isWindows ? 'windows' : ''}>
|
<ButtonsGroup className={isWindows ? 'windows' : ''}>
|
||||||
<Tooltip title={t('minapp.popup.refresh')} mouseEnterDelay={0.8} placement="bottom">
|
<Tooltip title={t('minapp.popup.refresh')} mouseEnterDelay={0.8} placement="bottom">
|
||||||
<Button onClick={() => handleReload(appInfo.id)}>
|
<Button onClick={() => handleReload(appInfo.id)}>
|
||||||
@ -272,13 +301,18 @@ const MinappPopupContainer: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{appInfo.canOpenExternalLink && (
|
<Tooltip
|
||||||
<Tooltip title={t('minapp.popup.openExternal')} mouseEnterDelay={0.8} placement="bottom">
|
title={
|
||||||
<Button onClick={() => handleOpenLink(url ?? appInfo.url)}>
|
minappsOpenLinkExternal
|
||||||
<ExportOutlined />
|
? t('minapp.popup.open_link_external_on')
|
||||||
</Button>
|
: t('minapp.popup.open_link_external_off')
|
||||||
</Tooltip>
|
}
|
||||||
)}
|
mouseEnterDelay={0.8}
|
||||||
|
placement="bottom">
|
||||||
|
<Button onClick={handleToggleOpenExternal} className={minappsOpenLinkExternal ? 'open-external' : ''}>
|
||||||
|
<LinkOutlined />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
{isInDevelopment && (
|
{isInDevelopment && (
|
||||||
<Tooltip title={t('minapp.popup.devtools')} mouseEnterDelay={0.8} placement="bottom">
|
<Tooltip title={t('minapp.popup.devtools')} mouseEnterDelay={0.8} placement="bottom">
|
||||||
<Button onClick={() => handleOpenDevTools(appInfo.id)}>
|
<Button onClick={() => handleOpenDevTools(appInfo.id)}>
|
||||||
@ -367,8 +401,8 @@ const TitleText = styled.div`
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
margin-right: 10px;
|
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
|
margin-right: 5px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const TitleTextTooltip = styled.span`
|
const TitleTextTooltip = styled.span`
|
||||||
@ -407,6 +441,7 @@ const Button = styled.div`
|
|||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-background-mute);
|
||||||
@ -415,6 +450,10 @@ const Button = styled.div`
|
|||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
background-color: var(--color-primary-bg);
|
background-color: var(--color-primary-bg);
|
||||||
}
|
}
|
||||||
|
&.open-external {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background-color: var(--color-primary-bg);
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const EmptyView = styled.div`
|
const EmptyView = styled.div`
|
||||||
@ -428,4 +467,8 @@ const EmptyView = styled.div`
|
|||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const Spacer = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`
|
||||||
|
|
||||||
export default MinappPopupContainer
|
export default MinappPopupContainer
|
||||||
|
|||||||
@ -60,9 +60,6 @@ const WebviewContainer = memo(
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [appid, url])
|
}, [appid, url])
|
||||||
|
|
||||||
//remove the tag of CherryStudio and Electron
|
|
||||||
const userAgent = navigator.userAgent.replace(/CherryStudio\/\S+\s/, '').replace(/Electron\/\S+\s/, '')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<webview
|
<webview
|
||||||
key={appid}
|
key={appid}
|
||||||
@ -70,7 +67,6 @@ const WebviewContainer = memo(
|
|||||||
style={WebviewStyle}
|
style={WebviewStyle}
|
||||||
allowpopups={'true' as any}
|
allowpopups={'true' as any}
|
||||||
partition="persist:webview"
|
partition="persist:webview"
|
||||||
useragent={userAgent}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -585,7 +585,9 @@
|
|||||||
"minimize": "Minimize MinApp",
|
"minimize": "Minimize MinApp",
|
||||||
"devtools": "Developer Tools",
|
"devtools": "Developer Tools",
|
||||||
"openExternal": "Open in Browser",
|
"openExternal": "Open in Browser",
|
||||||
"rightclick_copyurl": "Right-click to copy URL"
|
"rightclick_copyurl": "Right-click to copy URL",
|
||||||
|
"open_link_external_on": "Current: Open links in browser",
|
||||||
|
"open_link_external_off": "Current: Open links in default window"
|
||||||
},
|
},
|
||||||
"sidebar.add.title": "Add to sidebar",
|
"sidebar.add.title": "Add to sidebar",
|
||||||
"sidebar.remove.title": "Remove from sidebar",
|
"sidebar.remove.title": "Remove from sidebar",
|
||||||
@ -1020,6 +1022,9 @@
|
|||||||
"disabled": "Hidden Mini Apps",
|
"disabled": "Hidden Mini Apps",
|
||||||
"empty": "Drag mini apps from the left to hide them",
|
"empty": "Drag mini apps from the left to hide them",
|
||||||
"visible": "Visible Mini Apps",
|
"visible": "Visible Mini Apps",
|
||||||
|
"open_link_external": {
|
||||||
|
"title": "Open new-window links in browser"
|
||||||
|
},
|
||||||
"cache_settings": "Cache Settings",
|
"cache_settings": "Cache Settings",
|
||||||
"cache_title": "Mini App Cache Limit",
|
"cache_title": "Mini App Cache Limit",
|
||||||
"cache_description": "Set the maximum number of active mini apps to keep in memory",
|
"cache_description": "Set the maximum number of active mini apps to keep in memory",
|
||||||
|
|||||||
@ -585,7 +585,9 @@
|
|||||||
"minimize": "ミニアプリを最小化",
|
"minimize": "ミニアプリを最小化",
|
||||||
"devtools": "開発者ツール",
|
"devtools": "開発者ツール",
|
||||||
"openExternal": "ブラウザで開く",
|
"openExternal": "ブラウザで開く",
|
||||||
"rightclick_copyurl": "右クリックでURLをコピー"
|
"rightclick_copyurl": "右クリックでURLをコピー",
|
||||||
|
"open_link_external_on": "現在:ブラウザで開く",
|
||||||
|
"open_link_external_off": "現在:デフォルトのウィンドウで開く"
|
||||||
},
|
},
|
||||||
"sidebar.add.title": "サイドバーに追加",
|
"sidebar.add.title": "サイドバーに追加",
|
||||||
"sidebar.remove.title": "サイドバーから削除",
|
"sidebar.remove.title": "サイドバーから削除",
|
||||||
@ -1020,6 +1022,9 @@
|
|||||||
"disabled": "非表示のミニアプリ",
|
"disabled": "非表示のミニアプリ",
|
||||||
"empty": "非表示にするミニアプリを左側からここにドラッグしてください",
|
"empty": "非表示にするミニアプリを左側からここにドラッグしてください",
|
||||||
"visible": "表示するミニアプリ",
|
"visible": "表示するミニアプリ",
|
||||||
|
"open_link_external": {
|
||||||
|
"title": "新視窗のリンクをブラウザで開く"
|
||||||
|
},
|
||||||
"cache_settings": "キャッシュ設定",
|
"cache_settings": "キャッシュ設定",
|
||||||
"cache_title": "ミニアプリのキャッシュ数",
|
"cache_title": "ミニアプリのキャッシュ数",
|
||||||
"cache_description": "メモリに保持するアクティブなミニアプリの最大数を設定します",
|
"cache_description": "メモリに保持するアクティブなミニアプリの最大数を設定します",
|
||||||
|
|||||||
@ -585,7 +585,9 @@
|
|||||||
"minimize": "Свернуть встроенное приложение",
|
"minimize": "Свернуть встроенное приложение",
|
||||||
"devtools": "Инструменты разработчика",
|
"devtools": "Инструменты разработчика",
|
||||||
"openExternal": "Открыть в браузере",
|
"openExternal": "Открыть в браузере",
|
||||||
"rightclick_copyurl": "ПКМ → Копировать URL"
|
"rightclick_copyurl": "ПКМ → Копировать URL",
|
||||||
|
"open_link_external_on": "Текущий: Открыть ссылки в браузере",
|
||||||
|
"open_link_external_off": "Текущий: Открыть ссылки в окне по умолчанию"
|
||||||
},
|
},
|
||||||
"sidebar.add.title": "Добавить в боковую панель",
|
"sidebar.add.title": "Добавить в боковую панель",
|
||||||
"sidebar.remove.title": "Удалить из боковой панели",
|
"sidebar.remove.title": "Удалить из боковой панели",
|
||||||
@ -1020,6 +1022,9 @@
|
|||||||
"disabled": "Скрытые мини-приложения",
|
"disabled": "Скрытые мини-приложения",
|
||||||
"empty": "Перетащите мини-приложения слева, чтобы скрыть их",
|
"empty": "Перетащите мини-приложения слева, чтобы скрыть их",
|
||||||
"visible": "Отображаемые мини-приложения",
|
"visible": "Отображаемые мини-приложения",
|
||||||
|
"open_link_external": {
|
||||||
|
"title": "Открывать новые окна в браузере"
|
||||||
|
},
|
||||||
"cache_settings": "Настройки кэша",
|
"cache_settings": "Настройки кэша",
|
||||||
"cache_title": "Количество кэшируемых мини-приложений",
|
"cache_title": "Количество кэшируемых мини-приложений",
|
||||||
"cache_description": "Установить максимальное количество активных мини-приложений в памяти",
|
"cache_description": "Установить максимальное количество активных мини-приложений в памяти",
|
||||||
|
|||||||
@ -585,7 +585,9 @@
|
|||||||
"minimize": "最小化小程序",
|
"minimize": "最小化小程序",
|
||||||
"devtools": "开发者工具",
|
"devtools": "开发者工具",
|
||||||
"openExternal": "在浏览器中打开",
|
"openExternal": "在浏览器中打开",
|
||||||
"rightclick_copyurl": "右键复制URL"
|
"rightclick_copyurl": "右键复制URL",
|
||||||
|
"open_link_external_on": "当前:在浏览器中打开链接",
|
||||||
|
"open_link_external_off": "当前:使用默认窗口打开链接"
|
||||||
},
|
},
|
||||||
"sidebar.add.title": "添加到侧边栏",
|
"sidebar.add.title": "添加到侧边栏",
|
||||||
"sidebar.remove.title": "从侧边栏移除",
|
"sidebar.remove.title": "从侧边栏移除",
|
||||||
@ -1020,6 +1022,9 @@
|
|||||||
"disabled": "隐藏的小程序",
|
"disabled": "隐藏的小程序",
|
||||||
"empty": "把要隐藏的小程序从左侧拖拽到这里",
|
"empty": "把要隐藏的小程序从左侧拖拽到这里",
|
||||||
"visible": "显示的小程序",
|
"visible": "显示的小程序",
|
||||||
|
"open_link_external": {
|
||||||
|
"title": "在浏览器中打开新窗口链接"
|
||||||
|
},
|
||||||
"cache_settings": "缓存设置",
|
"cache_settings": "缓存设置",
|
||||||
"cache_title": "小程序缓存数量",
|
"cache_title": "小程序缓存数量",
|
||||||
"cache_description": "设置同时保持活跃状态的小程序最大数量",
|
"cache_description": "设置同时保持活跃状态的小程序最大数量",
|
||||||
|
|||||||
@ -585,7 +585,9 @@
|
|||||||
"minimize": "最小化小工具",
|
"minimize": "最小化小工具",
|
||||||
"devtools": "開發者工具",
|
"devtools": "開發者工具",
|
||||||
"openExternal": "在瀏覽器中開啟",
|
"openExternal": "在瀏覽器中開啟",
|
||||||
"rightclick_copyurl": "右鍵複製URL"
|
"rightclick_copyurl": "右鍵複製URL",
|
||||||
|
"open_link_external_on": "当前:在瀏覽器中開啟連結",
|
||||||
|
"open_link_external_off": "当前:使用預設視窗開啟連結"
|
||||||
},
|
},
|
||||||
"sidebar.add.title": "新增到側邊欄",
|
"sidebar.add.title": "新增到側邊欄",
|
||||||
"sidebar.remove.title": "從側邊欄移除",
|
"sidebar.remove.title": "從側邊欄移除",
|
||||||
@ -1020,6 +1022,9 @@
|
|||||||
"disabled": "隱藏的小程式",
|
"disabled": "隱藏的小程式",
|
||||||
"empty": "把要隱藏的小程式從左側拖拽到這裡",
|
"empty": "把要隱藏的小程式從左側拖拽到這裡",
|
||||||
"visible": "顯示的小程式",
|
"visible": "顯示的小程式",
|
||||||
|
"open_link_external": {
|
||||||
|
"title": "在瀏覽器中打開新視窗連結"
|
||||||
|
},
|
||||||
"cache_settings": "緩存設置",
|
"cache_settings": "緩存設置",
|
||||||
"cache_title": "小程式緩存數量",
|
"cache_title": "小程式緩存數量",
|
||||||
"cache_description": "設置同時保持活躍狀態的小程式最大數量",
|
"cache_description": "設置同時保持活躍狀態的小程式最大數量",
|
||||||
|
|||||||
@ -4,7 +4,11 @@ import { useTheme } from '@renderer/context/ThemeProvider'
|
|||||||
import { useMinapps } from '@renderer/hooks/useMinapps'
|
import { useMinapps } from '@renderer/hooks/useMinapps'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setMaxKeepAliveMinapps, setShowOpenedMinappsInSidebar } from '@renderer/store/settings'
|
import {
|
||||||
|
setMaxKeepAliveMinapps,
|
||||||
|
setMinappsOpenLinkExternal,
|
||||||
|
setShowOpenedMinappsInSidebar
|
||||||
|
} from '@renderer/store/settings'
|
||||||
import { Button, message, Slider, Switch, Tooltip } from 'antd'
|
import { Button, message, Slider, Switch, Tooltip } from 'antd'
|
||||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -20,7 +24,7 @@ const MiniAppSettings: FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { maxKeepAliveMinapps, showOpenedMinappsInSidebar } = useSettings()
|
const { maxKeepAliveMinapps, showOpenedMinappsInSidebar, minappsOpenLinkExternal } = useSettings()
|
||||||
const { minapps, disabled, updateMinapps, updateDisabledMinapps } = useMinapps()
|
const { minapps, disabled, updateMinapps, updateDisabledMinapps } = useMinapps()
|
||||||
|
|
||||||
const [visibleMiniApps, setVisibleMiniApps] = useState(minapps)
|
const [visibleMiniApps, setVisibleMiniApps] = useState(minapps)
|
||||||
@ -89,9 +93,19 @@ const MiniAppSettings: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</BorderedContainer>
|
</BorderedContainer>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingLabelGroup>
|
||||||
|
<SettingRowTitle>{t('settings.miniapps.open_link_external.title')}</SettingRowTitle>
|
||||||
|
</SettingLabelGroup>
|
||||||
|
<Switch
|
||||||
|
checked={minappsOpenLinkExternal}
|
||||||
|
onChange={(checked) => dispatch(setMinappsOpenLinkExternal(checked))}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
|
|
||||||
{/* 缓存小程序数量设置 */}
|
{/* 缓存小程序数量设置 */}
|
||||||
<CacheSettingRow>
|
<SettingRow>
|
||||||
<SettingLabelGroup>
|
<SettingLabelGroup>
|
||||||
<SettingRowTitle>{t('settings.miniapps.cache_title')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.miniapps.cache_title')}</SettingRowTitle>
|
||||||
<SettingDescription>{t('settings.miniapps.cache_description')}</SettingDescription>
|
<SettingDescription>{t('settings.miniapps.cache_description')}</SettingDescription>
|
||||||
@ -117,9 +131,9 @@ const MiniAppSettings: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</SliderWithResetContainer>
|
</SliderWithResetContainer>
|
||||||
</CacheSettingControls>
|
</CacheSettingControls>
|
||||||
</CacheSettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SidebarSettingRow>
|
<SettingRow>
|
||||||
<SettingLabelGroup>
|
<SettingLabelGroup>
|
||||||
<SettingRowTitle>{t('settings.miniapps.sidebar_title')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.miniapps.sidebar_title')}</SettingRowTitle>
|
||||||
<SettingDescription>{t('settings.miniapps.sidebar_description')}</SettingDescription>
|
<SettingDescription>{t('settings.miniapps.sidebar_description')}</SettingDescription>
|
||||||
@ -128,14 +142,14 @@ const MiniAppSettings: FC = () => {
|
|||||||
checked={showOpenedMinappsInSidebar}
|
checked={showOpenedMinappsInSidebar}
|
||||||
onChange={(checked) => dispatch(setShowOpenedMinappsInSidebar(checked))}
|
onChange={(checked) => dispatch(setShowOpenedMinappsInSidebar(checked))}
|
||||||
/>
|
/>
|
||||||
</SidebarSettingRow>
|
</SettingRow>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改和新增样式
|
// 修改和新增样式
|
||||||
const CacheSettingRow = styled.div`
|
const SettingRow = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -206,14 +220,6 @@ const ResetButtonWrapper = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
// 新增侧边栏设置行样式
|
|
||||||
const SidebarSettingRow = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 20px;
|
|
||||||
`
|
|
||||||
|
|
||||||
// 新增: 带边框的容器组件
|
// 新增: 带边框的容器组件
|
||||||
const BorderedContainer = styled.div`
|
const BorderedContainer = styled.div`
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
|
|||||||
@ -109,8 +109,10 @@ export interface SettingsState {
|
|||||||
siyuanToken: string | null
|
siyuanToken: string | null
|
||||||
siyuanBoxId: string | null
|
siyuanBoxId: string | null
|
||||||
siyuanRootPath: string | null
|
siyuanRootPath: string | null
|
||||||
|
// MinApps
|
||||||
maxKeepAliveMinapps: number
|
maxKeepAliveMinapps: number
|
||||||
showOpenedMinappsInSidebar: boolean
|
showOpenedMinappsInSidebar: boolean
|
||||||
|
minappsOpenLinkExternal: boolean
|
||||||
// 隐私设置
|
// 隐私设置
|
||||||
enableDataCollection: boolean
|
enableDataCollection: boolean
|
||||||
enableQuickPanelTriggers: boolean
|
enableQuickPanelTriggers: boolean
|
||||||
@ -211,8 +213,10 @@ export const initialState: SettingsState = {
|
|||||||
siyuanToken: null,
|
siyuanToken: null,
|
||||||
siyuanBoxId: null,
|
siyuanBoxId: null,
|
||||||
siyuanRootPath: null,
|
siyuanRootPath: null,
|
||||||
|
// MinApps
|
||||||
maxKeepAliveMinapps: 3,
|
maxKeepAliveMinapps: 3,
|
||||||
showOpenedMinappsInSidebar: true,
|
showOpenedMinappsInSidebar: true,
|
||||||
|
minappsOpenLinkExternal: false,
|
||||||
enableDataCollection: false,
|
enableDataCollection: false,
|
||||||
enableQuickPanelTriggers: false,
|
enableQuickPanelTriggers: false,
|
||||||
enableBackspaceDeleteModel: true,
|
enableBackspaceDeleteModel: true,
|
||||||
@ -482,6 +486,9 @@ const settingsSlice = createSlice({
|
|||||||
setShowOpenedMinappsInSidebar: (state, action: PayloadAction<boolean>) => {
|
setShowOpenedMinappsInSidebar: (state, action: PayloadAction<boolean>) => {
|
||||||
state.showOpenedMinappsInSidebar = action.payload
|
state.showOpenedMinappsInSidebar = action.payload
|
||||||
},
|
},
|
||||||
|
setMinappsOpenLinkExternal: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.minappsOpenLinkExternal = action.payload
|
||||||
|
},
|
||||||
setEnableDataCollection: (state, action: PayloadAction<boolean>) => {
|
setEnableDataCollection: (state, action: PayloadAction<boolean>) => {
|
||||||
state.enableDataCollection = action.payload
|
state.enableDataCollection = action.payload
|
||||||
},
|
},
|
||||||
@ -579,6 +586,7 @@ export const {
|
|||||||
setSiyuanRootPath,
|
setSiyuanRootPath,
|
||||||
setMaxKeepAliveMinapps,
|
setMaxKeepAliveMinapps,
|
||||||
setShowOpenedMinappsInSidebar,
|
setShowOpenedMinappsInSidebar,
|
||||||
|
setMinappsOpenLinkExternal,
|
||||||
setEnableDataCollection,
|
setEnableDataCollection,
|
||||||
setEnableQuickPanelTriggers,
|
setEnableQuickPanelTriggers,
|
||||||
setExportMenuOptions,
|
setExportMenuOptions,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user