/
raiden
/
obsidian-git-encrypt
Обзор
Документация
Войти
/
raiden
/
obsidian-git-encrypt
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/ui/diffView.ts
224 строки
7 KB
Robert Kuzhin
feat: add manual encrypted source control
09 авг 2026, 20:57
09 авг 2026, 20:57
3c8fa38
Код
Авторство
О чём код?
import { MergeView, unifiedMergeView } from "@codemirror/merge"; import { highlightSelectionMatches, search, searchKeymap } from "@codemirror/search"; import { EditorState, type Extension } from "@codemirror/state"; import { drawSelection, EditorView, keymap, lineNumbers } from "@codemirror/view"; import { ItemView, type App, type WorkspaceLeaf } from "obsidian"; import type { SourceDiff } from "../sourceControl/index.js"; export const ENCRYPTED_GIT_DIFF_VIEW = "encrypted-git-diff"; /** * Read-only encrypted-source diff view adapted from Vinzent03/obsidian-git. * See THIRD_PARTY_NOTICES.md. Plaintext is held in memory and never returned * from getState(), so Obsidian cannot persist it in workspace state. */ export class EncryptedGitDiffView extends ItemView { private diff: SourceDiff | undefined; private mergeView: MergeView | undefined; private unifiedView: EditorView | undefined; private layoutMode: DiffLayout | undefined; public constructor(leaf: WorkspaceLeaf) { super(leaf); this.navigation = true; } public getViewType(): string { return ENCRYPTED_GIT_DIFF_VIEW; } public getDisplayText(): string { const name = this.diff?.path.split("/").at(-1); return name === undefined ? "Encrypted Git Diff" : `Diff: ${name}`; } public override getIcon(): string { return "diff"; } public override getState(): Record<string, unknown> { return {}; } public override onOpen(): Promise<void> { this.render(); return Promise.resolve(); } public override onClose(): Promise<void> { this.destroyDiffEditors(); this.diff = undefined; this.contentEl.empty(); this.contentEl.removeClass( "encrypted-git-diff-view", "encrypted-git-diff-layout-split", "encrypted-git-diff-layout-unified", ); return Promise.resolve(); } public showDiff(diff: SourceDiff): void { this.diff = diff; this.render(); } public override onResize(): void { if ( this.diff === undefined || this.diff.binary || this.diff.beforeText === undefined || this.diff.afterText === undefined ) { return; } if (this.getDesiredLayout() !== this.layoutMode) this.render(); } private render(): void { this.destroyDiffEditors(); this.contentEl.empty(); this.contentEl.removeClass( "encrypted-git-diff-layout-split", "encrypted-git-diff-layout-unified", ); this.contentEl.addClass("encrypted-git-diff-view"); if (this.diff === undefined) { this.contentEl.createEl("p", { cls: "encrypted-git-diff-empty", text: "Choose a changed file in Source Control to show its diff here.", }); return; } const header = this.contentEl.createEl("header", { cls: "encrypted-git-diff-header" }); header.createEl("h2", { text: this.diff.path }); header.createEl("p", { cls: "encrypted-git-diff-summary", text: diffDescription(this.diff), }); if ( this.diff.binary || this.diff.beforeText === undefined || this.diff.afterText === undefined ) { this.contentEl.createEl("div", { cls: "encrypted-git-diff-binary", text: this.diff.text, }); return; } const before = beforeLabel(this.diff.target); const after = afterLabel(this.diff.target); const layout = this.getDesiredLayout(); this.layoutMode = layout; this.contentEl.addClass( layout === "unified" ? "encrypted-git-diff-layout-unified" : "encrypted-git-diff-layout-split", ); const labels = this.contentEl.createEl("div", { cls: `encrypted-git-diff-labels${layout === "unified" ? " is-unified" : ""}`, }); if (layout === "unified") { labels.createEl("span", { text: `${before} → ${after}` }); } else { labels.createEl("span", { text: before }); labels.createEl("span", { text: after }); } const container = this.contentEl.createEl("div", { cls: `encrypted-git-merge-container cm-s-obsidian mod-cm6 markdown-source-view${layout === "unified" ? " is-unified" : ""}`, }); const extensions: Extension[] = [ lineNumbers(), highlightSelectionMatches(), drawSelection(), keymap.of(searchKeymap), search(), EditorView.lineWrapping, EditorView.editable.of(false), EditorState.readOnly.of(true), ]; if (layout === "unified") { this.unifiedView = new EditorView({ doc: this.diff.afterText, extensions: [ ...extensions, EditorView.contentAttributes.of({ "aria-label": `${after} diff content` }), unifiedMergeView({ original: this.diff.beforeText, mergeControls: false, allowInlineDiffs: true, collapseUnchanged: { minSize: 6, margin: 3 }, diffConfig: { scanLimit: 10_000 }, }), ], parent: container, }); return; } this.mergeView = new MergeView({ a: { doc: this.diff.beforeText, extensions: [ ...extensions, EditorView.contentAttributes.of({ "aria-label": `${before} diff content` }), ], }, b: { doc: this.diff.afterText, extensions: [ ...extensions, EditorView.contentAttributes.of({ "aria-label": `${after} diff content` }), ], }, collapseUnchanged: { minSize: 6, margin: 3 }, diffConfig: { scanLimit: 10_000 }, parent: container, }); } private destroyDiffEditors(): void { this.mergeView?.destroy(); this.unifiedView?.destroy(); this.mergeView = undefined; this.unifiedView = undefined; this.layoutMode = undefined; } private getDesiredLayout(): DiffLayout { const width = this.contentEl.clientWidth; return width > 0 && width <= 700 ? "unified" : "split"; } } type DiffLayout = "split" | "unified"; export async function openSourceDiffView(app: App, diff: SourceDiff): Promise<void> { let leaf = app.workspace.getLeavesOfType(ENCRYPTED_GIT_DIFF_VIEW)[0]; leaf ??= app.workspace.getLeaf("tab"); if (leaf.getViewState().type !== ENCRYPTED_GIT_DIFF_VIEW) { await leaf.setViewState({ type: ENCRYPTED_GIT_DIFF_VIEW, active: true }); } if (leaf.isDeferred) await leaf.loadIfDeferred(); if (!(leaf.view instanceof EncryptedGitDiffView)) { throw new Error("Unable to open Encrypted Git diff view"); } leaf.view.showDiff(diff); await app.workspace.revealLeaf(leaf); } function diffDescription(diff: SourceDiff): string { if (diff.binary) return "Binary content · metadata only"; return `${beforeLabel(diff.target)} → ${afterLabel(diff.target)}`; } function beforeLabel(target: SourceDiff["target"]): string { return target === "after-staging" ? "Selected snapshot" : "Committed"; } function afterLabel(target: SourceDiff["target"]): string { return target === "staged" ? "Selected snapshot" : "Working copy"; }