Skip to content

Commit 40dc14c

Browse files
committed
Render codeblock wrapped commented encrypted text nicer
Fixes meld-cp#86
1 parent f25fc77 commit 40dc14c

File tree

5 files changed

+258
-42
lines changed

5 files changed

+258
-42
lines changed

src/features/feature-inplace-encrypt/DecryptModal.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { App, Modal, Notice, Setting, TextAreaComponent } from 'obsidian';
33
export default class DecryptModal extends Modal {
44
text: string;
55
decryptInPlace = false;
6+
7+
canDecryptInPlace = true;
68

79
constructor(
810
app: App,
@@ -44,17 +46,17 @@ export default class DecryptModal extends Modal {
4446
})
4547
;
4648
})
47-
.addButton( cb =>{
48-
cb
49-
.setWarning()
50-
.setButtonText('Decrypt in-place')
51-
.onClick( evt =>{
52-
this.decryptInPlace = true;
53-
this.close();
54-
})
55-
;
56-
})
5749
;
50+
if (this.canDecryptInPlace){
51+
sActions.addButton( cb =>{
52+
cb.setWarning()
53+
.setButtonText('Decrypt in-place')
54+
.onClick( evt =>{
55+
this.decryptInPlace = true;
56+
this.close();
57+
});
58+
});
59+
}
5860

5961
}
6062

src/features/feature-inplace-encrypt/FeatureInplaceEncrypt.ts

Lines changed: 201 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Editor, EditorPosition, Notice, Setting } from "obsidian";
1+
import { Editor, EditorPosition, Notice, Setting, MarkdownPostProcessorContext } from "obsidian";
22
import { CryptoHelper } from "../../services/CryptoHelper";
33
import { CryptoHelperObsolete } from "../../services/CryptoHelperObsolete";
44
import DecryptModal from "./DecryptModal";
@@ -42,6 +42,9 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
4242
this.pluginSettings = settings;
4343
this.featureSettings = settings.featureInplaceEncrypt;
4444

