rad_kit 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/components/captcha.rb +24 -0
- data/lib/components/captcha.yml +2 -0
- data/lib/kit/{http_controller → controller}/authorized.rb +1 -1
- data/lib/kit/controller/captcha.rb +23 -0
- data/lib/kit/{http_controller → controller}/localized.rb +1 -1
- data/lib/kit/controller.rb +5 -0
- data/lib/kit/gems.rb +10 -6
- data/lib/kit/i18n/locales/ru/pluralization.rb +62 -0
- data/lib/kit/i18n.rb +16 -0
- data/lib/kit/kit.rb +10 -7
- data/lib/kit/kit_text_utils.rb +30 -0
- data/lib/kit/misc/prepare_model.rb +16 -0
- data/lib/kit/misc/user_error.rb +12 -0
- data/lib/kit/models/attachments_uploader_helper.rb +1 -1
- data/lib/kit/models/authorized.rb +6 -8
- data/lib/kit/models/authorized_object.rb +3 -4
- data/lib/kit/models/{micelaneous.rb → miscellaneous.rb} +0 -0
- data/lib/kit/models_after.rb +1 -1
- data/lib/kit/mongoid/{rad_micelaneous.rb → rad_miscellaneous.rb} +1 -1
- data/lib/kit/mongoid/text_processor.rb +1 -1
- data/lib/kit/mongoid.rb +2 -2
- data/lib/kit/spec.rb +1 -2
- data/lib/kit/tasks.rb +1 -1
- data/lib/text_utils/code_highlighter.rb +75 -0
- data/lib/text_utils/custom_markdown.rb +64 -0
- data/lib/text_utils/ensure_utf.rb +14 -0
- data/lib/text_utils/format_qualifier.rb +13 -0
- data/lib/{kit/text_utils → text_utils}/html_sanitizer.rb +8 -6
- data/lib/text_utils/markdown.rb +33 -0
- data/lib/text_utils/pipe.rb +16 -0
- data/lib/text_utils/processor.rb +14 -0
- data/lib/text_utils/support.rb +15 -0
- data/lib/text_utils/truncate.rb +30 -0
- data/lib/text_utils.rb +23 -0
- data/readme.md +1 -8
- data/spec/controller/authorization_spec.rb +1 -1
- data/spec/controller/captcha_spec.rb +66 -0
- data/spec/i18n/i18n_spec/locales/en/general.yml +5 -0
- data/spec/i18n/i18n_spec/locales/ru/general.yml +7 -0
- data/spec/i18n/i18n_spec.rb +32 -0
- data/spec/misc/kit_text_utils_spec.rb +29 -0
- data/spec/misc/prepare_model_spec.rb +37 -0
- data/spec/misc/user_error_spec.rb +38 -0
- data/spec/models/authorized_object_spec.rb +10 -3
- data/spec/spec_helper/factories.rb +4 -0
- data/spec/spec_helper/user.rb +2 -3
- data/spec/spec_helper.rb +0 -5
- data/spec/text_utils/code_highlighter_spec.rb +38 -0
- data/spec/text_utils/custom_markdown_spec.rb +82 -0
- data/spec/text_utils/format_qualifier_spec.rb +37 -0
- data/spec/text_utils/html_sanitizer_spec.rb +88 -0
- data/spec/text_utils/markdown_spec.rb +114 -0
- data/spec/text_utils/pipe_spec.rb +35 -0
- data/spec/text_utils/spec_helper.rb +26 -0
- data/spec/text_utils/text_processor_shared.rb +9 -0
- data/spec/text_utils/truncate_spec.rb +22 -0
- metadata +57 -47
- data/lib/kit/http_controller.rb +0 -4
- data/lib/kit/text_utils/code_highlighter.rb +0 -58
- data/lib/kit/text_utils/custom_markdown.rb +0 -90
- data/lib/kit/text_utils/ensure_utf.rb +0 -8
- data/lib/kit/text_utils/github_flavoured_markdown.rb +0 -32
- data/lib/kit/text_utils/image_box.rb +0 -35
- data/lib/kit/text_utils/markup.rb +0 -43
- data/lib/kit/text_utils/processor.rb +0 -25
- data/lib/kit/text_utils/tag_shortcuts.rb +0 -14
- data/lib/kit/text_utils/truncate.rb +0 -29
- data/lib/kit/text_utils/truncator.rb +0 -15
- data/lib/kit/text_utils/urls.rb +0 -13
- data/lib/kit/text_utils.rb +0 -43
- data/spec/utils/text_utils_spec.rb +0 -280
@@ -0,0 +1,24 @@
|
|
1
|
+
class Rad::Captcha
|
2
|
+
attr_accessor :public_key, :private_key, :timeout, :enabled, :verify_url
|
3
|
+
attr_required :private_key, :public_key
|
4
|
+
def enabled?; !!enabled end
|
5
|
+
|
6
|
+
def verify request, params
|
7
|
+
recaptcha = nil
|
8
|
+
Timeout::timeout(timeout || 3) do
|
9
|
+
recaptcha = Net::HTTP.post_form URI.parse(verify_url), {
|
10
|
+
'privatekey' => private_key,
|
11
|
+
'remoteip' => request.ip,
|
12
|
+
'challenge' => params.recaptcha_challenge_field,
|
13
|
+
'response' => params.recaptcha_response_field
|
14
|
+
}
|
15
|
+
end
|
16
|
+
result = recaptcha.body.split.map { |s| s.chomp }
|
17
|
+
answer, error = result
|
18
|
+
answer == 'true'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
rad.register :captcha, depends_on: [:template, :controller] do
|
23
|
+
Rad::Captcha.new
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rad::Controller::Captcha
|
2
|
+
inherited do
|
3
|
+
around :ensure_registered_or_human
|
4
|
+
end
|
5
|
+
|
6
|
+
protected
|
7
|
+
def ensure_registered_or_human
|
8
|
+
if rad.captcha.enabled? and rad.user.anonymous? and !request.get?
|
9
|
+
# right now we support capcha for :js format only
|
10
|
+
if request.from_browser? and params.format == 'js'
|
11
|
+
if rad.captcha.verify request, params
|
12
|
+
yield
|
13
|
+
else
|
14
|
+
render '/kit/captcha/action'
|
15
|
+
end
|
16
|
+
else
|
17
|
+
raise_user_error t(:registered_user_or_human_required)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/kit/gems.rb
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
# core gems
|
2
|
-
gem '
|
2
|
+
gem 'i18n', '0.5.0'
|
3
|
+
gem 'redcarpet', '1.17.2'
|
3
4
|
gem 'sanitize', '1.2.1'
|
4
5
|
gem 'stringex', '1.2.0'
|
5
6
|
gem 'state_machine', '0.10.4'
|
6
|
-
# gem '
|
7
|
-
gem '
|
8
|
-
gem 'coderay', '0.9.7'
|
7
|
+
# gem 'factory_girl', '1.3.3'
|
8
|
+
gem 'recaptcha', '0.3.1'
|
9
9
|
|
10
10
|
if respond_to? :fake_gem
|
11
11
|
fake_gem 'rad_core'
|
12
|
-
fake_gem 'rad_ext'
|
13
12
|
fake_gem 'rad_common_interface'
|
14
13
|
fake_gem 'rad_assets'
|
15
14
|
fake_gem 'mongoid_misc'
|
16
|
-
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# old
|
18
|
+
# gem 'bluecloth', '2.0.9'
|
19
|
+
# gem 'paperclip', '2.3.1.1'
|
20
|
+
# gem 'coderay', '0.9.7'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# {
|
2
|
+
# :'ru' => {
|
3
|
+
# pluralize: lambda { |n|
|
4
|
+
# # Правило плюрализации для русского языка, взято из CLDR, http://unicode.org/cldr/
|
5
|
+
# #
|
6
|
+
# #
|
7
|
+
# # Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
|
8
|
+
# #
|
9
|
+
# # one -> n mod 10 is 1 and n mod 100 is not 11;
|
10
|
+
# # few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
|
11
|
+
# # many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
|
12
|
+
# # other -> everything else
|
13
|
+
# #
|
14
|
+
# # Пример
|
15
|
+
# #
|
16
|
+
# # :one = 1, 21, 31, 41, 51, 61...
|
17
|
+
# # :few = 2-4, 22-24, 32-34...
|
18
|
+
# # :many = 0, 5-20, 25-30, 35-40...
|
19
|
+
# # :other = 1.31, 2.31, 5.31...
|
20
|
+
# modulo10 = n.modulo(10)
|
21
|
+
# modulo100 = n.modulo(100)
|
22
|
+
#
|
23
|
+
# if modulo10 == 1 && modulo100 != 11
|
24
|
+
# :one
|
25
|
+
# elsif (modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)
|
26
|
+
# :few
|
27
|
+
# elsif modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)
|
28
|
+
# :many
|
29
|
+
# else
|
30
|
+
# :other
|
31
|
+
# end
|
32
|
+
# }
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
|
36
|
+
{
|
37
|
+
ru: {
|
38
|
+
i18n: {
|
39
|
+
plural: {
|
40
|
+
rule: lambda{|n|
|
41
|
+
# Правило плюрализации для русского языка, взято из CLDR, http://unicode.org/cldr/
|
42
|
+
#
|
43
|
+
#
|
44
|
+
# Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
|
45
|
+
#
|
46
|
+
# one -> n mod 10 is 1 and n mod 100 is not 11;
|
47
|
+
# few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
|
48
|
+
# many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
|
49
|
+
# other -> everything else
|
50
|
+
#
|
51
|
+
# Пример
|
52
|
+
#
|
53
|
+
# :one = 1, 21, 31, 41, 51, 61...
|
54
|
+
# :few = 2-4, 22-24, 32-34...
|
55
|
+
# :many = 0, 5-20, 25-30, 35-40...
|
56
|
+
# :other = 1.31, 2.31, 5.31...
|
57
|
+
n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
data/lib/kit/i18n.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Hack, ActiveSupport currently uses differrent version
|
2
|
+
# gem 'i18n', '>= 0.4.1'
|
3
|
+
# require 'i18n'
|
4
|
+
|
5
|
+
require "i18n/backend/pluralization"
|
6
|
+
I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
|
7
|
+
|
8
|
+
I18n.load_path += Dir["#{__FILE__.dirname}/i18n/locales/*/*.{rb,yml}"]
|
9
|
+
|
10
|
+
|
11
|
+
#
|
12
|
+
# Helpers for Rad
|
13
|
+
#
|
14
|
+
[Rad::Controller::Abstract, Rad::Controller::Context].each do |klass|
|
15
|
+
klass.delegate :t, to: I18n
|
16
|
+
end
|
data/lib/kit/kit.rb
CHANGED
@@ -2,14 +2,14 @@ require 'kit/support'
|
|
2
2
|
|
3
3
|
# Configs
|
4
4
|
class Rad::Kit
|
5
|
-
attr_accessor :default_item, :fs_prefix, :fs_type, :fs_cache_path, :fs_path, :tags_count
|
6
|
-
|
5
|
+
attr_accessor :default_item, :fs_prefix, :fs_type, :fs_cache_path, :fs_path, :tags_count, :use_code_highlighter
|
6
|
+
attr_required :tags_count, :fs_prefix, :fs_type, :fs_path, :fs_cache_path
|
7
7
|
def items; @items ||= [] end
|
8
8
|
end
|
9
9
|
|
10
10
|
rad.router.class.class_eval do
|
11
11
|
attr_accessor :default_url
|
12
|
-
|
12
|
+
attr_required :default_url
|
13
13
|
end
|
14
14
|
|
15
15
|
rad.config.class.class_eval do
|
@@ -18,12 +18,15 @@ rad.config.class.class_eval do
|
|
18
18
|
def default_viewers; @default_viewers ||= [] end
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
21
|
# Kit
|
23
22
|
#
|
24
|
-
# TODO3 move :text_utils to standalone
|
23
|
+
# TODO3 move :text_utils to standalone gem
|
25
24
|
%w(
|
26
25
|
support
|
27
|
-
|
28
|
-
|
26
|
+
controller
|
27
|
+
i18n
|
28
|
+
kit_text_utils
|
29
|
+
|
30
|
+
misc/prepare_model
|
31
|
+
misc/user_error
|
29
32
|
).each{|f| require "kit/#{f}"}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'text_utils'
|
2
|
+
|
3
|
+
module TextUtils
|
4
|
+
class << self
|
5
|
+
# TODO2 rename
|
6
|
+
def markup data
|
7
|
+
ps = []
|
8
|
+
ps << EnsureUtf
|
9
|
+
ps << HtmlSanitizer
|
10
|
+
ps << FormatQualifier
|
11
|
+
ps << CodeHighlighter if rad.config.use_code_highlighter
|
12
|
+
ps << CustomMarkdown
|
13
|
+
ps << Markdown
|
14
|
+
|
15
|
+
markup = Pipe.new *ps
|
16
|
+
markup.call data
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def truncate data, length
|
21
|
+
truncate = Pipe.new(
|
22
|
+
EnsureUtf,
|
23
|
+
HtmlSanitizer,
|
24
|
+
FormatQualifier,
|
25
|
+
[Truncate, length]
|
26
|
+
)
|
27
|
+
truncate.call data
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Rad::Controller::Abstract::ClassMethods.class_eval do
|
2
|
+
def prepare_model aclass, opt = {}
|
3
|
+
opt = opt.symbolize_keys
|
4
|
+
id = opt.delete(:id) || :id
|
5
|
+
variable = opt.delete(:variable) || aclass.alias.underscore
|
6
|
+
|
7
|
+
finder = opt.delete(:finder) || :find!
|
8
|
+
|
9
|
+
method = "prepare_#{variable}"
|
10
|
+
define_method method do
|
11
|
+
model = aclass.send finder, params[id]
|
12
|
+
instance_variable_set "@#{variable}", model
|
13
|
+
end
|
14
|
+
before method, opt
|
15
|
+
end
|
16
|
+
end
|
@@ -43,9 +43,8 @@ module Mongoid::Authorized
|
|
43
43
|
#
|
44
44
|
# Roles
|
45
45
|
#
|
46
|
-
def anonymous?
|
47
|
-
|
48
|
-
end
|
46
|
+
def self.anonymous? name; name == 'anonymous' end
|
47
|
+
def anonymous?; Mongoid::Authorized.anonymous?(name) end
|
49
48
|
|
50
49
|
def registered?
|
51
50
|
!anonymous?
|
@@ -120,8 +119,8 @@ module Mongoid::Authorized
|
|
120
119
|
operation = operation.to_s
|
121
120
|
|
122
121
|
return true if has_role?(:admin)
|
123
|
-
|
124
|
-
custom_method = "able_#{operation}?"
|
122
|
+
|
123
|
+
custom_method = "able_#{operation}?"
|
125
124
|
return object.send custom_method, self if object.respond_to? custom_method
|
126
125
|
|
127
126
|
(
|
@@ -155,10 +154,9 @@ module Mongoid::Authorized
|
|
155
154
|
end
|
156
155
|
|
157
156
|
protected
|
158
|
-
def calculate_effective_roles_for roles
|
157
|
+
def calculate_effective_roles_for roles
|
159
158
|
effective_permissions = {}
|
160
|
-
permissions = ::Mongoid::Authorized.permissions
|
161
|
-
# permissions = rad.config.permissions(DEFAULT_PERMISSIONS).to_h #.to_h(to_s: true)
|
159
|
+
permissions = ::Mongoid::Authorized.permissions
|
162
160
|
permissions.each do |operation, allowed_roles|
|
163
161
|
operation = operation.to_s
|
164
162
|
effective_permissions[operation.to_s] = roles.any?{|role| allowed_roles.include? role}
|
@@ -13,7 +13,7 @@ module Mongoid::AuthorizedObject
|
|
13
13
|
default: [],
|
14
14
|
protected: true
|
15
15
|
|
16
|
-
# Contains the role and all upper roles. So complex becouse we need it in indexes.
|
16
|
+
# Contains the role and all upper roles. So complex becouse we need it in indexes.
|
17
17
|
field :viewers,
|
18
18
|
type: Array,
|
19
19
|
default: lambda{(
|
@@ -128,14 +128,13 @@ module Mongoid::AuthorizedObject
|
|
128
128
|
def normalized_collaborators
|
129
129
|
unless normalized_collaborators = cache[:normalized_collaborators]
|
130
130
|
normalized_collaborators = Role.denormalize_to_higher_roles collaborators
|
131
|
-
normalized_collaborators << "user:#{owner_name}"
|
131
|
+
normalized_collaborators << "user:#{owner_name}" unless Mongoid::Authorized.anonymous?(owner_name)
|
132
132
|
normalized_collaborators.sort!
|
133
133
|
cache[:normalized_collaborators] = normalized_collaborators
|
134
134
|
end
|
135
135
|
normalized_collaborators
|
136
136
|
end
|
137
|
-
|
138
|
-
|
137
|
+
|
139
138
|
#
|
140
139
|
# Special Permissions
|
141
140
|
#
|
File without changes
|
data/lib/kit/models_after.rb
CHANGED
@@ -22,7 +22,7 @@ module Mongoid::TextProcessor
|
|
22
22
|
|
23
23
|
define_method "#{original_attr_name}=" do |value|
|
24
24
|
send "#{original_attr_name}_without_markup=", value
|
25
|
-
send "#{attr_name}_without_markup=",
|
25
|
+
send "#{attr_name}_without_markup=", TextUtils.markup(value)
|
26
26
|
end
|
27
27
|
|
28
28
|
define_method "#{attr_name}_as_text" do
|
data/lib/kit/mongoid.rb
CHANGED
@@ -14,9 +14,9 @@ end
|
|
14
14
|
|
15
15
|
%w(
|
16
16
|
text_processor
|
17
|
-
|
17
|
+
rad_miscellaneous
|
18
18
|
).each{|n| require "kit/mongoid/#{n}"}
|
19
19
|
|
20
|
-
(rad.extension(:mm_plugins){[]} + [Mongoid::
|
20
|
+
(rad.extension(:mm_plugins){[]} + [Mongoid::RadMiscellaneous]).each do |plugin|
|
21
21
|
Mongoid::Document.include plugin
|
22
22
|
end
|
data/lib/kit/spec.rb
CHANGED
data/lib/kit/tasks.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
class TextUtils::CodeHighlighter < TextUtils::Processor
|
2
|
+
# highlights code inside of <code lang/language='java'> ... code ... </code>
|
3
|
+
def call data, env
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
snippets = {}
|
7
|
+
|
8
|
+
# processign html :code tags
|
9
|
+
data = data.gsub /(<code.*?>)(.+?)(<\/code\s*>)/im do
|
10
|
+
node = Nokogiri::HTML($1 + $3).css('code').first
|
11
|
+
language = node.attributes['lang'].try(:value) || node.attributes['language'].try(:value)
|
12
|
+
code = $2
|
13
|
+
|
14
|
+
if language and code
|
15
|
+
attributes = {}; node.attributes.each{|name, value| attributes[name] = value.value}
|
16
|
+
code = colorize code, language, attributes
|
17
|
+
cut_snippet snippets, code
|
18
|
+
else
|
19
|
+
$&
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# processign markdown ``` tags
|
24
|
+
data = data.gsub /^```\s*([a-z\-_0-9]+)\s*\n(.+?)^```\s*$/im do
|
25
|
+
language, code = $1, $2
|
26
|
+
|
27
|
+
if language and code
|
28
|
+
code = colorize code, language, {language: language}
|
29
|
+
cut_snippet snippets, code
|
30
|
+
else
|
31
|
+
$&
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
data = call_next data, env
|
36
|
+
|
37
|
+
restore_snippet snippets, data
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
# temporarilly removing all highlighted code from html to prevent it's beed damaged by next processors
|
42
|
+
def cut_snippet snippets, code
|
43
|
+
key = "CODESNIPPET#{snippets.size}"
|
44
|
+
snippets[key] = code
|
45
|
+
key
|
46
|
+
end
|
47
|
+
|
48
|
+
# inserting cutted code back to html
|
49
|
+
def restore_snippet snippets, data
|
50
|
+
data = data.gsub /(CODESNIPPET[0-9]+)/ do |key|
|
51
|
+
snippets[key]
|
52
|
+
end
|
53
|
+
data
|
54
|
+
end
|
55
|
+
|
56
|
+
def colorize code, language, attributes
|
57
|
+
require 'albino'
|
58
|
+
code = Albino.colorize(code, language.to_sym)
|
59
|
+
code = "<code #{attributes.to_a.collect{|k, v| "#{k}='#{v}'"}.join(' ')}>\n#{code}\n</code>"
|
60
|
+
code = rewrite_styles code
|
61
|
+
end
|
62
|
+
|
63
|
+
# adding prefix 'hl_' to all class names
|
64
|
+
def rewrite_styles html
|
65
|
+
node = Nokogiri::HTML(html).css('code').first
|
66
|
+
node.css("*").each do |e|
|
67
|
+
classes = e.attribute 'class'
|
68
|
+
if classes and classes.value
|
69
|
+
classes = classes.value.strip.split(/\s+/).collect{|c| "hl_#{c}"}.join(' ')
|
70
|
+
e['class'] = classes
|
71
|
+
end
|
72
|
+
end
|
73
|
+
node.to_s
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class TextUtils::CustomMarkdown < TextUtils::Processor
|
2
|
+
def call data, env
|
3
|
+
if env[:format] == :markdown
|
4
|
+
hide_html_tags data do |data|
|
5
|
+
image_box data do |data|
|
6
|
+
call_next data, env
|
7
|
+
end
|
8
|
+
end
|
9
|
+
else
|
10
|
+
call_next data, env
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
def hide_html_tags data, &block
|
16
|
+
snippets, counter = {}, 0
|
17
|
+
data = data.gsub /<.+?>/ do
|
18
|
+
key = "HTMLSNIPPET#{counter}"; counter += 1
|
19
|
+
snippets[key] = $&
|
20
|
+
key
|
21
|
+
end
|
22
|
+
|
23
|
+
data = block.call data
|
24
|
+
|
25
|
+
data = data.gsub /HTMLSNIPPET\d+/ do
|
26
|
+
snippets[$&]
|
27
|
+
end
|
28
|
+
data
|
29
|
+
end
|
30
|
+
|
31
|
+
# !![img] => [![img_thumb]][img]
|
32
|
+
def image_box data, &block
|
33
|
+
img_urls = {}
|
34
|
+
data = data.gsub(/!!\[(.+?)\]/) do
|
35
|
+
img_key = $1 || ''
|
36
|
+
if url = data.scan(/\[#{img_key}\]:\s*([^\s]+)$/).first.try(:first)
|
37
|
+
unless url =~ /\.[^\.]+\.[^\.]+$/ # image.png
|
38
|
+
thumb_img_key = "#{img_key}_thumb"
|
39
|
+
|
40
|
+
# building url with thumb (foo.png => foo.thumb.png)
|
41
|
+
img_urls[thumb_img_key] = url.sub(/\.([^\.]+)$/){".thumb.#{$1}"}
|
42
|
+
|
43
|
+
"[![][#{thumb_img_key}]][#{img_key}]"
|
44
|
+
else # image.(icon|thumb|...).png
|
45
|
+
img_key_full = "#{img_key}_full"
|
46
|
+
|
47
|
+
# building url with thumb (foo.png => foo.thumb.png)
|
48
|
+
img_urls[img_key_full] = url.sub(/\.([^\.]+)\.([^\.]+)$/){".#{$2}"}
|
49
|
+
|
50
|
+
"[![][#{img_key}]][#{img_key_full}]"
|
51
|
+
end
|
52
|
+
else
|
53
|
+
$& || ''
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
unless img_urls.blank?
|
58
|
+
data << "\n"
|
59
|
+
data << img_urls.to_a.collect{|k, v| "[#{k}]: #{v}"}.join("\n")
|
60
|
+
end
|
61
|
+
|
62
|
+
block.call data
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class TextUtils::EnsureUtf < TextUtils::Processor
|
2
|
+
def call data, env
|
3
|
+
data = call_next data, env
|
4
|
+
|
5
|
+
# Escape all non-word unicode symbols, otherwise it will raise error when converting to BSON
|
6
|
+
data = Iconv.conv('UTF-8//IGNORE//TRANSLIT', 'UTF-8', data)
|
7
|
+
|
8
|
+
unless data.encoding == Encoding::UTF_8
|
9
|
+
raise "something wrong happens, invalid encoding (#{data.encoding} instead of utf-8)!"
|
10
|
+
end
|
11
|
+
|
12
|
+
data
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class TextUtils::FormatQualifier < TextUtils::Processor
|
2
|
+
def call data, env
|
3
|
+
env[:format] = (
|
4
|
+
(data =~ /\A\s*<[a-z_\-0-9]+>.*<\/[a-z_\-0-9]+>\s*\z/im) or
|
5
|
+
(data =~ /\A\s*<[a-z_\-0-9]+\/>\s*\z/i)
|
6
|
+
) ? :html : :markdown
|
7
|
+
|
8
|
+
data = call_next data, env
|
9
|
+
|
10
|
+
raise "some processor in pipe clear the data format!" unless env[:format]
|
11
|
+
data
|
12
|
+
end
|
13
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
class
|
1
|
+
class TextUtils::HtmlSanitizer < TextUtils::Processor
|
2
2
|
RELAXED = {
|
3
3
|
elements: [
|
4
4
|
'a', 'b', 'blockquote', 'br', 'caption', 'cite', 'code', 'col',
|
5
5
|
'colgroup', 'dd', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
6
6
|
'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong',
|
7
7
|
'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u',
|
8
|
-
'ul', 'div', 'font', 'span'],
|
8
|
+
'ul', 'div', 'font', 'span', 'iframe'],
|
9
9
|
|
10
10
|
attributes: {
|
11
11
|
:all => ['class', 'style'],
|
@@ -20,7 +20,9 @@ class Rad::TextUtils::HtmlSanitizer < Rad::TextUtils::Processor
|
|
20
20
|
'td' => ['abbr', 'axis', 'colspan', 'rowspan', 'width'],
|
21
21
|
'th' => ['abbr', 'axis', 'colspan', 'rowspan', 'scope', 'width'],
|
22
22
|
'ul' => ['type'],
|
23
|
-
'code' => ['lang', 'language']
|
23
|
+
'code' => ['lang', 'language'],
|
24
|
+
|
25
|
+
'iframe' => ['height', 'scrolling', 'src', 'width']
|
24
26
|
},
|
25
27
|
|
26
28
|
protocols: {
|
@@ -76,10 +78,10 @@ class Rad::TextUtils::HtmlSanitizer < Rad::TextUtils::Processor
|
|
76
78
|
{:whitelist_nodes => [node, parent]}
|
77
79
|
end
|
78
80
|
|
79
|
-
def
|
80
|
-
|
81
|
+
def call data, env
|
82
|
+
data = call_next data, env
|
81
83
|
|
82
|
-
Sanitize.clean(
|
84
|
+
Sanitize.clean(data, RELAXED.merge(
|
83
85
|
transformers: [EMBEDDED_VIDEO],
|
84
86
|
:add_attributes => {
|
85
87
|
all: [:class]
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class TextUtils::Markdown < TextUtils::Processor
|
2
|
+
DEFAULT_OPTIONS = [:autolink, :lax_htmlblock, :smart, :tables, :xhtml, :fenced_code, :strikethrough, :hard_wrap]
|
3
|
+
|
4
|
+
def initialize processor = nil, options = nil
|
5
|
+
super processor
|
6
|
+
@options = options || DEFAULT_OPTIONS
|
7
|
+
end
|
8
|
+
|
9
|
+
def call data, env
|
10
|
+
if env[:format] == :markdown
|
11
|
+
require 'redcarpet'
|
12
|
+
|
13
|
+
data = fix_new_lines data do |data|
|
14
|
+
markdown = Redcarpet.new(data, *@options)
|
15
|
+
markdown.to_html
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
call_next data, env
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
# remove line breaks after images
|
24
|
+
def fix_new_lines data, &block
|
25
|
+
|
26
|
+
|
27
|
+
data = block.call data
|
28
|
+
|
29
|
+
data.gsub /(<img.+?\/>)\s*(<br\s*\/>)\s*\n/ do
|
30
|
+
$1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|