Skip to content

Commit fa2fdec

Browse files
Merge pull request #2 from Linkon-lcw/main
恭喜Linkon成为SecScore项目的第一个PR发起人!!🎉
2 parents c350eae + 8c2377c commit fa2fdec

File tree

25 files changed

+1361
-127
lines changed

25 files changed

+1361
-127
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ on:
66
workflow_dispatch:
77
inputs:
88
version:
9-
description: "版本号(例如 1.2.3 或 v1.2.3)"
9+
description: '版本号(例如 1.2.3 或 v1.2.3)'
1010
required: false
1111
build:
12-
description: "构建命令标记(build:win|build:mac|build:linux|build:unpack|build:all)"
12+
description: '构建命令标记(build:win|build:mac|build:linux|build:unpack|build:all)'
1313
required: false
1414

1515
permissions:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ db.sqlite
1515
!.vscode/extensions.json
1616
!.vscode/launch.json
1717
!.vscode/settings.json
18+
/.trae/

ai_ref/disposable.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
- 为什么我们需要可逆的插件系统?
99
- Cordis 是如何实现资源安全的?
10-
:::
10+
:::
1111

1212
Koishi 的一切都从 Cordis 开始。但我想大部分 Koishi 的开发者都不知道 Cordis 是什么。如果让我来定义的话,Cordis 是一个**元框架 (Meta Framework)**,即一个用于构建框架的框架。
1313

@@ -112,8 +112,8 @@ function serve(port: number) {
112112
return () => server.close()
113113
}
114114

115-
const dispose = serve(80) // 监听端口 80
116-
dispose() // 回收副作用
115+
const dispose = serve(80) // 监听端口 80
116+
dispose() // 回收副作用
117117
```
118118

119119
在这个例子中,`serve()` 函数将会创建一个服务器并且监听 `port` 端口。同时,调用该函数也会返回一个新的函数,用于取消该端口的监听。
@@ -123,7 +123,7 @@ dispose() // 回收副作用
123123

124124
- $\mathcal{C}\times\mathfrak{F}$ 对应着全局环境 (我们稍后会提到全局环境的坏处,但不影响这里的理解)
125125
- `port` 对应于上面的 $\text{X}$,由于我们可以使用柯里化,所以在数学模型中并不需要考虑它
126-
:::
126+
:::
127127

128128
为什么需要引入这个 $\text{effect}$ 和 $\mathcal{C}\times\mathfrak{F}$ 呢?它的作用是将副作用从函数的返回值中分离出来,从而实现副作用的回收。只需定义 $\text{restore}$ 变换 (不难发现它确实是 $\text{effect}$ 的逆操作):
129129

@@ -142,9 +142,9 @@ function serve(port: number) {
142142
collectEffect(() => server.close())
143143
}
144144

145-
serve(80) // 监听端口 80 并记录副作用
146-
serve(443) // 监听端口 443 并记录副作用
147-
restore() // 回收所有副作用
145+
serve(80) // 监听端口 80 并记录副作用
146+
serve(443) // 监听端口 443 并记录副作用
147+
restore() // 回收所有副作用
148148
```
149149

150150
当副作用被记录到全局环境时,$\mathcal{C}\times\mathfrak{F}$ 也就变成了一个更大的 $\mathcal{C}$。我们便可以这样定义:

ai_ref/work_plan.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,3 @@
9191
- 本计划不包含“引入插件系统/插件化框架”的任何概念与实现。
9292
- 本计划不包含“删除 hosting 目录”。它会被保留;仅确保不再作为主路径依赖。
9393
- 本计划优先保证现有功能可用与类型安全,然后再做进一步抽象与模块扩展。
94-

scripts/ci/apply-version.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import fs from 'fs'
22
import path from 'path'
33

