mirror of
https://github.com/bfloydd/streams.git
synced 2026-07-22 12:50:25 +00:00
meld encrypt integration
This commit is contained in:
parent
74d452142b
commit
34cc1ffae4
15 changed files with 591 additions and 31 deletions
17
main.ts
17
main.ts
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,4 +71,5 @@ export interface StreamsPluginInterface extends Plugin {
|
|||
updateAllStreamsBarComponents(): void;
|
||||
setActiveStream(streamId: string, force?: boolean): void;
|
||||
getActiveStream(): Stream | undefined;
|
||||
getFileOperationsService(): any;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export interface Stream {
|
|||
icon: LucideIcon;
|
||||
showTodayInRibbon: boolean;
|
||||
addCommand: boolean;
|
||||
encryptThisStream: boolean; // New field for encryption toggle
|
||||
}
|
||||
|
||||
export interface StreamsSettings {
|
||||
|
|
|
|||
|
|
@ -323,7 +323,8 @@ export class CalendarNavigationService extends SettingsAwareSliceService {
|
|||
icon: 'book',
|
||||
folder: 'Streams',
|
||||
showTodayInRibbon: true,
|
||||
addCommand: true
|
||||
addCommand: true,
|
||||
encryptThisStream: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export type { FileCreationInterface } from './FileCreationStrategy';
|
||||
export { NormalFileStrategy } from './NormalFileStrategy';
|
||||
export { MeldEncryptedFileStrategy } from './MeldEncryptedFileStrategy';
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
83
src/slices/meld-integration/MeldDetectionService.ts
Normal file
83
src/slices/meld-integration/MeldDetectionService.ts
Normal 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.';
|
||||
}
|
||||
}
|
||||
1
src/slices/meld-integration/index.ts
Normal file
1
src/slices/meld-integration/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { MeldDetectionService } from './MeldDetectionService';
|
||||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue