jekyll-hive 1.1.1 → 2.0.0
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 +41 -10
- data/lib/jekyll-hive/hafbe_client.rb +109 -0
- data/lib/jekyll-hive/hive_tag.rb +157 -160
- data/lib/jekyll-hive/version.rb +1 -1
- data/lib/jekyll-hive.rb +1 -0
- metadata +7 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffed7dc4de0f8360a6e98e9470af905ea77c3ef6832af01a45ec85ff29d4d0f5
|
|
4
|
+
data.tar.gz: 21db6c03f3351c65dfba021232b7a966e89eab71878b902756f721b9faf7112c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 869388677bfaa380eaed0d995122d1d88e335f459ffdf9bb3d78e76a19dd89efcc1259083bbbebea2b3375a72a7b919596ce264084ca240015b8b9e6b7d61e33
|
|
7
|
+
data.tar.gz: cbea377733876792ffbe40b2ecdce1a6d154868c6c0778fa9f4201e151878a5917ab8c1af05d1d5c6485b714385f308683ed1f18f45577368517a199c2612c07
|
data/README.md
CHANGED
|
@@ -42,14 +42,46 @@ Use the tag as follows in your Jekyll pages, posts and collections:
|
|
|
42
42
|
```
|
|
43
43
|
This will place the associated content on the page.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
⚠️ The post body is published as-is, including any raw HTML the post contains. `jekyll-hive` does not sanitize it, so only embed posts you trust. The source link uses the post's `canonical_url` metadata when it is an `http`/`https` URL, otherwise the post's hive.blog URL.
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
As of version 2.0.0, `jekyll-hive` uses HAFBE-backed post retrieval and emits structured embed markup instead of injecting per-post scripts and styles.
|
|
48
|
+
|
|
49
|
+
## Front-end dependencies
|
|
50
|
+
|
|
51
|
+
As of version 2.0.0, the `{% hive %}` tag emits structured embed markup and expects rendering dependencies to be loaded once by your site layout instead of being injected for every post.
|
|
52
|
+
|
|
53
|
+
At minimum, load your renderer and any supporting scripts/styles in a shared include such as `_includes/head.html` or your main layout. For example:
|
|
48
54
|
|
|
49
55
|
```html
|
|
50
56
|
<script src='https://unpkg.com/steem-content-renderer'></script>
|
|
57
|
+
<script src='/assets/javascript/universal-bridge-against-phishing.js'></script>
|
|
58
|
+
<script src='/assets/javascript/lazysizes.min.js' async></script>
|
|
51
59
|
```
|
|
52
60
|
|
|
61
|
+
Then run a single page-level bootstrap pass against the generated embed targets, such as elements marked with `data-hive-render-target="true"`.
|
|
62
|
+
|
|
63
|
+
## Legacy cache warning
|
|
64
|
+
|
|
65
|
+
If `jekyll-hive` detects cache entries created by the older inline-script / inline-style output format, it will warn once per build and recommend deleting `.jekyll-hive-cache` so the cache can be regenerated in the version 2.0.0 format.
|
|
66
|
+
|
|
67
|
+
## Synthetic diff-match-patch conformance checks
|
|
68
|
+
|
|
69
|
+
The repo now includes a small synthetic diff-match-patch corpus under `spec/fixtures/diff_match_patch_vectors.json` to complement the real HAFBE history fixtures.
|
|
70
|
+
|
|
71
|
+
Purpose:
|
|
72
|
+
- pin down diff-match-patch semantics more surgically than the full history fixtures
|
|
73
|
+
- verify that percent-encoded inserted text remains literal when appropriate
|
|
74
|
+
- compare the Ruby `dimapa` path against optional external engines when available
|
|
75
|
+
|
|
76
|
+
Current helper layout:
|
|
77
|
+
- `script/dmp_engine_ruby.rb` — always-available Ruby helper used by the spec suite
|
|
78
|
+
- `script/dmp_engine_python.py` — optional Python helper using the `diff_match_patch` package
|
|
79
|
+
- `spec/fixtures/diff_match_patch_frozen_witnesses.json` — frozen external-engine witness outputs for durable regression coverage
|
|
80
|
+
|
|
81
|
+
If `.venv-dmp/` exists, the spec prefers that repo-local Python for the optional live Python agreement check. Otherwise it falls back to `python3` and cleanly skips the live Python comparison if the module is unavailable.
|
|
82
|
+
|
|
83
|
+
These synthetic vectors are not meant to replace the real HAFBE history fixtures; they exist to isolate patch semantics and cross-engine agreement.
|
|
84
|
+
|
|
53
85
|
## Jekyll Build
|
|
54
86
|
|
|
55
87
|
When building your site with jekyll, you can continue to use the default command:
|
|
@@ -58,10 +90,10 @@ When building your site with jekyll, you can continue to use the default command
|
|
|
58
90
|
jekyll build
|
|
59
91
|
```
|
|
60
92
|
|
|
61
|
-
If you would like to provide an alternate
|
|
93
|
+
If you would like to provide an alternate HAFBE endpoint:
|
|
62
94
|
|
|
63
95
|
```bash
|
|
64
|
-
|
|
96
|
+
HAFBE_API_URL=https://api.hive.blog/hafbe-api jekyll build
|
|
65
97
|
```
|
|
66
98
|
|
|
67
99
|
## Jekyll Clean
|
|
@@ -77,18 +109,17 @@ rm -rf .jekyll-hive-cache && jekyll clean && jekyll build
|
|
|
77
109
|
#### I saw several messages like:
|
|
78
110
|
|
|
79
111
|
```
|
|
80
|
-
Retrying:
|
|
81
|
-
Retrying: stoodkev/how-to-use-hivejs-or-other-modules-referencing-core-node-js-modules-on-react-native (Failed to open TCP connection to api.openhive.network:443 (getaddrinfo: nodename nor servname provided, or not known))
|
|
112
|
+
Retrying: author/permlink (HAFBE request failed for @author/permlink: HTTP 404)
|
|
82
113
|
.
|
|
83
114
|
.
|
|
84
115
|
.
|
|
85
|
-
Gave up on:
|
|
116
|
+
Gave up on: author/permlink
|
|
86
117
|
```
|
|
87
118
|
|
|
88
|
-
Check your internet connection or provide an alternate
|
|
119
|
+
Check your internet connection, confirm the post exists, or provide an alternate HAFBE endpoint:
|
|
89
120
|
|
|
90
|
-
```
|
|
91
|
-
|
|
121
|
+
```bash
|
|
122
|
+
HAFBE_API_URL=https://api.hive.blog/hafbe-api jekyll build
|
|
92
123
|
```
|
|
93
124
|
|
|
94
125
|
#### The post has changed on the blockchain, but `jekyll-hive` won't update to the latest post revision.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dimapa'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'uri'
|
|
7
|
+
|
|
8
|
+
module Jekyll
|
|
9
|
+
module Hive
|
|
10
|
+
class HafbeClient
|
|
11
|
+
DEFAULT_API_URL = 'https://api.hive.blog/hafbe-api'.freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :api_url
|
|
14
|
+
|
|
15
|
+
def initialize(api_url: ENV.fetch('HAFBE_API_URL', DEFAULT_API_URL))
|
|
16
|
+
@api_url = api_url
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def fetch_post(author, permlink)
|
|
20
|
+
response = get("/accounts/#{escape(author)}/operations/comments/#{escape(permlink)}")
|
|
21
|
+
normalize_post(response)
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
raise "HAFBE request failed for @#{author}/#{permlink}: #{e.message}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def get(path, params = {})
|
|
29
|
+
base = api_url.sub(%r{/$}, '')
|
|
30
|
+
uri = URI(base + path)
|
|
31
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
32
|
+
response = Net::HTTP.get_response(uri)
|
|
33
|
+
|
|
34
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
35
|
+
raise "HTTP #{response.code}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
JSON.parse(response.body)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def normalize_post(payload)
|
|
42
|
+
operations = Array(payload['operations_result'])
|
|
43
|
+
comment_operations = operations.select do |entry|
|
|
44
|
+
entry.dig('op', 'type') == 'comment_operation'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
raise 'Unexpected HAFBE response shape' if comment_operations.empty?
|
|
48
|
+
|
|
49
|
+
base_entry = comment_operations.find do |entry|
|
|
50
|
+
body = entry.dig('op', 'value', 'body').to_s
|
|
51
|
+
!diff_match_patch?(body)
|
|
52
|
+
end || comment_operations.first
|
|
53
|
+
|
|
54
|
+
post = deep_copy(base_entry.dig('op', 'value'))
|
|
55
|
+
raise 'Unexpected HAFBE response shape' unless post.is_a?(Hash)
|
|
56
|
+
|
|
57
|
+
post['created'] ||= base_entry['timestamp'] if base_entry['timestamp']
|
|
58
|
+
|
|
59
|
+
subsequent_entries = comment_operations.drop_while { |entry| entry != base_entry }.drop(1)
|
|
60
|
+
subsequent_entries.each do |entry|
|
|
61
|
+
apply_revision!(post, entry)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
post
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def apply_revision!(post, entry)
|
|
68
|
+
revision = entry.dig('op', 'value')
|
|
69
|
+
return unless revision.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
if revision.key?('title') && !revision['title'].to_s.empty?
|
|
72
|
+
post['title'] = revision['title']
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if revision.key?('json_metadata') && !revision['json_metadata'].to_s.empty?
|
|
76
|
+
post['json_metadata'] = revision['json_metadata']
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
body = revision['body'].to_s
|
|
80
|
+
return if body.empty?
|
|
81
|
+
|
|
82
|
+
post['body'] = if diff_match_patch?(body)
|
|
83
|
+
apply_diff_match_patch(post['body'].to_s, body)
|
|
84
|
+
else
|
|
85
|
+
body
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def diff_match_patch?(body)
|
|
90
|
+
body.start_with?('@@') || body.start_with?('@@ ')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def apply_diff_match_patch(original_body, patch_text)
|
|
94
|
+
dmp = DiMaPa.new
|
|
95
|
+
patches = dmp.patch_from_text(patch_text)
|
|
96
|
+
updated_body, = dmp.patch_apply(patches, original_body)
|
|
97
|
+
updated_body
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def deep_copy(value)
|
|
101
|
+
Marshal.load(Marshal.dump(value))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def escape(segment)
|
|
105
|
+
URI.encode_www_form_component(segment.to_s)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/jekyll-hive/hive_tag.rb
CHANGED
|
@@ -1,67 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'cgi'
|
|
4
|
-
require '
|
|
5
|
-
require 'open-uri'
|
|
4
|
+
require 'json'
|
|
6
5
|
require 'fileutils'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Net::ReadTimeout = Class.new(RuntimeError) unless Net.const_defined?(:ReadTimeout)
|
|
6
|
+
require 'set'
|
|
7
|
+
require 'time'
|
|
10
8
|
|
|
11
9
|
module Jekyll
|
|
12
10
|
module Hive
|
|
13
|
-
|
|
14
11
|
# Use the tag as follows in your Jekyll pages, posts and collections:
|
|
15
|
-
#
|
|
12
|
+
#
|
|
16
13
|
# {% hive author/permlink %}
|
|
17
14
|
class HiveTag < Liquid::Tag
|
|
15
|
+
LEGACY_CACHE_RECOMMENDATION = 'Delete .jekyll-hive-cache and rebuild so jekyll-hive can regenerate cache entries in the v2 format.'
|
|
16
|
+
V2_CACHE_MARKER = 'data-hive-render-target="true"'
|
|
17
|
+
LEGACY_CACHE_MARKERS = [
|
|
18
|
+
'SteemContentRenderer.DefaultRenderer',
|
|
19
|
+
'<script src=\'https://unpkg.com/steem-content-renderer\'',
|
|
20
|
+
'<script src="https://unpkg.com/steem-content-renderer"',
|
|
21
|
+
'<script crossorigin=\'anonymous\' integrity=\'sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=\' src=\'https://code.jquery.com/jquery-3.5.1.slim.min.js\'',
|
|
22
|
+
'<script src=\'https://code.jquery.com/jquery-3.5.1.slim.min.js\'',
|
|
23
|
+
'<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"'
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
18
26
|
def render(context)
|
|
19
|
-
@
|
|
20
|
-
@
|
|
21
|
-
|
|
27
|
+
@site = context.registers[:site]
|
|
28
|
+
@encoding = @site.config['encoding'] || 'utf-8'
|
|
29
|
+
@settings = @site.config['hive'] || {}
|
|
30
|
+
|
|
22
31
|
if (tag_contents = determine_arguments(@markup.strip))
|
|
23
32
|
hive_slug = tag_contents[0]
|
|
24
33
|
attempts = 0
|
|
25
34
|
content = nil
|
|
26
|
-
rebuild = ENV.fetch(
|
|
27
|
-
|
|
35
|
+
rebuild = ENV.fetch('JEKYLL_HIVE_REBUILD', 'false') == 'true'
|
|
36
|
+
|
|
28
37
|
loop do
|
|
29
38
|
warn "Attempts for #{hive_slug}: #{attempts}" if attempts > 0
|
|
30
|
-
|
|
39
|
+
|
|
31
40
|
if attempts > 4
|
|
32
41
|
warn "Gave up on: #{hive_slug}"
|
|
33
42
|
delete_content_cache(hive_slug)
|
|
34
43
|
break
|
|
35
44
|
end
|
|
36
|
-
|
|
45
|
+
|
|
37
46
|
begin
|
|
38
|
-
attempts
|
|
39
|
-
|
|
40
|
-
if
|
|
47
|
+
attempts += 1
|
|
48
|
+
|
|
49
|
+
if rebuild && content_cache(hive_slug)
|
|
41
50
|
if content_age(hive_slug) > 9000
|
|
42
|
-
# TODO quickly check if the cache should be cleared
|
|
43
|
-
|
|
44
51
|
warn "Checking for changes: #{hive_slug}"
|
|
45
|
-
|
|
52
|
+
|
|
46
53
|
if content_changed?(hive_slug)
|
|
47
54
|
warn "Refreshing: #{hive_slug}"
|
|
48
55
|
delete_content_cache(hive_slug)
|
|
49
56
|
end
|
|
50
57
|
end
|
|
51
|
-
|
|
58
|
+
|
|
52
59
|
content_touch(hive_slug)
|
|
53
60
|
end
|
|
54
|
-
|
|
61
|
+
|
|
55
62
|
content = content_cache(hive_slug) || hive_tag(hive_slug)
|
|
56
|
-
|
|
63
|
+
|
|
57
64
|
break
|
|
58
|
-
rescue => e
|
|
65
|
+
rescue StandardError => e
|
|
59
66
|
warn "Retrying: #{hive_slug} (#{e})"
|
|
60
|
-
@api = nil
|
|
61
67
|
sleep 3
|
|
62
68
|
end
|
|
63
69
|
end
|
|
64
|
-
|
|
70
|
+
|
|
65
71
|
content_cache(hive_slug, content)
|
|
66
72
|
else
|
|
67
73
|
raise ArgumentError, <<~ERROR
|
|
@@ -73,80 +79,137 @@ module Jekyll
|
|
|
73
79
|
ERROR
|
|
74
80
|
end
|
|
75
81
|
end
|
|
76
|
-
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
77
85
|
def cache_dir_path
|
|
78
86
|
ENV.fetch('JEKYLL_HIVE_CACHE', '.jekyll-hive-cache')
|
|
79
87
|
end
|
|
80
|
-
|
|
88
|
+
|
|
81
89
|
def parse_key(hive_slug)
|
|
82
90
|
hive_slug.gsub('/', '-')
|
|
83
91
|
end
|
|
84
|
-
|
|
92
|
+
|
|
85
93
|
def cached_file(hive_slug)
|
|
86
94
|
key = parse_key(hive_slug)
|
|
87
|
-
|
|
95
|
+
|
|
88
96
|
cache_dir_path + "/#{key}"
|
|
89
97
|
end
|
|
90
|
-
|
|
98
|
+
|
|
91
99
|
def content_cache(hive_slug, value = nil)
|
|
92
100
|
key = parse_key(hive_slug)
|
|
93
|
-
|
|
94
|
-
unless
|
|
95
|
-
@content_cache
|
|
101
|
+
|
|
102
|
+
unless @content_cache
|
|
103
|
+
@content_cache = {}
|
|
104
|
+
@legacy_cache_warned_keys = Set.new
|
|
96
105
|
|
|
97
106
|
FileUtils.mkdir_p(cache_dir_path)
|
|
98
|
-
|
|
107
|
+
|
|
99
108
|
Dir.glob("#{cache_dir_path}/*") do |filename|
|
|
100
|
-
next if File.directory?
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
next if File.directory?(filename)
|
|
110
|
+
|
|
111
|
+
cache_key = filename.split('/').last
|
|
112
|
+
cache_value = normalize_utf8(File.binread(filename))
|
|
113
|
+
@content_cache[cache_key] = cache_value
|
|
114
|
+
warn_if_legacy_cache_entry(cache_key, cache_value)
|
|
103
115
|
end
|
|
104
116
|
end
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
|
|
118
|
+
if value && value != ''
|
|
119
|
+
normalized_value = normalize_utf8(value)
|
|
120
|
+
@content_cache[key] = normalized_value
|
|
121
|
+
|
|
109
122
|
File.open(cached_file(hive_slug), 'wb') do |cache_file|
|
|
110
|
-
cache_file.write(
|
|
123
|
+
cache_file.write(normalized_value)
|
|
111
124
|
end
|
|
112
125
|
end
|
|
113
|
-
|
|
126
|
+
|
|
114
127
|
if @content_cache[key] == ''
|
|
115
128
|
delete_content_cache(hive_slug)
|
|
116
|
-
|
|
129
|
+
|
|
117
130
|
nil
|
|
118
131
|
else
|
|
119
132
|
@content_cache[key]
|
|
120
133
|
end
|
|
121
134
|
end
|
|
122
|
-
|
|
135
|
+
|
|
136
|
+
def warn_if_legacy_cache_entry(cache_key, cache_value)
|
|
137
|
+
return if @legacy_cache_warned_keys.include?(cache_key)
|
|
138
|
+
return unless legacy_cache_entry?(cache_value)
|
|
139
|
+
return if legacy_cache_warning_emitted?
|
|
140
|
+
|
|
141
|
+
@legacy_cache_warned_keys << cache_key
|
|
142
|
+
legacy_cache_warning_emitted!(cache_key)
|
|
143
|
+
|
|
144
|
+
warn <<~WARNING.gsub(/\s+/, ' ').strip
|
|
145
|
+
Detected legacy jekyll-hive cache entry #{cache_key.inspect} in #{cache_dir_path}.
|
|
146
|
+
#{LEGACY_CACHE_RECOMMENDATION}
|
|
147
|
+
WARNING
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def legacy_cache_entry?(cache_value)
|
|
151
|
+
return false unless cache_value && cache_value != ''
|
|
152
|
+
|
|
153
|
+
normalized_cache_value = normalize_utf8(cache_value)
|
|
154
|
+
return false if normalized_cache_value.include?(V2_CACHE_MARKER)
|
|
155
|
+
|
|
156
|
+
LEGACY_CACHE_MARKERS.any? { |marker| normalized_cache_value.include?(marker) }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def legacy_cache_warning_emitted?
|
|
160
|
+
return false unless @site
|
|
161
|
+
|
|
162
|
+
@site.config['jekyll_hive_v2_legacy_cache_warning_emitted'] == true
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def legacy_cache_warning_emitted!(cache_key)
|
|
166
|
+
return unless @site
|
|
167
|
+
|
|
168
|
+
@site.config['jekyll_hive_v2_legacy_cache_warning_emitted'] = true
|
|
169
|
+
@site.config['jekyll_hive_v2_legacy_cache_warning_key'] = cache_key
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def normalize_utf8(value)
|
|
173
|
+
return '' if value.nil?
|
|
174
|
+
|
|
175
|
+
string = value.is_a?(String) ? value.dup : value.to_s
|
|
176
|
+
string.force_encoding(Encoding::UTF_8)
|
|
177
|
+
|
|
178
|
+
return string if string.valid_encoding?
|
|
179
|
+
|
|
180
|
+
string.encode(Encoding::UTF_8, Encoding::BINARY, invalid: :replace, undef: :replace, replace: '')
|
|
181
|
+
rescue EncodingError
|
|
182
|
+
value.to_s.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: '')
|
|
183
|
+
end
|
|
184
|
+
|
|
123
185
|
def content_age(hive_slug)
|
|
124
186
|
age = Time.now - File.mtime(cached_file(hive_slug)) rescue 0
|
|
125
187
|
warn "Age for #{hive_slug}: #{age}"
|
|
126
|
-
|
|
188
|
+
|
|
127
189
|
age
|
|
128
190
|
end
|
|
129
|
-
|
|
191
|
+
|
|
130
192
|
def content_touch(hive_slug)
|
|
131
193
|
FileUtils.touch(cached_file(hive_slug))
|
|
132
194
|
end
|
|
133
|
-
|
|
195
|
+
|
|
134
196
|
def delete_content_cache(hive_slug)
|
|
197
|
+
@content_cache&.delete(parse_key(hive_slug))
|
|
135
198
|
File.delete(cached_file(hive_slug)) rescue false
|
|
136
199
|
end
|
|
137
|
-
|
|
200
|
+
|
|
138
201
|
def determine_arguments(input)
|
|
139
202
|
return unless input =~ /@?[^\/]+\/[^\/]+/i
|
|
140
|
-
|
|
203
|
+
|
|
141
204
|
[input]
|
|
142
205
|
end
|
|
143
|
-
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
@
|
|
206
|
+
|
|
207
|
+
def hafbe_client
|
|
208
|
+
api_url = @settings['hafbe_api_url'] || ENV['HAFBE_API_URL'] || Jekyll::Hive::HafbeClient::DEFAULT_API_URL
|
|
209
|
+
|
|
210
|
+
@hafbe_client ||= Jekyll::Hive::HafbeClient.new(api_url: api_url)
|
|
148
211
|
end
|
|
149
|
-
|
|
212
|
+
|
|
150
213
|
def parse_slug(hive_slug)
|
|
151
214
|
hive_slug = hive_slug.split('@').last
|
|
152
215
|
hive_slug = hive_slug.split('/')
|
|
@@ -154,120 +217,54 @@ module Jekyll
|
|
|
154
217
|
permlink = hive_slug[1..-1].join('/')
|
|
155
218
|
permlink = permlink.split('?').first
|
|
156
219
|
permlink = permlink.split('#').first
|
|
157
|
-
|
|
220
|
+
|
|
158
221
|
[author, permlink]
|
|
159
222
|
end
|
|
160
|
-
|
|
223
|
+
|
|
161
224
|
def content_changed?(hive_slug)
|
|
162
225
|
author, permlink = parse_slug(hive_slug)
|
|
163
226
|
cached_created = File.mtime(cached_file(hive_slug)) rescue Time.now
|
|
227
|
+
content = fetch_content(author, permlink)
|
|
228
|
+
created = Time.parse(content.fetch('created').to_s + 'Z')
|
|
164
229
|
|
|
165
|
-
|
|
166
|
-
# no faster than just parsing the content.
|
|
167
|
-
api.get_content(author, permlink) do |content|
|
|
168
|
-
created = Time.parse(content.created + 'Z')
|
|
169
|
-
|
|
170
|
-
cached_created > created
|
|
171
|
-
end
|
|
230
|
+
cached_created > created
|
|
172
231
|
end
|
|
173
|
-
|
|
232
|
+
|
|
233
|
+
def fetch_content(author, permlink)
|
|
234
|
+
hafbe_client.fetch_post(author, permlink)
|
|
235
|
+
end
|
|
236
|
+
|
|
174
237
|
def hive_tag(hive_slug)
|
|
175
238
|
author, permlink = parse_slug(hive_slug)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# This will normalize image hoster proxy URLs that the author copied
|
|
183
|
-
# from another post.
|
|
184
|
-
|
|
185
|
-
body = body.gsub(/https:\/\/steemitimages.com\/[0-9]+x0\/https:\/\//, 'https://')
|
|
186
|
-
body = body.gsub(/https:\/\/images.hive.blog\/[0-9]+x0\/https:\/\//, 'https://')
|
|
187
|
-
body = body.gsub(/https:\/\/images.esteem.app\/[0-9]+x0\/https:\/\//, 'https://')
|
|
188
|
-
|
|
189
|
-
scrape = URI::open(canonical_url).read
|
|
190
|
-
|
|
191
|
-
if scrape.include? 'rel="canonical"'
|
|
192
|
-
canonical_url = scrape.split('rel="canonical"')[1].split('"')[1]
|
|
193
|
-
end
|
|
239
|
+
content = fetch_content(author, permlink)
|
|
240
|
+
body = normalize_utf8(content.fetch('body'))
|
|
241
|
+
metadata = JSON.parse(normalize_utf8(content.fetch('json_metadata', '{}'))) rescue {}
|
|
242
|
+
category = normalize_utf8(content['category']).sub(%r{^/+}, '')
|
|
243
|
+
canonical_url = normalize_utf8(metadata['canonical_url'])
|
|
244
|
+
canonical_url = ["https://hive.blog", category, "@#{author}", permlink].reject(&:empty?).join('/') unless canonical_url =~ %r{\Ahttps?://}i
|
|
194
245
|
|
|
195
|
-
|
|
246
|
+
body = body.gsub(/https:\/\/steemitimages.com\/[0-9]+x0\/https:\/\//, 'https://')
|
|
247
|
+
body = body.gsub(/https:\/\/images.hive.blog\/[0-9]+x0\/https:\/\//, 'https://')
|
|
248
|
+
body = body.gsub(/https:\/\/images.esteem.app\/[0-9]+x0\/https:\/\//, 'https://')
|
|
249
|
+
|
|
250
|
+
rendered_canonical_url = CGI.escapeHTML(canonical_url)
|
|
251
|
+
rendered_title = CGI.escapeHTML(normalize_utf8(content.fetch('title', '')))
|
|
252
|
+
rendered_author = CGI.escapeHTML(normalize_utf8(author))
|
|
253
|
+
rendered_permlink = CGI.escapeHTML(normalize_utf8(permlink))
|
|
254
|
+
container_id = "content-#{author}-#{permlink}"
|
|
255
|
+
rendered_container_id = CGI.escapeHTML(normalize_utf8(container_id))
|
|
256
|
+
|
|
257
|
+
normalize_utf8(<<~HTML)
|
|
258
|
+
<div class="hive-post-embed" data-hive-author="#{rendered_author}" data-hive-permlink="#{rendered_permlink}">
|
|
259
|
+
<div id="#{rendered_container_id}" class="hive-post-embed__content" data-hive-render-target="true">#{body}</div>
|
|
260
|
+
<footer class="hive-post-embed__source">
|
|
261
|
+
<span class="hive-post-embed__source-label">Source:</span>
|
|
262
|
+
<a href="#{rendered_canonical_url}">#{rendered_title}</a>
|
|
263
|
+
by
|
|
264
|
+
<a href="https://hive.blog/@#{rendered_author}">@#{rendered_author}</a>
|
|
265
|
+
</footer>
|
|
196
266
|
</div>
|
|
197
|
-
|
|
198
|
-
<script src='https://unpkg.com/steem-content-renderer'></script>
|
|
199
|
-
<!-- <script src="https://cdn.jsdelivr.net/npm/hive-content-renderer/dist/hive-content-renderer.min.js"></script> -->
|
|
200
|
-
<script>
|
|
201
|
-
$(document).ready(function() {
|
|
202
|
-
try {
|
|
203
|
-
const renderer = new SteemContentRenderer.DefaultRenderer({
|
|
204
|
-
// const renderer = new HiveContentRenderer({
|
|
205
|
-
baseUrl: "https://hive.blog/",
|
|
206
|
-
breaks: true,
|
|
207
|
-
skipSanitization: false,
|
|
208
|
-
allowInsecureScriptTags: false,
|
|
209
|
-
addNofollowToLinks: true,
|
|
210
|
-
doNotShowImages: false,
|
|
211
|
-
ipfsPrefix: "",
|
|
212
|
-
assetsWidth: 640,
|
|
213
|
-
assetsHeight: 480,
|
|
214
|
-
imageProxyFn: (url) => url,
|
|
215
|
-
usertagUrlFn: (account) => "/@#{author}",
|
|
216
|
-
hashtagUrlFn: (hashtag) => "/#{permlink}",
|
|
217
|
-
isLinkSafeFn: (url) => true,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const inputElem = $('#content-#{author}-#{permlink}').html();
|
|
221
|
-
const outputElem = $('#content-#{author}-#{permlink}');
|
|
222
|
-
const output = renderer.render(inputElem);
|
|
223
|
-
|
|
224
|
-
outputElem.html(output);
|
|
225
|
-
} catch(e) {
|
|
226
|
-
console.log(e);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
</script>
|
|
230
|
-
<style>
|
|
231
|
-
#content-#{author}-#{permlink} {
|
|
232
|
-
padding: 0 3rem;
|
|
233
|
-
color: #444444;
|
|
234
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
235
|
-
font-size: 16px;
|
|
236
|
-
line-height: 1.8;
|
|
237
|
-
text-shadow: 0 1px 0 #ffffff;
|
|
238
|
-
padding: 0.5rem;
|
|
239
|
-
}
|
|
240
|
-
#content-#{author}-#{permlink} code {
|
|
241
|
-
background: white;
|
|
242
|
-
}
|
|
243
|
-
#content-#{author}-#{permlink} a {
|
|
244
|
-
border-bottom: 1px solid #444444; color: #444444; text-decoration: none;
|
|
245
|
-
}
|
|
246
|
-
#content-#{author}-#{permlink} a:hover {
|
|
247
|
-
border-bottom: 0;
|
|
248
|
-
}
|
|
249
|
-
#content-#{author}-#{permlink} h1 {
|
|
250
|
-
font-size: 2.2em;
|
|
251
|
-
}
|
|
252
|
-
#content-#{author}-#{permlink} h2, h3, h4, h5 {
|
|
253
|
-
margin-bottom: 0;
|
|
254
|
-
}
|
|
255
|
-
#content-#{author}-#{permlink} header small {
|
|
256
|
-
color: #999;
|
|
257
|
-
font-size: 50%;
|
|
258
|
-
}
|
|
259
|
-
#content-#{author}-#{permlink} img {
|
|
260
|
-
max-width: 100%;
|
|
261
|
-
}
|
|
262
|
-
</style>
|
|
263
|
-
\n<hr />
|
|
264
|
-
<p>
|
|
265
|
-
See: <a href="#{canonical_url}">#{content.title}</a>
|
|
266
|
-
by
|
|
267
|
-
<a href="https://hive.blog/@#{author}">@#{author}</a>
|
|
268
|
-
</p>
|
|
269
|
-
DONE
|
|
270
|
-
end
|
|
267
|
+
HTML
|
|
271
268
|
end
|
|
272
269
|
end
|
|
273
270
|
end
|
data/lib/jekyll-hive/version.rb
CHANGED
data/lib/jekyll-hive.rb
CHANGED
metadata
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-hive
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anthony Martin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: dimapa
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1
|
|
19
|
+
version: '0.1'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1
|
|
26
|
+
version: '0.1'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: jekyll
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -34,7 +34,7 @@ dependencies:
|
|
|
34
34
|
- - "<"
|
|
35
35
|
- !ruby/object:Gem::Version
|
|
36
36
|
version: '5.0'
|
|
37
|
-
type: :
|
|
37
|
+
type: :runtime
|
|
38
38
|
prerelease: false
|
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
40
|
requirements:
|
|
@@ -98,34 +98,6 @@ dependencies:
|
|
|
98
98
|
- - "<"
|
|
99
99
|
- !ruby/object:Gem::Version
|
|
100
100
|
version: '1.0'
|
|
101
|
-
- !ruby/object:Gem::Dependency
|
|
102
|
-
name: webmock
|
|
103
|
-
requirement: !ruby/object:Gem::Requirement
|
|
104
|
-
requirements:
|
|
105
|
-
- - "~>"
|
|
106
|
-
- !ruby/object:Gem::Version
|
|
107
|
-
version: '3.0'
|
|
108
|
-
type: :development
|
|
109
|
-
prerelease: false
|
|
110
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
111
|
-
requirements:
|
|
112
|
-
- - "~>"
|
|
113
|
-
- !ruby/object:Gem::Version
|
|
114
|
-
version: '3.0'
|
|
115
|
-
- !ruby/object:Gem::Dependency
|
|
116
|
-
name: vcr
|
|
117
|
-
requirement: !ruby/object:Gem::Requirement
|
|
118
|
-
requirements:
|
|
119
|
-
- - "~>"
|
|
120
|
-
- !ruby/object:Gem::Version
|
|
121
|
-
version: '6.0'
|
|
122
|
-
type: :development
|
|
123
|
-
prerelease: false
|
|
124
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
125
|
-
requirements:
|
|
126
|
-
- - "~>"
|
|
127
|
-
- !ruby/object:Gem::Version
|
|
128
|
-
version: '6.0'
|
|
129
101
|
- !ruby/object:Gem::Dependency
|
|
130
102
|
name: simplecov
|
|
131
103
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -164,6 +136,7 @@ files:
|
|
|
164
136
|
- LICENSE
|
|
165
137
|
- README.md
|
|
166
138
|
- lib/jekyll-hive.rb
|
|
139
|
+
- lib/jekyll-hive/hafbe_client.rb
|
|
167
140
|
- lib/jekyll-hive/hive_tag.rb
|
|
168
141
|
- lib/jekyll-hive/version.rb
|
|
169
142
|
homepage: https://github.com/inertia186/jekyll-hive
|