4-
const version = String(process.argv[2] || '').trim().replace(/^v/i, '')
4+
const version = String(process.argv[2] || '')
5+
.trim()
6+
.replace(/^v/i, '')
57
if (!version) {
68
process.stderr.write('缺少版本号参数,例如:node scripts/ci/apply-version.mjs 1.2.3\n')
79
process.exit(1)
@@ -17,4 +19,3 @@ const pkgPath = path.join(process.cwd(), 'package.json')
1719
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
1820
pkg.version = version
1921
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf-8')
20-

scripts/ci/parse-commit.mjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,3 @@ if (process.env.GITHUB_OUTPUT) {
8888
} else {
8989
process.stdout.write(`${JSON.stringify(outputs, null, 2)}\n`)
9090
}
91-

src/main/services/WindowManager.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export class WindowManager extends Service {
6262
height: 670,
6363
show: false,
6464
autoHideMenuBar: true,
65+
frame: false, // Custom title bar
6566
icon: this.opts.icon,
6667
title: input.title,
6768
webPreferences: {
@@ -80,6 +81,14 @@ export class WindowManager extends Service {
8081
win.show()
8182
})
8283

84+
// Notify renderer about maximize state changes
85+
win.on('maximize', () => {
86+
win.webContents.send('window:maximized-changed', true)
87+
})
88+
win.on('unmaximize', () => {
89+
win.webContents.send('window:maximized-changed', false)
90+
})
91+
8392
win.webContents.setWindowOpenHandler((details) => {
8493
shell.openExternal(details.url)
8594
return { action: 'deny' }
@@ -142,5 +151,46 @@ export class WindowManager extends Service {
142151
const ok = this.navigateWindow(win, route)
143152
return ok ? { success: true } : { success: false, message: 'Window not found' }
144153
})
154+
155+
// Window controls
156+
this.mainCtx.handle('window:minimize', (event) => {
157+
const win = BrowserWindow.fromWebContents(event.sender)
158+
if (win) win.minimize()
159+
})
160+
161+
this.mainCtx.handle('window:maximize', (event) => {
162+
const win = BrowserWindow.fromWebContents(event.sender)
163+
if (win) {
164+
if (win.isMaximized()) {
165+
win.unmaximize()
166+
return false
167+
} else {
168+
win.maximize()
169+
return true
170+
}
171+
}
172+
return false
173+
})
174+
175+
this.mainCtx.handle('window:close', (event) => {
176+
const win = BrowserWindow.fromWebContents(event.sender)
177+
if (win) win.close()
178+
})
179+
180+
this.mainCtx.handle('window:isMaximized', (event) => {
181+
const win = BrowserWindow.fromWebContents(event.sender)
182+
return win ? win.isMaximized() : false
183+
})
184+
185+
this.mainCtx.handle('window:toggle-devtools', (event) => {
186+
const win = BrowserWindow.fromWebContents(event.sender)
187+
if (win) {
188+
if (win.webContents.isDevToolsOpened()) {
189+
win.webContents.closeDevTools()
190+
} else {
191+
win.webContents.openDevTools()
192+
}
193+
}
194+
})
145195
}
146196
}

src/preload/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ const api = {
7171
ipcRenderer.invoke('window:open', input),
7272
navigateWindow: (input: { key?: string; route: string }) =>
7373
ipcRenderer.invoke('window:navigate', input),
74+
windowMinimize: () => ipcRenderer.invoke('window:minimize'),
75+
windowMaximize: () => ipcRenderer.invoke('window:maximize'),
76+
windowClose: () => ipcRenderer.invoke('window:close'),
77+
windowIsMaximized: () => ipcRenderer.invoke('window:isMaximized'),
78+
onWindowMaximizedChanged: (callback: (maximized: boolean) => void) => {
79+
const subscription = (_event: any, maximized: boolean) => callback(maximized)
80+
ipcRenderer.on('window:maximized-changed', subscription)
81+
return () => ipcRenderer.removeListener('window:maximized-changed', subscription)
82+
},
83+
toggleDevTools: () => ipcRenderer.invoke('window:toggle-devtools'),
7484

7585
// Logger
7686
queryLogs: (lines?: number) => ipcRenderer.invoke('log:query', lines),

src/preload/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ export interface electronApi {
116116
options?: any
117117
}) => Promise<ipcResponse<void>>
118118
navigateWindow: (input: { key?: string; route: string }) => Promise<ipcResponse<void>>
119+
windowMinimize: () => Promise<void>
120+
windowMaximize: () => Promise<boolean>
121+
windowClose: () => Promise<void>
122+
windowIsMaximized: () => Promise<boolean>
123+
onWindowMaximizedChanged: (callback: (maximized: boolean) => void) => () => void
124+
toggleDevTools: () => Promise<void>
119125

120126
// Logger
121127
queryLogs: (lines?: number) => Promise<ipcResponse<string[]>>

0 commit comments

Comments
 (0)