acts_as_avatar 1.8.4

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.
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsAvatar
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_avatar(**options)
10
+ random_image_engine = options.delete(:random_image_engine)
11
+ inline_svg_engine = options.delete(:inline_svg_engine)
12
+
13
+ define_singleton_method :random_image_engine do
14
+ random_image_engine.presence || ActsAsAvatar.configuration.random_image_engine
15
+ end
16
+
17
+ define_singleton_method :inline_svg_engine do
18
+ inline_svg_engine.presence || ActsAsAvatar.configuration.inline_svg_engine
19
+ end
20
+
21
+ has_one :avatar, as: :avatarable, class_name: "ActsAsAvatar::Avatar"
22
+ accepts_nested_attributes_for :avatar
23
+ delegate :current_avatar, :default_avatar, :upload_avatar, to: :avatar
24
+ include InstanceMethods
25
+ # after_create_commit { create_avatar }
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ def avatar
31
+ super || create_avatar
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/numeric/bytes"
4
+
5
+ module ActsAsAvatar
6
+ @mutex = Mutex.new
7
+
8
+ class << self
9
+ def configuration
10
+ @mutex.synchronize do
11
+ @configuration ||= Configuration.new
12
+ end
13
+ end
14
+ end
15
+
16
+ def self.configure
17
+ yield configuration
18
+ end
19
+
20
+ class Configuration
21
+ attr_accessor :uifaces_gender,
22
+ :uifaces_limit,
23
+ :uifaces_uri,
24
+ :uifaces_api_key,
25
+ :random_image_engine,
26
+ :default_file_name,
27
+ :upload_max_size,
28
+ :class_type,
29
+ :avatar_name,
30
+ :inline_svg_engine,
31
+ :avatar_size,
32
+ :github_complexity,
33
+ :github_render_method,
34
+ :github_rounded_circle
35
+
36
+ def initialize
37
+ @uifaces_uri = "https://api.uifaces.co"
38
+ @uifaces_api_key = "72694e5b5dee4706edc0acad3d4291"
39
+ @uifaces_gender = nil
40
+ @uifaces_limit = 72
41
+ @random_image_engine = nil
42
+ @default_file_name = "default_avatar"
43
+ @upload_max_size = 2.megabytes
44
+ @class_type = %w[User]
45
+ @avatar_name = :name
46
+ @inline_svg_engine = :initial_avatar
47
+ @avatar_size = 60
48
+ @github_complexity = 5
49
+ @github_render_method = "square"
50
+ @github_rounded_circle = false
51
+ end
52
+
53
+ def default_options
54
+ instance_variables.to_h do |key|
55
+ [key.to_s.sub("@", "").to_sym, instance_variable_get(key)]
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsAvatar
4
+ class Engine < Rails::Engine
5
+ isolate_namespace ActsAsAvatar
6
+
7
+ initializer "acts_as_avatar.insert_into_active_record" do
8
+ ActiveSupport.on_load :active_record do
9
+ # include ActsAsAvatar::ClassMethods
10
+ ActiveRecord::Base.include ActsAsAvatar
11
+ ActiveRecord::Base.include ActsAsAvatar::ClassMethods
12
+ end
13
+
14
+ ActiveSupport.on_load :action_controller do
15
+ ActionController::Base.send(:helper, ActsAsAvatar::Helper)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/migration"
5
+
6
+ module ActsAsAvatar
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ include Rails::Generators::Migration
10
+
11
+ source_root File.join(__dir__, "templates")
12
+ desc "Install acts_as_avatar"
13
+
14
+ def self.next_migration_number(dirname)
15
+ if ActiveRecord::Base.timestamped_migrations
16
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
17
+ else
18
+ format("%<migration_number>.3d",
19
+ migration_number: current_migration_number(dirname) + 1)
20
+ end
21
+ end
22
+
23
+ def create_initializer
24
+ template "initializer.rb", "config/initializers/acts_as_avatar.rb"
25
+ end
26
+
27
+ def create_migration_file
28
+ migration_template(
29
+ "migration.rb.erb",
30
+ "db/migrate/acts_as_avatar_migration.rb",
31
+ migration_version: migration_version
32
+ )
33
+ puts "\nPlease run this migration:\n\n rails db:migrate"
34
+ end
35
+
36
+ private
37
+
38
+ def migration_version
39
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActsAsAvatar.configure do |config|
4
+ #
5
+ # Global configuration
6
+ #
7
+ config.default_file_name = "default_avatar"
8
+ config.upload_max_size = 2.megabytes
9
+ config.class_type = %w[User]
10
+ config.avatar_name = :fullname # Name for show letter
11
+ config.avatar_size = 60
12
+
13
+ #
14
+ # Optional value is
15
+ # :letter_avatar, :uifaces_avatar, :github_avatar, or :identicon_avatar
16
+ # When value is nil, disable engine
17
+ #
18
+ config.random_image_engine = :letter_avatar
19
+
20
+ #
21
+ # When random_image_engine is :github_avatar
22
+ #
23
+ config.github_complexity = 5
24
+ config.github_render_method = "square"
25
+ config.github_rounded_circle = false
26
+
27
+ #
28
+ # Optional value is :initial_avatar, :initials or :icodi_avatar
29
+ #
30
+ config.inline_svg_engine = :initial_avatar
31
+
32
+ #
33
+ # When random_image_engine is :uifaces
34
+ #
35
+ config.uifaces_limit = 72
36
+ config.uifaces_gender = nil
37
+ config.uifaces_uri = "https://api.uifaces.co"
38
+ config.uifaces_api_key = ""
39
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateActsAsAvatars < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ create_table :acts_as_avatars do |t|
6
+ t.references :avatarable, polymorphic: true, index: true, null: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ module ActsAsAvatar
6
+ class GithubAvatar
7
+ include Singleton
8
+
9
+ #
10
+ # Default options:
11
+ #
12
+ # complexity: 16
13
+ #
14
+ # redner_method: squad
15
+ #
16
+ def random_svg_avatar(size:, github_complexity:, github_render_method:, github_rounded_circle:)
17
+ source = File.join(File.expand_path("../../", File.dirname(__FILE__)), "github_avatar.js")
18
+ js = File.read(source)
19
+ context = ExecJS.compile(js)
20
+ context.call("getRandomAvatar", github_complexity, github_render_method, size, github_rounded_circle)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "initials"
4
+ require "execjs"
5
+ require "initial_avatar"
6
+ require "icodi"
7
+ require "nokogiri"
8
+
9
+ module ActsAsAvatar
10
+ module Helper
11
+ include ActsAsAvatar::Scrubber
12
+
13
+ def acts_as_avatar_tag(object, name: nil, size: nil, **options)
14
+ size = size.presence || ActsAsAvatar.configuration.avatar_size
15
+
16
+ current_avatar = object.current_avatar
17
+ blob = current_avatar.blob
18
+
19
+ if current_avatar.attached?
20
+ if blob.content_type == "image/svg+xml"
21
+ render_svg blob, size, options
22
+ else
23
+ image_tag current_avatar.variant(resize_to_fill: [size, size]), **options
24
+ end
25
+ else
26
+ inline_avatar_tag(object, name: name, size: size, **options)
27
+ end
28
+ rescue StandardError => e
29
+ puts e.inspect
30
+ puts e.backtrace.inspect
31
+ "".html_safe
32
+ end
33
+
34
+ def inline_avatar_tag(object, size:, name: nil, **options)
35
+ name = name.presence || ActsAsAvatar.configuration.avatar_name
36
+ text = object.send(name.to_sym)
37
+
38
+ inline_svg_engine = object.class.inline_svg_engine.to_sym
39
+
40
+ case inline_svg_engine.to_sym
41
+ when :initial_avatar
42
+ initial_avatar_tag(text, size: size, **options)
43
+ when :icodi_avatar
44
+ icodi_avatar_tag(text, size: size, **options)
45
+ when :initials
46
+ initials_tag(text, size: size, **options)
47
+ end
48
+ end
49
+
50
+ def initial_avatar_tag(text, size:, **options)
51
+ opts = options.extract!(:colors, :text_color, :font_weight, :font_family).compact
52
+
53
+ limit = options[:limit] || 1
54
+
55
+ image_tag InitialAvatar.avatar_data_uri(text.first(limit), size: size, **opts), **options
56
+ end
57
+
58
+ def initials_tag(text, size:, **options)
59
+ #
60
+ # number of different colors, default: 12
61
+ # maximal initials length, default: 3
62
+ # background shape, default: :cirlce
63
+ #
64
+ opts = options.extract!(:colors, :limit, :shape).compact
65
+
66
+ Initials.svg text, size: size, **opts
67
+ end
68
+
69
+ def github_avatar_tag(size:, **options)
70
+ opts = options.extract!(:github_complexity, :github_render_method, :github_rounded_circle).compact
71
+
72
+ opts = ActsAsAvatar.configuration.default_options.merge(opts)
73
+
74
+ ActsAsAvatar::GithubAvatar.instance.random_svg_avatar(
75
+ size: size,
76
+ github_complexity: opts[:github_complexity],
77
+ github_render_method: opts[:github_render_method],
78
+ github_rounded_circle: opts[:github_rounded_circle]
79
+ ).html_safe
80
+ end
81
+
82
+ def icodi_avatar_tag(text, size:, **options)
83
+ #
84
+ # https://github.com/DannyBen/icodi
85
+ #
86
+ # Default value:
87
+ #
88
+ # custom attribute:
89
+ #
90
+ # rounded_circle: nil
91
+ #
92
+ # pixels: 5
93
+ # mirror: :x
94
+ # color:
95
+ # density: 0.5
96
+ # stroke: 0.1
97
+ # jitter: 0
98
+ # background: #fff
99
+ # id: icodi
100
+ #
101
+ # SVG template to use.
102
+ # Can be :default, :html or a path to a file. Read more on Victor SVG Templates.
103
+ #
104
+ # template: default
105
+ opts = options.extract!(:pixels,
106
+ :density,
107
+ :mirror,
108
+ :color,
109
+ :stroke,
110
+ :jitter,
111
+ :id,
112
+ :template,
113
+ :rounded_circle,
114
+ :background)
115
+
116
+ pixels = opts[:pixels] || 8
117
+ density = opts[:density] || 0.33
118
+
119
+ svg = Icodi.new text, size: size, pixels: pixels, density: density, **opts
120
+ svg.render.html_safe
121
+ end
122
+
123
+ private
124
+
125
+ def render_svg(blob, size, options)
126
+ doc = Nokogiri::XML::Document.parse blob.download.to_s
127
+ svg = doc.root
128
+
129
+ title = options.delete(:title)
130
+ svg.add_child("<title>#{title}</title>") if title
131
+
132
+ svg[:width] = size
133
+ svg[:height] = size
134
+
135
+ options.each do |key, value|
136
+ svg[key] = value
137
+ end
138
+
139
+ sanitize(svg.to_s).html_safe
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "forwardable"
5
+ require "http"
6
+ require "uri"
7
+
8
+ module ActsAsAvatar
9
+ class Request
10
+ # private_class_method :new
11
+ include Singleton
12
+
13
+ URL = ActsAsAvatar.configuration.uifaces_uri
14
+ API_KEY = ActsAsAvatar.configuration.uifaces_api_key
15
+
16
+ %w[uifaces_gender uifaces_limit].each do |method_name|
17
+ attr_writer method_name.to_sym
18
+
19
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ #
21
+ # def uifaces_gender
22
+ # @uifaces_gender ||= ActsAsAvatar.configuration.instance_variable_get("@uifaces_gender")
23
+ # end
24
+ #
25
+ def #{method_name}
26
+ @#{method_name} ||= ActsAsAvatar.configuration.instance_variable_get("@#{method_name}")
27
+ end
28
+ RUBY
29
+ end
30
+
31
+ class << self
32
+ extend Forwardable
33
+ delegate %i[read_image] => :instance
34
+ end
35
+
36
+ def params
37
+ if uifaces_gender.nil?
38
+ "popular&limit=#{uifaces_limit}"
39
+ else
40
+ "gender%5B%5D=#{uifaces_gender}&limit=#{uifaces_limit}"
41
+ end
42
+ end
43
+
44
+ # Get a random image
45
+ #
46
+ # @example
47
+ # ActsAsAvatar::Request.read_image
48
+ #
49
+ # ActsAsAvatar::Request.read_image uifaces_limit: 20, uifaces_gender: "female"
50
+ #
51
+ # instance = ActsAsAvatar::Request.instance
52
+ # instance.uifaces_gender = "male"
53
+ # instance.uifaces_limit = 20
54
+ # instance.read_image
55
+ #
56
+ # @param [String] uifaces_gender
57
+ # the gender is male or female
58
+ # @param [Integer] uifaces_limit
59
+ # the count of the image records
60
+ #
61
+ # @api public
62
+ def read_image(options={})
63
+ # configuration = {
64
+ # uifaces_gender: ActsAsAvatar.configuration.uifaces_gender,
65
+ # uifaces_limit: ActsAsAvatar.configuration.uifaces_limit
66
+ # }
67
+ #
68
+ # configuration.update(options) if options.is_a?(Hash)
69
+
70
+ opts = ActsAsAvatar.configuration.default_options.merge(options)
71
+
72
+ @uifaces_gender = opts[:uifaces_gender]
73
+ @uifaces_limit = opts[:uifaces_limit]
74
+
75
+ URI.parse(image_url).open if image_url
76
+ end
77
+
78
+ private
79
+
80
+ def request
81
+ { success: true, res: HTTP.headers("x-api-key": API_KEY).get("#{URL}?#{params}") }
82
+ rescue StandardError => e
83
+ { success: false, message: e.to_s }
84
+ end
85
+
86
+ def image_url
87
+ return unless request[:success] && request[:res].status.success?
88
+
89
+ result = request[:res].parse(:json)
90
+ result[rand(uifaces_limit.to_i - 1)]["photo"]
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "loofah"
4
+
5
+ module ActsAsAvatar
6
+ module Scrubber
7
+ #
8
+ # rails: sanitize(svg.to_s, scrubber: scrubber).html_safe
9
+ #
10
+ def sanitize(unsafe_xml)
11
+ unsafe_xml = unsafe_xml.to_s
12
+ unsafe_xml.force_encoding "UTF-8"
13
+ return Loofah.xml_document(unsafe_xml).scrub!(scrubber).to_s if document?(unsafe_xml)
14
+
15
+ Loofah.xml_fragment(unsafe_xml).scrub!(scrubber).to_s
16
+ end
17
+
18
+ private
19
+
20
+ def scrubber
21
+ Loofah::Scrubber.new do |node|
22
+ node.remove if node.name == "script"
23
+ end
24
+ end
25
+
26
+ def document?(unsafe)
27
+ unsafe.include?("<?xml") || unsafe.include?("<!DOCTYPE")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsAvatar
4
+ VERSION = "1.8.4"
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "acts_as_avatar/generators/install_generator"
4
+
5
+ module ActsAsAvatar
6
+ autoload :VERSION, "acts_as_avatar/version"
7
+ autoload :Request, "acts_as_avatar/request"
8
+ autoload :GithubAvatar, "acts_as_avatar/github_avatar"
9
+ autoload :Scrubber, "acts_as_avatar/scrubber"
10
+
11
+ require_relative "acts_as_avatar/configuration"
12
+ require_relative "acts_as_avatar/class_methods"
13
+ require_relative "acts_as_avatar/helper"
14
+ require_relative "acts_as_avatar/engine" if defined? Rails::Engine
15
+ end