This commit is contained in:
ShadiestGoat 2025-04-17 00:44:09 +01:00
parent bac7923d58
commit f527d77d1f
9 changed files with 600 additions and 594 deletions

View file

@ -1,5 +1,5 @@
{
"useTabs": false,
"useTabs": true,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "none",

View file

@ -1,56 +1,56 @@
<script lang="ts">
let wrongCount = $state(0)
let wrongCount = $state(0)
interface Props {
correctPassword: string
hint: string
value?: string
interface Props {
correctPassword: string
hint: string
value?: string
placeholder?: string
}
}
let { correctPassword, hint, value = $bindable(''), placeholder = "Password" }: Props = $props()
let { correctPassword, hint, value = $bindable(''), placeholder = 'Password' }: Props = $props()
export function submit(shoulClear: boolean): boolean {
const correct = value == correctPassword
export function submit(shoulClear: boolean): boolean {
const correct = value == correctPassword
if (shoulClear) {
value = ''
}
if (shoulClear) {
value = ''
}
if (correct) {
return true
}
if (correct) {
return true
}
wrongCount++
wrongCount++
return false
}
return false
}
</script>
<div class="sp-password-input">
<!-- svelte-ignore a11y_autofocus -->
<!-- This is only rendered in modals, at which point focus SHOULD jump to the new element -->
<input type="password" {placeholder} autofocus bind:value />
{#if wrongCount > 0}
<p class="sp-wrong">
Wrong Password!
{#if wrongCount > 2 && hint}
Hint: <span class="sp-hint">{hint}</span>
{/if}
</p>
{/if}
<input type="password" {placeholder} autofocus bind:value />
{#if wrongCount > 0}
<p class="sp-wrong">
Wrong Password!
{#if wrongCount > 2 && hint}
Hint: <span class="sp-hint">{hint}</span>
{/if}
</p>
{/if}
</div>
<style>
.sp-hint {
font-style: italic;
}
.sp-hint {
font-style: italic;
}
.sp-password-input {
width: 100%;
}
.sp-password-input {
width: 100%;
}
input {
width: 100%;
}
input {
width: 100%;
}
</style>

View file

@ -1,21 +1,21 @@
<script lang="ts">
import type { App } from 'obsidian'
import { PathSuggestions } from 'src/settings'
import { onMount } from 'svelte'
import type { App } from 'obsidian'
import { PathSuggestions } from 'src/settings'
import { onMount } from 'svelte'
let inputEl: HTMLInputElement
let inputEl: HTMLInputElement
onMount(() => {
new PathSuggestions(app, inputEl).onSelect((v) => (path = v))
})
onMount(() => {
new PathSuggestions(app, inputEl).onSelect((v) => (path = v))
})
interface Props {
app: App
path: string
disabled: boolean
}
interface Props {
app: App
path: string
disabled: boolean
}
let { app, path = $bindable(''), disabled }: Props = $props()
let { app, path = $bindable(''), disabled }: Props = $props()
</script>
<input type="text" bind:this={inputEl} placeholder="Path" bind:value={path} {disabled} />

View file

@ -1,36 +1,36 @@
<svelte:options runes />
<script lang="ts">
import PasswordInput from './PasswordInput.svelte'
import PasswordInput from './PasswordInput.svelte'
interface Props {
correctPassword: string
hint: string
onCorrect?: () => void
}
interface Props {
correctPassword: string
hint: string
onCorrect?: () => void
}
let { correctPassword, hint, onCorrect = () => {} }: Props = $props()
let { correctPassword, hint, onCorrect = () => {} }: Props = $props()
let passInput: PasswordInput
let value = $state('')
let passInput: PasswordInput
let value = $state('')
</script>
<p>This file is protected, please input your password</p>
<form
class="sp-container"
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
class="sp-container"
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
if (passInput.submit(true)) {
onCorrect()
}
}}
if (passInput.submit(true)) {
onCorrect()
}
}}
>
<PasswordInput bind:this={passInput} bind:value {correctPassword} {hint} />
<PasswordInput bind:this={passInput} bind:value {correctPassword} {hint} />
<button type="submit" disabled={value == ''} aria-label="Submit Button" class="mod-cta">
Ok
</button>
<button type="submit" disabled={value == ''} aria-label="Submit Button" class="mod-cta">
Ok
</button>
</form>

View file

@ -1,77 +1,83 @@
<svelte:options runes />
<script lang="ts">
import PasswordInput from './PasswordInput.svelte'
import PasswordInput from './PasswordInput.svelte'
let newPassword = $state('')
let conPassword = $state('')
let oldPassword = $state('')
let newPassword = $state('')
let conPassword = $state('')
let oldPassword = $state('')
interface Props {
onSubmit: (newPassword: string) => void
requireOldPassword: boolean
correctPassword?: string
hint?: string
}
interface Props {
onSubmit: (newPassword: string) => void
requireOldPassword: boolean
correctPassword?: string
hint?: string
}
let { requireOldPassword, hint = '', correctPassword = '', onSubmit }: Props = $props()
let { requireOldPassword, hint = '', correctPassword = '', onSubmit }: Props = $props()
let passInp: PasswordInput | undefined = $state()
let displayMismatch = $derived(
!!newPassword && !!conPassword && (newPassword != conPassword || newPassword.length < 3)
)
let passInp: PasswordInput | undefined = $state()
let displayMismatch = $derived(
!!newPassword && !!conPassword && (newPassword != conPassword || newPassword.length < 3)
)
</script>
<form
class="sp-container sp-container-new-password"
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
class="sp-container sp-container-new-password"
onsubmit={(e) => {
e.preventDefault()
e.stopPropagation()
let happy = true
let happy = true
if (!passInp?.submit(false)) {
happy = false
}
if (!passInp?.submit(false)) {
happy = false
}
if (newPassword != conPassword) {
happy = false
}
if (newPassword != conPassword) {
happy = false
}
if (happy) {
onSubmit(newPassword)
}
}}
if (happy) {
onSubmit(newPassword)
}
}}
>
{#if requireOldPassword}
<PasswordInput {correctPassword} {hint} bind:this={passInp} bind:value={oldPassword} placeholder="Old Password" />
{/if}
{#if requireOldPassword}
<PasswordInput
{correctPassword}
{hint}
bind:this={passInp}
bind:value={oldPassword}
placeholder="Old Password"
/>
{/if}
<input type="password" placeholder="New Password" bind:value={newPassword} />
<input type="password" placeholder="Confirm Password" bind:value={conPassword} />
{#if displayMismatch}
{#if newPassword.length < 3}
<p class="sp-wrong">Password needs to be longer than 3 chracters</p>
{:else}
<p class="sp-wrong">Passwords don't match</p>
{/if}
{/if}
<input type="password" placeholder="New Password" bind:value={newPassword} />
<input type="password" placeholder="Confirm Password" bind:value={conPassword} />
{#if displayMismatch}
{#if newPassword.length < 3}
<p class="sp-wrong">Password needs to be longer than 3 chracters</p>
{:else}
<p class="sp-wrong">Passwords don't match</p>
{/if}
{/if}
<button
type="submit"
disabled={!newPassword ||
!conPassword ||
!(requireOldPassword && oldPassword) ||
displayMismatch}
aria-label="Submit Button"
class="mod-cta"
>
Submit
</button>
<button
type="submit"
disabled={!newPassword ||
!conPassword ||
!(requireOldPassword && oldPassword) ||
displayMismatch}
aria-label="Submit Button"
class="mod-cta"
>
Submit
</button>
</form>
<style>
.sp-container-new-password {
flex-direction: column;
}
.sp-container-new-password {
flex-direction: column;
}
</style>

View file

@ -1,29 +1,29 @@
<script lang="ts">
import type { App } from 'obsidian'
import Path from './Path.svelte'
import type { Writable, Readable } from 'svelte/store'
import type { App } from 'obsidian'
import Path from './Path.svelte'
import type { Writable, Readable } from 'svelte/store'
interface Props {
paths: Writable<string[]>
app: App
disabled: Readable<boolean>
}
interface Props {
paths: Writable<string[]>
app: App
disabled: Readable<boolean>
}
let { paths, app, disabled }: Props = $props()
let { paths, app, disabled }: Props = $props()
function evWrapper(cb: () => void): {
onkeypress: (e: KeyboardEvent) => void
onclick: () => void
} {
return {
onclick: cb,
onkeypress: (e) => {
if (e.key == ' ' || e.key == 'Enter') {
cb()
}
}
}
}
function evWrapper(cb: () => void): {
onkeypress: (e: KeyboardEvent) => void
onclick: () => void
} {
return {
onclick: cb,
onkeypress: (e) => {
if (e.key == ' ' || e.key == 'Enter') {
cb()
}
}
}
}
</script>
<hr />
@ -31,46 +31,46 @@
<h3>Protected Paths</h3>
<div class="sp-paths">
{#each $paths as _, i (i)}
<div class="sp-path-container">
<Path {app} bind:path={$paths[i]} disabled={$disabled} />
<button
disabled={$disabled}
class="mod-err"
aria-label="Remove Path"
{...evWrapper(() => {
$paths.splice(i, 1)
{#each $paths as _, i (i)}
<div class="sp-path-container">
<Path {app} bind:path={$paths[i]} disabled={$disabled} />
<button
disabled={$disabled}
class="mod-err"
aria-label="Remove Path"
{...evWrapper(() => {
$paths.splice(i, 1)
$paths = $paths
})}
>
-
</button>
</div>
{/each}
$paths = $paths
})}
>
-
</button>
</div>
{/each}
<button
disabled={$disabled}
class="mod-cta"
aria-label="Add New Path"
{...evWrapper(() => ($paths = [...$paths, '/']))}
>
+
</button>
<button
disabled={$disabled}
class="mod-cta"
aria-label="Add New Path"
{...evWrapper(() => ($paths = [...$paths, '/']))}
>
+
</button>
</div>
<style>
.sp-paths {
display: flex;
flex-direction: column;
gap: var(--size-4-2);
}
.sp-paths {
display: flex;
flex-direction: column;
gap: var(--size-4-2);
}
.mod-err {
background: var(--background-modifier-error);
}
.mod-err {
background: var(--background-modifier-error);
}
.sp-path-container {
flex-grow: 1;
}
.sp-path-container {
flex-grow: 1;
}
</style>

View file

@ -12,235 +12,235 @@ const ICON_RIBBON_UNLOCKED = 'unlock'
// So we have a problem when closing tabs: the File is set to null
type SafeLeaf = {
leaf: WorkspaceLeaf
file: TFile
leaf: WorkspaceLeaf
file: TFile
}
export default class SimplePassword extends Plugin {
settings: Settings
ribbonIconEl: HTMLElement
autolockTimeoutID: ReturnType<typeof setTimeout>
lastHoveredFile: string
settings: Settings
ribbonIconEl: HTMLElement
autolockTimeoutID: ReturnType<typeof setTimeout>
lastHoveredFile: string
isLocked: boolean
isLocking = false
isLocked: boolean
isLocking = false
async onload() {
await this.loadSettings()
this.isLocked = true
async onload() {
await this.loadSettings()
this.isLocked = true
this.ribbonIconEl = this.addRibbonIcon(ICON_RIBBON_LOCKED, STR_RIBBON_LOCKED, () => {
this.onIconClick()
})
this.ribbonIconEl = this.addRibbonIcon(ICON_RIBBON_LOCKED, STR_RIBBON_LOCKED, () => {
this.onIconClick()
})
this.addCommand({
id: 'sp-lock-vault',
name: 'Lock Vault',
callback: () => {
this.lock()
}
})
this.addCommand({
id: 'sp-lock-vault',
name: 'Lock Vault',
callback: () => {
this.lock()
}
})
this.registerObsidianProtocolHandler('lock', () => {
this.lock()
})
this.registerObsidianProtocolHandler('lock', () => {
this.lock()
})
this.registerEvent(
this.app.workspace.on('file-open', (f: TFile | null) => {
if (!f || !this.isLocked) return
this.registerEvent(
this.app.workspace.on('file-open', (f: TFile | null) => {
if (!f || !this.isLocked) return
if (this.isPathLocked(f.path)) {
this.lock()
}
})
)
if (this.isPathLocked(f.path)) {
this.lock()
}
})
)
this.app.workspace.onLayoutReady(() => {
;['create', 'modify', 'delete', 'rename'].forEach((evName) => {
this.registerEvent(
// @ts-expect-error Overloads + Paramters don't play well
this.app.vault.on(evName, () => {
this.resetAutolock()
})
)
})
})
this.app.workspace.onLayoutReady(() => {
;['create', 'modify', 'delete', 'rename'].forEach((evName) => {
this.registerEvent(
// @ts-expect-error Overloads + Paramters don't play well
this.app.vault.on(evName, () => {
this.resetAutolock()
})
)
})
})
this.register(() => this.removeAutolock())
this.registerEvent(
this.register(() => this.removeAutolock())
this.registerEvent(
this.app.workspace.on(
// @ts-expect-error Undocumented api
'hover-link',
({ linktext }: { linktext: string }) => {
'hover-link',
({ linktext }: { linktext: string }) => {
// Name aside, linktext is always the correct link
// Though not always an abs path so
this.lastHoveredFile = linktext
}
)
)
)
)
this.watchDom()
this.watchDom()
this.addSettingTab(new SettingsTab(this.app, this))
this.addSettingTab(new SettingsTab(this.app, this))
// If there are any unprotected things, lock!
this.lock()
}
// If there are any unprotected things, lock!
this.lock()
}
removeAutolock(): void {
if (this.autolockTimeoutID) {
clearTimeout(this.autolockTimeoutID)
}
}
removeAutolock(): void {
if (this.autolockTimeoutID) {
clearTimeout(this.autolockTimeoutID)
}
}
resetAutolock(): void {
this.removeAutolock()
resetAutolock(): void {
this.removeAutolock()
if (this.settings.autoLockMinutes) {
this.autolockTimeoutID = setTimeout(
() => {
this.lock()
},
60 * 1000 * this.settings.autoLockMinutes
)
}
}
if (this.settings.autoLockMinutes) {
this.autolockTimeoutID = setTimeout(
() => {
this.lock()
},
60 * 1000 * this.settings.autoLockMinutes
)
}
}
isPathLocked(testPath: string): boolean {
for (const _p of this.settings.protectedPaths) {
const p = new URL(_p, 'file://').pathname
isPathLocked(testPath: string): boolean {
for (const _p of this.settings.protectedPaths) {
const p = new URL(_p, 'file://').pathname
if (('/' + testPath).startsWith(p)) {
return true
}
}
if (('/' + testPath).startsWith(p)) {
return true
}
}
return false
}
return false
}
watchDom() {
const domObs = new MutationObserver(() => {
if (!this.isLocked || this.isLocking) {
return
}
watchDom() {
const domObs = new MutationObserver(() => {
if (!this.isLocked || this.isLocking) {
return
}
// I decied to add a dom observer clause here, since a user could embed the thing on the fly
const embedDoc = document.querySelector('.internal-embed[src]')
if (embedDoc) {
const path = embedDoc.attributes.getNamedItem('src')?.value
if (path) {
const realPath = this.app.metadataCache.getFirstLinkpathDest(path, '')?.path
if (realPath) {
const curFile = this.app.workspace.getActiveFile()
const curLeaf = this.app.workspace.getMostRecentLeaf()
// I decied to add a dom observer clause here, since a user could embed the thing on the fly
const embedDoc = document.querySelector('.internal-embed[src]')
if (embedDoc) {
const path = embedDoc.attributes.getNamedItem('src')?.value
if (path) {
const realPath = this.app.metadataCache.getFirstLinkpathDest(path, '')?.path
if (realPath) {
const curFile = this.app.workspace.getActiveFile()
const curLeaf = this.app.workspace.getMostRecentLeaf()
if (curLeaf && curFile) {
this.lock(true, [{ leaf: curLeaf, file: curFile }])
return
}
}
}
}
if (curLeaf && curFile) {
this.lock(true, [{ leaf: curLeaf, file: curFile }])
return
}
}
}
}
const popover = document.querySelector('.popover.hover-popover')
if (!popover) return
const popover = document.querySelector('.popover.hover-popover')
if (!popover) return
console.log("hover", this.lastHoveredFile)
console.log('hover', this.lastHoveredFile)
if (this.isPathLocked(this.lastHoveredFile)) {
popover.remove()
this.lock(true)
return true
}
})
popover.remove()
this.lock(true)
return true
}
})
domObs.observe(document.body, {
childList: true,
subtree: true
})
domObs.observe(document.body, {
childList: true,
subtree: true
})
this.register(() => domObs.disconnect())
}
this.register(() => domObs.disconnect())
}
/**
* Performs all the needed actions to initiate a lock
*/
async lock(alwaysRequestPassword = false, extraLeaves: SafeLeaf[] = []) {
this.isLocked = true
this.isLocking = true
this.setRibbonIcon(true)
this.removeAutolock()
/**
* Performs all the needed actions to initiate a lock
*/
async lock(alwaysRequestPassword = false, extraLeaves: SafeLeaf[] = []) {
this.isLocked = true
this.isLocking = true
this.setRibbonIcon(true)
this.removeAutolock()
const protectedLeaves: SafeLeaf[] = [...extraLeaves]
const protectedLeaves: SafeLeaf[] = [...extraLeaves]
// We always need to know if theres active files, no matter the privacy mode
this.app.workspace.iterateRootLeaves((l) => {
if (l?.view instanceof FileView && l.view?.file) {
if (this.isPathLocked(l.view.file.path)) {
protectedLeaves.push({ leaf: l, file: l.view.file })
}
}
})
// We always need to know if theres active files, no matter the privacy mode
this.app.workspace.iterateRootLeaves((l) => {
if (l?.view instanceof FileView && l.view?.file) {
if (this.isPathLocked(l.view.file.path)) {
protectedLeaves.push({ leaf: l, file: l.view.file })
}
}
})
if (!alwaysRequestPassword && protectedLeaves.length == 0) {
this.isLocking = false
return
}
if (!alwaysRequestPassword && protectedLeaves.length == 0) {
this.isLocking = false
return
}
if (this.settings.privacyMode == PrivacyMode.CLOSE) {
protectedLeaves.forEach((v) => {
v.leaf.detach()
})
}
if (this.settings.privacyMode == PrivacyMode.CLOSE) {
protectedLeaves.forEach((v) => {
v.leaf.detach()
})
}
this.requestUserToUnlock(protectedLeaves)
}
this.requestUserToUnlock(protectedLeaves)
}
requestUserToUnlock(protectedViews: SafeLeaf[] = []) {
console.log('Password requested')
requestUserToUnlock(protectedViews: SafeLeaf[] = []) {
console.log('Password requested')
new RequirePasswordModal(this.app, this.settings, (success) => {
this.isLocked = !success
this.isLocking = false
new RequirePasswordModal(this.app, this.settings, (success) => {
this.isLocked = !success
this.isLocking = false
if (success) {
this.setRibbonIcon(false)
this.resetAutolock()
new Notice('Vault unlocked')
if (success) {
this.setRibbonIcon(false)
this.resetAutolock()
new Notice('Vault unlocked')
if (this.settings.privacyMode == PrivacyMode.CLOSE) {
protectedViews.forEach((v) => {
this.app.workspace.getLeaf('tab').openFile(v.file)
})
}
} else {
new Notice('Password prompt cancelled')
if (this.settings.privacyMode == PrivacyMode.CLOSE) {
protectedViews.forEach((v) => {
this.app.workspace.getLeaf('tab').openFile(v.file)
})
}
} else {
new Notice('Password prompt cancelled')
if (this.settings.privacyMode == PrivacyMode.BLUR) {
protectedViews.forEach((v) => v.leaf.detach())
}
}
}).open()
}
if (this.settings.privacyMode == PrivacyMode.BLUR) {
protectedViews.forEach((v) => v.leaf.detach())
}
}
}).open()
}
onIconClick() {
if (this.isLocked) {
this.requestUserToUnlock()
} else {
this.lock()
}
}
onIconClick() {
if (this.isLocked) {
this.requestUserToUnlock()
} else {
this.lock()
}
}
setRibbonIcon(locked: boolean) {
this.ribbonIconEl.ariaLabel = locked ? STR_RIBBON_LOCKED : STR_RIBBON_UNLOCKED
setIcon(this.ribbonIconEl, locked ? ICON_RIBBON_LOCKED : ICON_RIBBON_UNLOCKED)
}
setRibbonIcon(locked: boolean) {
this.ribbonIconEl.ariaLabel = locked ? STR_RIBBON_LOCKED : STR_RIBBON_UNLOCKED
setIcon(this.ribbonIconEl, locked ? ICON_RIBBON_LOCKED : ICON_RIBBON_UNLOCKED)
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
}
async saveSettings() {
await this.saveData(this.settings)
}
async saveSettings() {
await this.saveData(this.settings)
}
}

View file

@ -7,89 +7,89 @@ import SettingsPassword from './components/SettingsPassword.svelte'
type PasswordCallback = (success: boolean) => void
export class RequirePasswordModal extends Modal {
svelteContent: Record<string, never>
settings: Settings
succeeded: boolean
callback: PasswordCallback
svelteContent: Record<string, never>
settings: Settings
succeeded: boolean
callback: PasswordCallback
constructor(app: App, settings: Settings, cb: PasswordCallback) {
super(app)
constructor(app: App, settings: Settings, cb: PasswordCallback) {
super(app)
this.settings = settings
this.callback = cb
this.succeeded = false
}
this.settings = settings
this.callback = cb
this.succeeded = false
}
onOpen(): void {
this.setTitle('Password Required!')
this.shouldRestoreSelection = true
this.containerEl.addClass('sp-modal-container')
this.containerEl.toggleClass('sp-modal-blur', this.settings.privacyMode == PrivacyMode.BLUR)
onOpen(): void {
this.setTitle('Password Required!')
this.shouldRestoreSelection = true
this.containerEl.addClass('sp-modal-container')
this.containerEl.toggleClass('sp-modal-blur', this.settings.privacyMode == PrivacyMode.BLUR)
this.svelteContent = mount(RequirePassword, {
target: this.contentEl,
props: {
correctPassword: this.settings.password,
hint: this.settings.hint,
onCorrect: () => {
this.succeeded = true
this.close()
}
}
})
}
this.svelteContent = mount(RequirePassword, {
target: this.contentEl,
props: {
correctPassword: this.settings.password,
hint: this.settings.hint,
onCorrect: () => {
this.succeeded = true
this.close()
}
}
})
}
close(): void {
// This needs to be done BEFORE the actual modal closes
// This is to ensure that everything is clsoed in case of a non-success
this.callback(this.succeeded)
close(): void {
// This needs to be done BEFORE the actual modal closes
// This is to ensure that everything is clsoed in case of a non-success
this.callback(this.succeeded)
super.close()
}
super.close()
}
async onClose() {
unmount(this.svelteContent, { outro: true })
}
async onClose() {
unmount(this.svelteContent, { outro: true })
}
}
type NewPasswordCallback = (password: string) => void
export class NewPasswordModal extends Modal {
svelteContent: Record<string, never>
settings: Settings
callback: NewPasswordCallback
password: string
svelteContent: Record<string, never>
settings: Settings
callback: NewPasswordCallback
password: string
constructor(app: App, settings: Settings, cb: NewPasswordCallback) {
super(app)
constructor(app: App, settings: Settings, cb: NewPasswordCallback) {
super(app)
this.settings = settings
this.callback = cb
this.password = ''
}
this.settings = settings
this.callback = cb
this.password = ''
}
onOpen(): void {
this.setTitle('Password Required!')
this.shouldRestoreSelection = true
this.containerEl.addClass('sp-modal-container')
this.containerEl.toggleClass('sp-modal-blur', this.settings.privacyMode == PrivacyMode.BLUR)
onOpen(): void {
this.setTitle('Password Required!')
this.shouldRestoreSelection = true
this.containerEl.addClass('sp-modal-container')
this.containerEl.toggleClass('sp-modal-blur', this.settings.privacyMode == PrivacyMode.BLUR)
this.svelteContent = mount(SettingsPassword, {
target: this.contentEl,
props: {
requireOldPassword: this.settings.password != '',
correctPassword: this.settings.password,
hint: this.settings.hint,
onSubmit: (pass) => {
this.password = pass
this.close()
}
}
})
}
this.svelteContent = mount(SettingsPassword, {
target: this.contentEl,
props: {
requireOldPassword: this.settings.password != '',
correctPassword: this.settings.password,
hint: this.settings.hint,
onSubmit: (pass) => {
this.password = pass
this.close()
}
}
})
}
async onClose() {
this.callback(this.password)
unmount(this.svelteContent, { outro: true })
}
async onClose() {
this.callback(this.password)
unmount(this.svelteContent, { outro: true })
}
}

View file

@ -7,198 +7,198 @@ import { mount, unmount } from 'svelte'
import { writable, get } from 'svelte/store'
export enum PrivacyMode {
BLUR = 'blur',
CLOSE = 'close',
NONE = ''
BLUR = 'blur',
CLOSE = 'close',
NONE = ''
}
export interface Settings {
autoLockMinutes: number
privacyMode: PrivacyMode
protectedPaths: string[]
hint: string
password: string
autoLockMinutes: number
privacyMode: PrivacyMode
protectedPaths: string[]
hint: string
password: string
}
export const DEFAULT_SETTINGS: Settings = {
autoLockMinutes: 15,
privacyMode: PrivacyMode.BLUR,
protectedPaths: ['/'],
hint: '',
password: ''
autoLockMinutes: 15,
privacyMode: PrivacyMode.BLUR,
protectedPaths: ['/'],
hint: '',
password: ''
}
export class SettingsTab extends PluginSettingTab {
plugin: SimplePassword
lockBtn: ButtonComponent
lockSet: Setting
lockableSettings: Setting[]
svelteComp: SettingsPaths
plugin: SimplePassword
lockBtn: ButtonComponent
lockSet: Setting
lockableSettings: Setting[]
svelteComp: SettingsPaths
locked = writable(true)
locked = writable(true)
constructor(app: App, plugin: SimplePassword) {
super(app, plugin)
constructor(app: App, plugin: SimplePassword) {
super(app, plugin)
this.plugin = plugin
this.plugin = plugin
this.locked.subscribe((v) => {
if (!this.lockableSettings || !this.lockBtn || !this.lockSet) return
this.locked.subscribe((v) => {
if (!this.lockableSettings || !this.lockBtn || !this.lockSet) return
if (v) {
this.lockBtn.setIcon('unlock')
this.lockSet.setName('Unlock settings below')
} else {
this.lockBtn.setIcon('lock')
this.lockSet.setName('Lock settings below')
}
if (v) {
this.lockBtn.setIcon('unlock')
this.lockSet.setName('Unlock settings below')
} else {
this.lockBtn.setIcon('lock')
this.lockSet.setName('Lock settings below')
}
this.lockableSettings.forEach((s) => {
s.setDisabled(v)
})
})
}
this.lockableSettings.forEach((s) => {
s.setDisabled(v)
})
})
}
display() {
const { containerEl } = this
display() {
const { containerEl } = this
containerEl.empty()
containerEl.addClass('sp-settings')
containerEl.empty()
containerEl.addClass('sp-settings')
new Setting(containerEl)
.setName(this.plugin.settings.password ? 'Change Password' : 'Set Password')
.addButton((btn) => {
btn.setCta()
.setButtonText('Change')
.onClick(() => this.onPasswordChangeButton())
})
new Setting(containerEl)
.setName(this.plugin.settings.password ? 'Change Password' : 'Set Password')
.addButton((btn) => {
btn.setCta()
.setButtonText('Change')
.onClick(() => this.onPasswordChangeButton())
})
this.lockSet = new Setting(containerEl)
.setName('Unlock settings below')
.setDesc(
'The following settings are sensetive, and therefore require that you enter your password'
)
.addButton((btn) => {
btn.setIcon('unlock').onClick(() => this.onLockBtn())
this.lockBtn = btn
})
.setDisabled(this.plugin.settings.password == '')
this.lockSet = new Setting(containerEl)
.setName('Unlock settings below')
.setDesc(
'The following settings are sensetive, and therefore require that you enter your password'
)
.addButton((btn) => {
btn.setIcon('unlock').onClick(() => this.onLockBtn())
this.lockBtn = btn
})
.setDisabled(this.plugin.settings.password == '')
containerEl.createEl('hr')
containerEl.createEl('hr')
this.lockableSettings = [
new Setting(containerEl)
.setName('Auto Lock Minutes')
.setDesc('After x minutes of inactivity, lock the vault. Set to 0 for no auto lock')
.addText((text) => {
text.setPlaceholder('# of Minutes').setValue(
this.plugin.settings.autoLockMinutes.toString()
)
text.inputEl.type = 'number'
text.onChange((v) => {
const f = parseFloat(v)
if (!isNaN(f)) {
this.plugin.settings.autoLockMinutes = f
this.plugin.resetAutolock()
this.plugin.saveSettings()
}
})
})
.setDisabled(true),
new Setting(containerEl)
.setName('Privacy Mode')
.setDesc('What should we do on lock?')
.addDropdown((drop) => {
drop.addOptions({
[PrivacyMode.BLUR]: 'Blur Background',
[PrivacyMode.CLOSE]: 'Close Affected Tabs',
[PrivacyMode.NONE]: 'Nothing'
})
.onChange((v) => {
this.plugin.settings.privacyMode = v as PrivacyMode
this.plugin.saveSettings()
})
.setValue(this.plugin.settings.privacyMode)
})
.setDisabled(true),
new Setting(containerEl)
.setName('Hint')
.setDesc('Add a hint for your password (shown in case of > 3 failed attempts)')
.addText((text) => {
text.setPlaceholder('Hint')
.onChange((v) => {
this.plugin.settings.hint = v
this.plugin.saveSettings()
})
.setValue(this.plugin.settings.hint)
})
.setDisabled(true)
]
this.lockableSettings = [
new Setting(containerEl)
.setName('Auto Lock Minutes')
.setDesc('After x minutes of inactivity, lock the vault. Set to 0 for no auto lock')
.addText((text) => {
text.setPlaceholder('# of Minutes').setValue(
this.plugin.settings.autoLockMinutes.toString()
)
text.inputEl.type = 'number'
text.onChange((v) => {
const f = parseFloat(v)
if (!isNaN(f)) {
this.plugin.settings.autoLockMinutes = f
this.plugin.resetAutolock()
this.plugin.saveSettings()
}
})
})
.setDisabled(true),
new Setting(containerEl)
.setName('Privacy Mode')
.setDesc('What should we do on lock?')
.addDropdown((drop) => {
drop.addOptions({
[PrivacyMode.BLUR]: 'Blur Background',
[PrivacyMode.CLOSE]: 'Close Affected Tabs',
[PrivacyMode.NONE]: 'Nothing'
})
.onChange((v) => {
this.plugin.settings.privacyMode = v as PrivacyMode
this.plugin.saveSettings()
})
.setValue(this.plugin.settings.privacyMode)
})
.setDisabled(true),
new Setting(containerEl)
.setName('Hint')
.setDesc('Add a hint for your password (shown in case of > 3 failed attempts)')
.addText((text) => {
text.setPlaceholder('Hint')
.onChange((v) => {
this.plugin.settings.hint = v
this.plugin.saveSettings()
})
.setValue(this.plugin.settings.hint)
})
.setDisabled(true)
]
const paths = writable(this.plugin.settings.protectedPaths)
paths.subscribe((v) => {
this.plugin.settings.protectedPaths = v
this.plugin.saveSettings()
})
const paths = writable(this.plugin.settings.protectedPaths)
paths.subscribe((v) => {
this.plugin.settings.protectedPaths = v
this.plugin.saveSettings()
})
this.svelteComp = mount(SettingsPaths, {
target: containerEl,
props: {
app: this.app,
disabled: this.locked,
paths
}
})
}
this.svelteComp = mount(SettingsPaths, {
target: containerEl,
props: {
app: this.app,
disabled: this.locked,
paths
}
})
}
onLockBtn() {
if (get(this.locked)) {
new RequirePasswordModal(this.app, this.plugin.settings, (success) => {
if (success) {
this.locked.set(false)
}
}).open()
} else {
this.locked.set(true)
}
}
onLockBtn() {
if (get(this.locked)) {
new RequirePasswordModal(this.app, this.plugin.settings, (success) => {
if (success) {
this.locked.set(false)
}
}).open()
} else {
this.locked.set(true)
}
}
onPasswordChangeButton() {
new NewPasswordModal(this.app, this.plugin.settings, (p) => {
if (p) {
new Notice('Changed Password')
this.plugin.settings.password = p
this.plugin.saveSettings()
onPasswordChangeButton() {
new NewPasswordModal(this.app, this.plugin.settings, (p) => {
if (p) {
new Notice('Changed Password')
this.plugin.settings.password = p
this.plugin.saveSettings()
this.lockSet.setDisabled(false)
this.locked.set(true)
} else {
new Notice('Password Change Cancelled')
}
}).open()
}
this.lockSet.setDisabled(false)
this.locked.set(true)
} else {
new Notice('Password Change Cancelled')
}
}).open()
}
hide(): void {
unmount(this.svelteComp)
super.hide()
}
hide(): void {
unmount(this.svelteComp)
super.hide()
}
}
export class PathSuggestions extends AbstractInputSuggest<string> {
protected getSuggestions(query: string): string[] {
const p = new URL(query, 'file://').pathname
protected getSuggestions(query: string): string[] {
const p = new URL(query, 'file://').pathname
return this.app.vault
.getAllFolders(true)
.filter((v) => {
if (('/' + v.path).startsWith(p)) {
return true
}
})
.map((v) => v.path)
}
return this.app.vault
.getAllFolders(true)
.filter((v) => {
if (('/' + v.path).startsWith(p)) {
return true
}
})
.map((v) => v.path)
}
renderSuggestion(value: string, el: HTMLElement): void {
el.setText(value)
}
renderSuggestion(value: string, el: HTMLElement): void {
el.setText(value)
}
}