Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions SettingsTemplate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
body:
- type: textarea
attributes:
name: excluded_vault_paths
label: "Paths to be excluded from search."
description: "Exclude paths from being included in the search. One aboslute path per line."
48 changes: 34 additions & 14 deletions plugin/main.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,83 @@
from difflib import SequenceMatcher as SM

from flox import Flox
from pathlib import Path

import obsidian

CHECK_BOX_GLYPH = '\ue003'
MARKED_CHECK_BOX_GLYPH = '\ue005'
CHECK_BOX_GLYPH = "\ue003"
MARKED_CHECK_BOX_GLYPH = "\ue005"


def match(query, match):
return int(SM(lambda x: x == " ", query.lower().replace('\\', ' '), match.lower().replace('\\', ' '), autojunk=False).ratio() * 100)
return int(
SM(
lambda x: x == " ",
query.lower().replace("\\", " "),
match.lower().replace("\\", " "),
autojunk=False,
).ratio()
* 100
)


class Obsidian(Flox):
def __init__(self):
super().__init__()

lines = self.settings.get("excluded_vault_paths").split("\n")
paths = [Path(line.strip()) for line in lines if line.strip()]

self.excluded_notes = set()
for p in paths:
self.excluded_notes.update(p for p in p.rglob("*.md"))

def query(self, query):
try:
vaults = obsidian.get_vaults()
except FileNotFoundError:
self.add_item(
title='Obsidian not found',
subtitle='Please install Obsidian',
title="Obsidian not found",
subtitle="Please install Obsidian",
)
return

for vault in vaults:
for note in vault.notes():
for note in vault.notes(self.excluded_notes):
title_score = match(query, note.title)
subtitle_score = match(query, str(note.vault_path))
score = max(title_score, subtitle_score)
if score > 20 or query == '':
if score > 20 or query == "":
self.add_item(
title=note.title,
subtitle=str(note.vault_path),
icon=self.icon,
method=self.open_note,
parameters=[vault.name, str(note.relative_path)],
score=score,
context=[vault.id, str(note.path), note.checklists()]
context=[vault.id, str(note.path), note.checklists()],
)

def context_menu(self, data):
vault_id = data[0]
note_path = data[1]
for checks in data[2]:
self.add_item(
title=checks['description'],
subtitle=checks['title'],
glyph=MARKED_CHECK_BOX_GLYPH if checks['checked'] else CHECK_BOX_GLYPH,
title=checks["description"],
subtitle=checks["title"],
glyph=MARKED_CHECK_BOX_GLYPH if checks["checked"] else CHECK_BOX_GLYPH,
method=self.toggle_checkbox,
parameters=[vault_id, note_path, checks['raw']],
dont_hide=True
parameters=[vault_id, note_path, checks["raw"]],
dont_hide=True,
)

def toggle_checkbox(self, vault_id, note_path, raw):
note = obsidian.get_note(vault_id, note_path)
note.toggle_checkbox(raw)


def open_note(self, vault_name, note_path):
obsidian.open_note(vault_name, note_path)


if __name__ == "__main__":
Obsidian()
97 changes: 51 additions & 46 deletions plugin/obsidian.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,86 +6,89 @@

logger = logging.getLogger(__name__)

VAULTS_FILE = 'obsidian.json'
VAULTS_PATH = Path(os.getenv('APPDATA'), 'obsidian', VAULTS_FILE)
CHECK_BOX = '- [ ]'
MARKED_CHECK_BOX = '- [x]'
VAULTS_FILE = "obsidian.json"
VAULTS_PATH = Path(os.getenv("APPDATA"), "obsidian", VAULTS_FILE)
CHECK_BOX = "- [ ]"
MARKED_CHECK_BOX = "- [x]"


def get_vaults():
vaults = []
try:
with open(VAULTS_PATH, 'r', encoding='utf-8', errors='replace') as f:
with open(VAULTS_PATH, "r", encoding="utf-8", errors="replace") as f:
data = json.load(f)
except FileNotFoundError:
logger.error(f'{VAULTS_PATH} not found!\nIs obsidian installed?')
logger.error(f"{VAULTS_PATH} not found!\nIs obsidian installed?")
raise
else:
for vault in data['vaults'].keys():
vaults.append(Vault(vault, data['vaults'][vault]))
for vault in data["vaults"].keys():
vaults.append(Vault(vault, data["vaults"][vault]))
return vaults


