meld encrypt integration

This commit is contained in:
Ben Floyd 2025-10-06 14:19:14 -06:00
parent 74d452142b
commit 34cc1ffae4
15 changed files with 591 additions and 31 deletions

17
main.ts
View file

@ -36,6 +36,19 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI {
this.settings.barStyle = 'default';
await this.saveSettings();
}
// Migration: ensure encryptThisStream exists for existing streams
let needsSave = false;
for (const stream of this.settings.streams) {
if (stream.encryptThisStream === undefined) {
stream.encryptThisStream = false;
needsSave = true;
}
}
if (needsSave) {
await this.saveSettings();
}
}
async saveSettings() {
@ -97,4 +110,8 @@ export default class StreamsPlugin extends Plugin implements StreamsAPI {
hasStreams(): boolean {
return serviceRegistry.api?.hasStreams() || false;
}
getFileOperationsService(): any {
return serviceRegistry.fileOperations;
}
}

View file

@ -71,4 +71,5 @@ export interface StreamsPluginInterface extends Plugin {
updateAllStreamsBarComponents(): void;
setActiveStream(streamId: string, force?: boolean): void;
getActiveStream(): Stream | undefined;
getFileOperationsService(): any;
}

View file

@ -6,6 +6,7 @@ import { sliceContainer } from './container';
import { DebugLoggingService } from '../slices/debug-logging';
import { StreamManagementService } from '../slices/stream-management';
import { APIService } from '../slices/api';
import { FileOperationsService } from '../slices/file-operations';
export class ServiceRegistry {
private static instance: ServiceRegistry;
@ -31,6 +32,10 @@ export class ServiceRegistry {
get api(): APIService | undefined {
return sliceContainer.get('api') as APIService;
}
get fileOperations(): FileOperationsService | undefined {
return sliceContainer.get('file-operations') as FileOperationsService;
}
}
// Export singleton instance

View file

@ -23,6 +23,7 @@ export interface Stream {
icon: LucideIcon;
showTodayInRibbon: boolean;
addCommand: boolean;
encryptThisStream: boolean; // New field for encryption toggle
}
export interface StreamsSettings {

View file

@ -323,7 +323,8 @@ export class CalendarNavigationService extends SettingsAwareSliceService {
icon: 'book',
folder: 'Streams',
showTodayInRibbon: true,
addCommand: true
addCommand: true,
encryptThisStream: false
};
}

View file

@ -214,7 +214,6 @@ export class StreamsBarComponent extends Component {
}
} else {
// Don't add calendar component to sidebars or other panes
centralizedLogger.debug('Calendar component not added - not in main editor area');
this.component.remove();
return;
}
@ -1031,8 +1030,6 @@ export class StreamsBarComponent extends Component {
// Refresh the streams dropdown to show the correct selection
this.refreshStreamsDropdown();
centralizedLogger.debug(`StreamsBarComponent updated to active stream: ${newActiveStream.name}`);
}
private handleSettingsChange(settings: any): void {

View file

@ -36,7 +36,6 @@ export class CreateFileView extends ItemView {
this.filePath = filePath;
this.stream = stream;
this.dateStateManager = DateStateManager.getInstance();
centralizedLogger.debug(`CreateFileView constructor called with filePath: ${filePath}`);
}
getViewType(): string {
@ -67,17 +66,13 @@ export class CreateFileView extends ItemView {
async setState(state: { stream?: Stream; date?: string | Date; filePath?: string }, result?: unknown): Promise<void> {
try {
centralizedLogger.debug(`CreateFileView setState called with state:`, state);
// Check if the view is still valid - more comprehensive checks
if (!this || !this.contentEl || !this.leaf || this.contentEl === null || this.leaf === null) {
centralizedLogger.debug(`CreateFileView setState called but view is no longer valid, skipping`);
return;
}
// Additional safety check - ensure the view is still attached to the DOM
if (!document.contains(this.contentEl)) {
centralizedLogger.debug(`CreateFileView setState called but view is no longer in DOM, skipping`);
return;
}
@ -135,8 +130,6 @@ export class CreateFileView extends ItemView {
}
async onOpen(): Promise<void> {
centralizedLogger.debug(`CreateFileView onOpen called with filePath: ${this.filePath}, stream: ${this.stream.name}`);
// Set this as the active stream in the main plugin
this.setActiveStream();
@ -191,13 +184,9 @@ export class CreateFileView extends ItemView {
// Create our create file view content
this.createFileViewContent(this.contentEl);
centralizedLogger.debug('CreateFileView content rendered and made visible');
}
async onClose(): Promise<void> {
centralizedLogger.debug(`CreateFileView onClose called for filePath: ${this.filePath}`);
// Clean up the MutationObserver
if ((this as any).emptyStateObserver) {
(this as any).emptyStateObserver.disconnect();
@ -262,7 +251,7 @@ export class CreateFileView extends ItemView {
eventBus.emit('create-file-view-opened', this.leaf);
});
} catch (error) {
centralizedLogger.debug('Could not trigger calendar component:', error);
// Calendar component trigger failed - not critical
}
}
@ -291,6 +280,45 @@ export class CreateFileView extends ItemView {
}
private async createAndOpenFile(): Promise<void> {
try {
// Get the file operations service to use the strategy pattern
const plugin = (this.app as any).plugins?.plugins?.['streams'];
if (!plugin) {
centralizedLogger.error('Streams plugin not found');
return;
}
// Check if encryption is enabled but Meld is not available
if (this.stream.encryptThisStream) {
const fileOpsService = plugin.getFileOperationsService?.();
if (fileOpsService && !fileOpsService.isMeldPluginAvailable()) {
// Show error and don't create file
new (this.app as any).Notice(fileOpsService.getMeldUnavailableMessage());
return;
}
}
// Create the file normally first (without encryption)
const file = await this.createFileNormally();
if (file instanceof TFile) {
// Open the file in the current leaf (this will replace CreateFileView)
await this.leaf.openFile(file);
// If encryption is enabled, trigger it after the file is opened
if (this.stream.encryptThisStream) {
// Small delay to ensure the file is fully loaded
setTimeout(async () => {
await this.triggerEncryption(file);
}, 200);
}
}
} catch (error) {
centralizedLogger.error('Error creating file:', error);
}
}
private async createFileNormally(): Promise<TFile | null> {
try {
const folderPath = this.filePath.substring(0, this.filePath.lastIndexOf('/'));
@ -305,13 +333,99 @@ export class CreateFileView extends ItemView {
}
}
// Create the file normally (without encryption)
const file = await this.app.vault.create(this.filePath, '');
return file instanceof TFile ? file : null;
} catch (error) {
centralizedLogger.error('Error creating file normally:', error);
return null;
}
}
private async triggerEncryption(file: TFile): Promise<void> {
try {
// Ensure the file is the active file
const activeFile = this.app.workspace.getActiveFile();
if (file instanceof TFile) {
await this.leaf.openFile(file);
if (activeFile?.path !== file.path) {
// Find a leaf with this file and make it active
const fileLeaf = this.app.workspace.getLeavesOfType('markdown')
.find(leaf => {
try {
const view = leaf.view as any;
return view?.file?.path === file.path;
} catch (e) {
return false;
}
});
if (fileLeaf) {
this.app.workspace.setActiveLeaf(fileLeaf, { focus: true });
} else {
centralizedLogger.error(`Could not find leaf with file: ${file.path}`);
return;
}
}
// Small delay to ensure the file is properly active
await new Promise(resolve => setTimeout(resolve, 100));
// Try to execute the Meld encryption command
const command = (this.app as any).commands?.commands?.['meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note'];
if (command && command.callback && typeof command.callback === 'function') {
try {
await command.callback();
} catch (cmdError) {
centralizedLogger.error(`Meld command execution failed:`, cmdError);
}
} else {
// Fallback: Use command palette API
try {
await (this.app as any).commands.executeCommandById('meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note');
} catch (altError) {
centralizedLogger.error('Meld encryption command failed:', altError);
}
}
} catch (error) {
centralizedLogger.error('Error creating file:', error);
centralizedLogger.error(`Error triggering encryption for file ${file.path}:`, error);
}
}
private async createFileWithStrategy(): Promise<TFile | null> {
try {
const folderPath = this.filePath.substring(0, this.filePath.lastIndexOf('/'));
if (folderPath) {
try {
const folderExists = this.app.vault.getAbstractFileByPath(folderPath);
if (!folderExists) {
await this.app.vault.createFolder(folderPath);
}
} catch (error) {
// Using existing folder
}
}
// Get the file operations service
const plugin = (this.app as any).plugins?.plugins?.['streams'];
if (!plugin) {
centralizedLogger.error('Streams plugin not found');
return null;
}
const fileOpsService = plugin.getFileOperationsService?.();
if (fileOpsService) {
// Use the strategy pattern
return await fileOpsService.createFile(this.filePath, '', this.stream);
} else {
// Fallback to normal file creation
const file = await this.app.vault.create(this.filePath, '');
return file instanceof TFile ? file : null;
}
} catch (error) {
centralizedLogger.error('Error creating file with strategy:', error);
return null;
}
}

View file

@ -1,17 +1,31 @@
import { App } from 'obsidian';
import { App, TFile, WorkspaceLeaf } from 'obsidian';
import { PluginAwareSliceService } from '../../shared/base-slice';
import { CommandService, ViewService } from '../../shared/interfaces';
import { OpenStreamDateCommand } from './OpenStreamDateCommand';
import { OpenTodayStreamCommand } from './OpenTodayStreamCommand';
import { OpenTodayCurrentStreamCommand } from './OpenTodayCurrentStreamCommand';
import { CreateFileView, CREATE_FILE_VIEW_TYPE } from './CreateFileView';
import { FileCreationInterface, NormalFileStrategy, MeldEncryptedFileStrategy } from './file-creation-strategies';
import { MeldDetectionService } from '../meld-integration';
import { centralizedLogger } from '../../shared/centralized-logger';
export class FileOperationsService extends PluginAwareSliceService implements CommandService, ViewService {
private registeredCommands: string[] = [];
private meldDetectionService: MeldDetectionService;
private normalFileStrategy: FileCreationInterface;
private meldEncryptedFileStrategy: FileCreationInterface;
async initialize(): Promise<void> {
if (this.initialized) return;
// Initialize strategies
this.meldDetectionService = new MeldDetectionService();
this.meldDetectionService.setPlugin(this.getPlugin());
await this.meldDetectionService.initialize();
this.normalFileStrategy = new NormalFileStrategy();
this.meldEncryptedFileStrategy = new MeldEncryptedFileStrategy();
this.registerViews();
this.registerCommands();
@ -21,11 +35,17 @@ export class FileOperationsService extends PluginAwareSliceService implements Co
cleanup(): void {
this.unregisterCommands();
this.unregisterViews();
// Cleanup Meld detection service
if (this.meldDetectionService) {
this.meldDetectionService.cleanup();
}
this.initialized = false;
}
registerViews(): void {
// No views to register - CreateFileView is created directly
// No views to register
}
unregisterViews(): void {
@ -83,4 +103,60 @@ export class FileOperationsService extends PluginAwareSliceService implements Co
const plugin = this.getPlugin() as any;
return plugin.settings || {};
}
/**
* Get the appropriate file creation strategy for a stream
*/
private getFileCreationStrategy(stream: any): FileCreationInterface {
if (stream.encryptThisStream) {
// Check if Meld is available before using encryption strategy
if (this.meldDetectionService.isMeldPluginAvailable()) {
return this.meldEncryptedFileStrategy;
} else {
// Fall back to normal strategy if Meld is not available
console.warn('Meld plugin not available, falling back to normal file creation');
return this.normalFileStrategy;
}
}
return this.normalFileStrategy;
}
/**
* Create a file using the appropriate strategy
*/
async createFile(filePath: string, content: string, stream: any): Promise<any> {
const strategy = this.getFileCreationStrategy(stream);
return await strategy.createFile(this.getPlugin().app, filePath, content);
}
/**
* Check if Meld plugin is available
*/
isMeldPluginAvailable(): boolean {
return this.meldDetectionService?.isMeldPluginAvailable() || false;
}
/**
* Get Meld unavailable message
*/
getMeldUnavailableMessage(): string {
return this.meldDetectionService?.getMeldUnavailableMessage() || 'Meld plugin is not available';
}
/**
* Check if file content appears to be encrypted
*/
public isEncryptedContent(content: string): boolean {
// Common patterns that indicate encrypted content
const encryptedPatterns = [
/^-----BEGIN PGP MESSAGE-----/,
/^-----BEGIN ENCRYPTED MESSAGE-----/,
/^-----BEGIN MESSAGE-----/,
/^U2FsdGVkX1/, // Base64 encoded encrypted content (common in some encryption tools)
/^[A-Za-z0-9+/]{100,}={0,2}$/ // Long base64 strings (potential encrypted content)
];
return encryptedPatterns.some(pattern => pattern.test(content.trim()));
}
}

View file

@ -0,0 +1,137 @@
import { App, TFile } from 'obsidian';
import { FileCreationInterface } from './FileCreationStrategy';
import { centralizedLogger } from '../../../shared/centralized-logger';
/**
* Strategy for creating encrypted files using Meld plugin
*/
export class MeldEncryptedFileStrategy implements FileCreationInterface {
private meldCommandId = 'meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note';
async createFile(app: App, filePath: string, content: string): Promise<TFile | null> {
try {
// Ensure the folder exists
const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));
if (folderPath && !app.vault.getAbstractFileByPath(folderPath)) {
await app.vault.createFolder(folderPath);
}
// First create the file normally
const file = await app.vault.create(filePath, content);
if (!(file instanceof TFile)) {
centralizedLogger.error(`Failed to create file before encryption: ${filePath} - result is not a TFile`);
return null;
}
// Then encrypt it using Meld plugin
await this.encryptFile(app, file);
return file;
} catch (error) {
centralizedLogger.error(`Error creating encrypted file ${filePath}:`, error);
return null;
}
}
private async encryptFile(app: App, file: TFile): Promise<void> {
try {
// Check if Meld plugin is available
if (!this.isMeldPluginAvailable(app)) {
throw new Error('Meld plugin is not available or not enabled');
}
// Get the Meld plugin instance
const meldPlugin = (app as any).plugins?.plugins?.['meld-encrypt'];
if (!meldPlugin) {
throw new Error('Meld plugin instance not found');
}
// Try to call the encryption method directly on the plugin
if (meldPlugin.encryptFile && typeof meldPlugin.encryptFile === 'function') {
await meldPlugin.encryptFile(file);
return;
}
// Try to call encryption with file path parameter
if (meldPlugin.encryptFileByPath && typeof meldPlugin.encryptFileByPath === 'function') {
await meldPlugin.encryptFileByPath(file.path);
return;
}
// Fallback: Use command execution
const command = (app as any).commands?.commands?.[this.meldCommandId];
if (command) {
if (command.callback.length > 0) {
// Command accepts parameters
await command.callback(file.path);
} else {
// Command needs active file context
const activeLeaf = app.workspace.activeLeaf;
let fileLeaf = null;
try {
// Find or create a leaf with the file
const existingLeaf = app.workspace.getLeavesOfType('markdown')
.find(leaf => {
try {
const view = leaf.view as any;
return view?.file?.path === file.path;
} catch (e) {
return false;
}
});
if (existingLeaf) {
fileLeaf = existingLeaf;
} else {
fileLeaf = app.workspace.getLeaf('tab');
await fileLeaf.openFile(file);
}
// Set as active leaf and execute command
app.workspace.setActiveLeaf(fileLeaf, { focus: true });
await new Promise(resolve => setTimeout(resolve, 100));
await command.callback();
} finally {
// Restore original active leaf
if (activeLeaf && activeLeaf !== fileLeaf) {
app.workspace.setActiveLeaf(activeLeaf, { focus: false });
}
}
}
} else {
throw new Error(`Meld encryption command not found: ${this.meldCommandId}`);
}
} catch (error) {
centralizedLogger.error(`Error encrypting file ${file.path}:`, error);
throw error;
}
}
private isMeldPluginAvailable(app: App): boolean {
try {
// Check if the Meld plugin is installed and enabled
const plugins = (app as any).plugins?.plugins;
if (!plugins) return false;
// Check for Meld plugin
const meldPlugin = plugins['meld-encrypt'];
if (!meldPlugin) return false;
// Check if the specific command exists
const commands = (app as any).commands?.commands;
if (!commands) return false;
return !!commands[this.meldCommandId];
} catch (error) {
centralizedLogger.error('Error checking Meld plugin availability:', error);
return false;
}
}
getStrategyName(): string {
return 'MeldEncryptedFile';
}
}

View file

@ -0,0 +1,35 @@
import { App, TFile } from 'obsidian';
import { FileCreationInterface } from './FileCreationStrategy';
import { centralizedLogger } from '../../../shared/centralized-logger';
/**
* Strategy for creating normal (unencrypted) files
*/
export class NormalFileStrategy implements FileCreationInterface {
async createFile(app: App, filePath: string, content: string): Promise<TFile | null> {
try {
// Ensure the folder exists
const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));
if (folderPath && !app.vault.getAbstractFileByPath(folderPath)) {
await app.vault.createFolder(folderPath);
}
// Create the file
const file = await app.vault.create(filePath, content);
if (file instanceof TFile) {
return file;
} else {
centralizedLogger.error(`Failed to create normal file: ${filePath} - result is not a TFile`);
return null;
}
} catch (error) {
centralizedLogger.error(`Error creating normal file ${filePath}:`, error);
return null;
}
}
getStrategyName(): string {
return 'NormalFile';
}
}

View file

@ -0,0 +1,3 @@
export type { FileCreationInterface } from './FileCreationStrategy';
export { NormalFileStrategy } from './NormalFileStrategy';
export { MeldEncryptedFileStrategy } from './MeldEncryptedFileStrategy';

View file

@ -4,6 +4,22 @@ import { centralizedLogger } from '../../shared/centralized-logger';
import { CreateFileView, CREATE_FILE_VIEW_TYPE } from './CreateFileView';
import { DateStateManager } from '../../shared/date-state-manager';
/**
* Check if file content appears to be encrypted
*/
function isEncryptedContent(content: string): boolean {
// Common patterns that indicate encrypted content
const encryptedPatterns = [
/^-----BEGIN PGP MESSAGE-----/,
/^-----BEGIN ENCRYPTED MESSAGE-----/,
/^-----BEGIN MESSAGE-----/,
/^U2FsdGVkX1/, // Base64 encoded encrypted content (common in some encryption tools)
/^[A-Za-z0-9+/]{100,}={0,2}$/ // Long base64 strings (potential encrypted content)
];
return encryptedPatterns.some(pattern => pattern.test(content.trim()));
}
/**
* Interface for Obsidian's internal ViewRegistry, which manages all view registrations
*/
@ -109,7 +125,12 @@ export async function openStreamDate(app: App, stream: Stream, date: Date = new
// Looking for file at path
let file = app.vault.getAbstractFileByPath(filePath);
// File exists check
// If file not found, check for encrypted version (.mdenc)
if (!file) {
const encryptedFilePath = filePath.replace(/\.md$/, '.mdenc');
file = app.vault.getAbstractFileByPath(encryptedFilePath);
}
if (!file) {
// File not found, showing create file view
@ -186,7 +207,6 @@ export async function openStreamDate(app: App, stream: Stream, date: Date = new
filePath: filePath
}
});
centralizedLogger.debug(`Successfully set view state for CreateFileView`);
} catch (error) {
centralizedLogger.error(`Error setting view state for CreateFileView:`, error);
// If setViewState fails, we can't proceed
@ -206,6 +226,39 @@ export async function openStreamDate(app: App, stream: Stream, date: Date = new
if (file instanceof TFile) {
try {
// Check if this is a .mdenc file
const isMdencFile = file.path.endsWith('.mdenc');
if (isMdencFile) {
// For .mdenc files, just open them normally and let Meld handle the encryption
// Update the date state manager to reflect the current date
const dateStateManager = DateStateManager.getInstance();
dateStateManager.setCurrentDate(date);
let leaf: WorkspaceLeaf | null = null;
if (reuseCurrentTab) {
const activeLeaf = app.workspace.activeLeaf;
leaf = activeLeaf || app.workspace.getLeaf('tab');
} else {
leaf = app.workspace.getLeaf('tab');
}
if (leaf) {
await leaf.openFile(file);
app.workspace.setActiveLeaf(leaf, { focus: true });
}
return;
}
// For .md files, just open them normally
// Update the date state manager to reflect the current date
const dateStateManager = DateStateManager.getInstance();
dateStateManager.setCurrentDate(date);
// File is not encrypted, proceed with normal opening
let leaf: WorkspaceLeaf | null = null;
if (reuseCurrentTab) {
@ -213,7 +266,6 @@ export async function openStreamDate(app: App, stream: Stream, date: Date = new
const activeLeaf = app.workspace.activeLeaf;
if (activeLeaf) {
leaf = activeLeaf;
// log.debug('Reusing current active leaf for markdown view (reuseCurrentTab enabled)');
} else {
// Fallback: look for existing leaf with the same file
const existingLeaf = app.workspace.getLeavesOfType('markdown')
@ -227,17 +279,14 @@ export async function openStreamDate(app: App, stream: Stream, date: Date = new
const filePath = normalizePath(file.path);
return viewPath === filePath;
} catch (e) {
// log.debug('Error comparing files:', e);
return false;
}
});
if (existingLeaf) {
leaf = existingLeaf;
// log.debug('Found existing leaf with same file');
} else {
leaf = app.workspace.getLeaf('tab');
// log.debug('Created a new leaf for markdown view');
}
}
} else {
@ -253,17 +302,14 @@ export async function openStreamDate(app: App, stream: Stream, date: Date = new
const filePath = normalizePath(file.path);
return viewPath === filePath;
} catch (e) {
// log.debug('Error comparing files:', e);
return false;
}
});
if (existingLeaf) {
leaf = existingLeaf;
// log.debug('Found existing leaf with same file');
} else {
leaf = app.workspace.getLeaf('tab');
// log.debug('Created a new leaf for markdown view');
}
}

View file

@ -0,0 +1,83 @@
import { App } from 'obsidian';
import { PluginAwareSliceService } from '../../shared/base-slice';
import { centralizedLogger } from '../../shared/centralized-logger';
/**
* Service for detecting and validating Meld plugin availability
*/
export class MeldDetectionService extends PluginAwareSliceService {
private meldCommandId = 'meld-encrypt:meld-encrypt-convert-to-or-from-encrypted-note';
async initialize(): Promise<void> {
if (this.initialized) return;
this.initialized = true;
}
cleanup(): void {
this.initialized = false;
}
/**
* Check if Meld plugin is installed and enabled
*/
isMeldPluginAvailable(): boolean {
try {
const app = this.getPlugin().app;
// Check if the Meld plugin is installed and enabled
const plugins = (app as any).plugins?.plugins;
if (!plugins) return false;
// Check for Meld plugin
const meldPlugin = plugins['meld-encrypt'];
if (!meldPlugin) return false;
// Check if the specific command exists
const commands = (app as any).commands?.commands;
if (!commands) return false;
return !!commands[this.meldCommandId];
} catch (error) {
centralizedLogger.error('Error checking Meld plugin availability:', error);
return false;
}
}
/**
* Get the Meld encryption command ID
*/
getMeldCommandId(): string {
return this.meldCommandId;
}
/**
* Execute the Meld encryption command
*/
async executeMeldEncryption(): Promise<boolean> {
try {
if (!this.isMeldPluginAvailable()) {
throw new Error('Meld plugin is not available or not enabled');
}
const app = this.getPlugin().app;
const command = (app as any).commands?.commands?.[this.meldCommandId];
if (command) {
await command.callback();
return true;
} else {
throw new Error(`Meld encryption command not found: ${this.meldCommandId}`);
}
} catch (error) {
centralizedLogger.error('Error executing Meld encryption command:', error);
return false;
}
}
/**
* Get a user-friendly error message for when Meld is not available
*/
getMeldUnavailableMessage(): string {
return 'Meld plugin is not installed or not enabled. Please install and enable the Meld plugin to use encryption features.';
}
}

View file

@ -0,0 +1 @@
export { MeldDetectionService } from './MeldDetectionService';

View file

@ -129,7 +129,8 @@ export class StreamsSettingTab extends PluginSettingTab {
folder: '',
icon: 'file-text' as LucideIcon,
showTodayInRibbon: true,
addCommand: false
addCommand: false,
encryptThisStream: false
};
this.plugin.settings.streams.push(newStream);
await this.plugin.saveSettings();
@ -200,6 +201,9 @@ export class StreamsSettingTab extends PluginSettingTab {
eventBus.emit(EVENTS.SETTINGS_CHANGED, this.plugin.settings, 'settings-management');
}));
// Encrypt this stream
this.addEncryptionToggle(container, stream);
// Remove stream
new Setting(container)
.addButton(button => button
@ -212,4 +216,43 @@ export class StreamsSettingTab extends PluginSettingTab {
this.display();
}));
}
private addEncryptionToggle(container: HTMLElement, stream: Stream): void {
// Check if Meld plugin is available
const fileOpsService = this.plugin.getFileOperationsService?.();
const isMeldAvailable = fileOpsService?.isMeldPluginAvailable() || false;
const encryptionSetting = new Setting(container)
.setName('Encrypt this stream')
.setDesc(isMeldAvailable
? 'When enabled, files created in this stream will be encrypted using the Meld plugin'
: 'Meld plugin is not available. Please install and enable the Meld plugin to use encryption features.'
)
.addToggle(toggle => {
toggle
.setValue(stream.encryptThisStream || false)
.setDisabled(!isMeldAvailable)
.onChange(async (value) => {
if (value && !isMeldAvailable) {
new Notice('Meld plugin is not available. Please install and enable the Meld plugin first.');
return;
}
stream.encryptThisStream = value;
await this.plugin.saveSettings();
eventBus.emit(EVENTS.SETTINGS_CHANGED, this.plugin.settings, 'settings-management');
new Notice(`Encryption ${value ? 'enabled' : 'disabled'} for stream "${stream.name}"`);
});
});
// Add warning if Meld is not available
if (!isMeldAvailable) {
const warningEl = container.createDiv('streams-encryption-warning');
warningEl.style.color = 'var(--text-error)';
warningEl.style.fontSize = '0.9em';
warningEl.style.marginTop = '0.5em';
warningEl.textContent = '⚠️ Meld plugin is required for encryption features';
}
}
}