mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-01-16 08:21:55 +00:00
添加对 CodeMirror 的支持,更新相关依赖版本,并在样式中增加 JSON 视图的样式。新增 JSON 视图类型并实现打开配置文件的功能,更新国际化文本以支持配置文件操作。
This commit is contained in:
parent
0f04b3c413
commit
3ca234c1a2
@ -55,6 +55,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.27.3",
|
"@anthropic-ai/sdk": "^0.27.3",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
|
"@codemirror/commands": "^6.7.1",
|
||||||
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
|
"@codemirror/state": "^6.5.2",
|
||||||
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
"@codemirror/basic-setup": "^0.20.0",
|
"@codemirror/basic-setup": "^0.20.0",
|
||||||
"@codemirror/lang-markdown": "^6.3.2",
|
"@codemirror/lang-markdown": "^6.3.2",
|
||||||
"@codemirror/merge": "^6.10.0",
|
"@codemirror/merge": "^6.10.0",
|
||||||
|
|||||||
77
pnpm-lock.yaml
generated
77
pnpm-lock.yaml
generated
@ -18,12 +18,27 @@ importers:
|
|||||||
'@codemirror/basic-setup':
|
'@codemirror/basic-setup':
|
||||||
specifier: ^0.20.0
|
specifier: ^0.20.0
|
||||||
version: 0.20.0
|
version: 0.20.0
|
||||||
|
'@codemirror/commands':
|
||||||
|
specifier: ^6.7.1
|
||||||
|
version: 6.8.1
|
||||||
|
'@codemirror/lang-json':
|
||||||
|
specifier: ^6.0.1
|
||||||
|
version: 6.0.2
|
||||||
'@codemirror/lang-markdown':
|
'@codemirror/lang-markdown':
|
||||||
specifier: ^6.3.2
|
specifier: ^6.3.2
|
||||||
version: 6.3.3
|
version: 6.3.3
|
||||||
'@codemirror/merge':
|
'@codemirror/merge':
|
||||||
specifier: ^6.10.0
|
specifier: ^6.10.0
|
||||||
version: 6.10.2
|
version: 6.10.2
|
||||||
|
'@codemirror/state':
|
||||||
|
specifier: ^6.5.2
|
||||||
|
version: 6.5.2
|
||||||
|
'@codemirror/theme-one-dark':
|
||||||
|
specifier: ^6.1.2
|
||||||
|
version: 6.1.3
|
||||||
|
'@codemirror/view':
|
||||||
|
specifier: ^6.35.0
|
||||||
|
version: 6.38.0
|
||||||
'@electric-sql/pglite':
|
'@electric-sql/pglite':
|
||||||
specifier: 0.2.14
|
specifier: 0.2.14
|
||||||
version: 0.2.14
|
version: 0.2.14
|
||||||
@ -87,6 +102,9 @@ importers:
|
|||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
codemirror:
|
||||||
|
specifier: ^6.0.1
|
||||||
|
version: 6.0.2
|
||||||
delay:
|
delay:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
@ -565,6 +583,9 @@ packages:
|
|||||||
'@codemirror/commands@0.20.0':
|
'@codemirror/commands@0.20.0':
|
||||||
resolution: {integrity: sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==}
|
resolution: {integrity: sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==}
|
||||||
|
|
||||||
|
'@codemirror/commands@6.8.1':
|
||||||
|
resolution: {integrity: sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==}
|
||||||
|
|
||||||
'@codemirror/lang-css@6.3.1':
|
'@codemirror/lang-css@6.3.1':
|
||||||
resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
|
resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
|
||||||
|
|
||||||
@ -574,6 +595,9 @@ packages:
|
|||||||
'@codemirror/lang-javascript@6.2.4':
|
'@codemirror/lang-javascript@6.2.4':
|
||||||
resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
|
resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==}
|
||||||
|
|
||||||
|
'@codemirror/lang-json@6.0.2':
|
||||||
|
resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==}
|
||||||
|
|
||||||
'@codemirror/lang-markdown@6.3.3':
|
'@codemirror/lang-markdown@6.3.3':
|
||||||
resolution: {integrity: sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==}
|
resolution: {integrity: sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==}
|
||||||
|
|
||||||
@ -599,12 +623,18 @@ packages:
|
|||||||
'@codemirror/search@0.20.1':
|
'@codemirror/search@0.20.1':
|
||||||
resolution: {integrity: sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==}
|
resolution: {integrity: sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==}
|
||||||
|
|
||||||
|
'@codemirror/search@6.5.11':
|
||||||
|
resolution: {integrity: sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==}
|
||||||
|
|
||||||
'@codemirror/state@0.20.1':
|
'@codemirror/state@0.20.1':
|
||||||
resolution: {integrity: sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==}
|
resolution: {integrity: sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==}
|
||||||
|
|
||||||
'@codemirror/state@6.5.2':
|
'@codemirror/state@6.5.2':
|
||||||
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
|
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
|
||||||
|
|
||||||
|
'@codemirror/theme-one-dark@6.1.3':
|
||||||
|
resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==}
|
||||||
|
|
||||||
'@codemirror/view@0.20.7':
|
'@codemirror/view@0.20.7':
|
||||||
resolution: {integrity: sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==}
|
resolution: {integrity: sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==}
|
||||||
|
|
||||||
@ -1650,6 +1680,9 @@ packages:
|
|||||||
'@lezer/javascript@1.5.1':
|
'@lezer/javascript@1.5.1':
|
||||||
resolution: {integrity: sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==}
|
resolution: {integrity: sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==}
|
||||||
|
|
||||||
|
'@lezer/json@1.0.3':
|
||||||
|
resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==}
|
||||||
|
|
||||||
'@lezer/lr@0.16.3':
|
'@lezer/lr@0.16.3':
|
||||||
resolution: {integrity: sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==}
|
resolution: {integrity: sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==}
|
||||||
|
|
||||||
@ -3107,6 +3140,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
||||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||||
|
|
||||||
|
codemirror@6.0.2:
|
||||||
|
resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
|
||||||
|
|
||||||
collect-v8-coverage@1.0.2:
|
collect-v8-coverage@1.0.2:
|
||||||
resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==}
|
resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==}
|
||||||
|
|
||||||
@ -7037,6 +7073,13 @@ snapshots:
|
|||||||
'@codemirror/view': 0.20.7
|
'@codemirror/view': 0.20.7
|
||||||
'@lezer/common': 0.16.1
|
'@lezer/common': 0.16.1
|
||||||
|
|
||||||
|
'@codemirror/commands@6.8.1':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.11.2
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.38.0
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
|
||||||
'@codemirror/lang-css@6.3.1':
|
'@codemirror/lang-css@6.3.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/autocomplete': 6.18.6
|
'@codemirror/autocomplete': 6.18.6
|
||||||
@ -7067,6 +7110,11 @@ snapshots:
|
|||||||
'@lezer/common': 1.2.3
|
'@lezer/common': 1.2.3
|
||||||
'@lezer/javascript': 1.5.1
|
'@lezer/javascript': 1.5.1
|
||||||
|
|
||||||
|
'@codemirror/lang-json@6.0.2':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.11.2
|
||||||
|
'@lezer/json': 1.0.3
|
||||||
|
|
||||||
'@codemirror/lang-markdown@6.3.3':
|
'@codemirror/lang-markdown@6.3.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/autocomplete': 6.18.6
|
'@codemirror/autocomplete': 6.18.6
|
||||||
@ -7130,12 +7178,25 @@ snapshots:
|
|||||||
'@codemirror/view': 0.20.7
|
'@codemirror/view': 0.20.7
|
||||||
crelt: 1.0.6
|
crelt: 1.0.6
|
||||||
|
|
||||||
|
'@codemirror/search@6.5.11':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.38.0
|
||||||
|
crelt: 1.0.6
|
||||||
|
|
||||||
'@codemirror/state@0.20.1': {}
|
'@codemirror/state@0.20.1': {}
|
||||||
|
|
||||||
'@codemirror/state@6.5.2':
|
'@codemirror/state@6.5.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@marijn/find-cluster-break': 1.0.2
|
'@marijn/find-cluster-break': 1.0.2
|
||||||
|
|
||||||
|
'@codemirror/theme-one-dark@6.1.3':
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/language': 6.11.2
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.38.0
|
||||||
|
'@lezer/highlight': 1.2.1
|
||||||
|
|
||||||
'@codemirror/view@0.20.7':
|
'@codemirror/view@0.20.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@codemirror/state': 0.20.1
|
'@codemirror/state': 0.20.1
|
||||||
@ -8107,6 +8168,12 @@ snapshots:
|
|||||||
'@lezer/highlight': 1.2.1
|
'@lezer/highlight': 1.2.1
|
||||||
'@lezer/lr': 1.4.2
|
'@lezer/lr': 1.4.2
|
||||||
|
|
||||||
|
'@lezer/json@1.0.3':
|
||||||
|
dependencies:
|
||||||
|
'@lezer/common': 1.2.3
|
||||||
|
'@lezer/highlight': 1.2.1
|
||||||
|
'@lezer/lr': 1.4.2
|
||||||
|
|
||||||
'@lezer/lr@0.16.3':
|
'@lezer/lr@0.16.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@lezer/common': 0.16.1
|
'@lezer/common': 0.16.1
|
||||||
@ -9758,6 +9825,16 @@ snapshots:
|
|||||||
|
|
||||||
co@4.6.0: {}
|
co@4.6.0: {}
|
||||||
|
|
||||||
|
codemirror@6.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.18.6
|
||||||
|
'@codemirror/commands': 6.8.1
|
||||||
|
'@codemirror/language': 6.11.2
|
||||||
|
'@codemirror/lint': 6.8.5
|
||||||
|
'@codemirror/search': 6.5.11
|
||||||
|
'@codemirror/state': 6.5.2
|
||||||
|
'@codemirror/view': 6.38.0
|
||||||
|
|
||||||
collect-v8-coverage@1.0.2: {}
|
collect-v8-coverage@1.0.2: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
|
|||||||
168
src/BaseFileView.tsx
Normal file
168
src/BaseFileView.tsx
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { EditorState, Extension } from "@codemirror/state";
|
||||||
|
import { EditorView, ViewUpdate } from "@codemirror/view";
|
||||||
|
import { TFile, TextFileView, WorkspaceLeaf } from "obsidian";
|
||||||
|
|
||||||
|
import InfioPlugin from './main';
|
||||||
|
|
||||||
|
export default abstract class BaseView extends TextFileView {
|
||||||
|
public plugin: InfioPlugin;
|
||||||
|
protected cmEditor: EditorView;
|
||||||
|
protected editorEl: HTMLElement;
|
||||||
|
protected state: { filePath?: string } | null = null;
|
||||||
|
protected isEditorLoaded: boolean = false;
|
||||||
|
protected currentFilePath: string | null = null;
|
||||||
|
|
||||||
|
protected constructor(leaf: WorkspaceLeaf, plugin: InfioPlugin) {
|
||||||
|
super(leaf);
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
onload(): void {
|
||||||
|
super.onload();
|
||||||
|
this.editorEl = this.contentEl.createDiv("datafile-source-view mod-cm6");
|
||||||
|
|
||||||
|
this.cmEditor = new EditorView({
|
||||||
|
state: this.createDefaultEditorState(),
|
||||||
|
parent: this.editorEl,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.workspace.trigger("codemirror", this.cmEditor);
|
||||||
|
this.isEditorLoaded = true;
|
||||||
|
|
||||||
|
// Load file content if state contains filePath and editor is now loaded
|
||||||
|
if (this.state?.filePath) {
|
||||||
|
this.loadFileFromPath(this.state.filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async setState(state: { filePath?: string }): Promise<void> {
|
||||||
|
this.state = state;
|
||||||
|
// If filePath is provided and editor is loaded, load the file immediately
|
||||||
|
if (state.filePath && this.isEditorLoaded) {
|
||||||
|
await this.loadFileFromPath(state.filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(): { filePath?: string } {
|
||||||
|
return { filePath: this.currentFilePath };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadFileFromPath(filePath: string): Promise<void> {
|
||||||
|
// Store the current file path for saving
|
||||||
|
this.currentFilePath = filePath;
|
||||||
|
|
||||||
|
// Try to get the file from vault first (for regular files)
|
||||||
|
const file = this.app.vault.getAbstractFileByPath(filePath);
|
||||||
|
|
||||||
|
if (file && file instanceof TFile) {
|
||||||
|
// Regular file in vault
|
||||||
|
this.file = file;
|
||||||
|
await this.onLoadFile(file);
|
||||||
|
} else {
|
||||||
|
// File not in vault (hidden directory), read directly from filesystem
|
||||||
|
console.log('File not in vault, reading directly from filesystem');
|
||||||
|
await this.loadFileFromFilesystem(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadFileFromFilesystem(filePath: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Use vault adapter to read file directly from filesystem
|
||||||
|
const content = await this.app.vault.adapter.read(filePath);
|
||||||
|
this.setViewData(content, true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load file from filesystem:', error);
|
||||||
|
// If file doesn't exist, create it with empty content
|
||||||
|
this.setViewData('{}', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLoadFile(file: TFile): Promise<void> {
|
||||||
|
try {
|
||||||
|
const content = await this.app.vault.read(file);
|
||||||
|
this.setViewData(content, true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load file content:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewData(): string {
|
||||||
|
return this.cmEditor.state.doc.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
setViewData(data: string, clear: boolean): void {
|
||||||
|
if (clear) {
|
||||||
|
this.cmEditor.dispatch({ changes: { from: 0, to: this.cmEditor.state.doc.length, insert: data } });
|
||||||
|
} else {
|
||||||
|
this.cmEditor.dispatch({ changes: { from: 0, to: this.cmEditor.state.doc.length, insert: data } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
this.setViewData('', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(clear?: boolean): Promise<void> {
|
||||||
|
const content = this.getViewData();
|
||||||
|
|
||||||
|
if (this.file) {
|
||||||
|
// Regular file in vault
|
||||||
|
await this.app.vault.modify(this.file, content);
|
||||||
|
} else if (this.currentFilePath) {
|
||||||
|
// File in hidden directory, save directly to filesystem
|
||||||
|
await this.app.vault.adapter.write(this.currentFilePath, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clear) {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gets the title of the document
|
||||||
|
getDisplayText(): string {
|
||||||
|
if (this.file) {
|
||||||
|
return this.file.basename;
|
||||||
|
}
|
||||||
|
if (this.currentFilePath) {
|
||||||
|
return this.currentFilePath.split('/').pop() || "JSON File";
|
||||||
|
}
|
||||||
|
if (this.state?.filePath) {
|
||||||
|
return this.state.filePath.split('/').pop() || "JSON File";
|
||||||
|
}
|
||||||
|
return "NOFILE";
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(): Promise<void> {
|
||||||
|
return super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload(): Promise<void> {
|
||||||
|
await this.save(false);
|
||||||
|
|
||||||
|
const data = this.getViewData();
|
||||||
|
this.cmEditor.setState(this.createDefaultEditorState());
|
||||||
|
this.setViewData(data, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onEditorUpdate(update: ViewUpdate): void {
|
||||||
|
if (update.docChanged) {
|
||||||
|
this.requestSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getViewType(): string;
|
||||||
|
|
||||||
|
protected abstract getEditorExtensions(): Extension[];
|
||||||
|
|
||||||
|
private createDefaultEditorState(): EditorState {
|
||||||
|
return EditorState.create({
|
||||||
|
extensions: [...this.getCommonEditorExtensions(), ...this.getEditorExtensions()]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCommonEditorExtensions(): Extension[] {
|
||||||
|
const extensions: Extension[] = [];
|
||||||
|
extensions.push(EditorView.lineWrapping);
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/JsonFileView.tsx
Normal file
32
src/JsonFileView.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
import { json } from "@codemirror/lang-json";
|
||||||
|
import { Extension } from "@codemirror/state";
|
||||||
|
import { EditorView } from "@codemirror/view";
|
||||||
|
import { basicSetup } from "codemirror";
|
||||||
|
import { WorkspaceLeaf } from "obsidian";
|
||||||
|
|
||||||
|
import BaseView from "./BaseFileView";
|
||||||
|
import { JSON_VIEW_TYPE } from './constants';
|
||||||
|
import InfioPlugin from './main';
|
||||||
|
import { getIndentByTabExtension } from "./utils/indentation-provider";
|
||||||
|
|
||||||
|
export default class JsonView extends BaseView {
|
||||||
|
constructor(leaf: WorkspaceLeaf, plugin: InfioPlugin) {
|
||||||
|
super(leaf, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewType(): string {
|
||||||
|
return JSON_VIEW_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getEditorExtensions(): Extension[] {
|
||||||
|
const extensions = [
|
||||||
|
basicSetup,
|
||||||
|
getIndentByTabExtension(),
|
||||||
|
json(),
|
||||||
|
EditorView.updateListener.of(this.onEditorUpdate.bind(this))
|
||||||
|
];
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { AlertTriangle, ChevronDown, ChevronRight, FileText, Folder, Power, RotateCcw, Trash2, Wrench } from 'lucide-react'
|
import { AlertTriangle, ChevronDown, ChevronRight, ExternalLink, FileText, Folder, Power, RotateCcw, Trash2, Wrench } from 'lucide-react'
|
||||||
import { Notice } from 'obsidian'
|
import { Notice } from 'obsidian'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
@ -108,6 +108,17 @@ const McpHubView = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenConfigFile = async () => {
|
||||||
|
const hub = await getMcpHub();
|
||||||
|
if (hub) {
|
||||||
|
try {
|
||||||
|
await hub.openMcpSettingsFile();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to open config file:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toggleServerExpansion = (serverKey: string) => {
|
const toggleServerExpansion = (serverKey: string) => {
|
||||||
setExpandedServers(prev => ({ ...prev, [serverKey]: !prev[serverKey] }));
|
setExpandedServers(prev => ({ ...prev, [serverKey]: !prev[serverKey] }));
|
||||||
if (!expandedServers[serverKey] && !activeServerDetailTab[serverKey]) {
|
if (!expandedServers[serverKey] && !activeServerDetailTab[serverKey]) {
|
||||||
@ -196,7 +207,15 @@ const McpHubView = () => {
|
|||||||
<div className="infio-mcp-hub-container">
|
<div className="infio-mcp-hub-container">
|
||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<div className="infio-mcp-hub-header">
|
<div className="infio-mcp-hub-header">
|
||||||
<h2 className="infio-mcp-hub-title">{t('mcpHub.title')}</h2>
|
<h3 className="infio-mcp-hub-title">{t('mcpHub.title')}</h3>
|
||||||
|
<div className="infio-mcp-hub-actions">
|
||||||
|
<button
|
||||||
|
onClick={fetchServers}
|
||||||
|
className="obsidian-insight-refresh-btn"
|
||||||
|
>
|
||||||
|
<RotateCcw size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MCP Settings */}
|
{/* MCP Settings */}
|
||||||
@ -218,6 +237,15 @@ const McpHubView = () => {
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Configuration File Access */}
|
||||||
|
<button
|
||||||
|
onClick={handleOpenConfigFile}
|
||||||
|
className="infio-mcp-config-button"
|
||||||
|
>
|
||||||
|
<ExternalLink size={16} />
|
||||||
|
<span>{t('mcpHub.openConfigFile')}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Create New Server Section */}
|
{/* Create New Server Section */}
|
||||||
@ -448,6 +476,57 @@ const McpHubView = () => {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.infio-mcp-hub-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--size-2-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.obsidian-insight-refresh-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
width: 24px !important;
|
||||||
|
height: 24px !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--background-modifier-hover) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.obsidian-insight-refresh-btn:hover:not(:disabled) {
|
||||||
|
background-color: var(--interactive-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.infio-mcp-config-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background-color: var(--interactive-normal);
|
||||||
|
color: var(--text-normal);
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infio-mcp-config-button:hover {
|
||||||
|
background-color: var(--interactive-hover);
|
||||||
|
border-color: var(--interactive-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.infio-mcp-config-button:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
/* Search Section */
|
/* Search Section */
|
||||||
.infio-mcp-search-section {
|
.infio-mcp-search-section {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|||||||
@ -3,22 +3,10 @@ import { LLMModel } from './types/llm/model'
|
|||||||
export const CHAT_VIEW_TYPE = 'infio-chat-view'
|
export const CHAT_VIEW_TYPE = 'infio-chat-view'
|
||||||
export const APPLY_VIEW_TYPE = 'infio-apply-view'
|
export const APPLY_VIEW_TYPE = 'infio-apply-view'
|
||||||
export const PREVIEW_VIEW_TYPE = 'infio-preview-view'
|
export const PREVIEW_VIEW_TYPE = 'infio-preview-view'
|
||||||
|
export const JSON_VIEW_TYPE = 'infio-json-view'
|
||||||
|
|
||||||
export const DEFAULT_MODELS: LLMModel[] = []
|
export const DEFAULT_MODELS: LLMModel[] = []
|
||||||
|
|
||||||
// export const PROVIDERS: ApiProvider[] = [
|
|
||||||
// 'Infio',
|
|
||||||
// 'OpenRouter',
|
|
||||||
// 'SiliconFlow',
|
|
||||||
// 'Anthropic',
|
|
||||||
// 'Deepseek',
|
|
||||||
// 'OpenAI',
|
|
||||||
// 'Google',
|
|
||||||
// 'Groq',
|
|
||||||
// 'Ollama',
|
|
||||||
// 'OpenAICompatible',
|
|
||||||
// ]
|
|
||||||
|
|
||||||
export const SUPPORT_EMBEDDING_SIMENTION: number[] = [
|
export const SUPPORT_EMBEDDING_SIMENTION: number[] = [
|
||||||
384,
|
384,
|
||||||
512,
|
512,
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
// Obsidian
|
|
||||||
import { App, EventRef, Notice, TFile, normalizePath } from 'obsidian';
|
|
||||||
|
|
||||||
// Node built-in
|
import { App, EventRef, Notice, TFile, normalizePath } from 'obsidian';
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
// SDK / External Libraries
|
// SDK / External Libraries
|
||||||
@ -23,7 +21,7 @@ import { EnvironmentVariables, shellEnvSync } from 'shell-env';
|
|||||||
import { z } from "zod"; // Keep zod
|
import { z } from "zod"; // Keep zod
|
||||||
// Internal/Project imports
|
// Internal/Project imports
|
||||||
|
|
||||||
import { INFIO_BASE_URL } from '../../constants'
|
import { INFIO_BASE_URL, JSON_VIEW_TYPE } from '../../constants';
|
||||||
import { t } from "../../lang/helpers";
|
import { t } from "../../lang/helpers";
|
||||||
import InfioPlugin from "../../main";
|
import InfioPlugin from "../../main";
|
||||||
// Assuming path is correct and will be resolved, if not, this will remain an error.
|
// Assuming path is correct and will be resolved, if not, this will remain an error.
|
||||||
@ -331,24 +329,16 @@ export class McpHub {
|
|||||||
async ensureMcpFileExists(): Promise<void> {
|
async ensureMcpFileExists(): Promise<void> {
|
||||||
const mcpFolderPath = ".infio_json_db/mcp"
|
const mcpFolderPath = ".infio_json_db/mcp"
|
||||||
if (!await this.app.vault.adapter.exists(normalizePath(mcpFolderPath))) {
|
if (!await this.app.vault.adapter.exists(normalizePath(mcpFolderPath))) {
|
||||||
await this.app.vault.createFolder(mcpFolderPath);
|
await this.app.vault.createFolder(normalizePath(mcpFolderPath));
|
||||||
}
|
}
|
||||||
this.mcpSettingsFilePath = normalizePath(path.join(mcpFolderPath, "settings.json"))
|
this.mcpSettingsFilePath = normalizePath(path.join(mcpFolderPath, "settings.json"))
|
||||||
const fileExists = await this.app.vault.adapter.exists(this.mcpSettingsFilePath);
|
const fileExists = await this.app.vault.adapter.exists(normalizePath(this.mcpSettingsFilePath));
|
||||||
if (!fileExists) {
|
if (!fileExists) {
|
||||||
await this.app.vault.adapter.write(
|
await this.app.vault.create(
|
||||||
this.mcpSettingsFilePath,
|
normalizePath(this.mcpSettingsFilePath),
|
||||||
JSON.stringify({ mcpServers: {} }, null, 2)
|
JSON.stringify({ mcpServers: {} }, null, 2)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// this.globalMcpFilePath = normalizePath(path.join(mcpFolderPath, "global.json"))
|
|
||||||
// const fileExists1 = await this.app.vault.adapter.exists(this.globalMcpFilePath);
|
|
||||||
// if (!fileExists1) {
|
|
||||||
// await this.app.vault.adapter.write(
|
|
||||||
// this.globalMcpFilePath,
|
|
||||||
// JSON.stringify({ mcpServers: {} }, null, 2)
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMcpSettingsFilePath(): Promise<string> {
|
async getMcpSettingsFilePath(): Promise<string> {
|
||||||
@ -363,6 +353,61 @@ export class McpHub {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the MCP settings file in Obsidian
|
||||||
|
*/
|
||||||
|
async openMcpSettingsFile(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.ensureMcpFileExists();
|
||||||
|
const filePath = this.mcpSettingsFilePath;
|
||||||
|
|
||||||
|
console.log('Attempting to open MCP settings file:', filePath);
|
||||||
|
|
||||||
|
// 检查文件是否已经打开
|
||||||
|
let existingLeaf: any = null;
|
||||||
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
||||||
|
if (leaf.view.getViewType() === JSON_VIEW_TYPE) {
|
||||||
|
// 检查视图状态中的文件路径
|
||||||
|
const viewState = leaf.view.getState();
|
||||||
|
if (viewState && viewState.filePath === filePath) {
|
||||||
|
existingLeaf = leaf;
|
||||||
|
return false; // 停止遍历
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingLeaf) {
|
||||||
|
// 如果文件已经打开,重新加载最新内容并激活 leaf
|
||||||
|
await existingLeaf.setViewState({
|
||||||
|
type: JSON_VIEW_TYPE,
|
||||||
|
active: true,
|
||||||
|
state: { filePath } // 重新设置状态以触发重新加载
|
||||||
|
});
|
||||||
|
this.app.workspace.setActiveLeaf(existingLeaf);
|
||||||
|
this.app.workspace.revealLeaf(existingLeaf);
|
||||||
|
console.log('MCP settings file is already open, reloading content and activating existing view:', filePath);
|
||||||
|
} else {
|
||||||
|
// 如果文件没有打开,创建新的 leaf
|
||||||
|
const leaf = this.app.workspace.getLeaf(true);
|
||||||
|
|
||||||
|
if (leaf) {
|
||||||
|
await leaf.setViewState({
|
||||||
|
type: JSON_VIEW_TYPE,
|
||||||
|
active: true,
|
||||||
|
state: { filePath } // 传递文件路径到视图
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.workspace.revealLeaf(leaf);
|
||||||
|
console.log('Successfully opened MCP settings file in JSON view:', filePath);
|
||||||
|
} else {
|
||||||
|
console.error('Failed to get workspace leaf for JSON view');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to open MCP settings file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Combined and simplified initializeMcpServers, only for global scope
|
// Combined and simplified initializeMcpServers, only for global scope
|
||||||
private async initializeGlobalMcpServers(): Promise<void> {
|
private async initializeGlobalMcpServers(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -529,6 +529,13 @@ export default {
|
|||||||
parameters: "Parameters",
|
parameters: "Parameters",
|
||||||
toolNoDescription: "No description",
|
toolNoDescription: "No description",
|
||||||
useMcpToolFrom: "Use MCP tool from",
|
useMcpToolFrom: "Use MCP tool from",
|
||||||
|
configurationFile: "Configuration File",
|
||||||
|
configurationFileDescription: "Directly edit the MCP servers configuration file to add, modify, or remove servers.",
|
||||||
|
openConfigFile: "Open Configuration File",
|
||||||
|
configFileOpened: "Configuration file opened in Obsidian",
|
||||||
|
configFileNotFound: "Configuration file not found",
|
||||||
|
failedToOpenConfig: "Failed to open configuration file",
|
||||||
|
openedWithSystemApp: "Configuration file opened with system default application",
|
||||||
},
|
},
|
||||||
semanticSearch: {
|
semanticSearch: {
|
||||||
title: "Semantic Index",
|
title: "Semantic Index",
|
||||||
|
|||||||
@ -525,10 +525,17 @@ export default {
|
|||||||
errors: "错误",
|
errors: "错误",
|
||||||
noTools: "没有可用工具",
|
noTools: "没有可用工具",
|
||||||
noResources: "没有可用资源",
|
noResources: "没有可用资源",
|
||||||
noErrors: "没有错误记录",
|
noErrors: "没有错误记录",
|
||||||
parameters: "参数",
|
parameters: "参数",
|
||||||
toolNoDescription: "无描述",
|
toolNoDescription: "无描述",
|
||||||
useMcpToolFrom: "使用来自以下的 MCP 工具:",
|
useMcpToolFrom: "使用来自以下的 MCP 工具:",
|
||||||
|
configurationFile: "配置文件",
|
||||||
|
configurationFileDescription: "直接编辑 MCP 服务器配置文件来添加、修改或删除服务器。",
|
||||||
|
openConfigFile: "打开配置文件",
|
||||||
|
configFileOpened: "配置文件已在 Obsidian 中打开",
|
||||||
|
configFileNotFound: "配置文件未找到",
|
||||||
|
failedToOpenConfig: "打开配置文件失败",
|
||||||
|
openedWithSystemApp: "配置文件已使用系统默认应用程序打开",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
semanticSearch: {
|
semanticSearch: {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { Editor, MarkdownView, Modal, Notice, Plugin, TFile } from 'obsidian'
|
|||||||
import { ApplyView } from './ApplyView'
|
import { ApplyView } from './ApplyView'
|
||||||
import { ChatView } from './ChatView'
|
import { ChatView } from './ChatView'
|
||||||
import { ChatProps } from './components/chat-view/ChatView'
|
import { ChatProps } from './components/chat-view/ChatView'
|
||||||
import { APPLY_VIEW_TYPE, CHAT_VIEW_TYPE, PREVIEW_VIEW_TYPE } from './constants'
|
import { APPLY_VIEW_TYPE, CHAT_VIEW_TYPE, JSON_VIEW_TYPE, PREVIEW_VIEW_TYPE } from './constants'
|
||||||
import { getDiffStrategy } from "./core/diff/DiffStrategy"
|
import { getDiffStrategy } from "./core/diff/DiffStrategy"
|
||||||
import { InlineEdit } from './core/edit/inline-edit-processor'
|
import { InlineEdit } from './core/edit/inline-edit-processor'
|
||||||
import { McpHub } from './core/mcp/McpHub'
|
import { McpHub } from './core/mcp/McpHub'
|
||||||
@ -16,6 +16,7 @@ import { DBManager } from './database/database-manager'
|
|||||||
import { migrateToJsonDatabase } from './database/json/migrateToJsonDatabase'
|
import { migrateToJsonDatabase } from './database/json/migrateToJsonDatabase'
|
||||||
import { EmbeddingManager } from './embedworker/EmbeddingManager'
|
import { EmbeddingManager } from './embedworker/EmbeddingManager'
|
||||||
import EventListener from "./event-listener"
|
import EventListener from "./event-listener"
|
||||||
|
import JsonView from './JsonFileView'
|
||||||
import { t } from './lang/helpers'
|
import { t } from './lang/helpers'
|
||||||
import { PreviewView } from './PreviewView'
|
import { PreviewView } from './PreviewView'
|
||||||
import CompletionKeyWatcher from "./render-plugin/completion-key-watcher"
|
import CompletionKeyWatcher from "./render-plugin/completion-key-watcher"
|
||||||
@ -88,6 +89,7 @@ export default class InfioPlugin extends Plugin {
|
|||||||
this.registerView(CHAT_VIEW_TYPE, (leaf) => new ChatView(leaf, this))
|
this.registerView(CHAT_VIEW_TYPE, (leaf) => new ChatView(leaf, this))
|
||||||
this.registerView(APPLY_VIEW_TYPE, (leaf) => new ApplyView(leaf))
|
this.registerView(APPLY_VIEW_TYPE, (leaf) => new ApplyView(leaf))
|
||||||
this.registerView(PREVIEW_VIEW_TYPE, (leaf) => new PreviewView(leaf))
|
this.registerView(PREVIEW_VIEW_TYPE, (leaf) => new PreviewView(leaf))
|
||||||
|
this.registerView(JSON_VIEW_TYPE, (leaf) => new JsonView(leaf, this))
|
||||||
|
|
||||||
// register markdown processor for Inline Edit
|
// register markdown processor for Inline Edit
|
||||||
this.inlineEdit = new InlineEdit(this, this.settings);
|
this.inlineEdit = new InlineEdit(this, this.settings);
|
||||||
|
|||||||
26
src/utils/indentation-provider.ts
Normal file
26
src/utils/indentation-provider.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { indentLess, indentWithTab } from "@codemirror/commands";
|
||||||
|
import { indentUnit } from "@codemirror/language";
|
||||||
|
import { Extension } from "@codemirror/state";
|
||||||
|
import { keymap } from "@codemirror/view";
|
||||||
|
|
||||||
|
export const getIndentByTabExtension = (): Extension[] =>
|
||||||
|
[
|
||||||
|
keymap.of([indentWithTab]),
|
||||||
|
indentUnit.of(" ")
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getInsertTabsExtension = (): Extension[] =>
|
||||||
|
[
|
||||||
|
keymap.of([
|
||||||
|
// {
|
||||||
|
// key: 'Tab',
|
||||||
|
// preventDefault: true,
|
||||||
|
// run: insertTab,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
key: 'Shift-Tab',
|
||||||
|
preventDefault: true,
|
||||||
|
run: indentLess,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
];
|
||||||
18
styles.css
18
styles.css
@ -2598,3 +2598,21 @@ button.infio-chat-input-model-select {
|
|||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JSON View Styles
|
||||||
|
*/
|
||||||
|
.datafile-source-view.mod-cm6 .cm-gutters {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-faint) !important;
|
||||||
|
border-right: none !important;
|
||||||
|
margin-inline-end: var(--file-folding-offset);
|
||||||
|
font-size: var(--font-ui-smaller);
|
||||||
|
z-index: 1;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-gutterElement.cm-activeLineGutter {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user