45+
46+
this.plugin.registerMarkdownPostProcessor( (el,ctx) => this.processEncryptedCodeBlockProcessor(el, ctx) );
47+
4548
plugin.addCommand({
4649
id: 'meld-encrypt',
4750
name: 'Encrypt/Decrypt',
@@ -62,6 +65,137 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
6265

6366
}
6467

68+
private processEncryptedCodeBlockProcessor(el: HTMLElement, ctx: MarkdownPostProcessorContext){
69+
70+
const si = ctx.getSectionInfo(el);
71+
if (si == null){
72+
return;
73+
}
74+
75+
// isolate code block lines
76+
const text = InplaceTextHelper.extractTextLines( si.text, si.lineStart, si.lineEnd );
77+
78+
79+
const markerStart = InplaceTextHelper.findFirstMarker( _PREFIXES, text );
80+
if ( markerStart == null || markerStart.marker != _PREFIX_A_VISIBLE ){
81+
//console.debug( 'not visible or null', markerStart );
82+
return;
83+
}
84+
85+
const markerEnd = InplaceTextHelper.findFirstMarker( _SUFFIXES, text, markerStart.position + markerStart.marker.length);
86+
if ( markerEnd == null ){
87+
return;
88+
}
89+
90+
const encryptedText = InplaceTextHelper.removeMarkers( text, markerStart, markerEnd );
91+
92+
const selectionAnalysis = new SelectionAnalysis( encryptedText );
93+
94+
if ( !selectionAnalysis.canDecrypt ){
95+
return;
96+
}
97+
98+
const textBeforeIndicator = InplaceTextHelper.extractTextBeforeMarker(text, markerStart );
99+
const textAfterIndicator = InplaceTextHelper.extractTextAfterMarker(text, markerEnd);
100+
101+
102+
// create elements
103+
const elPreIndicator = createSpan( { text: textBeforeIndicator } );
104+
const elPostIndicator = createSpan( { text: textAfterIndicator } );
105+
106+
const elIndicator = createSpan( { text: '🔐' } );
107+
elIndicator.style.cursor = 'pointer';
108+
109+
elIndicator.onClickEvent( async ev => {
110+
// indicator click handler
111+
112+
if ( await this.showDecryptedTextIfPasswordKnown( ctx.sourcePath, selectionAnalysis.decryptable ) ){
113+
return;
114+
}
115+
116+
const pw = await this.fetchPasswordFromUser( selectionAnalysis.decryptable.hint );
117+
118+
if ( pw == null ){
119+
return;
120+
}
121+
122+
// decrypt
123+
if ( await this.showDecryptedResultForPassword( selectionAnalysis.decryptable, pw ) ){
124+
SessionPasswordService.putByPath(
125+
{
126+
password: pw,
127+
hint: selectionAnalysis.decryptable.hint
128+
},
129+
ctx.sourcePath
130+
);
131+
}else{
132+
new Notice('❌ Decryption failed!');
133+
}
134+
135+
});
136+
137+
el.empty();
138+
el.append( elPreIndicator, elIndicator, elPostIndicator );
139+
140+
}
141+
142+
private async showDecryptedResultForPassword( decryptable: Decryptable, pw:string ): Promise<boolean> {
143+
const crypto = new CryptoHelper();
144+
const decryptedText = await crypto.decryptFromBase64(
145+
decryptable.base64CipherText,
146+
pw
147+
);
148+
// show result
149+
if (decryptedText === null) {
150+
return false;
151+
}
152+
153+
return new Promise<boolean>( (resolve) => {
154+
const decryptModal = new DecryptModal(this.plugin.app, '🔓', decryptedText );
155+
decryptModal.canDecryptInPlace = false;
156+
decryptModal.onClose = () =>{
157+
resolve(true);
158+
}
159+
decryptModal.open();
160+
} )
161+
162+
163+
}
164+
165+
private async fetchPasswordFromUser( hint:string ): Promise<string|null|undefined> {
166+
// fetch password
167+
return new Promise<string|null|undefined>( (resolve) => {
168+
const pwModal = new PasswordModal(
169+
this.plugin.app,
170+
/*isEncrypting*/ false,
171+
/*confirmPassword*/ false,
172+
/*defaultShowInReadingView*/ true /* TODO: get from settings */,
173+
'',
174+
hint
175+
);
176+
177+
pwModal.onClose = () =>{
178+
resolve( pwModal.resultPassword );
179+
}
180+
181+
pwModal.open();
182+
183+
184+
} );
185+
}
186+
187+
private async showDecryptedTextIfPasswordKnown( filePath: string, decryptable: Decryptable ) : Promise<boolean> {
188+
const bestGuessPasswordAndHint = SessionPasswordService.getByPath( filePath );
189+
if ( bestGuessPasswordAndHint.password == null ){
190+
return false;
191+
}
192+
193+
return await this.showDecryptedResultForPassword(
194+
decryptable,
195+
bestGuessPasswordAndHint.password
196+
);
197+
}
198+
65199
public buildSettingsUi(
66200
containerEl: HTMLElement,
67201
saveSettingCallback : () => Promise<void>
@@ -86,8 +220,6 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
86220
;
87221
}
88222

89-
90-
91223
private processEncryptDecryptCommand(
92224
checking: boolean,
93225
editor: Editor,
@@ -241,7 +373,7 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
241373
let defaultPassword = '';
242374
let defaultHint = selectionAnalysis.decryptable?.hint;
243375
if ( this.pluginSettings.rememberPassword ){
244-
const bestGuessPasswordAndHint = SessionPasswordService.get( activeFile );
376+
const bestGuessPasswordAndHint = SessionPasswordService.getByPath( activeFile.path );
245377
//console.debug({bestGuessPasswordAndHint});
246378

247379
defaultPassword = bestGuessPasswordAndHint.password;
@@ -254,6 +386,7 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
254386
this.plugin.app,
255387
selectionAnalysis.canEncrypt,
256388
confirmPassword,
389+
/*defaultShowInReadingView*/ true /* TODO: get from settings */,
257390
defaultPassword,
258391
defaultHint
259392
);
@@ -275,11 +408,12 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
275408
encryptable,
276409
pw,
277410
finalSelectionStart,
278-
finalSelectionEnd
411+
finalSelectionEnd,
412+
pwModal.resultShowInReadingView ?? true /* TODO: get from settings */
279413
);
280414

281415
// remember password
282-
SessionPasswordService.put( { password:pw, hint: hint }, activeFile );
416+
SessionPasswordService.putByPath( { password:pw, hint: hint }, activeFile.path );
283417

284418
} else {
285419

@@ -306,7 +440,7 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
306440

307441
// remember password?
308442
if ( decryptSuccess ) {
309-
SessionPasswordService.put( { password:pw, hint: hint }, activeFile );
443+
SessionPasswordService.putByPath( { password:pw, hint: hint }, activeFile.path );
310444
}
311445

312446
}
@@ -322,12 +456,14 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
322456
password: string,
323457
finalSelectionStart: CodeMirror.Position,
324458
finalSelectionEnd: CodeMirror.Position,
459+
showInReadingView: boolean
325460
) {
326461
//encrypt
327462
const crypto = new CryptoHelper();
328463
const encodedText = this.encodeEncryption(
329464
await crypto.encryptToBase64(encryptable.text, password),
330-
encryptable.hint
465+
encryptable.hint,
466+
showInReadingView
331467
);
332468
editor.setSelection(finalSelectionStart, finalSelectionEnd);
333469
editor.replaceSelection(encodedText);
@@ -405,15 +541,18 @@ export default class FeatureInplaceEncrypt implements IMeldEncryptPluginFeature{
405541
}
406542

407543

408-
private encodeEncryption( encryptedText: string, hint: string ): string {
544+
private encodeEncryption( encryptedText: string, hint: string, showInReadingView: boolean ): string {
409545
if (
410546
!_PREFIXES.some( (prefix) => encryptedText.contains(prefix) )
411547
&& !_SUFFIXES.some( (suffix) => encryptedText.contains(suffix) )
412548
) {
413-
if (hint.length > 0){
414-
return _PREFIX_A.concat(_HINT, hint, _HINT, encryptedText, _SUFFIX_WITH_COMMENT);
549+
const prefix = showInReadingView ? _PREFIX_A_VISIBLE : _PREFIX_A;
550+
const suffix = showInReadingView ? _SUFFIX_NO_COMMENT : _SUFFIX_WITH_COMMENT;
551+
552+
if ( hint.length > 0 ){
553+
return prefix.concat(_HINT, hint, _HINT, encryptedText, suffix);
415554
}
416-
return _PREFIX_A.concat(encryptedText, _SUFFIX_WITH_COMMENT);
555+
return prefix.concat(encryptedText, suffix);
417556
}
418557
return encryptedText;
419558
}
@@ -517,4 +656,54 @@ class Decryptable{
517656
version: number;
518657
base64CipherText:string;
519658
hint:string;
659+
}
660+
661+
662+
interface IMarkerPosition{
663+
marker:string;
664+
position:number;
665+
}
666+
667+
class InplaceTextHelper{
668+
static extractTextBeforeMarker(text: string, marker: IMarkerPosition) {
669+
return text.substring( 0, marker.position );
670+
}
671+
static extractTextAfterMarker(text: string, marker: IMarkerPosition) {
672+
return text.substring( marker.position + marker.marker.length );
673+
}
674+
675+
public static removeMarkers(text: string, markerStart: IMarkerPosition, markerEnd: IMarkerPosition) {
676+
return text.substring( markerStart.position, markerEnd.position + markerEnd.marker.length );
677+
}
678+
679+
public static extractTextLines(text: string, lineStart: number, lineEnd: number) {
680+
return text.split('\n').slice(lineStart, lineEnd+1).join('\n');
681+
}
682+
683+
public static findFirstMarker( markers:string[], text:string, startPos = 0 ) : IMarkerPosition | null {
684+
685+
let firstMarkerPos : number | null = null;
686+
let firstMarker : string | null = null;
687+
688+
markers.forEach(m => {
689+
const pos = text.indexOf( m, startPos );
690+
//console.debug({m,pos});
691+
if ( pos != -1 && ( firstMarkerPos == null || pos < firstMarkerPos ) ){
692+
firstMarkerPos = pos;
693+
firstMarker = m;
694+
}
695+
});
696+
697+
if ( firstMarker == null || firstMarkerPos == null ){
698+
return null;
699+
}
700+
701+
return {
702+
marker: firstMarker,
703+
position: firstMarkerPos
704+
};
705+
}
706+
707+
708+
520709
}

0 commit comments

Comments
 (0)