Skip to content
Merged
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
9 changes: 9 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Braindrop ChangeLog

## Unreleased

**Released: WiP**

- Fixed unnecessary error notifications when asking raindrop.io for
suggestions for an URL that isn't really an URL.
- Suggested URL when making a new raindrop is now taken from the first line
of the clipboard, ignoring any other text.

## v0.1.0

**Released: 2025-01-03**
Expand Down
47 changes: 39 additions & 8 deletions src/braindrop/app/screens/raindrop_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
##############################################################################
# Python imports.
from typing import Iterator
from urllib.parse import urlparse

##############################################################################
# httpx imports.
from httpx import URL

##############################################################################
# Pyperclip imports.
Expand All @@ -27,6 +30,22 @@
from ..suggestions import SuggestTags


##############################################################################
def looks_urlish(possible_url: str) -> bool:
"""Test if a string looks like a web-oriented URL.

Args:
possible_url: The string that might be a URL.

Returns:
`True` if the string looks like it might be a URL, `False` if not.
"""
return (url := URL(possible_url)).is_absolute_url and url.scheme in (
"http",
"https",
)


##############################################################################
class RaindropInput(ModalScreen[Raindrop | None]):
"""The raindrop editing dialog."""
Expand Down Expand Up @@ -175,10 +194,13 @@ def compose(self) -> ComposeResult:
@work(exclusive=True)
async def _get_tag_suggestions(self) -> None:
"""Load up fresh tag suggestions based on the URL."""
# Don't bother trying to get suggestions if the URL in the URL input
# doesn't look like an URL.
if not looks_urlish(url := self.query_one("#url", Input).value):
return
# Ask raindrop.io for suggestions.
try:
suggestions = await self._api.suggestions_for(
self.query_one("#url", Input).value
)
suggestions = await self._api.suggestions_for(url)
except API.Error as error:
self.app.bell()
self.notify(
Expand All @@ -188,6 +210,8 @@ async def _get_tag_suggestions(self) -> None:
timeout=8,
)
return
# We got suggestions, so show them and set them up for
# auto-completion too.
self.query_one("#tag-suggestions", Label).update(
f"[b]Suggested:[/] {Raindrop.tags_to_string(suggestions.tags)}"
if suggestions.tags
Expand Down Expand Up @@ -217,15 +241,22 @@ def _paste(self, url: str) -> None:
@work(thread=True)
def _suggest_link(self) -> None:
"""Get a link suggestion by peeking in the user's clipboard."""
# Look for something in the external clipboard.
try:
external = from_clipboard()
except PyperclipException:
external = ""
# Looking at the Textual-internal clipboard, then the external
# clipboard...
for candidate in (self.app.clipboard, external):
if urlparse(candidate).scheme in ( # pylint:disable=no-member
"http",
"https",
):
# ...only looking at the first line of what we find...
try:
candidate = candidate.strip().splitlines()[0]
except IndexError:
candidate = ""
# If it looks like it might be a URL...
if looks_urlish(candidate):
# ...paste it into the URL field.
self.app.call_from_thread(self._paste, candidate)
break

Expand Down