You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1510 lines
226 KiB

3 years ago
'use strict';
var obsidian = require('obsidian');
2 years ago
/******************************************************************************
3 years ago
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
2 years ago
class MemoryCache {
constructor() {
this.values = new Map();
3 years ago
}
2 years ago
put(key, value) {
//console.debug('MemoryCache.put', {key, value});
this.values.set(key, value);
}
get(key, defaultValue) {
//console.debug('MemoryCache.get', {key, defaultValue});
return this.values.has(key) ? this.values.get(key) : defaultValue;
}
getFirst(keys, defaultValue) {
//console.debug('MemoryCache.getFirst', {keys, defaultValue});
for (let index = 0; index < keys.length; index++) {
const key = keys[index];
if (this.containsKey(key)) {
return this.get(key, defaultValue);
}
3 years ago
}
2 years ago
return defaultValue;
}
containsKey(key) {
//console.debug('MemoryCache.containsKey', {key});
return this.values.has(key);
}
getKeys() {
//console.debug('MemoryCache.getKeys');
return Array.from(this.values.keys());
}
clear() {
//console.debug('MemoryCache.clear');
this.values.clear();
3 years ago
}
}
2 years ago
class SessionPasswordService {
static setActive(isActive) {
SessionPasswordService.isActive = isActive;
if (!SessionPasswordService.isActive) {
this.clear();
3 years ago
}
2 years ago
}
/**
*
* @param minutesToExpire set to 0 to never expire
*/
static setAutoExpire(minutesToExpire) {
SessionPasswordService.baseMinutesToExpire = minutesToExpire;
SessionPasswordService.updateExpiryTime();
}
static updateExpiryTime() {
if (SessionPasswordService.baseMinutesToExpire == 0
|| SessionPasswordService.baseMinutesToExpire == null) {
SessionPasswordService.expiryTime = null;
3 years ago
}
2 years ago
else {
SessionPasswordService.expiryTime = Date.now() + SessionPasswordService.baseMinutesToExpire * 1000 * 60;
3 years ago
}
2 years ago
console.debug('SessionPasswordService.updateExpiryTime', { expiryTime: SessionPasswordService.expiryTime });
}
static put(pw, file) {
console.debug('SessionPasswordService.put', { pw, file });
console.debug(file.parent.path);
this.cache.put(file.path, pw);
this.cache.put(file.parent.path, pw);
this.cache.put(file.basename, pw);
SessionPasswordService.updateExpiryTime();
}
static getExact(file) {
this.clearIfExpired();
SessionPasswordService.updateExpiryTime();
return this.cache.get(file.path, SessionPasswordService.blankPasswordAndHint);
}
static getBestGuess(file) {
this.clearIfExpired();
//console.debug('SessionPasswordService.getBestGuess', {file})
SessionPasswordService.updateExpiryTime();
const buestGuess = this.cache.getFirst([
file.path,
file.parent.path,
file.basename
], SessionPasswordService.blankPasswordAndHint);
console.debug('SessionPasswordService.getBestGuess', { file, buestGuess });
return buestGuess;
}
static clearIfExpired() {
if (SessionPasswordService.expiryTime == null) {
return;
3 years ago
}
2 years ago
if (Date.now() < SessionPasswordService.expiryTime) {
return;
3 years ago
}
2 years ago
this.clear();
}
static clear() {
this.cache.clear();
}
}
SessionPasswordService.isActive = true;
SessionPasswordService.blankPasswordAndHint = { password: '', hint: '' };
SessionPasswordService.cache = new MemoryCache();
SessionPasswordService.baseMinutesToExpire = 0;
SessionPasswordService.expiryTime = null;
class MeldEncryptSettingsTab extends obsidian.PluginSettingTab {
constructor(app, plugin, settings, features) {
super(app, plugin);
this.plugin = plugin;
this.settings = settings;
this.features = features;
}
display() {
let { containerEl } = this;
containerEl.empty();
containerEl.createEl('h1', { text: 'Settings for Meld Encrypt' });
// build common settings
new obsidian.Setting(containerEl)
.setHeading()
.setName('Common Settings');
new obsidian.Setting(containerEl)
.setName('Confirm password?')
.setDesc('Confirm password when encrypting.')
.addToggle(toggle => {
toggle
.setValue(this.settings.confirmPassword)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.confirmPassword = value;
yield this.plugin.saveSettings();
}));
3 years ago
});
2 years ago
const updateRememberPasswordSettingsUi = () => {
if (!this.settings.rememberPassword) {
pwTimeoutSetting.settingEl.hide();
3 years ago
return;
}
2 years ago
pwTimeoutSetting.settingEl.show();
const rememberPasswordTimeout = this.settings.rememberPasswordTimeout;
let timeoutString = `${rememberPasswordTimeout} minutes`;
if (rememberPasswordTimeout == 0) {
timeoutString = 'Never forget';
3 years ago
}
2 years ago
pwTimeoutSetting.setName(`Remember Password Timeout (${timeoutString})`);
3 years ago
};
2 years ago
new obsidian.Setting(containerEl)
.setName('Remember password?')
.setDesc('Remember the last used passwords when encrypting or decrypting.')
.addToggle(toggle => {
toggle
.setValue(this.settings.rememberPassword)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.rememberPassword = value;
yield this.plugin.saveSettings();
SessionPasswordService.setActive(this.settings.rememberPassword);
updateRememberPasswordSettingsUi();
}));
3 years ago
});
2 years ago
const pwTimeoutSetting = new obsidian.Setting(containerEl)
.setDesc('The number of minutes to remember passwords.')
.addSlider(slider => {
slider
.setLimits(0, 120, 5)
.setValue(this.settings.rememberPasswordTimeout)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.rememberPasswordTimeout = value;
yield this.plugin.saveSettings();
SessionPasswordService.setAutoExpire(this.settings.rememberPasswordTimeout);
updateRememberPasswordSettingsUi();
}));
3 years ago
});
2 years ago
updateRememberPasswordSettingsUi();
// build feature settings
this.features.forEach(f => {
f.buildSettingsUi(containerEl, () => __awaiter(this, void 0, void 0, function* () { return yield this.plugin.saveSettings(); }));
3 years ago
});
}
}
const vectorSize = 16;
const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder();
const iterations = 1000;
const salt = utf8Encoder.encode('XHWnDAT6ehMVY2zD');
2 years ago
class CryptoHelper {
3 years ago
deriveKey(password) {
return __awaiter(this, void 0, void 0, function* () {
const buffer = utf8Encoder.encode(password);
const key = yield crypto.subtle.importKey('raw', buffer, { name: 'PBKDF2' }, false, ['deriveKey']);
const privateKey = crypto.subtle.deriveKey({
name: 'PBKDF2',
hash: { name: 'SHA-256' },
iterations,
salt
}, key, {
name: 'AES-GCM',
length: 256
}, false, ['encrypt', 'decrypt']);
return privateKey;
});
}
2 years ago
encryptToBytes(text, password) {
3 years ago
return __awaiter(this, void 0, void 0, function* () {
const key = yield this.deriveKey(password);
const textBytesToEncrypt = utf8Encoder.encode(text);
const vector = crypto.getRandomValues(new Uint8Array(vectorSize));
// encrypt into bytes
const encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt({ name: 'AES-GCM', iv: vector }, key, textBytesToEncrypt));
const finalBytes = new Uint8Array(vector.byteLength + encryptedBytes.byteLength);
finalBytes.set(vector, 0);
finalBytes.set(encryptedBytes, vector.byteLength);
2 years ago
return finalBytes;
});
}
convertToString(bytes) {
let result = '';
for (let idx = 0; idx < bytes.length; idx++) {
// append to result
result += String.fromCharCode(bytes[idx]);
}
return result;
}
encryptToBase64(text, password) {
return __awaiter(this, void 0, void 0, function* () {
const finalBytes = yield this.encryptToBytes(text, password);
3 years ago
//convert array to base64
2 years ago
const base64Text = btoa(this.convertToString(finalBytes));
3 years ago
return base64Text;
});
}
stringToArray(str) {
var result = [];
for (var i = 0; i < str.length; i++) {
result.push(str.charCodeAt(i));
}
return new Uint8Array(result);
}
2 years ago
decryptFromBytes(encryptedBytes, password) {
3 years ago
return __awaiter(this, void 0, void 0, function* () {
try {
// extract iv
2 years ago
const vector = encryptedBytes.slice(0, vectorSize);
3 years ago
// extract encrypted text
2 years ago
const encryptedTextBytes = encryptedBytes.slice(vectorSize);
3 years ago
const key = yield this.deriveKey(password);
// decrypt into bytes
let decryptedBytes = yield crypto.subtle.decrypt({ name: 'AES-GCM', iv: vector }, key, encryptedTextBytes);
// convert bytes to text
let decryptedText = utf8Decoder.decode(decryptedBytes);
return decryptedText;
}
catch (e) {
//console.error(e);
return null;
}
});
}
2 years ago
decryptFromBase64(base64Encoded, password) {
return __awaiter(this, void 0, void 0, function* () {
try {
let bytesToDecode = this.stringToArray(atob(base64Encoded));
return yield this.decryptFromBytes(bytesToDecode, password);
// // extract iv
// const vector = bytesToDecode.slice(0,vectorSize);
// // extract encrypted text
// const encryptedTextBytes = bytesToDecode.slice(vectorSize);
// const key = await this.deriveKey(password);
// // decrypt into bytes
// let decryptedBytes = await crypto.subtle.decrypt(
// {name: 'AES-GCM', iv: vector},
// key,
// encryptedTextBytes
// );
// // convert bytes to text
// let decryptedText = utf8Decoder.decode(decryptedBytes);
// return decryptedText;
}
catch (e) {
//console.error(e);
return null;
}
});
}
}
3 years ago
const algorithmObsolete = {
name: 'AES-GCM',
iv: new Uint8Array([196, 190, 240, 190, 188, 78, 41, 132, 15, 220, 84, 211]),
tagLength: 128
};
class CryptoHelperObsolete {
buildKey(password) {
return __awaiter(this, void 0, void 0, function* () {
let utf8Encode = new TextEncoder();
let passwordBytes = utf8Encode.encode(password);
let passwordDigest = yield crypto.subtle.digest({ name: 'SHA-256' }, passwordBytes);
let key = yield crypto.subtle.importKey('raw', passwordDigest, algorithmObsolete, false, ['encrypt', 'decrypt']);
return key;
});
}
2 years ago
/**
* @deprecated
*/
3 years ago
encryptToBase64(text, password) {
return __awaiter(this, void 0, void 0, function* () {
let key = yield this.buildKey(password);
let utf8Encode = new TextEncoder();
let bytesToEncrypt = utf8Encode.encode(text);
// encrypt into bytes
let encryptedBytes = new Uint8Array(yield crypto.subtle.encrypt(algorithmObsolete, key, bytesToEncrypt));
//convert array to base64
let base64Text = btoa(String.fromCharCode(...encryptedBytes));
return base64Text;
});
}
stringToArray(str) {
var result = [];
for (var i = 0; i < str.length; i++) {
result.push(str.charCodeAt(i));
}
return new Uint8Array(result);
}
decryptFromBase64(base64Encoded, password) {
return __awaiter(this, void 0, void 0, function* () {
try {
// convert base 64 to array
let bytesToDecrypt = this.stringToArray(atob(base64Encoded));
let key = yield this.buildKey(password);
// decrypt into bytes
let decryptedBytes = yield crypto.subtle.decrypt(algorithmObsolete, key, bytesToDecrypt);
// convert bytes to text
let utf8Decode = new TextDecoder();
let decryptedText = utf8Decode.decode(decryptedBytes);
return decryptedText;
}
catch (e) {
return null;
}
});
}
}
2 years ago
class DecryptModal extends obsidian.Modal {
constructor(app, title, text = '', showCopyButton) {
super(app);
this.decryptInPlace = false;
this.titleEl.setText(title);
this.text = text;
this.showCopyButton = showCopyButton;
3 years ago
}
2 years ago
onOpen() {
let { contentEl } = this;
let cTextArea;
const sText = new obsidian.Setting(contentEl)
.addTextArea(cb => {
cTextArea = cb;
cb.setValue(this.text);
cb.inputEl.setSelectionRange(0, 0);
cb.inputEl.readOnly = true;
cb.inputEl.rows = 10;
cb.inputEl.style.width = '100%';
cb.inputEl.style.minHeight = '3em';
cb.inputEl.style.resize = 'vertical';
3 years ago
});
2 years ago
sText.settingEl.querySelector('.setting-item-info').remove();
const sActions = new obsidian.Setting(contentEl);
if (this.showCopyButton) {
sActions
.addButton(cb => {
cb
.setButtonText('Copy')
.onClick(evt => {
navigator.clipboard.writeText(cTextArea.getValue());
new obsidian.Notice('Copied!');
});
if (!this.showCopyButton) ;
});
}
sActions
.addButton(cb => {
cb
.setWarning()
.setButtonText('Decrypt in-place')
.onClick(evt => {
this.decryptInPlace = true;
this.close();
});
3 years ago
});
2 years ago
}
}
class UiHelper {
/**
Check if the Settings modal is open
*/
static isSettingsModalOpen() {
return document.querySelector('.mod-settings') !== null;
}
static buildPasswordSetting({ container, name, desc = '', autoFocus = false, placeholder = '', initialValue = '', onChangeCallback, onEnterCallback, }) {
const sControl = new obsidian.Setting(container)
.setName(name)
.setDesc(desc)
.addButton(cb => {
cb
.setIcon('reading-glasses')
.onClick(evt => {
// toggle view password
const inputCtrl = sControl.components.find((bc, idx, obj) => bc instanceof obsidian.TextComponent);
if (inputCtrl instanceof obsidian.TextComponent) {
inputCtrl.inputEl.type = inputCtrl.inputEl.type == 'password' ? 'text' : 'password';
}
});
})
.addText(tc => {
tc.setPlaceholder(placeholder);
tc.setValue(initialValue);
tc.inputEl.type = 'password';
if (onChangeCallback != null) {
tc.onChange(onChangeCallback);
}
if (onEnterCallback != null) {
tc.inputEl.onkeydown = (ev) => {
if (ev.key === 'Enter') {
ev.preventDefault();
onEnterCallback(tc.getValue());
}
};
}
if (autoFocus) {
setTimeout(() => tc.inputEl.focus(), 0);
}
3 years ago
});
2 years ago
return sControl;
}
}
class PasswordModal extends obsidian.Modal {
constructor(app, isEncrypting, confirmPassword, defaultPassword = null, hint = null) {
super(app);
// input
this.defaultPassword = null;
this.defaultHint = null;
// output
this.resultConfirmed = false;
this.resultPassword = null;
this.resultHint = null;
this.defaultPassword = defaultPassword;
this.confirmPassword = confirmPassword;
this.isEncrypting = isEncrypting;
this.defaultHint = hint;
}
onOpen() {
var _a, _b;
let { contentEl } = this;
contentEl.empty();
//this.contentEl.style.width = 'auto';
this.invalidate();
let password = (_a = this.defaultPassword) !== null && _a !== void 0 ? _a : '';
let confirmPass = '';
let hint = (_b = this.defaultHint) !== null && _b !== void 0 ? _b : '';
new obsidian.Setting(contentEl).setHeading().setName(this.isEncrypting ? 'Encrypting' : 'Decrypting');
/* Main password input*/
UiHelper.buildPasswordSetting({
container: contentEl,
name: 'Password:',
placeholder: this.isEncrypting ? '' : `Hint: ${this.defaultHint}`,
initialValue: password,
autoFocus: true,
onChangeCallback: (value) => {
password = value;
this.invalidate();
},
onEnterCallback: (value) => {
password = value;
this.invalidate();
if (password.length > 0) {
if (sConfirmPassword.settingEl.isShown()) {
//tcConfirmPassword.inputEl.focus();
const elInp = sConfirmPassword.components.find((bc) => bc instanceof obsidian.TextComponent);
if (elInp instanceof obsidian.TextComponent) {
elInp.inputEl.focus();
}
}
else if (sHint.settingEl.isShown()) {
//tcHint.inputEl.focus();
const elInp = sHint.components.find((bc) => bc instanceof obsidian.TextComponent);
if (elInp instanceof obsidian.TextComponent) {
elInp.inputEl.focus();
}
}
else if (validate()) {
this.close();
}
}
}
3 years ago
});
2 years ago
/* End Main password input row */
/* Confirm password input row */
const sConfirmPassword = UiHelper.buildPasswordSetting({
container: contentEl,
name: 'Confirm Password:',
onChangeCallback: (value) => {
confirmPass = value;
this.invalidate();
},
onEnterCallback: (value) => {
confirmPass = value;
this.invalidate();
if (confirmPass.length > 0) {
if (validate()) {
if (sHint.settingEl.isShown()) {
//tcHint.inputEl.focus();
const elInp = sHint.components.find((bc) => bc instanceof obsidian.TextComponent);
if (elInp instanceof obsidian.TextComponent) {
elInp.inputEl.focus();
}
}
}
}
}
3 years ago
});
2 years ago
if (!this.confirmPassword) {
sConfirmPassword.settingEl.hide();
3 years ago
}
2 years ago
/* End Confirm password input row */
/* Hint input row */
const sHint = new obsidian.Setting(contentEl)
.setName('Optional Password Hint')
.addText(tc => {
//tcHint = tc;
tc.inputEl.placeholder = `Password Hint`;
tc.setValue(hint);
tc.onChange(v => hint = v);
tc.inputEl.on('keypress', '*', (ev, target) => {
if (ev.key == 'Enter'
&& target instanceof HTMLInputElement
&& target.value.length > 0) {
ev.preventDefault();
if (validate()) {
this.close();
}
}
});
});
if (!this.isEncrypting) {
sHint.settingEl.hide();
3 years ago
}
2 years ago
/* END Hint text row */
new obsidian.Setting(contentEl).addButton(cb => {
cb
.setButtonText('Confirm')
.onClick(evt => {
if (validate()) {
this.close();
}
});
});
const validate = () => {
this.invalidate();
sConfirmPassword.setDesc('');
if (this.confirmPassword) {
if (password != confirmPass) {
// passwords don't match
sConfirmPassword.setDesc('Passwords don\'t match');
return false;
}
}
this.resultConfirmed = true;
this.resultPassword = password;
this.resultHint = hint;
return true;
};
3 years ago
}
2 years ago
invalidate() {
this.resultConfirmed = false;
this.resultPassword = null;
this.resultHint = null;
3 years ago
}
}
3 years ago
const _PREFIX = '%%🔐';
const _PREFIX_OBSOLETE = _PREFIX + ' ';
const _PREFIX_A = _PREFIX + 'α ';
3 years ago
const _SUFFIX = ' 🔐%%';
3 years ago
const _HINT = '💡';
2 years ago
class FeatureInplaceEncrypt {
onload(plugin, settings) {
3 years ago
return __awaiter(this, void 0, void 0, function* () {
2 years ago
this.plugin = plugin;
this.pluginSettings = settings;
this.featureSettings = settings.featureInplaceEncrypt;
plugin.addCommand({
3 years ago
id: 'meld-encrypt',
name: 'Encrypt/Decrypt',
2 years ago
icon: 'lock',
3 years ago
editorCheckCallback: (checking, editor, view) => this.processEncryptDecryptCommand(checking, editor, view, false)
});
2 years ago
plugin.addCommand({
3 years ago
id: 'meld-encrypt-in-place',
name: 'Encrypt/Decrypt In-place',
2 years ago
icon: 'lock',
3 years ago
editorCheckCallback: (checking, editor, view) => this.processEncryptDecryptCommand(checking, editor, view, true)
});
});
}
2 years ago
onunload() {
3 years ago
}
2 years ago
buildSettingsUi(containerEl, saveSettingCallback) {
new obsidian.Setting(containerEl)
.setHeading()
.setName('In-place Encryption Settings');
// Selection encrypt feature settings below
new obsidian.Setting(containerEl)
.setName('Expand selection to whole line?')
.setDesc('Partial selections will get expanded to the whole line.')
.addToggle(toggle => {
toggle
.setValue(this.featureSettings.expandToWholeLines)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.featureSettings.expandToWholeLines = value;
yield saveSettingCallback();
}));
});
new obsidian.Setting(containerEl)
.setName('Copy button?')
.setDesc('Show a button to copy decrypted text.')
.addToggle(toggle => {
toggle
.setValue(this.featureSettings.showCopyButton)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.featureSettings.showCopyButton = value;
yield saveSettingCallback();
}));
3 years ago
});
}
processEncryptDecryptCommand(checking, editor, view, decryptInPlace) {
2 years ago
if (checking && UiHelper.isSettingsModalOpen()) {
3 years ago
// Settings is open, ensures this command can show up in other
// plugins which list commands e.g. customizable-sidebar
return true;
}
let startPos = editor.getCursor('from');
let endPos = editor.getCursor('to');
2 years ago
if (this.featureSettings.expandToWholeLines) {
3 years ago
const startLine = startPos.line;
startPos = { line: startLine, ch: 0 }; // want the start of the first line
const endLine = endPos.line;
const endLineText = editor.getLine(endLine);
endPos = { line: endLine, ch: endLineText.length }; // want the end of last line
}
3 years ago
else {
if (!editor.somethingSelected()) {
// nothing selected, assume user wants to decrypt, expand to start and end markers
startPos = this.getClosestPrevTextCursorPos(editor, _PREFIX, startPos);
endPos = this.getClosestNextTextCursorPos(editor, _SUFFIX, endPos);
}
}
3 years ago
const selectionText = editor.getRange(startPos, endPos);
return this.processSelection(checking, editor, selectionText, startPos, endPos, decryptInPlace);
}
3 years ago
getClosestPrevTextCursorPos(editor, text, defaultValue) {
const initOffset = editor.posToOffset(editor.getCursor("from"));
for (let offset = initOffset; offset >= 0; offset--) {
const offsetPos = editor.offsetToPos(offset);
const textEndOffset = offset + text.length;
const prefixEndPos = editor.offsetToPos(textEndOffset);
const testText = editor.getRange(offsetPos, prefixEndPos);
if (testText == text) {
return offsetPos;
}
}
return defaultValue;
}
getClosestNextTextCursorPos(editor, text, defaultValue) {
const initOffset = editor.posToOffset(editor.getCursor("from"));
3 years ago
const lastLineNum = editor.lastLine();
let maxOffset = editor.posToOffset({ line: lastLineNum, ch: editor.getLine(lastLineNum).length });
3 years ago
for (let offset = initOffset; offset <= maxOffset - text.length; offset++) {
const offsetPos = editor.offsetToPos(offset);
const textEndOffset = offset + text.length;
const prefixEndPos = editor.offsetToPos(textEndOffset);
const testText = editor.getRange(offsetPos, prefixEndPos);
if (testText == text) {
return prefixEndPos;
}
}
return defaultValue;
}
3 years ago
analyseSelection(selectionText) {
const result = new SelectionAnalysis();
result.isEmpty = selectionText.length === 0;
result.hasObsoleteEncryptedPrefix = selectionText.startsWith(_PREFIX_OBSOLETE);
result.hasEncryptedPrefix = result.hasObsoleteEncryptedPrefix || selectionText.startsWith(_PREFIX_A);
result.hasDecryptSuffix = selectionText.endsWith(_SUFFIX);
result.containsEncryptedMarkers =
selectionText.contains(_PREFIX_OBSOLETE)
|| selectionText.contains(_PREFIX_A)
|| selectionText.contains(_SUFFIX);
result.canDecrypt = result.hasEncryptedPrefix && result.hasDecryptSuffix;
result.canEncrypt = !result.hasEncryptedPrefix && !result.containsEncryptedMarkers;
3 years ago
if (result.canDecrypt) {
result.decryptable = this.parseDecryptableContent(selectionText);
if (result.decryptable == null) {
result.canDecrypt = false;
}
}
3 years ago
return result;
}
2 years ago
processSelection(checking, editor, selectionText, finalSelectionStart, finalSelectionEnd, decryptInPlace, allowEncryption = true) {
3 years ago
var _a;
3 years ago
const selectionAnalysis = this.analyseSelection(selectionText);
if (selectionAnalysis.isEmpty) {
if (!checking) {
new obsidian.Notice('Nothing to Encrypt.');
}
return false;
}
if (!selectionAnalysis.canDecrypt && !selectionAnalysis.canEncrypt) {
if (!checking) {
new obsidian.Notice('Unable to Encrypt or Decrypt that.');
}
return false;
}
2 years ago
if (selectionAnalysis.canEncrypt && !allowEncryption) {
return false;
}
3 years ago
if (checking) {
return true;
}
2 years ago
const activeFile = this.plugin.app.workspace.getActiveFile();
3 years ago
// Fetch password from user
2 years ago
// determine default password and hint
let defaultPassword = '';
let defaultHint = (_a = selectionAnalysis.decryptable) === null || _a === void 0 ? void 0 : _a.hint;
if (this.pluginSettings.rememberPassword) {
const bestGuessPasswordAndHint = SessionPasswordService.getBestGuess(activeFile);
console.debug({ bestGuessPasswordAndHint });
defaultPassword = bestGuessPasswordAndHint.password;
defaultHint = defaultHint !== null && defaultHint !== void 0 ? defaultHint : bestGuessPasswordAndHint.hint;
}
const confirmPassword = selectionAnalysis.canEncrypt && this.pluginSettings.confirmPassword;
const pwModal = new PasswordModal(this.plugin.app, selectionAnalysis.canEncrypt, confirmPassword, defaultPassword, defaultHint);
pwModal.onClose = () => __awaiter(this, void 0, void 0, function* () {
var _b, _c;
if (!pwModal.resultConfirmed) {
3 years ago
return;
}
2 years ago
const pw = (_b = pwModal.resultPassword) !== null && _b !== void 0 ? _b : '';
const hint = (_c = pwModal.resultHint) !== null && _c !== void 0 ? _c : '';
3 years ago
if (selectionAnalysis.canEncrypt) {
3 years ago
const encryptable = new Encryptable();
encryptable.text = selectionText;
encryptable.hint = hint;
this.encryptSelection(editor, encryptable, pw, finalSelectionStart, finalSelectionEnd);
2 years ago
// remember password
SessionPasswordService.put({ password: pw, hint: hint }, activeFile);
3 years ago
}
else {
2 years ago
let decryptSuccess;
3 years ago
if (selectionAnalysis.decryptable.version == 1) {
2 years ago
decryptSuccess = yield this.decryptSelection_a(editor, selectionAnalysis.decryptable, pw, finalSelectionStart, finalSelectionEnd, decryptInPlace);
3 years ago
}
else {
2 years ago
decryptSuccess = yield this.decryptSelectionObsolete(editor, selectionAnalysis.decryptable, pw, finalSelectionStart, finalSelectionEnd, decryptInPlace);
}
// remember password?
if (decryptSuccess) {
SessionPasswordService.put({ password: pw, hint: hint }, activeFile);
3 years ago
}
}
2 years ago
});
3 years ago
pwModal.open();
return true;
}
3 years ago
encryptSelection(editor, encryptable, password, finalSelectionStart, finalSelectionEnd) {
3 years ago
return __awaiter(this, void 0, void 0, function* () {
//encrypt
2 years ago
const crypto = new CryptoHelper();
3 years ago
const encodedText = this.encodeEncryption(yield crypto.encryptToBase64(encryptable.text, password), encryptable.hint);
3 years ago
editor.setSelection(finalSelectionStart, finalSelectionEnd);
3 years ago
editor.replaceSelection(encodedText);
3 years ago
});
}
3 years ago
decryptSelection_a(editor, decryptable, password, selectionStart, selectionEnd, decryptInPlace) {
3 years ago
return __awaiter(this, void 0, void 0, function* () {
// decrypt
2 years ago
const crypto = new CryptoHelper();
3 years ago
const decryptedText = yield crypto.decryptFromBase64(decryptable.base64CipherText, password);
3 years ago
if (decryptedText === null) {
new obsidian.Notice('❌ Decryption failed!');
2 years ago
return false;
3 years ago
}
else {
if (decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
else {
2 years ago
const decryptModal = new DecryptModal(this.plugin.app, '🔓', decryptedText, this.featureSettings.showCopyButton);
3 years ago
decryptModal.onClose = () => {
editor.focus();
if (decryptModal.decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
};
decryptModal.open();
}
}
2 years ago
return true;
3 years ago
});
}
3 years ago
decryptSelectionObsolete(editor, decryptable, password, selectionStart, selectionEnd, decryptInPlace) {
3 years ago
return __awaiter(this, void 0, void 0, function* () {
// decrypt
3 years ago
const base64CipherText = this.removeMarkers(decryptable.base64CipherText);
3 years ago
const crypto = new CryptoHelperObsolete();
const decryptedText = yield crypto.decryptFromBase64(base64CipherText, password);
if (decryptedText === null) {
new obsidian.Notice('❌ Decryption failed!');
2 years ago
return false;
3 years ago
}
else {
if (decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
else {
2 years ago
const decryptModal = new DecryptModal(this.plugin.app, '🔓', decryptedText, this.featureSettings.showCopyButton);
3 years ago
decryptModal.onClose = () => {
editor.focus();
if (decryptModal.decryptInPlace) {
editor.setSelection(selectionStart, selectionEnd);
editor.replaceSelection(decryptedText);
}
};
decryptModal.open();
}
}
2 years ago
return true;
3 years ago
});
}
3 years ago
parseDecryptableContent(text) {
const result = new Decryptable();
let content = text;
if (content.startsWith(_PREFIX_A) && content.endsWith(_SUFFIX)) {
result.version = 1;
content = content.replace(_PREFIX_A, '').replace(_SUFFIX, '');
}
else if (content.startsWith(_PREFIX_OBSOLETE) && content.endsWith(_SUFFIX)) {
result.version = 0;
content = content.replace(_PREFIX_OBSOLETE, '').replace(_SUFFIX, '');
}
else {
return null; // invalid format
}
// check if there is a hint
2 years ago
if (content.substring(0, _HINT.length) == _HINT) {
3 years ago
const endHintMarker = content.indexOf(_HINT, _HINT.length);
if (endHintMarker < 0) {
return null; // invalid format
}
result.hint = content.substring(_HINT.length, endHintMarker);
result.base64CipherText = content.substring(endHintMarker + _HINT.length);
}
else {
result.base64CipherText = content;
}
return result;
}
3 years ago
removeMarkers(text) {
if (text.startsWith(_PREFIX_A) && text.endsWith(_SUFFIX)) {
return text.replace(_PREFIX_A, '').replace(_SUFFIX, '');
}
if (text.startsWith(_PREFIX_OBSOLETE) && text.endsWith(_SUFFIX)) {
return text.replace(_PREFIX_OBSOLETE, '').replace(_SUFFIX, '');
}
return text;
}
3 years ago
encodeEncryption(encryptedText, hint) {
if (!encryptedText.contains(_PREFIX_OBSOLETE) && !encryptedText.contains(_PREFIX_A) && !encryptedText.contains(_SUFFIX)) {
if (hint) {
return _PREFIX_A.concat(_HINT, hint, _HINT, encryptedText, _SUFFIX);
}
return _PREFIX_A.concat(encryptedText, _SUFFIX);
3 years ago
}
3 years ago
return encryptedText;
3 years ago
}
}
class SelectionAnalysis {
3 years ago
}
class Encryptable {
}
class Decryptable {
3 years ago
}
2 years ago
var EncryptedFileContentViewStateEnum;
(function (EncryptedFileContentViewStateEnum) {
EncryptedFileContentViewStateEnum[EncryptedFileContentViewStateEnum["init"] = 0] = "init";
EncryptedFileContentViewStateEnum[EncryptedFileContentViewStateEnum["decryptNote"] = 1] = "decryptNote";
EncryptedFileContentViewStateEnum[EncryptedFileContentViewStateEnum["editNote"] = 2] = "editNote";
EncryptedFileContentViewStateEnum[EncryptedFileContentViewStateEnum["changePassword"] = 3] = "changePassword";
EncryptedFileContentViewStateEnum[EncryptedFileContentViewStateEnum["newNote"] = 4] = "newNote";
})(EncryptedFileContentViewStateEnum || (EncryptedFileContentViewStateEnum = {}));
const VIEW_TYPE_ENCRYPTED_FILE_CONTENT = "meld-encrypted-file-content-view";
class EncryptedFileContentView extends obsidian.TextFileView {
constructor(leaf) {
super(leaf);
// State
this.currentView = EncryptedFileContentViewStateEnum.init;
this.encryptionPassword = '';
this.hint = '';
this.currentEditorText = '';
//console.debug('EncryptedFileContentView.constructor', {leaf});
this.elActionIconLockNote = this.addAction('lock', 'Lock', () => this.actionLockFile());
this.elActionChangePassword = this.addAction('key', 'Change Password', () => this.actionChangePassword());
this.contentEl.style.display = 'flex';
this.contentEl.style.flexDirection = 'column';
this.contentEl.style.alignItems = 'center';
}
actionLockFile() {
this.encryptionPassword = '';
this.refreshView(EncryptedFileContentViewStateEnum.decryptNote);
}
actionChangePassword() {
this.refreshView(EncryptedFileContentViewStateEnum.changePassword);
}
onPaneMenu(menu, source) {
//console.debug( {menu, source, 'view': this.currentView});
if (source == 'tab-header' && this.currentView == EncryptedFileContentViewStateEnum.editNote) {
menu.addItem(m => {
m
.setSection('action')
.setIcon('lock')
.setTitle('Lock')
.onClick(() => this.actionLockFile());
});
menu.addItem(m => {
m
.setSection('action')
.setIcon('key')
.setTitle('Change Password')
.onClick(() => this.actionChangePassword());
});
}
super.onPaneMenu(menu, source);
}
createTitle(title) {
return this.contentEl.createDiv({
text: `🔐 ${title} 🔐`,
attr: {
style: 'margin-bottom:2em;'
}
});
}
validatePassword(pw) {
if (pw.length == 0) {
return 'Password is too short';
}
return '';
}
validateConfirm(pw, cpw) {
const passwordMatch = pw === cpw;
return passwordMatch ? '' : 'Password doesn\'t match';
}
createNewNoteView() {
//console.debug('createDecryptNoteView', { "hint": this.hint} );
const container = this.createInputContainer();
new obsidian.Setting(container)
.setDesc('Please provide a password and hint to start editing this note.');
const submit = (password, confirm, hint) => __awaiter(this, void 0, void 0, function* () {
var validPw = this.validatePassword(password);
var validCpw = this.validateConfirm(password, confirm);
sPassword.setDesc(validPw);
sConfirm.setDesc(validCpw);
if (validPw.length === 0 && validCpw.length === 0) {
//set password and hint and open note
this.encryptionPassword = password;
this.hint = hint;
this.currentEditorText = this.file.basename;
yield this.encodeAndSave();
SessionPasswordService.put({ password: password, hint: hint }, this.file);
this.refreshView(EncryptedFileContentViewStateEnum.editNote);
}
});
const bestGuessPassAndHint = SessionPasswordService.getBestGuess(this.file);
let password = bestGuessPassAndHint.password;
let confirm = '';
let hint = bestGuessPassAndHint.hint;
const sPassword = UiHelper.buildPasswordSetting({
container,
name: 'Password:',
autoFocus: true,
initialValue: password,
onChangeCallback: (value) => {
password = value;
sPassword.setDesc(this.validatePassword(password));
sConfirm.setDesc(this.validateConfirm(password, confirm));
},
onEnterCallback: (value) => {
password = value;
if (password.length > 0) {
sConfirm.controlEl.querySelector('input').focus();
}
}
});
const sConfirm = UiHelper.buildPasswordSetting({
container,
name: 'Confirm:',
autoFocus: false,
onChangeCallback: (value) => {
confirm = value;
sPassword.setDesc(this.validatePassword(password));
sConfirm.setDesc(this.validateConfirm(password, confirm));
},
onEnterCallback: (value) => {
confirm = value;
const passwordMatch = password === confirm;
if (passwordMatch) {
sHint.controlEl.querySelector('input').focus();
}
}
});
const sHint = new obsidian.Setting(container)
.setName("Hint:")
.addText((tc) => {
tc.setValue(hint);
tc.onChange(v => {
hint = v;
});
});
sHint.controlEl.on('keydown', '*', (ev) => {
if (ev.key === 'Enter') {
ev.preventDefault();
submit(password, confirm, hint);
}
});
new obsidian.Setting(container)
.addButton(bc => {
bc
.setCta()
.setIcon('go-to-file')
.setTooltip('Edit')
.onClick((ev) => submit(password, confirm, hint));
});
return container;
}
createDecryptNoteView() {
const container = this.createInputContainer();
new obsidian.Setting(container)
.setDesc('Please provide a password to unlock this note.');
const bestGuessPassAndHint = SessionPasswordService.getBestGuess(this.file);
this.encryptionPassword = bestGuessPassAndHint.password;
UiHelper.buildPasswordSetting({
container,
name: 'Password:',
initialValue: this.encryptionPassword,
autoFocus: true,
placeholder: this.formatHint(this.hint),
onChangeCallback: (value) => {
this.encryptionPassword = value;
},
onEnterCallback: () => __awaiter(this, void 0, void 0, function* () { return yield this.handleDecryptButtonClick(); })
});
new obsidian.Setting(container)
.addButton(bc => {
bc
.setCta()
.setIcon('checkmark')
.setTooltip('Unlock & Edit')
.onClick((evt) => this.handleDecryptButtonClick());
});
return container;
}
encodeAndSave() {
return __awaiter(this, void 0, void 0, function* () {
try {
//console.debug('encodeAndSave');
var fileData = yield FileDataHelper.encode(this.encryptionPassword, this.hint, this.currentEditorText);
this.data = JsonFileEncoding.encode(fileData);
this.requestSave();
}
catch (e) {
console.error(e);
new obsidian.Notice(e, 10000);
}
});
}
createEditorView() {
//const container = this.contentEl.createEl('textarea');
const container = this.contentEl.createDiv();
container.contentEditable = 'true';
container.style.flexGrow = '1';
container.style.alignSelf = 'stretch';
//container.value = this.currentEditorText
container.innerText = this.currentEditorText;
container.focus();
container.on('input', '*', (ev, target) => __awaiter(this, void 0, void 0, function* () {
//console.debug('editor input',{ev, target});
//this.currentEditorText = container.value;
this.currentEditorText = container.innerText;
yield this.encodeAndSave();
}));
return container;
}
createInputContainer() {
return this.contentEl.createDiv({
'attr': {
'style': 'width:100%; max-width:400px;'
}
});
}
createChangePasswordView() {
const container = this.createInputContainer();
let newPassword = '';
let confirm = '';
let newHint = '';
const submit = (newPassword, confirm, newHint) => __awaiter(this, void 0, void 0, function* () {
var validPw = this.validatePassword(newPassword);
var validCpw = this.validateConfirm(newPassword, confirm);
sNewPassword.setDesc(validPw);
sConfirm.setDesc(validCpw);
if (validPw.length === 0 && validCpw.length === 0) {
//set password and hint and open note
//console.debug('createChangePasswordView submit');
this.encryptionPassword = newPassword;
this.hint = newHint;
this.encodeAndSave();
this.refreshView(EncryptedFileContentViewStateEnum.editNote);
new obsidian.Notice('Password and Hint were changed');
}
});
const sNewPassword = UiHelper.buildPasswordSetting({
container,
name: 'New Password:',
autoFocus: true,
onChangeCallback: (value) => {
newPassword = value;
sNewPassword.setDesc(this.validatePassword(newPassword));
sConfirm.setDesc(this.validateConfirm(newPassword, confirm));
},
onEnterCallback: (value) => {
newPassword = value;
if (newPassword.length > 0) {
sConfirm.controlEl.querySelector('input').focus();
}
}
});
const sConfirm = UiHelper.buildPasswordSetting({
container,
name: 'Confirm:',
onChangeCallback: (value) => {
confirm = value;
sNewPassword.setDesc(this.validatePassword(newPassword));
sConfirm.setDesc(this.validateConfirm(newPassword, confirm));
},
onEnterCallback: (value) => {
confirm = value;
// validate confirm
const passwordMatch = newPassword === confirm;
if (passwordMatch) {
sHint.controlEl.querySelector('input').focus();
}
}
});
const sHint = new obsidian.Setting(container)
.setName("New Hint:")
.addText((tc) => {
tc.onChange(v => {
newHint = v;
});
});
sHint.controlEl.on('keydown', '*', (ev) => {
if (ev.key === 'Enter') {
ev.preventDefault();
submit(newPassword, confirm, newHint);
}
});
new obsidian.Setting(container)
.addButton(bc => {
bc
.removeCta()
.setIcon('cross')
//.setButtonText('Cancel')
.setTooltip('Cancel')
.onClick(() => {
this.refreshView(EncryptedFileContentViewStateEnum.editNote);
});
}).addButton(bc => {
bc
.setCta()
.setIcon('checkmark')
.setTooltip('Change Password')
//.setButtonText('Change Password')
.setWarning()
.onClick((ev) => {
submit(newPassword, confirm, newHint);
});
});
return container;
}
formatHint(hint) {
if (hint.length > 0) {
return `Hint: ${hint}`;
}
else {
return '';
}
}
refreshView(newView) {
//console.debug('refreshView',{'currentView':this.currentView, newView});
this.elActionIconLockNote.hide();
this.elActionChangePassword.hide();
// clear view
this.contentEl.empty();
this.currentView = newView;
switch (this.currentView) {
case EncryptedFileContentViewStateEnum.newNote:
this.createTitle('This note will be encrypted');
this.createNewNoteView();
break;
case EncryptedFileContentViewStateEnum.decryptNote:
this.createTitle('This note is encrypted');
this.createDecryptNoteView();
break;
case EncryptedFileContentViewStateEnum.editNote:
this.elActionIconLockNote.show();
this.elActionChangePassword.show();
this.createTitle('This note is encrypted');
this.createEditorView();
break;
case EncryptedFileContentViewStateEnum.changePassword:
this.createTitle('Change encrypted note password');
this.createChangePasswordView();
break;
}
}
handleDecryptButtonClick() {
return __awaiter(this, void 0, void 0, function* () {
var fileData = JsonFileEncoding.decode(this.data);
//console.debug('Decrypt button', fileData);
const decryptedText = yield FileDataHelper.decrypt(fileData, this.encryptionPassword);
if (decryptedText === null) {
new obsidian.Notice('Decryption failed');
}
else {
SessionPasswordService.put({ password: this.encryptionPassword, hint: this.hint }, this.file);
this.currentEditorText = decryptedText;
this.refreshView(EncryptedFileContentViewStateEnum.editNote);
}
});
}
// important
canAcceptExtension(extension) {
//console.debug('EncryptedFileContentView.canAcceptExtension', {extension});
return extension == 'encrypted';
}
// important
getViewType() {
return VIEW_TYPE_ENCRYPTED_FILE_CONTENT;
}
// the data to show on the view
setViewData(data, clear) {
// console.debug('EncryptedFileContentView.setViewData', {
// data,
// clear,
// 'pass':this.encryptionPassword,
// //'mode':this.getMode(),
// //'mode-data':this.currentMode.get(),
// //'preview-mode-data':this.previewMode.get()
// });
if (clear) {
var newView;
if (data === '') {
// blank new file
newView = EncryptedFileContentViewStateEnum.newNote;
}
else {
newView = EncryptedFileContentViewStateEnum.decryptNote;
}
// new file, we don't know what the password is yet
this.encryptionPassword = '';
// json decode file data to get the Hint
var fileData = JsonFileEncoding.decode(this.data);
this.hint = fileData.hint;
this.refreshView(newView);
}
else {
this.leaf.detach();
new obsidian.Notice('Multiple views of the same encrypted note isn\'t supported');
}
}
// the data to save to disk
getViewData() {
// console.debug('EncryptedFileContentView.getViewData', {
// 'this':this,
// 'data':this.data,
// });
return this.data;
}
clear() {
//console.debug('EncryptedFileContentView.clear');
}
}
class FileData {
constructor(hint, encodedData) {
this.version = "1.0";
this.hint = hint;
this.encodedData = encodedData;
}
}
class FileDataHelper {
static encode(pass, hint, text) {
return __awaiter(this, void 0, void 0, function* () {
const crypto = new CryptoHelper();
const encryptedData = yield crypto.encryptToBase64(text, pass);
return new FileData(hint, encryptedData);
});
}
static decrypt(data, pass) {
return __awaiter(this, void 0, void 0, function* () {
if (data.encodedData == '') {
return '';
}
const crypto = new CryptoHelper();
return yield crypto.decryptFromBase64(data.encodedData, pass);
});
}
}
class JsonFileEncoding {
static encode(data) {
return JSON.stringify(data, null, 2);
}
static decode(encodedText) {
//console.debug('JsonFileEncoding.decode',{encodedText});
if (encodedText === '') {
return new FileData("", "");
}
return JSON.parse(encodedText);
}
}
class FeatureWholeNoteEncrypt {
onload(plugin, settings) {
return __awaiter(this, void 0, void 0, function* () {
this.plugin = plugin;
this.settings = settings.featureWholeNoteEncrypt;
this.updateUiForSettings();
this.plugin.registerView(VIEW_TYPE_ENCRYPTED_FILE_CONTENT, (leaf) => new EncryptedFileContentView(leaf));
this.plugin.registerExtensions(['encrypted'], VIEW_TYPE_ENCRYPTED_FILE_CONTENT);
this.plugin.addCommand({
id: 'meld-encrypt-create-new-note',
name: 'Create new encrypted note',
icon: 'lock',
checkCallback: (checking) => this.processCreateNewEncryptedNoteCommand(checking)
});
});
}
onunload() {
this.plugin.app.workspace.detachLeavesOfType(VIEW_TYPE_ENCRYPTED_FILE_CONTENT);
}
processCreateNewEncryptedNoteCommand(checking) {
//console.debug('processCreateNewEncryptedNoteCommand', {checking});
try {
if (checking || UiHelper.isSettingsModalOpen()) {
return true;
}
let newFilename = obsidian.moment().format('[Untitled] YYYYMMDD hhmmss[.encrypted]');
let newFileFolder;
const activeFile = this.plugin.app.workspace.getActiveFile();
if (activeFile != null) {
newFileFolder = this.plugin.app.fileManager.getNewFileParent(activeFile.path);
}
else {
newFileFolder = this.plugin.app.fileManager.getNewFileParent('');
}
const newFilepath = obsidian.normalizePath(newFileFolder.path + "/" + newFilename);
//console.debug('processCreateNewEncryptedNoteCommand', {newFilepath});
this.plugin.app.vault.create(newFilepath, '').then(f => {
const leaf = this.plugin.app.workspace.getLeaf(false);
leaf.openFile(f);
}).catch(reason => {
new obsidian.Notice(reason, 10000);
});
return true;
}
catch (e) {
console.error(e);
new obsidian.Notice(e, 10000);
}
}
buildSettingsUi(containerEl, saveSettingCallback) {
new obsidian.Setting(containerEl)
.setHeading()
.setName('Whole Note Encryption Settings');
new obsidian.Setting(containerEl)
.setName('Add ribbon icon to create note')
.setDesc('Adds a ribbon icon to the left bar to create an encrypted note.')
.addToggle(toggle => {
toggle
.setValue(this.settings.addRibbonIconToCreateNote)
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
this.settings.addRibbonIconToCreateNote = value;
yield saveSettingCallback();
this.updateUiForSettings();
}));
});
}
updateUiForSettings() {
if (this.settings.addRibbonIconToCreateNote) {
// turn on ribbon icon
if (this.ribbonIconCreateNewNote == null) {
this.ribbonIconCreateNewNote = this.plugin.addRibbonIcon('lock', 'Create new encrypted note', (ev) => {
this.processCreateNewEncryptedNoteCommand(false);
});
}
}
else {
// turn off ribbon icon
if (this.ribbonIconCreateNewNote != null) {
this.ribbonIconCreateNewNote.remove();
this.ribbonIconCreateNewNote = null;
}
}
}
}
class MeldEncrypt extends obsidian.Plugin {
constructor() {
super(...arguments);
this.enabledFeatures = [];
}
onload() {
return __awaiter(this, void 0, void 0, function* () {
// Settings
yield this.loadSettings();
this.enabledFeatures.push(new FeatureWholeNoteEncrypt(), new FeatureInplaceEncrypt());
this.addSettingTab(new MeldEncryptSettingsTab(this.app, this, this.settings, this.enabledFeatures));
// End Settings
// load features
this.enabledFeatures.forEach((f) => __awaiter(this, void 0, void 0, function* () {
yield f.onload(this, this.settings);
}));
});
}
onunload() {
this.enabledFeatures.forEach((f) => __awaiter(this, void 0, void 0, function* () {
f.onunload();
}));
}
loadSettings() {
return __awaiter(this, void 0, void 0, function* () {
const DEFAULT_SETTINGS = {
confirmPassword: true,
rememberPassword: true,
rememberPasswordTimeout: 30,
featureWholeNoteEncrypt: {
addRibbonIconToCreateNote: true,
},
featureInplaceEncrypt: {
expandToWholeLines: false,
showCopyButton: true,
}
};
this.settings = Object.assign(DEFAULT_SETTINGS, yield this.loadData());
// apply settings
SessionPasswordService.setActive(this.settings.rememberPassword);
SessionPasswordService.setAutoExpire(this.settings.rememberPasswordTimeout == 0
? null
: this.settings.rememberPasswordTimeout);
});
}
saveSettings() {
return __awaiter(this, void 0, void 0, function* () {
yield this.saveData(this.settings);
});
}
}
3 years ago
module.exports = MeldEncrypt;
2 years ago
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsiLi4vbm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsIi4uL3NyYy9zZXJ2aWNlcy9NZW1vcnlDYWNoZS50cyIsIi4uL3NyYy9zZXJ2aWNlcy9TZXNzaW9uUGFzc3dvcmRTZXJ2aWNlLnRzIiwiLi4vc3JjL3NldHRpbmdzL01lbGRFbmNyeXB0U2V0dGluZ3NUYWIudHMiLCIuLi9zcmMvc2VydmljZXMvQ3J5cHRvSGVscGVyLnRzIiwiLi4vc3JjL3NlcnZpY2VzL0NyeXB0b0hlbHBlck9ic29sZXRlLnRzIiwiLi4vc3JjL2ZlYXR1cmVzL2ZlYXR1cmUtaW5wbGFjZS1lbmNyeXB0L0RlY3J5cHRNb2RhbC50cyIsIi4uL3NyYy9zZXJ2aWNlcy9VaUhlbHBlci50cyIsIi4uL3NyYy9mZWF0dXJlcy9mZWF0dXJlLWlucGxhY2UtZW5jcnlwdC9QYXNzd29yZE1vZGFsLnRzIiwiLi4vc3JjL2ZlYXR1cmVzL2ZlYXR1cmUtaW5wbGFjZS1lbmNyeXB0L0ZlYXR1cmVJbnBsYWNlRW5jcnlwdC50cyIsIi4uL3NyYy9mZWF0dXJlcy9mZWF0dXJlLXdob2xlLW5vdGUtZW5jcnlwdC9FbmNyeXB0ZWRGaWxlQ29udGVudFZpZXcudHMiLCIuLi9zcmMvZmVhdHVyZXMvZmVhdHVyZS13aG9sZS1ub3RlLWVuY3J5cHQvRmVhdHVyZVdob2xlTm90ZUVuY3J5cHQudHMiLCIuLi9zcmMvbWFpbi50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqXHJcbkNvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLlxyXG5cclxuUGVybWlzc2lvbiB0byB1c2UsIGNvcHksIG1vZGlmeSwgYW5kL29yIGRpc3RyaWJ1dGUgdGhpcyBzb2Z0d2FyZSBmb3IgYW55XHJcbnB1cnBvc2Ugd2l0aCBvciB3aXRob3V0IGZlZSBpcyBoZXJlYnkgZ3JhbnRlZC5cclxuXHJcblRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIgQU5EIFRIRSBBVVRIT1IgRElTQ0xBSU1TIEFMTCBXQVJSQU5USUVTIFdJVEhcclxuUkVHQVJEIFRPIFRISVMgU09GVFdBUkUgSU5DTFVESU5HIEFMTCBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZXHJcbkFORCBGSVRORVNTLiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQVVUSE9SIEJFIExJQUJMRSBGT1IgQU5ZIFNQRUNJQUwsIERJUkVDVCxcclxuSU5ESVJFQ1QsIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyBPUiBBTlkgREFNQUdFUyBXSEFUU09FVkVSIFJFU1VMVElORyBGUk9NXHJcbkxPU1MgT0YgVVNFLCBEQVRBIE9SIFBST0ZJVFMsIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBORUdMSUdFTkNFIE9SXHJcbk9USEVSIFRPUlRJT1VTIEFDVElPTiwgQVJJU0lORyBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBVU0UgT1JcclxuUEVSRk9STUFOQ0UgT0YgVEhJUyBTT0ZUV0FSRS5cclxuKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKiogKi9cclxuLyogZ2xvYmFsIFJlZmxlY3QsIFByb21pc2UgKi9cclxuXHJcbnZhciBleHRlbmRTdGF0aWNzID0gZnVuY3Rpb24oZCwgYikge1xyXG4gICAgZXh0ZW5kU3RhdGljcyA9IE9iamVjdC5zZXRQcm90b3R5cGVPZiB8fFxyXG4gICAgICAgICh7IF9fcHJvdG9fXzogW10gfSBpbnN0YW5jZW9mIEFycmF5ICYmIGZ1bmN0aW9uIChkLCBiKSB7IGQuX19wcm90b19fID0gYjsgfSkgfHxcclxuICAgICAgICBmdW5jdGlvbiAoZCwgYikgeyBmb3IgKHZhciBwIGluIGIpIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoYiwgcCkpIGRbcF0gPSBiW3BdOyB9O1xyXG4gICAgcmV0dXJuIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbn07XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19leHRlbmRzKGQsIGIpIHtcclxuICAgIGlmICh0eXBlb2YgYiAhPT0gXCJmdW5jdGlvblwiICYmIGIgIT09IG51bGwpXHJcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIkNsYXNzIGV4dGVuZHMgdmFsdWUgXCIgKyBTdHJpbmcoYikgKyBcIiBpcyBub3QgYSBjb25zdHJ1Y3RvciBvciBudWxsXCIpO1xyXG4gICAgZXh0ZW5kU3RhdGljcyhkLCBiKTtcclxuICAgIGZ1bmN0aW9uIF9fKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gZDsgfVxyXG4gICAgZC5wcm90b3R5cGUgPSBiID09PSBudWxsID8gT2JqZWN0LmNyZWF0ZShiKSA6IChfXy5wcm90b3R5cGUgPSBiLnByb3RvdHlwZSwgbmV3IF9fKCkpO1xyXG59XHJcblxyXG5leHBvcnQgdmFyIF9fYXNzaWduID0gZnVuY3Rpb24oKSB7XHJcbiAgICBfX2Fzc2lnbiA9IE9iamVjdC5hc3NpZ24gfHwgZnVuY3Rpb24gX19hc3NpZ24odCkge1xyXG4gICAgICAgIGZvciAodmFyIHMsIGkgPSAxLCBuID0gYXJndW1lbnRzLmxlbmd0aDsgaSA8IG47IGkrKykge1xyXG4gICAgICAgICAgICBzID0gYXJndW1lbnRzW2ldO1xyXG4gICAgICAgICAgICBmb3IgKHZhciBwIGluIHMpIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwocywgcCkpIHRbcF0gPSBzW3BdO1xyXG4gICAgICAgIH1cclxuICAgICAgICByZXR1cm4gdDtcclxuICAgIH1cclxuICAgIHJldHVybiBfX2Fzc2lnbi5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gX19yZXN0KHMsIGUpIHtcclxuICAgIHZhciB0ID0ge307XHJcbiAgICBmb3IgKHZhciBwIGluIHMpIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwocywgcCkgJiYgZS5pbmRleE9mKHApIDwgMClcclxuICAgICAgICB0W3BdID0gc1twXTtcclxuICAgIGlmIChzICE9IG51bGwgJiYgdHlwZW9mIE9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMgPT09IFwiZnVuY3Rpb25cIilcclxuICAgICAgICBmb3IgKHZhciBpID0gMCwgcCA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eVN5bWJvbHMocyk7IGkgPCBwLmxlbmd0aDsgaSsrKSB7XHJcbiAgICAgICAgICAgIGlmIChlLmluZGV4T2YocFtpXSkgPCAwICYmIE9