mirror of
https://github.com/shadiestgoat/obsidian-simple-password.git
synced 2026-07-22 05:46:29 +00:00
Slight refactor, add support for exact & glob matchers
This commit is contained in:
parent
a8e1e3d09f
commit
dc5bdd8609
9 changed files with 235 additions and 57 deletions
|
|
@ -40,5 +40,8 @@
|
|||
"tslib": "2.4.0",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "^8.29.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimatch": "^10.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ settings:
|
|||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
minimatch:
|
||||
specifier: ^10.1.1
|
||||
version: 10.1.1
|
||||
devDependencies:
|
||||
'@eslint/js':
|
||||
specifier: ^9.24.0
|
||||
|
|
@ -287,6 +291,14 @@ packages:
|
|||
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@isaacs/balanced-match@4.0.1':
|
||||
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
|
@ -435,36 +447,43 @@ packages:
|
|||
resolution: {integrity: sha512-FX2FV7vpLE/+Z0NZX9/1pwWud5Wocm/2PgpUXbT5aSV3QEB10kBPJAzssOQylvdj8mOHoKl5pVkXpbCwww/T2g==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-arm64-musl@1.5.0':
|
||||
resolution: {integrity: sha512-+gF97xst1BZb28T3nwwzEtq2ewCoMDGKsenYsZuvpmNrW0019G1iUAunZN+FG55L21y+uP7zsGX06OXDQ/viKw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-linux-ppc64-gnu@1.5.0':
|
||||
resolution: {integrity: sha512-5bEmVcQw9js8JYM2LkUBw5SeELSIxX+qKf9bFrfFINKAp4noZ//hUxLpbF7u/3gTBN1GsER6xOzIZlw/VTdXtA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-riscv64-gnu@1.5.0':
|
||||
resolution: {integrity: sha512-GGk/8TPUsf1Q99F+lzMdjE6sGL26uJCwQ9TlvBs8zR3cLQNw/MIumPN7zrs3GFGySjnwXc8gA6J3HKbejywmqA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-s390x-gnu@1.5.0':
|
||||
resolution: {integrity: sha512-5uRkFYYVNAeVaA4W/CwugjFN3iDOHCPqsBLCCOoJiMfFMMz4evBRsg+498OFa9w6VcTn2bD5aI+RRayaIgk2Sw==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-x64-gnu@1.5.0':
|
||||
resolution: {integrity: sha512-j905CZH3nehYy6NimNqC2B14pxn4Ltd7guKMyPTzKehbFXTUgihQS/ZfHQTdojkMzbSwBOSgq1dOrY+IpgxDsA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-x64-musl@1.5.0':
|
||||
resolution: {integrity: sha512-dmLevQTuzQRwu5A+mvj54R5aye5I4PVKiWqGxg8tTaYP2k2oTs/3Mo8mgnhPk28VoYCi0fdFYpgzCd4AJndQvQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-wasm32-wasi@1.5.0':
|
||||
resolution: {integrity: sha512-LtJMhwu7avhoi+kKfAZOKN773RtzLBVVF90YJbB0wyMpUj9yQPeA+mteVUI9P70OG/opH47FeV5AWeaNWWgqJg==}
|
||||
|
|
@ -853,8 +872,8 @@ packages:
|
|||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
minimatch@10.0.1:
|
||||
resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
|
||||
minimatch@10.1.1:
|
||||
resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
minimatch@3.1.2:
|
||||
|
|
@ -1328,6 +1347,12 @@ snapshots:
|
|||
|
||||
'@humanwhocodes/retry@0.4.2': {}
|
||||
|
||||
'@isaacs/balanced-match@4.0.1': {}
|
||||
|
||||
'@isaacs/brace-expansion@5.0.0':
|
||||
dependencies:
|
||||
'@isaacs/balanced-match': 4.0.1
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
|
|
@ -1685,7 +1710,7 @@ snapshots:
|
|||
eslint-import-resolver-node: 0.3.9
|
||||
get-tsconfig: 4.10.0
|
||||
is-glob: 4.0.3
|
||||
minimatch: 10.0.1
|
||||
minimatch: 10.1.1
|
||||
semver: 7.7.1
|
||||
stable-hash: 0.0.5
|
||||
tslib: 2.8.1
|
||||
|
|
@ -1925,9 +1950,9 @@ snapshots:
|
|||
braces: 3.0.3
|
||||
picomatch: 2.3.1
|
||||
|
||||
minimatch@10.0.1:
|
||||
minimatch@10.1.1:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
'@isaacs/brace-expansion': 5.0.0
|
||||
|
||||
minimatch@3.1.2:
|
||||
dependencies:
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { App } from 'obsidian'
|
||||
import { PathSuggestions } from 'src/settings'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
let inputEl: HTMLInputElement
|
||||
|
||||
onMount(() => {
|
||||
new PathSuggestions(app, inputEl).onSelect((v) => (path = v))
|
||||
})
|
||||
|
||||
interface Props {
|
||||
app: App
|
||||
path: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
let { app, path = $bindable(''), disabled }: Props = $props()
|
||||
</script>
|
||||
|
||||
<input type="text" bind:this={inputEl} placeholder="Path" bind:value={path} {disabled} />
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
<script lang="ts">
|
||||
import type { App } from 'obsidian'
|
||||
import Path from './Path.svelte'
|
||||
import Path from './path/SinglePath.svelte'
|
||||
import type { Writable, Readable } from 'svelte/store'
|
||||
import { ProtectedPathType, type ProtectedPath } from 'src/settings'
|
||||
|
||||
interface Props {
|
||||
paths: Writable<string[]>
|
||||
paths: Writable<ProtectedPath[]>
|
||||
app: App
|
||||
disabled: Readable<boolean>
|
||||
}
|
||||
|
|
@ -29,18 +30,33 @@
|
|||
<hr />
|
||||
|
||||
<h3>Protected paths</h3>
|
||||
<div class="setting-item-description">
|
||||
These are the actual files, folders or patterns you want to protect. <br />
|
||||
For protecting entire folders, make sure there is a '/' at the end. <br />
|
||||
<a
|
||||
aria-label="Glob Matching Guide Link"
|
||||
href="https://www.malikbrowne.com/blog/a-beginners-guide-glob-patterns/"
|
||||
rel="noopener nofollow"
|
||||
class="external-link"
|
||||
target="_blank">Rules for globs can be learn here</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="sp-paths">
|
||||
{#each $paths as _, i (i)}
|
||||
<div class="sp-path-container">
|
||||
<Path {app} bind:path={$paths[i]} disabled={$disabled} />
|
||||
<Path
|
||||
{app}
|
||||
bind:content={$paths[i].content}
|
||||
bind:type={$paths[i].type}
|
||||
disabled={$disabled}
|
||||
/>
|
||||
<button
|
||||
disabled={$disabled}
|
||||
class="mod-err"
|
||||
aria-label="Remove path"
|
||||
{...evWrapper(() => {
|
||||
$paths.splice(i, 1)
|
||||
|
||||
$paths = $paths
|
||||
})}
|
||||
>
|
||||
|
|
@ -53,7 +69,9 @@
|
|||
disabled={$disabled}
|
||||
class="mod-cta"
|
||||
aria-label="Add new path"
|
||||
{...evWrapper(() => ($paths = [...$paths, '/']))}
|
||||
{...evWrapper(
|
||||
() => ($paths = [...$paths, { type: ProtectedPathType.PREFIX, content: '/' }])
|
||||
)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
|
|
@ -61,6 +79,7 @@
|
|||
|
||||
<style>
|
||||
.sp-paths {
|
||||
margin-top: var(--size-4-2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-4-2);
|
||||
|
|
|
|||
87
src/components/path/PathWithSuggestion.svelte
Normal file
87
src/components/path/PathWithSuggestion.svelte
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<script lang="ts">
|
||||
import { AbstractInputSuggest, App } from 'obsidian'
|
||||
import { ProtectedPathType } from 'src/settings'
|
||||
import { normalizePath } from 'src/utils'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
interface Props {
|
||||
app: App
|
||||
type: ProtectedPathType
|
||||
content: string
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
let { content = $bindable(), type, app, disabled }: Props = $props()
|
||||
|
||||
class PathSuggester extends AbstractInputSuggest<string> {
|
||||
filesOnly: boolean
|
||||
|
||||
protected getSuggestions(query: string): string[] {
|
||||
const p = normalizePath(query)
|
||||
let paths = []
|
||||
if (this.filesOnly) {
|
||||
paths = this.app.vault.getFiles().map((f) => {
|
||||
return f.path
|
||||
})
|
||||
} else {
|
||||
paths = this.app.vault.getAllFolders(true).map((f) => {
|
||||
return f.path
|
||||
})
|
||||
}
|
||||
|
||||
return paths.filter((v) => {
|
||||
const sug = '/' + v
|
||||
if (sug.startsWith(p)) return true
|
||||
if (p.endsWith('/')) return sug == p.slice(0, -1)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
renderSuggestion(value: string, el: HTMLElement): void {
|
||||
el.setText(value)
|
||||
}
|
||||
}
|
||||
|
||||
let inputEl: HTMLInputElement
|
||||
let activeElement = $state<HTMLElement>()
|
||||
|
||||
onMount(() => {
|
||||
if (type == ProtectedPathType.GLOB) {
|
||||
return
|
||||
}
|
||||
|
||||
const suggester = new PathSuggester(app, inputEl).onSelect((v) => {
|
||||
content = "/" + v
|
||||
if (type == ProtectedPathType.PREFIX) {
|
||||
content += '/'
|
||||
}
|
||||
suggester.close()
|
||||
})
|
||||
suggester.filesOnly = type == ProtectedPathType.EXACT
|
||||
})
|
||||
|
||||
// $effect(() => {
|
||||
// if (disabled || activeElement != inputEl || type == ProtectedPathType.GLOB) {
|
||||
// console.log("Closing...")
|
||||
// suggester?.close()
|
||||
// return
|
||||
// }
|
||||
|
||||
// if (!suggester) {
|
||||
// suggester = new PathSuggester(app, inputEl).onSelect((v) => {
|
||||
// content = "/" + v
|
||||
// if (type == ProtectedPathType.PREFIX) {
|
||||
// content += '/'
|
||||
// }
|
||||
// suggester.close()
|
||||
// })
|
||||
// suggester.filesOnly = type == ProtectedPathType.EXACT
|
||||
// }
|
||||
|
||||
// suggester.open()
|
||||
// })
|
||||
</script>
|
||||
|
||||
<input type="text" bind:this={inputEl} placeholder="Matcher" bind:value={content} {disabled} />
|
||||
|
||||
<svelte:document bind:activeElement={activeElement} />
|
||||
29
src/components/path/SinglePath.svelte
Normal file
29
src/components/path/SinglePath.svelte
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import type { App } from 'obsidian'
|
||||
import { ProtectedPathType } from 'src/settings'
|
||||
import PathWithSuggestion from './PathWithSuggestion.svelte'
|
||||
|
||||
interface Props {
|
||||
app: App
|
||||
content: string,
|
||||
type: ProtectedPathType,
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
app,
|
||||
content = $bindable<string>(),
|
||||
type = $bindable<ProtectedPathType>(),
|
||||
disabled
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
<select class="dropdown" bind:value={type} {disabled}>
|
||||
<option value={ProtectedPathType.PREFIX}>Prefix Match (Folders)</option>
|
||||
<option value={ProtectedPathType.EXACT}>Exact File</option>
|
||||
<option value={ProtectedPathType.GLOB}>Glob Matching</option>
|
||||
</select>
|
||||
|
||||
{#key type}
|
||||
<PathWithSuggestion {app} bind:content {disabled} {type} />
|
||||
{/key}
|
||||
31
src/main.ts
31
src/main.ts
|
|
@ -2,9 +2,10 @@ import type { WorkspaceLeaf } from 'obsidian'
|
|||
import { type TFile } from 'obsidian'
|
||||
import { FileView, Notice, Plugin, setIcon } from 'obsidian'
|
||||
import type { Settings } from './settings'
|
||||
import { DEFAULT_SETTINGS, PrivacyMode, SettingsTab } from './settings'
|
||||
import { DEFAULT_SETTINGS, PrivacyMode, ProtectedPathType, SettingsTab } from './settings'
|
||||
import { RequirePasswordModal } from './modals'
|
||||
import { modifySearchLeaf } from './search'
|
||||
import { normalizePath, pathComparer } from './utils'
|
||||
|
||||
const STR_RIBBON_LOCKED = 'Vault is locked'
|
||||
const STR_RIBBON_UNLOCKED = 'Vault is unlocked'
|
||||
|
|
@ -124,10 +125,9 @@ export default class SimplePassword extends Plugin {
|
|||
}
|
||||
|
||||
isPathLocked(testPath: string): boolean {
|
||||
for (const _p of this.settings.protectedPaths) {
|
||||
const p = decodeURIComponent(new URL(_p, 'file://').pathname)
|
||||
|
||||
if (('/' + testPath).startsWith(p)) {
|
||||
testPath = normalizePath(testPath)
|
||||
for (const pp of this.settings.protectedPaths) {
|
||||
if (pathComparer[pp.type](pp.content, testPath)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -263,6 +263,27 @@ export default class SimplePassword extends Plugin {
|
|||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData())
|
||||
await this.migrateSettings()
|
||||
}
|
||||
|
||||
async migrateSettings() {
|
||||
let changed = false
|
||||
if (this.settings.protectedPaths.length != 0) {
|
||||
this.settings.protectedPaths = this.settings.protectedPaths.map(v => {
|
||||
if (typeof v == "string") {
|
||||
changed = true
|
||||
return {
|
||||
type: ProtectedPathType.PREFIX,
|
||||
content: v
|
||||
}
|
||||
}
|
||||
return v
|
||||
})
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
await this.saveSettings()
|
||||
}
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
|
|||
|
|
@ -12,19 +12,35 @@ export enum PrivacyMode {
|
|||
NONE = ''
|
||||
}
|
||||
|
||||
export enum ProtectedPathType {
|
||||
PREFIX = 'prefix',
|
||||
EXACT = 'exact',
|
||||
GLOB = 'glob',
|
||||
}
|
||||
|
||||
export interface ProtectedPath {
|
||||
type: ProtectedPathType
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
autoLockMinutes: number
|
||||
privacyMode: PrivacyMode
|
||||
protectedPaths: string[]
|
||||
hint: string
|
||||
password: string
|
||||
blockGraph: boolean
|
||||
protectedPaths: ProtectedPath[]
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: Settings = {
|
||||
autoLockMinutes: 15,
|
||||
privacyMode: PrivacyMode.BLUR,
|
||||
protectedPaths: ['/'],
|
||||
protectedPaths: [
|
||||
{
|
||||
type: ProtectedPathType.PREFIX,
|
||||
content: '/'
|
||||
}
|
||||
],
|
||||
hint: '',
|
||||
password: '',
|
||||
blockGraph: true
|
||||
|
|
@ -214,22 +230,3 @@ export class SettingsTab extends PluginSettingTab {
|
|||
super.hide()
|
||||
}
|
||||
}
|
||||
|
||||
export class PathSuggestions extends AbstractInputSuggest<string> {
|
||||
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)
|
||||
}
|
||||
|
||||
renderSuggestion(value: string, el: HTMLElement): void {
|
||||
el.setText(value)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
src/utils.ts
Normal file
18
src/utils.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { minimatch } from "minimatch"
|
||||
import { ProtectedPathType } from "./settings"
|
||||
|
||||
export function normalizePath(str: string) {
|
||||
return decodeURIComponent(new URL(str, 'file://').pathname)
|
||||
}
|
||||
|
||||
export const pathComparer: Record<ProtectedPathType, (content: string, realPath: string) => boolean> = {
|
||||
[ProtectedPathType.PREFIX]: (content, realPath) => {
|
||||
return realPath.startsWith(content)
|
||||
},
|
||||
[ProtectedPathType.EXACT]: (content, realPath) => {
|
||||
return normalizePath(content) == normalizePath(realPath)
|
||||
},
|
||||
[ProtectedPathType.GLOB]: (content, realPath) => {
|
||||
return minimatch(realPath, content)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue