libertree-model 0.9.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/libertree/compat.rb +29 -0
- data/lib/libertree/console.rb +17 -0
- data/lib/libertree/db.rb +37 -0
- data/lib/libertree/embedder.rb +69 -0
- data/lib/libertree/embedding/custom-providers.rb +148 -0
- data/lib/libertree/model/account-settings.rb +14 -0
- data/lib/libertree/model/account.rb +487 -0
- data/lib/libertree/model/chat-message.rb +75 -0
- data/lib/libertree/model/comment-like.rb +68 -0
- data/lib/libertree/model/comment.rb +164 -0
- data/lib/libertree/model/contact-list.rb +81 -0
- data/lib/libertree/model/embed-cache.rb +6 -0
- data/lib/libertree/model/file.rb +6 -0
- data/lib/libertree/model/forest.rb +82 -0
- data/lib/libertree/model/group-member.rb +13 -0
- data/lib/libertree/model/group.rb +47 -0
- data/lib/libertree/model/has-display-text.rb +34 -0
- data/lib/libertree/model/has-searchable-text.rb +19 -0
- data/lib/libertree/model/ignored-member.rb +13 -0
- data/lib/libertree/model/invitation.rb +6 -0
- data/lib/libertree/model/is-remote-or-local.rb +30 -0
- data/lib/libertree/model/job.rb +93 -0
- data/lib/libertree/model/member.rb +222 -0
- data/lib/libertree/model/message.rb +154 -0
- data/lib/libertree/model/node.rb +22 -0
- data/lib/libertree/model/node_affiliation.rb +14 -0
- data/lib/libertree/model/node_subscription.rb +23 -0
- data/lib/libertree/model/notification.rb +71 -0
- data/lib/libertree/model/pool-post.rb +17 -0
- data/lib/libertree/model/pool.rb +172 -0
- data/lib/libertree/model/post-hidden.rb +21 -0
- data/lib/libertree/model/post-like.rb +72 -0
- data/lib/libertree/model/post-revision.rb +9 -0
- data/lib/libertree/model/post.rb +735 -0
- data/lib/libertree/model/profile.rb +25 -0
- data/lib/libertree/model/remote-storage-connection.rb +6 -0
- data/lib/libertree/model/river.rb +249 -0
- data/lib/libertree/model/server.rb +35 -0
- data/lib/libertree/model/session-account.rb +10 -0
- data/lib/libertree/model/url-expansion.rb +6 -0
- data/lib/libertree/model.rb +48 -0
- data/lib/libertree/query.rb +167 -0
- data/lib/libertree/render.rb +28 -0
- metadata +198 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Libertree
|
2
|
+
module Model
|
3
|
+
class Profile < Sequel::Model(:profiles)
|
4
|
+
def after_update
|
5
|
+
super
|
6
|
+
if self.member.local?
|
7
|
+
Libertree::Model::Job.create_for_forests(
|
8
|
+
{
|
9
|
+
task: 'request:MEMBER',
|
10
|
+
params: { 'username' => self.member.account.username, }
|
11
|
+
}
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def member
|
17
|
+
@member ||= Member[ self.member_id ]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.search(query)
|
21
|
+
self.where("(to_tsvector('simple', description) || to_tsvector('english', description)) @@ plainto_tsquery(?)", query).or("name_display ILIKE '%' || ? || '%'", query)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
module Libertree
|
2
|
+
module Model
|
3
|
+
class River < Sequel::Model(:rivers)
|
4
|
+
def account
|
5
|
+
@account ||= Account[self.account_id]
|
6
|
+
end
|
7
|
+
|
8
|
+
def should_contain?( post )
|
9
|
+
! self.contains?(post) && ! post.hidden_by?(self.account) && self.matches_post?(post)
|
10
|
+
end
|
11
|
+
|
12
|
+
def contains?( post )
|
13
|
+
Libertree::DB.dbh[ "SELECT river_contains_post(?, ?)", self.id, post.id ].single_value
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_post( post )
|
17
|
+
Libertree::DB.dbh[ "INSERT INTO river_posts ( river_id, post_id ) VALUES ( ?, ? )", self.id, post.id ].get
|
18
|
+
end
|
19
|
+
|
20
|
+
def posts( opts = {} )
|
21
|
+
time = Time.at( opts.fetch(:time, Time.now.to_f) ).strftime("%Y-%m-%d %H:%M:%S.%6N%z")
|
22
|
+
Post.s(%{SELECT * FROM posts_in_river(?,?,?,?,?,?)},
|
23
|
+
self.id,
|
24
|
+
self.account.id,
|
25
|
+
time,
|
26
|
+
opts[:newer],
|
27
|
+
opts[:order_by] == :comment,
|
28
|
+
opts.fetch(:limit, 30))
|
29
|
+
end
|
30
|
+
|
31
|
+
def parsed_query(override_cache=false)
|
32
|
+
return @parsed_query if @parsed_query && ! override_cache
|
33
|
+
|
34
|
+
full_query = self.query
|
35
|
+
if ! self.appended_to_all
|
36
|
+
full_query += ' ' + self.account.rivers_appended.map(&:query).join(' ')
|
37
|
+
full_query.strip!
|
38
|
+
end
|
39
|
+
|
40
|
+
@parsed_query = Libertree::Query.new(full_query, self.account.id, self.id).parsed
|
41
|
+
end
|
42
|
+
|
43
|
+
def term_matches_post?(term, post, data)
|
44
|
+
case term
|
45
|
+
when 'flag'
|
46
|
+
# TODO: most of these are slow
|
47
|
+
case data
|
48
|
+
when 'forest'
|
49
|
+
true # Every post is a post in the forest. :forest is sort of a no-op term
|
50
|
+
when 'tree'
|
51
|
+
post.local?
|
52
|
+
when 'unread'
|
53
|
+
! post.read_by?(self.account)
|
54
|
+
when 'liked'
|
55
|
+
post.liked_by? self.account.member
|
56
|
+
when 'commented'
|
57
|
+
post.commented_on_by? self.account.member
|
58
|
+
when 'subscribed'
|
59
|
+
self.account.subscribed_to? post
|
60
|
+
end
|
61
|
+
when 'contact-list'
|
62
|
+
data.last.include? post.member_id
|
63
|
+
when 'from'
|
64
|
+
post.member_id == data
|
65
|
+
when 'river'
|
66
|
+
data.matches_post?(post)
|
67
|
+
when 'visibility'
|
68
|
+
post.visibility == data
|
69
|
+
when 'word-count'
|
70
|
+
case data
|
71
|
+
when /^< ?([0-9]+)$/
|
72
|
+
n = $1.to_i
|
73
|
+
post.text.scan(/\S+/).count < n
|
74
|
+
when /^> ?([0-9]+)$/
|
75
|
+
n = $1.to_i
|
76
|
+
post.text.scan(/\S+/).count > n
|
77
|
+
end
|
78
|
+
when 'spring'
|
79
|
+
data.includes?(post)
|
80
|
+
when 'via'
|
81
|
+
post.via == data
|
82
|
+
when 'tag'
|
83
|
+
post.hashtags.include? data
|
84
|
+
when 'phrase', 'word'
|
85
|
+
/(?:^|\b|\s)#{Regexp.escape(data)}(?:\b|\s|$)/i === post.text
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def matches_post?(post, ignore_keys=[])
|
90
|
+
# Negations: Must not satisfy any of the conditions
|
91
|
+
# Requirements: Must satisfy every required condition
|
92
|
+
# Regular terms: Must satisfy at least one condition
|
93
|
+
|
94
|
+
conditions = {
|
95
|
+
negations: [],
|
96
|
+
requirements: [],
|
97
|
+
regular: []
|
98
|
+
}
|
99
|
+
|
100
|
+
query = self.parsed_query
|
101
|
+
keys = query.keys - ignore_keys
|
102
|
+
keys.each do |term|
|
103
|
+
test = lambda {|data| term_matches_post?(term, post, data)}
|
104
|
+
query[term].keys.each do |group|
|
105
|
+
conditions[group] += query[term][group].map(&test)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
conditions[:negations].none? &&
|
110
|
+
conditions[:requirements].all? &&
|
111
|
+
(conditions[:regular].count > 0 ? conditions[:regular].any? : true)
|
112
|
+
end
|
113
|
+
|
114
|
+
def refresh_posts( n = 512 )
|
115
|
+
# delete posts early to avoid confusion about posts that don't
|
116
|
+
# match the new query
|
117
|
+
DB.dbh[ "DELETE FROM river_posts WHERE river_id = ?", self.id ].get
|
118
|
+
|
119
|
+
# TODO: this is slow despite indices.
|
120
|
+
#posts = Post.where{|p| ~Sequel.function(:post_hidden_by_account, p.id, account.id)}
|
121
|
+
|
122
|
+
# get posts that are not hidden by account and get cracking
|
123
|
+
posts = Post.filter_by_query(self.parsed_query, self.account, Post.not_hidden_by(account))
|
124
|
+
|
125
|
+
# get up to n posts
|
126
|
+
# this is faster than using find_all on the set
|
127
|
+
count = 0
|
128
|
+
matching = []
|
129
|
+
posts.reverse_order(:id).each do |post|
|
130
|
+
break if count >= n
|
131
|
+
|
132
|
+
if res = self.matches_post?(post, ['flag', 'word', 'phrase', 'tag', 'visibility', 'from', 'via'])
|
133
|
+
count += 1
|
134
|
+
matching << post
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
if matching.any?
|
139
|
+
DB.dbh[ "INSERT INTO river_posts SELECT ?, id FROM posts WHERE id IN ?", self.id, matching.map(&:id)].get
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# @param params Untrusted parameter Hash. Be careful, this input usually comes from the outside world.
|
144
|
+
def revise( params )
|
145
|
+
self.label = params['label'].to_s
|
146
|
+
self.query = params['query'].to_s
|
147
|
+
|
148
|
+
n = River.num_appended_to_all
|
149
|
+
self.appended_to_all = !! params['appended_to_all']
|
150
|
+
if River.num_appended_to_all != n || self.appended_to_all
|
151
|
+
job_data = {
|
152
|
+
task: 'river:refresh-all',
|
153
|
+
params: {
|
154
|
+
'account_id' => self.account_id,
|
155
|
+
}.to_json
|
156
|
+
}
|
157
|
+
existing_jobs = Job.pending_where(
|
158
|
+
%{
|
159
|
+
task = ?
|
160
|
+
AND params = ?
|
161
|
+
},
|
162
|
+
job_data[:task],
|
163
|
+
job_data[:params]
|
164
|
+
)
|
165
|
+
if existing_jobs.empty?
|
166
|
+
Job.create job_data
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
if ! self.appended_to_all
|
171
|
+
Libertree::Model::Job.create(
|
172
|
+
task: 'river:refresh',
|
173
|
+
params: {
|
174
|
+
'river_id' => self.id,
|
175
|
+
}.to_json
|
176
|
+
)
|
177
|
+
end
|
178
|
+
self.save
|
179
|
+
end
|
180
|
+
|
181
|
+
def delete_cascade(force=false)
|
182
|
+
if ! force && self.appended_to_all
|
183
|
+
Libertree::Model::Job.create(
|
184
|
+
task: 'river:refresh-all',
|
185
|
+
params: {
|
186
|
+
'account_id' => self.account_id,
|
187
|
+
}.to_json
|
188
|
+
)
|
189
|
+
end
|
190
|
+
DB.dbh["SELECT delete_cascade_river(?)", self.id].get
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.num_appended_to_all
|
194
|
+
self.where(:appended_to_all).count
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.create(*args)
|
198
|
+
n = River.num_appended_to_all
|
199
|
+
river = super
|
200
|
+
|
201
|
+
if River.num_appended_to_all != n
|
202
|
+
Libertree::Model::Job.create(
|
203
|
+
task: 'river:refresh-all',
|
204
|
+
params: {
|
205
|
+
'account_id' => river.account_id,
|
206
|
+
}.to_json
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
if ! river.appended_to_all
|
211
|
+
Libertree::Model::Job.create(
|
212
|
+
task: 'river:refresh',
|
213
|
+
params: {
|
214
|
+
'river_id' => river.id,
|
215
|
+
}.to_json
|
216
|
+
)
|
217
|
+
end
|
218
|
+
|
219
|
+
river
|
220
|
+
end
|
221
|
+
|
222
|
+
def home?
|
223
|
+
self.home
|
224
|
+
end
|
225
|
+
|
226
|
+
def to_hash
|
227
|
+
{
|
228
|
+
'id' => self.id,
|
229
|
+
'label' => self.label,
|
230
|
+
'query' => self.query,
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
def being_processed?
|
235
|
+
!! Job[
|
236
|
+
task: 'river:refresh',
|
237
|
+
params: %|{"river_id":#{self.id}}|,
|
238
|
+
time_finished: nil
|
239
|
+
]
|
240
|
+
end
|
241
|
+
|
242
|
+
def mark_all_posts_as_read
|
243
|
+
DB.dbh[ %{SELECT mark_all_posts_in_river_as_read_by(?,?)},
|
244
|
+
self.id,
|
245
|
+
self.account.id ].get
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Libertree
|
2
|
+
module Model
|
3
|
+
class Server < Sequel::Model(:servers)
|
4
|
+
@@own_domain = nil
|
5
|
+
|
6
|
+
def self.own_domain=(domain)
|
7
|
+
@@own_domain = domain
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.own_domain
|
11
|
+
@@own_domain
|
12
|
+
end
|
13
|
+
|
14
|
+
def name_display
|
15
|
+
self.domain || self.ip || "(unknown)"
|
16
|
+
end
|
17
|
+
|
18
|
+
def forests
|
19
|
+
Forest.s(
|
20
|
+
%{
|
21
|
+
SELECT
|
22
|
+
f.*
|
23
|
+
FROM
|
24
|
+
forests f
|
25
|
+
, forests_servers fs
|
26
|
+
WHERE
|
27
|
+
fs.server_id = ?
|
28
|
+
AND f.id = fs.forest_id
|
29
|
+
},
|
30
|
+
self.id
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'libertree/db'
|
2
|
+
require_relative 'compat'
|
3
|
+
Sequel::Model.plugin :dirty
|
4
|
+
Sequel::Model.plugin :compat
|
5
|
+
Sequel::Model.unrestrict_primary_key
|
6
|
+
Sequel::Model.plugin :json_serializer
|
7
|
+
|
8
|
+
Libertree::DB.dbh.extension :pg_array
|
9
|
+
Sequel.extension :pg_array_ops
|
10
|
+
|
11
|
+
require_relative 'query'
|
12
|
+
|
13
|
+
require_relative 'model/is-remote-or-local'
|
14
|
+
require_relative 'model/has-searchable-text'
|
15
|
+
require_relative 'model/has-display-text'
|
16
|
+
|
17
|
+
require_relative 'model/account'
|
18
|
+
require_relative 'model/account-settings'
|
19
|
+
require_relative 'model/chat-message'
|
20
|
+
require_relative 'model/comment'
|
21
|
+
require_relative 'model/comment-like'
|
22
|
+
require_relative 'model/contact-list'
|
23
|
+
require_relative 'model/file'
|
24
|
+
require_relative 'model/forest'
|
25
|
+
require_relative 'model/embed-cache'
|
26
|
+
require_relative 'model/ignored-member'
|
27
|
+
require_relative 'model/invitation'
|
28
|
+
require_relative 'model/group'
|
29
|
+
require_relative 'model/group-member'
|
30
|
+
require_relative 'model/job'
|
31
|
+
require_relative 'model/member'
|
32
|
+
require_relative 'model/message'
|
33
|
+
require_relative 'model/node'
|
34
|
+
require_relative 'model/node_affiliation'
|
35
|
+
require_relative 'model/node_subscription'
|
36
|
+
require_relative 'model/notification'
|
37
|
+
require_relative 'model/pool'
|
38
|
+
require_relative 'model/pool-post'
|
39
|
+
require_relative 'model/post'
|
40
|
+
require_relative 'model/post-hidden'
|
41
|
+
require_relative 'model/post-like'
|
42
|
+
require_relative 'model/post-revision'
|
43
|
+
require_relative 'model/profile'
|
44
|
+
require_relative 'model/river'
|
45
|
+
require_relative 'model/remote-storage-connection'
|
46
|
+
require_relative 'model/server'
|
47
|
+
require_relative 'model/session-account'
|
48
|
+
require_relative 'model/url-expansion'
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Libertree
|
2
|
+
class Query
|
3
|
+
private
|
4
|
+
class ParseError < StandardError; end
|
5
|
+
|
6
|
+
def patterns
|
7
|
+
{
|
8
|
+
'phrase' => /(?<sign>[+-])?"(?<arg>[^"]+)"/,
|
9
|
+
'from' => /(?<sign>[+-])?:from ("(?<arg>.+?)"|(?<arg>[^ ]+))/,
|
10
|
+
'river' => /(?<sign>[+-])?:river "(?<arg>.+?)"/,
|
11
|
+
'contact-list' => /(?<sign>[+-])?:contact-list "(?<arg>.+?)"/,
|
12
|
+
'via' => /(?<sign>[+-])?:via "(?<arg>.+?)"/,
|
13
|
+
'visibility' => /(?<sign>[+-])?:visibility (?<arg>[a-z-]+)/,
|
14
|
+
'word-count' => /(?<sign>[+-])?:word-count ?(?<arg>(?<comp>[<>]) ?(?<num>[0-9]+))/,
|
15
|
+
'spring' => /(?<sign>[+-])?:spring (?<arg>"(?<spring_name>.+?)" "(?<handle>.+?)")/,
|
16
|
+
'flag' => /(?<sign>[+-])?:(?<arg>forest|tree|unread|liked|commented|subscribed)/,
|
17
|
+
'tag' => /(?<sign>[+-])?#(?<arg>\S+)/,
|
18
|
+
'word' => /(?<sign>[+-])?(?<arg>\S+)/,
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_resource(res, term, &block)
|
23
|
+
err = if res.is_a? Array
|
24
|
+
res.any?(&:nil?)
|
25
|
+
else
|
26
|
+
res.nil?
|
27
|
+
end
|
28
|
+
if err
|
29
|
+
if @fail_on_error
|
30
|
+
fail ParseError, term
|
31
|
+
end
|
32
|
+
else
|
33
|
+
yield(*res)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# We need the river id only to prevent self-referential river
|
38
|
+
# queries. For general purpose queries this is not required.
|
39
|
+
def initialize(query, account_id, river_id=nil, fail_on_error=false)
|
40
|
+
@fail_on_error = fail_on_error
|
41
|
+
@parsed_query = Hash.new
|
42
|
+
@parsed_query.default_proc = proc do |hash,key|
|
43
|
+
hash[key] = {
|
44
|
+
:negations => [],
|
45
|
+
:requirements => [],
|
46
|
+
:regular => []
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
scanner = StringScanner.new(query)
|
51
|
+
until scanner.eos? do
|
52
|
+
scanner.skip(/\s+/)
|
53
|
+
patterns.each_pair do |key, pattern|
|
54
|
+
if term = scanner.scan(pattern)
|
55
|
+
match = term.match(pattern)
|
56
|
+
group = case match[:sign]
|
57
|
+
when '+'
|
58
|
+
:requirements
|
59
|
+
when '-'
|
60
|
+
:negations
|
61
|
+
else
|
62
|
+
:regular
|
63
|
+
end
|
64
|
+
|
65
|
+
case key
|
66
|
+
when
|
67
|
+
'phrase',
|
68
|
+
'via',
|
69
|
+
'visibility',
|
70
|
+
'word-count',
|
71
|
+
'flag',
|
72
|
+
'tag'
|
73
|
+
@parsed_query[key][group] << match[:arg]
|
74
|
+
when 'from'
|
75
|
+
# TODO: eventually remove with_display_name check
|
76
|
+
member = (Model::Member.with_handle(match[:arg]) || Model::Member.with_display_name(match[:arg]))
|
77
|
+
check_resource(member, term) do |member|
|
78
|
+
@parsed_query[key][group] << member.id
|
79
|
+
end
|
80
|
+
when 'river'
|
81
|
+
river = Model::River[label: match[:arg], account_id: account_id]
|
82
|
+
check_resource(river, term) do |river|
|
83
|
+
@parsed_query[key][group] << river if river_id && river.id != river_id
|
84
|
+
end
|
85
|
+
when 'contact-list'
|
86
|
+
list = Model::ContactList[ account_id: account_id, name: match[:arg] ]
|
87
|
+
check_resource(list, term) do |list|
|
88
|
+
ids = list.member_ids
|
89
|
+
@parsed_query[key][group] << [list.id, ids] unless ids.empty?
|
90
|
+
end
|
91
|
+
when 'spring'
|
92
|
+
# TODO: eventually remove with_display_name check
|
93
|
+
member = (Model::Member.with_handle(match[:handle]) || Model::Member.with_display_name(match[:handle]))
|
94
|
+
pool = Model::Pool[ member_id: member.id, name: match[:spring_name], sprung: true ] if member
|
95
|
+
check_resource([member, pool], term) do |list, pool|
|
96
|
+
@parsed_query[key][group] << pool
|
97
|
+
end
|
98
|
+
when 'word'
|
99
|
+
# Only treat a matched word as a simple word if it consists only of word
|
100
|
+
# characters. This excludes URLs or other terms with special characters.
|
101
|
+
if match[:arg] =~ /^[[:word:]]+$/
|
102
|
+
@parsed_query['word'][group] << match[:arg]
|
103
|
+
else
|
104
|
+
@parsed_query['phrase'][group] << match[:arg]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# move on to the next term
|
109
|
+
next @parsed_query
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
@parsed_query
|
114
|
+
end
|
115
|
+
|
116
|
+
public
|
117
|
+
def parsed
|
118
|
+
@parsed_query.dup
|
119
|
+
end
|
120
|
+
|
121
|
+
def simple
|
122
|
+
tags = @parsed_query['tag'][:regular].map {|t| "##{t}"}
|
123
|
+
rest = @parsed_query.
|
124
|
+
select {|k| ['phrase', 'word'].include? k}.
|
125
|
+
flat_map {|h| h.last[:regular]}
|
126
|
+
(tags + rest).join(' ')
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_s
|
130
|
+
res = []
|
131
|
+
apply_template = lambda do |template, groups|
|
132
|
+
res += groups[:negations].map {|v| '-' + template.call(v)}
|
133
|
+
res += groups[:requirements].map {|v| '+' + template.call(v)}
|
134
|
+
res += groups[:regular].map {|v| template.call(v)}
|
135
|
+
end
|
136
|
+
|
137
|
+
@parsed_query.each_pair do |key, groups|
|
138
|
+
template = case key
|
139
|
+
when 'phrase'
|
140
|
+
lambda {|v| "\"%s\"" % v }
|
141
|
+
when 'via'
|
142
|
+
lambda {|v| ":via \"%s\"" % v }
|
143
|
+
when 'visibility'
|
144
|
+
lambda {|v| ":visibility %s" % v }
|
145
|
+
when 'word-count'
|
146
|
+
lambda {|v| ":word-count %s" % v }
|
147
|
+
when 'flag'
|
148
|
+
lambda {|v| ":%s" % v }
|
149
|
+
when 'word'
|
150
|
+
lambda {|v| v }
|
151
|
+
when 'tag'
|
152
|
+
lambda {|v| "#%s" % v }
|
153
|
+
when 'from'
|
154
|
+
lambda {|v| ":from %s" % Model::Member[v.to_i].handle }
|
155
|
+
when 'river'
|
156
|
+
lambda {|v| ":river \"%s\"" % Model::River[v.id.to_i].label }
|
157
|
+
when 'contact-list'
|
158
|
+
template = lambda {|v| ":contact-list \"%s\"" % Model::ContactList[v.first.to_i].name }
|
159
|
+
when 'spring'
|
160
|
+
lambda {|v| pool = Model::Pool[v.id.to_i]; ":spring \"%s\" \"%s\"" % [pool.name, pool.member.handle] }
|
161
|
+
end
|
162
|
+
apply_template.call(template, groups)
|
163
|
+
end
|
164
|
+
res.join(' ')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'markdown'
|
4
|
+
|
5
|
+
module Libertree
|
6
|
+
module Render
|
7
|
+
Options = [ :filter_html,
|
8
|
+
:smart,
|
9
|
+
:strike,
|
10
|
+
:autolink,
|
11
|
+
:hard_wrap,
|
12
|
+
:notes,
|
13
|
+
:codeblock,
|
14
|
+
:hashtags,
|
15
|
+
:usernames,
|
16
|
+
:spoilerblock
|
17
|
+
]
|
18
|
+
|
19
|
+
def self.to_html_string(s, opts=Options)
|
20
|
+
return '' if s.nil? or s.empty?
|
21
|
+
Markdown.new( s, *opts ).to_html.force_encoding('utf-8')
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.to_html_nodeset(s, opts=Options)
|
25
|
+
Nokogiri::HTML.fragment(self.to_html_string(s, opts))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|