tempest-rb 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/tempest/post.rb +27 -0
- data/lib/tempest/repl/formatter.rb +9 -4
- data/lib/tempest/repl/screen.rb +32 -4
- data/lib/tempest/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e74ff03fab2ee34902b7836bd77f0a41752135cda5aca37ca97570ac5ced79f3
|
|
4
|
+
data.tar.gz: edb35af23affe941bfdd361a04d86e18ded6ac068afd33df4d97d82aa62f445b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0453a2009c3bed58216a221aad38a71b5cd97529e5b6a0dc210a9c2b72be3172ffc9ec3593679c577540fa937cd3226940eba11e02e952bf8255d445da1226ba
|
|
7
|
+
data.tar.gz: ef8abc6996dfdc86fb54d3ffdcfc7025c37444042a3e8c3fb3008a87bd3aa613d737f873a999541e73b7393c692569ab7ff3d6e226757618115350c83e66f4c1
|
data/README.md
CHANGED
|
@@ -62,7 +62,7 @@ The first sign-in may prompt for the email code Bluesky sends as a second factor
|
|
|
62
62
|
|
|
63
63
|
Anything else you type is sent as a new post.
|
|
64
64
|
|
|
65
|
-
Each post in the timeline is prefixed with a short `$XX` id, and URLs found inside posts get their own `$LX` ids. Use those ids with `$XX <text>` to reply or `:open $LX` to open a link.
|
|
65
|
+
Each post in the timeline is prefixed with a short `$XX` id, and URLs found inside posts get their own `$LX` ids. Use those ids with `$XX <text>` to reply or `:open $LX` to open a link. Like and repost events show the subject post's `$XX` id in trailing brackets (for example `liked @bob's post [$AA]`) whenever the original post is still in the session registry, so you can reply to it directly.
|
|
66
66
|
|
|
67
67
|
### CLI options
|
|
68
68
|
|
data/lib/tempest/post.rb
CHANGED
|
@@ -43,6 +43,9 @@ module Tempest
|
|
|
43
43
|
record["reply"] = { "root" => ref, "parent" => ref }
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
link_facets = detect_link_facets(text)
|
|
47
|
+
record["facets"] = link_facets unless link_facets.empty?
|
|
48
|
+
|
|
46
49
|
client.post(
|
|
47
50
|
"com.atproto.repo.createRecord",
|
|
48
51
|
body: {
|
|
@@ -52,5 +55,29 @@ module Tempest
|
|
|
52
55
|
},
|
|
53
56
|
)
|
|
54
57
|
end
|
|
58
|
+
|
|
59
|
+
# Scans `text` for bare URLs and builds AT Protocol link facets pointing
|
|
60
|
+
# at each match. Without this, the AppView treats URLs as plain text and
|
|
61
|
+
# does not render them as clickable links.
|
|
62
|
+
def self.detect_link_facets(text)
|
|
63
|
+
return [] if text.nil? || text.empty?
|
|
64
|
+
|
|
65
|
+
bytes = text.b
|
|
66
|
+
facets = []
|
|
67
|
+
pos = 0
|
|
68
|
+
while (match = /https?:\/\/\S+/n.match(bytes, pos))
|
|
69
|
+
byte_start = match.begin(0)
|
|
70
|
+
byte_end = match.end(0)
|
|
71
|
+
uri = match[0].dup.force_encoding(Encoding::UTF_8)
|
|
72
|
+
facets << {
|
|
73
|
+
"index" => { "byteStart" => byte_start, "byteEnd" => byte_end },
|
|
74
|
+
"features" => [
|
|
75
|
+
{ "$type" => "app.bsky.richtext.facet#link", "uri" => uri },
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
pos = byte_end
|
|
79
|
+
end
|
|
80
|
+
facets
|
|
81
|
+
end
|
|
55
82
|
end
|
|
56
83
|
end
|
|
@@ -76,10 +76,10 @@ module Tempest
|
|
|
76
76
|
body = "(deleted #{event.collection}/#{event.rkey})"
|
|
77
77
|
var = nil
|
|
78
78
|
elsif event.respond_to?(:like?) && event.like?
|
|
79
|
-
body = "liked #{subject_owner_label(event.subject_uri, resolver)}"
|
|
79
|
+
body = "liked #{subject_owner_label(event.subject_uri, resolver, registry)}"
|
|
80
80
|
var = nil
|
|
81
81
|
elsif event.respond_to?(:repost?) && event.repost?
|
|
82
|
-
body = "reposted #{subject_owner_label(event.subject_uri, resolver)}"
|
|
82
|
+
body = "reposted #{subject_owner_label(event.subject_uri, resolver, registry)}"
|
|
83
83
|
var = nil
|
|
84
84
|
else
|
|
85
85
|
facets = event.respond_to?(:facets) ? event.facets : nil
|
|
@@ -91,13 +91,18 @@ module Tempest
|
|
|
91
91
|
compose(var, format_time(event.created_at), handle, event.did, body)
|
|
92
92
|
end
|
|
93
93
|
|
|
94
|
-
def subject_owner_label(subject_uri, resolver)
|
|
94
|
+
def subject_owner_label(subject_uri, resolver, registry = nil)
|
|
95
95
|
did = subject_did(subject_uri)
|
|
96
96
|
return "a post" unless did
|
|
97
97
|
|
|
98
98
|
handle = resolver&.resolve(did)
|
|
99
99
|
owner = handle ? handle_label(handle) : did_label(did)
|
|
100
|
-
"#{owner}'s post"
|
|
100
|
+
label = "#{owner}'s post"
|
|
101
|
+
var = registry&.var_for_uri(subject_uri)
|
|
102
|
+
return label unless var
|
|
103
|
+
|
|
104
|
+
bracket = Formatter.color ? "#{DIM}[#{var}]#{RESET}" : "[#{var}]"
|
|
105
|
+
"#{label} #{bracket}"
|
|
101
106
|
end
|
|
102
107
|
|
|
103
108
|
def subject_did(subject_uri)
|
data/lib/tempest/repl/screen.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "reline"
|
|
1
2
|
require_relative "../../tempest"
|
|
2
3
|
|
|
3
4
|
module Tempest
|
|
@@ -13,9 +14,10 @@ module Tempest
|
|
|
13
14
|
# CSI row;col H move cursor
|
|
14
15
|
# ESC 7 / ESC 8 save/restore cursor (DECSC/DECRC)
|
|
15
16
|
class Screen
|
|
16
|
-
def initialize(io:, rows: nil)
|
|
17
|
+
def initialize(io:, rows: nil, cols: nil)
|
|
17
18
|
@io = io
|
|
18
19
|
@rows = rows
|
|
20
|
+
@cols = cols
|
|
19
21
|
@enabled = false
|
|
20
22
|
@mutex = Mutex.new
|
|
21
23
|
end
|
|
@@ -26,6 +28,7 @@ module Tempest
|
|
|
26
28
|
return unless rows && rows >= 4
|
|
27
29
|
|
|
28
30
|
@rows = rows
|
|
31
|
+
@cols ||= detect_cols
|
|
29
32
|
@io.print "\e[1;#{rows - 1}r" # scrolling region: rows 1..rows-1
|
|
30
33
|
@io.print "\e[#{rows};1H" # park cursor on the final row (prompt)
|
|
31
34
|
@io.flush if @io.respond_to?(:flush)
|
|
@@ -98,16 +101,41 @@ module Tempest
|
|
|
98
101
|
nil
|
|
99
102
|
end
|
|
100
103
|
|
|
104
|
+
def detect_cols
|
|
105
|
+
return nil unless defined?(IO) && IO.respond_to?(:console)
|
|
106
|
+
console = IO.console
|
|
107
|
+
return nil unless console
|
|
108
|
+
_rows, cols = console.winsize
|
|
109
|
+
cols
|
|
110
|
+
rescue StandardError
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# The terminal would otherwise wrap a line that overflows `@cols` past
|
|
115
|
+
# the bottom of the scrolling region and into the prompt row. Split the
|
|
116
|
+
# line into width-bounded chunks so each one fits and scrolls the region
|
|
117
|
+
# cleanly.
|
|
101
118
|
def insert_above_prompt(line)
|
|
119
|
+
chunks = wrap_to_cols(line)
|
|
102
120
|
bottom_of_region = @rows - 1
|
|
103
121
|
@io.print "\e7" # save cursor
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
chunks.each do |chunk|
|
|
123
|
+
@io.print "\e[#{bottom_of_region};1H" # move to last row of scrolling region
|
|
124
|
+
@io.print "\r\e[2K" # clear that row first
|
|
125
|
+
@io.print "#{chunk}\n" # write chunk; \n scrolls region up by 1
|
|
126
|
+
end
|
|
107
127
|
@io.print "\e8" # restore cursor
|
|
108
128
|
@io.flush if @io.respond_to?(:flush)
|
|
109
129
|
end
|
|
110
130
|
|
|
131
|
+
def wrap_to_cols(line)
|
|
132
|
+
return [line] unless @cols && @cols.positive?
|
|
133
|
+
return [line] if Reline::Unicode.calculate_width(line, true) <= @cols
|
|
134
|
+
|
|
135
|
+
chunks, _ = Reline::Unicode.split_by_width(line, @cols)
|
|
136
|
+
chunks.compact.reject(&:empty?)
|
|
137
|
+
end
|
|
138
|
+
|
|
111
139
|
def rerender_prompt
|
|
112
140
|
return unless defined?(Reline)
|
|
113
141
|
Reline.line_editor&.rerender
|
data/lib/tempest/version.rb
CHANGED