def get_vault(id):
try:
with open(VAULTS_PATH, 'r', encoding='utf-8', errors='replace') as f:
with open(VAULTS_PATH, "r", encoding="utf-8", errors="replace") as f:
data = json.load(f)
except FileNotFoundError:
logger.error(f'{VAULTS_PATH} not found!\nIs obsidian installed?')
logger.error(f"{VAULTS_PATH} not found!\nIs obsidian installed?")
raise
else:
try:
return Vault(id, data['vaults'][id])
return Vault(id, data["vaults"][id])
except KeyError:
logger.error(f'{id} not found!')
logger.error(f"{id} not found!")
raise


def get_note(vault_id, note_path):
vault = get_vault(vault_id)
return Note(vault, note_path)


def open_note(vault_name, note_path):
URI = f'open?vault={vault_name}&file={note_path}'.replace(' ', '%20').replace('/', '%2F').replace('\\', '%2F')
URI = f'obsidian://{URI}'
URI = (
f"open?vault={vault_name}&file={note_path}".replace(" ", "%20")
.replace("/", "%2F")
.replace("\\", "%2F")
)
URI = f"obsidian://{URI}"
webbrowser.open(URI)

class Vault(object):

def __init__(self, id:str, vault: dict):
class Vault(object):
def __init__(self, id: str, vault: dict):
self._data = vault
self.id = id
self.name = Path(vault['path']).name
self.name = Path(vault["path"]).name
for key, value in vault.items():
setattr(self, key, value)

def notes(self):
def notes(self, excluded: set):
notes = []
for note in Path(self.path).glob('**/*.md'):
notes.append(Note(self, note))
return notes

def note(self, note_path):
for note in self.notes():
if str(note.relative_path) == note_path:
return note
for note in Path(self.path).glob("**/*.md"):
if note not in excluded:
notes.append(Note(self, note))

return notes

class Note(object):

class Note(object):
def __init__(self, vault: Vault, full_path: str):
self.vault = vault
self.path = full_path
self.title = Path(full_path).name.replace('.md', '')
self.relative_path = Path(str(full_path).replace(f'{self.vault.path}', ''))
self.vault_path = f'{self.vault.name}{self.relative_path}'
self.title = Path(full_path).name.replace(".md", "")
self.relative_path = Path(str(full_path).replace(f"{self.vault.path}", ""))
self.vault_path = f"{self.vault.name}{self.relative_path}"

def open_note(self):
open_note(self.vault.name, self.relative_path)

def content(self):
with open(self.path, 'r', encoding='utf-8', errors='replace') as f:
with open(self.path, "r", encoding="utf-8", errors="replace") as f:
return f.read()


def toggle_checkbox(self, raw):
content = self.content()
for line in content.splitlines():
Expand All @@ -96,42 +99,44 @@ def toggle_checkbox(self, raw):
toggled_line = line.replace(CHECK_BOX, MARKED_CHECK_BOX)
break
content = content.replace(line, toggled_line)
with open(self.path, 'w', encoding='utf-8', errors='replace') as f:
with open(self.path, "w", encoding="utf-8", errors="replace") as f:
f.write(content)

def checklists(self):
checklists = []
title = ''
prev_line = ''
title = ""
prev_line = ""
for line in self.content().splitlines():
if CHECK_BOX in line or MARKED_CHECK_BOX in line:
description = line.replace(CHECK_BOX, '').replace(MARKED_CHECK_BOX, '').strip()
description = (
line.replace(CHECK_BOX, "").replace(MARKED_CHECK_BOX, "").strip()
)
if MARKED_CHECK_BOX in line:
checked = True
else:
checked = False
if (CHECK_BOX not in prev_line and MARKED_CHECK_BOX not in prev_line) and prev_line.endswith(':'):
title = prev_line.replace(':', '').strip()
if (
CHECK_BOX not in prev_line and MARKED_CHECK_BOX not in prev_line
) and prev_line.endswith(":"):
title = prev_line.replace(":", "").strip()
checklists.append(
{
'title': title,
'description': description,
'checked': checked,
'raw': line
"title": title,
"description": description,
"checked": checked,
"raw": line,
}
)
else:
title = ''
title = ""
prev_line = line
return checklists




if __name__ == "__main__":
vaults = get_vaults()
for vault in vaults:
for note in vault.notes():
for note in vault.notes(set()):
print(note.title)
open_note(vault.name, str(note.relative_path))
break
break