A Swift API for interacting with 🐻 Bear. If it's on Bear's documentation, Honey can do it.
Honey is based on Middleman, a completely type-safe way of handling the x-callback-url scheme. x-callback-url
can be a fickle thing to work with: type-safety can go a long way in helping you work with this. Auto-complete will enable you to discover the API as you work with it. Honey also handles repetitive tasks, like passing Bear's API token where it's required or base64-encoding images and files.
Action | Implemented as |
---|---|
/open-note | open(note:) |
/create | create(note:) |
/add-text | add(text:) |
/add-file | add(file:) |
/tags | allTags() |
/open-tag | open(tag:) |
/rename-tag | rename(tag:) |
/delete-tag | delete(tag:) |
/trash | trash(id:) |
/archive | archive(id:) |
/untagged | allUntagged() searchUntagged() |
/todo | allTodos() searchTodos() |
/today | allToday() searchToday() |
/locked | searchLocked() |
/search | search(for:) |
/grab-url | create(from:) |
/change-theme | change(theme:) |
/change-font | change(font:) |
read(note:)
Returns the content of a note without opening it.open(tab:)
Opens one of Bear's tabs (Untagged/Locked/Trash, etc.) or any of your tags.pin(note:)
Pins a note.
- Implement a command-line interface using
apple/swift-argument-parser
- Refactor. Right now the API is relatively close to Bear's documentation. For example, functions like the various
searchX
actions could be consolidated into one. - Migrate from callbacks to
async
in Swift 6. - Add tests
Let's create a shopping list.
let note = Note(
title: "🛍 Shopping list",
body: """
- 🍎 Apples
- 🥣 Cereal
"""
}
Bear.create(note, options: .pin) { shoppingList in
// We forgot cheese!
Bear.add(
text: "- 🧀 Cheese",
to: .id(shoppingList.id),
mode: .append
)
}
Honey is a Swift Package. Install it by pasting this in your Package.swift
:
let package = Package(
...
dependencies: [
.package(url: "https://github.com/ValentinWalter/honey.git", from: "1.0.0")
],
...
)
Provide your API token if you plan on using any actions that require an API token. You do this directly by setting Bear.token
. Or preferably with an environment variable called BEAR_API_TOKEN
. In Xcode:
Edit scheme… > Run > Arguments > Environment Variables
As Honey is based on Middleman, you will need to configure Middleman as well. To receive callbacks you need to make sure your app has a custom url scheme implemented. Middleman will then automatically read the first entry in the CFBundleURLTypes
array in the main bundle's Info.plist.
For Middleman to be able to parse incoming urls, you need to put one of the following methods in the delegate (UIKit/Cocoa) appropriate for your platform or in the onOpenURL
SwiftUI modifier.
// SwiftUI
// On any view (maybe in your `App`)
.onOpenURL { url in
Middleman.receive(url)
}
// macOS
// In your `NSAppDelegate`:
func application(_ application: NSApplication, open urls: [URL]) {
Middleman.receive(urls)
}
// iOS 13 and up
// In your `UISceneDelegate`:
func scene(_ scene: UIScene, openURLContexts urlContexts: Set<UIOpenURLContext>) {
Middleman.receive(urlContexts)
}
// iOS 12 and below
// In your `UIAppDelegate`:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
Middleman.receive(url)
}
All actions and types Honey implements are namespaced under Bear
. The general workflow is to type Bear.
and choose your desired action from the auto-complete menu. All actions follow the same kind of structure:
// The full signature of most actions
Bear.action(parameter: value, ...) { output in
...
} onError: {
...
}
// Trailing closure syntax of most actions
// Unlabeled trailing closures always mean success
Bear.action(parameter: value, ...) { output in
...
}
// No closures syntax of most actions
// Most action's paramaters are optional, same with callbacks
Bear.action(parameter: value, ...)
Here are some cool examples. You can find a full list of actions in the overview.
Bear.open(note: .id("9ASG...JA2FJ", at: "Header")
Bear.read(note: .title("🛍 Shopping list") { note in
print(note.body)
print(note.id)
}
let note = Bear.Note(
title: "Title",
body: "body",
tags: ["Tag"],
isPinned: false
)
Bear.create(note) { note in
print(note.id)
}
Bear.add(
text: "\(Date())",
to: .selected,
mode: .append
)
let url = URL(string: "https://apod.nasa.gov/apod/image/2105/M8_rim2geminicrop600.jpg")!
let data = Data(contentsOf: url)!
let image = Bear.File(name: "The Southern Cliff in the Lagoon", data: data)
Bear.add(
file: image,
to: .title("🪐 Daily astronomy pictures"),
at: "Sat May 15",
mode: .prepend
)
Bear.search(
for: "important notes",
in: "some tag"
) { notes in
print(notes.map(\.title))
}
Bear.change(theme: .oliveDunk)
Bear.change(font: .avenirNext)
Honey implements various concepts of Bear's API as types in Swift.
A Note
is used to create and read notes.
// Create notes for the create(_:) action
let note = Bear.Note(
title: "A Title",
body: "A paragraph...",
tags: ["A", "few", "tags"],
isPinned: false
)
// Or when you received a note via the output of an action
note.modificationDate
note.creationDate
note.id
You use this enum to find already existing notes.
case title(String)
case id(String)
case selected // requires an API token
// Use like this
Bear.open(note: .title("🛍 Shopping list"))
Bear.read(note: .id("9ASG...JA2FJ"))
Bear.add(text: "...", note: .selected)
You can get extra fancy by using Note.Lookup
as namespace for notes you access often.
extension Bear.Note.Lookup {
static let home: Self = .title("🏡 Home")
// Or better yet, use an ID namespaced in Bear
static let journal: Self = .id(Bear.journalID)
}
extension Bear {
static let journalID = "E3F...2A8"
}
// You can now do this 🥳
Bear.open(note: .home)
Bear.read(note: .journal)
The Options
type is an OptionSet
. This means you can mix and match any of the options.
// Don't show the note.
static let hideNote
// Open a new window.
static let newWindow
// Make the new window float (contingent on `newWindow`).
static let float
// Don't show Bear's window.
static let hideWindow
// Exclude notes in the trash from the action.
static let excludeTrashed
// Pin the note.
static let pin
// Place a cursor inside the note.
static let edit
// Append the current date and time at the end of the note.
static let timestamp
// Use like this
Bear.open(..., options: .edit)
Bear.create(..., options: [.pin, .newWindow])
Tag
behaves the same way a usual String
does. Similar to Note.Lookup
you can use this type to namespace your frequently used tags.
extension Tag {
static let work: Self = "👾 Work"
}
When dealing with files in either create(_:)
or add(file:)
, the File
type comes in handy.
let url = URL(string: "https://apod.nasa.gov/apod/image/2105/M8_rim2geminicrop600.jpg")!
let data = Data(contentsOf: url)!
let file = Bear.File(
name: "The Southern Cliff in the Lagoon",
data: data
)
// Tab lets you open tabs or tags from Bear's sidebar via open(tab:)
case all
case untagged
case ...
case trash
case tag(String)
// Theme lets you change themes via change(theme:)
case redGraphite
case ...
case lighthouse
// Font lets you change fonts via change(font:)
case avenirNext
case ...
case openDyslexic