zettelkasten

Search IconIcon to open search
Dark ModeDark Mode

From Alfred to Raycast

Date: 2 Aug 2022

#post

This post originally appeared on Blog 2.0


I recently moved from Alfred to Raycast. Overall, I quite like the approach that Raycast takes.

H2 Why the Switch

Raycast just feels more modern than Alfred. It comes with a nice-looking interface out of the box that blends in well with macOS aesthetics. Alfred’s is extensible by all means, but Workflows feels way too overcomplicated for me. With Raycast, however, I can just throw in a single Python script and forget about passing variables between every block. Building a Raycast extension looks reasonably easy too. It’s all familiar technologies — TypeScript and React, plus its easy-to-use API. Overall, Raycast makes a great environment for tinkering.

Not to say that Raycast is perfect. There are frustrating things like… having to hit esc a million times to get out of nested searches.

(The Raycast team also takes some really interesting development approaches)

H2 Updated Obsidian Scripts

It was quite necessary that I upgrade my Obsidian Alfred Workflow for Raycast since my brain already relies on it. Turned out it makes a pretty chill coding assignment. Here’s the code.

(See GitHub repo for newest code)

H3 Natural Language Date for Task Capture

Right, this is a new feature. With the ability to pass on multiple arguments, we can capture a task and give it a due date at the same time like this:

optimage - Capture d’écran 2022-08-05 at 12.24.19@2x.jpg

I resorted to the dateparser python package. It doesn’t behave exactly the same as Obsidian Tasks but it was close enough.

#!/usr/bin/env python3

# NOTE: dateparser package required!!!

# Required parameters: text
# @raycast.schemaVersion 1
# @raycast.title Obsidian Capture Task
# @raycast.mode silent

# Optional parameters:
# @raycast.icon ./assets/obsidian.png
# @raycast.argument1 { "type": "text", "placeholder": "task" }
# @raycast.argument2 { "type": "text", "placeholder": "when", "optional": true }

# Documentation:
# @raycast.author chaosarium
# @raycast.authorURL https://github.com/chaosarium

import sys, os, datetime, dateparser
from lib.insert_to_md_heading import insert_to_heading
query = sys.argv[1]
when = sys.argv[2]

# set vault path
OBSIDIAN_VAULT_PATH = "~/Library/Mobile Documents/iCloud~md~obsidian/Documents/Zettelkasten"
# file to manipulate within vault
FILE_PATH = f"050 Periodic/051 Daily/{datetime.datetime.now().strftime('%Y/%m-%B/%Y-%m-%d')}.md"
# format appended text
heading = '### Inbox'

if len(when):
  if when == 'td' or when == 't':
    when = 'today'
  if when == 'tm':
    when = 'tomorrow'
  if when == 'tw':
    when = 'saturday'
    
  parsed_date = dateparser.parse(when, settings={'PREFER_DATES_FROM': 'future', 'PREFER_DAY_OF_MONTH': 'first'})
  if parsed_date:
    insertion = f'''- [ ] #t #inbox {query} 📅 {parsed_date.strftime('%Y-%m-%d')}
'''
  else:
    insertion = f'''- [ ] #t #inbox {query}
'''
else:
  insertion = f'''- [ ] #t #inbox {query}
'''
  
print(insertion)

abs_path = os.path.expanduser(os.path.join(OBSIDIAN_VAULT_PATH, FILE_PATH))

try: 
  file = open(abs_path, "r")
  original_text = file.read() 
  file.close()
  
  processed_text = insert_to_heading(original_text, heading, insertion)
  file = open(abs_path, "w")
  file.write(processed_text)
  file.close()
  
  if when and not parsed_date: 
    print('failed to parse date; due date ignored')
  else: 
    print('success')
  
  sys.exit(0)
except Exception as e:
  print(e)
  sys.exit(1)

H3 Idea Capture

#!/usr/bin/env python3

# Required parameters: text
# @raycast.schemaVersion 1
# @raycast.title Obsidian Capture Idea
# @raycast.mode silent

# Optional parameters:
# @raycast.packageName obsidian-capture-idea
# @raycast.icon ./assets/obsidian.png
# @raycast.argument1 { "type": "text", "placeholder": "entry" }

# Documentation:
# @raycast.author chaosarium
# @raycast.authorURL https://github.com/chaosarium

import sys, os, datetime
query = sys.argv[1]

# set vault path
OBSIDIAN_VAULT_PATH = "~/Library/Mobile Documents/iCloud~md~obsidian/Documents/Zettelkasten"
# file to manipulate within vault
FILE_PATH = "000 System/080 Inboxes/Ideas Inbox.md"
# format appended text
insertion = f"""

%%captured {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}%%
{query}

---"""

abs_path = os.path.expanduser(os.path.join(OBSIDIAN_VAULT_PATH, FILE_PATH))

try: 
  file = open(abs_path, "a")
  file.write(insertion)
  file.close()
  print('success')
  sys.exit(0)
except Exception as e:
  print(e)
  sys.exit(1)

H3 Replace in Heading

import re, sys, os, unicodedata

def insert_to_heading(original_text, heading, insertion):
  
  find = re.compile(r'(?<=' + heading + r'\n)((\n.*?)+)(?=#+ |$)', flags = re.MULTILINE|re.DOTALL)
  found = re.findall(find, original_text)

  replace = f'\\1{insertion}'

  if re.findall(find, original_text) == []:
    new_content = original_text + heading + '\n\n' + insertion
  else:
    new_content = re.sub(find, replace, original_text)

  # print(unicodedata.normalize("NFC",new_content))
  return unicodedata.normalize("NFC",new_content)

H3 Example: Capture Journal

#!/usr/bin/env python3

# Required parameters: text
# @raycast.schemaVersion 1
# @raycast.title Obsidian Capture Journal
# @raycast.mode silent

# Optional parameters:
# @raycast.icon ./assets/obsidian.png
# @raycast.argument1 { "type": "text", "placeholder": "entry" }

# Documentation:
# @raycast.author chaosarium
# @raycast.authorURL https://github.com/chaosarium

import sys, os, datetime
from lib.insert_to_md_heading import insert_to_heading
query = sys.argv[1]

# set vault path
OBSIDIAN_VAULT_PATH = "~/Library/Mobile Documents/iCloud~md~obsidian/Documents/Zettelkasten"
# file to manipulate within vault
FILE_PATH = f"050 Periodic/051 Daily/{datetime.datetime.now().strftime('%Y/%m-%B/%Y-%m-%d')}.md"
# format appended text
heading = '### Journal'
insertion = f'''> **{datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}**
> {query}

'''

abs_path = os.path.expanduser(os.path.join(OBSIDIAN_VAULT_PATH, FILE_PATH))

try: 
  file = open(abs_path, "r")
  original_text = file.read() 
  file.close()
  
  processed_text = insert_to_heading(original_text, heading, insertion)
  file = open(abs_path, "w")
  file.write(processed_text)
  file.close()
  
  print('success')
  sys.exit(0)
except Exception as e:
  print(e)
  sys.exit(1)

H2 Moving Forward

There are a few things that I find quite frustrated, such as the inability to batch edit snippets. I guess some follow-up projects will be on the way… maybe an extension.