From 9724d3bf420e7f24d70602e85ced1ab9b18c9528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 16 Aug 2013 00:24:21 +0200 Subject: [PATCH 01/19] FIX: avatars in discourse's topic oneboxes --- lib/oneboxer/discourse_local_onebox.rb | 12 ++++---- .../templates/discourse_topic_onebox.hbrs | 29 +++++++++---------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/oneboxer/discourse_local_onebox.rb b/lib/oneboxer/discourse_local_onebox.rb index 3d500acb926..8329737dc9c 100644 --- a/lib/oneboxer/discourse_local_onebox.rb +++ b/lib/oneboxer/discourse_local_onebox.rb @@ -57,10 +57,12 @@ module Oneboxer post = topic.posts.first posters = topic.posters_summary.map do |p| - {username: p[:user][:username], - avatar: PrettyText.avatar_img(p[:user][:avatar_template], 'tiny'), - description: p[:description], - extras: p[:extras]} + { + username: p[:user].username, + avatar: PrettyText.avatar_img(p[:user].avatar_template, 'tiny'), + description: p[:description], + extras: p[:extras] + } end category = topic.category @@ -70,7 +72,7 @@ module Oneboxer quote = post.excerpt(SiteSetting.post_onebox_maxlength) args.merge! title: topic.title, - avatar: PrettyText.avatar_img(topic.user.username, 'tiny'), + avatar: PrettyText.avatar_img(topic.user.avatar_template, 'tiny'), posts_count: topic.posts_count, last_post: FreedomPatches::Rails4.time_ago_in_words(topic.last_posted_at, false, scope: :'datetime.distance_in_words_verbose'), age: FreedomPatches::Rails4.time_ago_in_words(topic.created_at, false, scope: :'datetime.distance_in_words_verbose'), diff --git a/lib/oneboxer/templates/discourse_topic_onebox.hbrs b/lib/oneboxer/templates/discourse_topic_onebox.hbrs index bcced049435..cce69d1baef 100644 --- a/lib/oneboxer/templates/discourse_topic_onebox.hbrs +++ b/lib/oneboxer/templates/discourse_topic_onebox.hbrs @@ -1,23 +1,20 @@ - - - From c9969738bfb4501f40cee5ebcee01703deb93396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 16 Aug 2013 00:26:22 +0200 Subject: [PATCH 02/19] FIX: N+1 query for avatars when searching for users --- app/models/user.rb | 9 +-------- lib/search/search_result.rb | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 56557b0e00c..6fdae105e50 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -297,13 +297,6 @@ class User < ActiveRecord::Base end end - def self.avatar_template(email) - user = User.select([:email, :use_uploaded_avatar, :uploaded_avatar_template, :uploaded_avatar_id]) - .where(email: Email.downcase(email)) - .first - user.avatar_template if user.present? - end - def self.gravatar_template(email) email_hash = self.email_hash(email) "//www.gravatar.com/avatar/#{email_hash}.png?s={size}&r=pg&d=identicon" @@ -314,7 +307,7 @@ class User < ActiveRecord::Base # - self oneboxes in open graph data # - emails def small_avatar_url - template = User.avatar_template(email) + template = avatar_template template.gsub("{size}", "60") end diff --git a/lib/search/search_result.rb b/lib/search/search_result.rb index 6e45bdb5324..17ef418da0d 100644 --- a/lib/search/search_result.rb +++ b/lib/search/search_result.rb @@ -32,7 +32,7 @@ class Search def self.from_user(u) SearchResult.new(type: :user, id: u.username_lower, title: u.username, url: "/users/#{u.username_lower}").tap do |r| - r.avatar_template = User.avatar_template(u.email) + r.avatar_template = u.avatar_template end end @@ -43,7 +43,7 @@ class Search def self.from_post(p) if p.post_number == 1 # we want the topic link when it's the OP - SearchResult.from_topic(p.topic) + SearchResult.from_topic(p.topic) else SearchResult.new(type: :topic, id: p.topic.id, title: p.topic.title, url: p.url) end From 490b057922b2c76a75c5a80663df313ad7487933 Mon Sep 17 00:00:00 2001 From: ofGEEK Date: Fri, 16 Aug 2013 11:07:25 +0800 Subject: [PATCH 03/19] Update client.zh_CN.yml --- config/locales/client.zh_CN.yml | 62 ++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 228da6e1a66..ca51e11b354 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -23,31 +23,31 @@ zh_CN: tiny: half_a_minute: "刚刚" less_than_x_seconds: - one: "< 1秒" + one: "< 1秒" other: "< %{count}秒" x_seconds: - one: "1秒" + one: "1秒" other: "%{count}秒" less_than_x_minutes: - one: "< 1分钟" + one: "< 1分钟" other: "< %{count}分钟" x_minutes: - one: "1分钟" + one: "1分钟" other: "%{count}分钟" about_x_hours: - one: "1小时" + one: "1小时" other: "%{count}小时" x_days: - one: "1天" + one: "1天" other: "%{count}天" about_x_years: - one: "1年" + one: "1年" other: "%{count}年" over_x_years: - one: "> 1年" + one: "> 1年" other: "> %{count}年" almost_x_years: - one: "1年" + one: "1年" other: "%{count}年" medium: x_minutes: @@ -132,6 +132,10 @@ zh_CN: saving: "保存中……" saved: "已保存!" + upload: "Upload" + uploading: "Uploading..." + uploaded: "Uploaded!" + choose_topic: none_found: "没有找到主题" title: @@ -211,6 +215,15 @@ zh_CN: error: "抱歉在修改你的电子邮箱时发生了错误,可能此邮箱已经被使用了?" success: "我们发送了一封确认信到此邮箱地址,请按照邮箱内指示完成确认。" + change_avatar: + title: "修改头像" + upload_instructions: "也可上传头像" + upload: "上传图片" + uploading: "正在上传图片……" + gravatar: "Gravatar" + gravatar_title: "修改你在Gravatar的头像" + uploaded_avatar: "已上传图片" + email: title: "电子邮箱" instructions: "你的电子邮箱绝不会公开给他人。" @@ -304,7 +317,9 @@ zh_CN: title: "最后使用的IP地址" avatar: title: "头像" - instructions: "我们目前使用 Gravatar 来基于你的邮箱生成头像" + instructions: + gravatar: "正在使用Gravatar头像" + uploaded_avatar: "正在使用上传的头像" title: title: "头衔" @@ -753,7 +768,7 @@ zh_CN: reply_as_new_topic: "回复为新主题" continue_discussion: "从 {{postLink}} 继续讨论:" follow_quote: "跳转至所引用的帖子" - deleted_by_author: + deleted_by_author: one: "(该帖已被作者撤销,如无报告则将在 %{count} 小时后自动被删除。)" other: "(该帖已被作者撤销,如无报告则将在 %{count} 小时后自动被删除。)" deleted_by: "删除者为" @@ -1166,15 +1181,11 @@ zh_CN: title: "日志" action: "操作" created_at: "创建" - screened_emails: - title: "被屏蔽的邮件地址" - description: "当有人试图用以下邮件地址注册时,将受到阻止或其它系统操作。" - email: "邮件地址" - last_match_at: "最近匹配" - match_count: "匹配" - actions: - block: "阻挡" - do_nothing: "无操作" + last_match_at: "最近匹配" + match_count: "匹配" + screened_actions: + block: "阻挡" + do_nothing: "无操作" staff_actions: title: "管理人员操作" instructions: "点击用户名和操作可以过滤列表。点击头像可以访问用户个人页面。" @@ -1187,6 +1198,14 @@ zh_CN: actions: delete_user: "删除用户" change_trust_level: "更改信任等级" + screened_emails: + title: "被屏蔽的邮件地址" + description: "当有人试图用以下邮件地址注册时,将受到阻止或其它系统操作。" + email: "邮件地址" + screened_urls: + title: "被屏蔽的URL" + description: "The URLs listed here were used in posts by users who have been identified as spammers." + url: "URL" impersonate: title: "假冒用户" @@ -1232,6 +1251,7 @@ zh_CN: unban_failed: "解禁此用户时发生了错误 {{error}}" ban_duration: "你计划禁止该用户多久?(天)" delete_all_posts: "删除所有帖子" + delete_all_posts_confirm: "You are about to delete %{posts} posts and %{topics} topics. Are you sure?" ban: "禁止" unban: "解禁" banned: "已禁止?" @@ -1266,7 +1286,7 @@ zh_CN: approve_bulk_success: "成功!所有选定的用户已批准并通知。" time_read: "阅读次数" delete: "删除用户" - delete_forbidden: + delete_forbidden: one: "用户已注册 %{count} 天或已有发帖后,则无法被删除。请先删除该用户的所有发帖后再试。" other: "用户已注册 %{count} 天或已有发帖后,则无法被删除。请先删除该用户的所有发帖后再试。" delete_confirm: "你确定要永久地从本站删除此用户?该操作无法撤销!" From 3a8fbdb9a87178f54e286be7b96278530e79085a Mon Sep 17 00:00:00 2001 From: ofGEEK Date: Fri, 16 Aug 2013 11:13:50 +0800 Subject: [PATCH 04/19] Update client.zh_CN.yml --- config/locales/client.zh_CN.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index ca51e11b354..186df4ed037 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -23,51 +23,51 @@ zh_CN: tiny: half_a_minute: "刚刚" less_than_x_seconds: - one: "< 1秒" + one: "< 1秒" other: "< %{count}秒" x_seconds: - one: "1秒" + one: "1秒" other: "%{count}秒" less_than_x_minutes: - one: "< 1分钟" + one: "< 1分钟" other: "< %{count}分钟" x_minutes: - one: "1分钟" + one: "1分钟" other: "%{count}分钟" about_x_hours: - one: "1小时" + one: "1小时" other: "%{count}小时" x_days: - one: "1天" + one: "1天" other: "%{count}天" about_x_years: - one: "1年" + one: "1年" other: "%{count}年" over_x_years: - one: "> 1年" + one: "> 1年" other: "> %{count}年" almost_x_years: - one: "1年" + one: "1年" other: "%{count}年" medium: x_minutes: - one: "1分钟" + one: "1分钟" other: "%{count}分钟" x_hours: - one: "1小时" + one: "1小时" other: "%{count}小时" x_days: - one: "1天" + one: "1天" other: "%{count}天" medium_with_ago: x_minutes: - one: "1分钟前" + one: "1分钟前" other: "%{count}分钟前" x_hours: - one: "1小时之前" + one: "1小时之前" other: "%{count}小时之前" x_days: - one: "1天前" + one: "1天前" other: "%{count}天前" share: topic: '分享一个到本主题的链接' From b2acf33a74dbd22e36b94c6247c9e0f8e57e9105 Mon Sep 17 00:00:00 2001 From: ofGEEK Date: Fri, 16 Aug 2013 11:16:17 +0800 Subject: [PATCH 05/19] Update client.zh_CN.yml --- config/locales/client.zh_CN.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 186df4ed037..cf07724b14f 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -86,7 +86,7 @@ zh_CN: generic_error: "抱歉,发生了一个错误。" generic_error_with_reason: "发生一个错误:%{error}" log_in: "登录" - age: "寿命" + age: "时间" last_post: "最后一帖" admin_title: "管理员" flags_title: "报告" @@ -132,9 +132,9 @@ zh_CN: saving: "保存中……" saved: "已保存!" - upload: "Upload" - uploading: "Uploading..." - uploaded: "Uploaded!" + upload: "上传" + uploading: "上传中……" + uploaded: "上传完成!" choose_topic: none_found: "没有找到主题" From 946b02533b9a5133edf6560e8d9848b60caa643e Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Thu, 15 Aug 2013 22:24:48 -0700 Subject: [PATCH 06/19] better copy for password change button --- .../discourse/templates/user/preferences.js.handlebars | 2 +- config/locales/client.en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars index f8c86ba31fe..361525de797 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars @@ -37,7 +37,7 @@
- {{passwordProgress}} + {{i18n user.change_password.action}} {{passwordProgress}}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ce9e289f19f..94d693b7d43 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -198,6 +198,7 @@ en: success: "(email sent)" in_progress: "(sending email)" error: "(error)" + action: "Send Password Reset Email" change_about: title: "Change About Me" From 2119774fb651b59c2d61054dc22dd53c551622de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 16 Aug 2013 09:58:20 +0200 Subject: [PATCH 07/19] FIX: custom avatars in email --- app/models/user.rb | 2 +- lib/jobs/generate_avatars.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 6fdae105e50..b014563c42f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -308,7 +308,7 @@ class User < ActiveRecord::Base # - emails def small_avatar_url template = avatar_template - template.gsub("{size}", "60") + template.gsub("{size}", "45") end def avatar_template diff --git a/lib/jobs/generate_avatars.rb b/lib/jobs/generate_avatars.rb index 9a22220eee7..abc4960a07f 100644 --- a/lib/jobs/generate_avatars.rb +++ b/lib/jobs/generate_avatars.rb @@ -21,8 +21,10 @@ module Jobs # create a temp file with the same extension as the original temp_file = Tempfile.new(["discourse-avatar", File.extname(original_path)]) temp_path = temp_file.path - # - Discourse.store.store_avatar(temp_file, upload, size) if ImageSorcery.new(original_path).convert(temp_path, gravity: "center", thumbnail: "#{size}x#{size}^", extent: "#{size}x#{size}", background: "transparent") + # create a centered square thumbnail + if ImageSorcery.new(original_path).convert(temp_path, gravity: "center", thumbnail: "#{size}x#{size}^", extent: "#{size}x#{size}", background: "transparent") + Discourse.store.store_avatar(temp_file, upload, size) + end # close && remove temp file temp_file.close! end From 865c882a61bdec4c8ef4a3711e1dc8e76d1f5fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 16 Aug 2013 10:14:05 +0200 Subject: [PATCH 08/19] larger avatars in embedded posts --- .../javascripts/discourse/templates/embedded_post.js.handlebars | 2 +- app/assets/stylesheets/application/topic-post.css.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars b/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars index 8c1e7d6f8b6..76da62dcae1 100644 --- a/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars +++ b/app/assets/javascripts/discourse/templates/embedded_post.js.handlebars @@ -2,7 +2,7 @@ \ No newline at end of file + diff --git a/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars b/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars new file mode 100644 index 00000000000..46e2be87b9d --- /dev/null +++ b/app/assets/javascripts/discourse/templates/modal/avatar_selector.js.handlebars @@ -0,0 +1,29 @@ + + + diff --git a/app/assets/javascripts/discourse/templates/user/avatar.js.handlebars b/app/assets/javascripts/discourse/templates/user/avatar.js.handlebars deleted file mode 100644 index e72fb175e27..00000000000 --- a/app/assets/javascripts/discourse/templates/user/avatar.js.handlebars +++ /dev/null @@ -1,39 +0,0 @@ -
- -
-
-

{{i18n user.change_avatar.title}}

-
-
- -
- -
- - {{#if has_uploaded_avatar}} - - {{/if}} -
-
- -
-
{{i18n user.change_avatar.upload_instructions}}
-
-
- -
- - {{#if uploading}} - {{i18n upload_selector.uploading}} {{uploadProgress}}% - {{/if}} -
-
- -
diff --git a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars index 361525de797..a3faeecaa04 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars @@ -44,7 +44,8 @@
- {{avatar model imageSize="large"}} + {{boundAvatar model imageSize="large"}} +
{{#if Discourse.SiteSettings.allow_uploaded_avatars}} @@ -53,7 +54,6 @@ {{else}} {{{i18n user.avatar.instructions.gravatar}}} {{email}} {{/if}} - {{#linkTo "preferences.avatar" class="btn pad-left"}}{{i18n user.change}}{{/linkTo}} {{else}} {{{i18n user.avatar.instructions.gravatar}}} {{email}} {{i18n user.change}} diff --git a/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js b/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js new file mode 100644 index 00000000000..dde8c1c0df0 --- /dev/null +++ b/app/assets/javascripts/discourse/views/modal/avatar_selector_view.js @@ -0,0 +1,89 @@ +/** + This view handles the avatar selection interface + + @class AvatarSelectorView + @extends Discourse.ModalBodyView + @namespace Discourse + @module Discourse +**/ +Discourse.AvatarSelectorView = Discourse.ModalBodyView.extend({ + templateName: 'modal/avatar_selector', + classNames: ['avatar-selector'], + title: I18n.t('user.change_avatar.title'), + uploading: false, + uploadProgress: 0, + uploadedAvatarDisabled: Em.computed.not("controller.has_uploaded_avatar"), + + didInsertElement: function() { + var view = this; + var $upload = $("#avatar-input"); + + this._super(); + + // simulate a click on the hidden file input when clicking on our fake file input + $("#fake-avatar-input").on("click", function(e) { + // do *NOT* use the cached `$upload` variable, because fileupload is cloning & replacing the input + // cf. https://github.com/blueimp/jQuery-File-Upload/wiki/Frequently-Asked-Questions#why-is-the-file-input-field-cloned-and-replaced-after-each-selection + $("#avatar-input").click(); + e.preventDefault(); + }); + + // define the upload endpoint + $upload.fileupload({ + url: Discourse.getURL("/users/" + this.get("controller.username") + "/preferences/avatar"), + dataType: "json", + timeout: 20000, + fileInput: $upload + }); + + // when a file has been selected + $upload.on("fileuploadadd", function (e, data) { + view.set("uploading", true); + }); + + // when there is a progression for the upload + $upload.on("fileuploadprogressall", function (e, data) { + var progress = parseInt(data.loaded / data.total * 100, 10); + view.set("uploadProgress", progress); + }); + + // when the upload is successful + $upload.on("fileuploaddone", function (e, data) { + // set some properties + view.get("controller").setProperties({ + has_uploaded_avatar: true, + use_uploaded_avatar: true, + uploaded_avatar_template: data.result.url + }); + }); + + // when there has been an error with the upload + $upload.on("fileuploadfail", function (e, data) { + Discourse.Utilities.displayErrorForUpload(data); + }); + + // when the upload is done + $upload.on("fileuploadalways", function (e, data) { + view.setProperties({ uploading: false, uploadProgress: 0 }); + }); + }, + + willDestroyElement: function() { + $("#fake-avatar-input").off("click"); + $("#avatar-input").fileupload("destroy"); + }, + + // *HACK* used to select the proper radio button + selectedChanged: function() { + var view = this; + Em.run.next(function() { + var value = view.get('controller.use_uploaded_avatar') ? 'uploaded_avatar' : 'gravatar'; + view.$('input:radio[name="avatar"]').val([value]); + }); + }.observes('controller.use_uploaded_avatar'), + + uploadButtonText: function() { + return this.get("uploading") ? I18n.t("uploading") : I18n.t("upload"); + }.property("uploading") + +}); diff --git a/app/assets/javascripts/discourse/views/user/preferences_avatar_view.js b/app/assets/javascripts/discourse/views/user/preferences_avatar_view.js deleted file mode 100644 index 4eb5d6eff6c..00000000000 --- a/app/assets/javascripts/discourse/views/user/preferences_avatar_view.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - This view handles rendering of a user's avatar uploader - - @class PreferencesAvatarView - @extends Discourse.View - @namespace Discourse - @module Discourse -**/ -Discourse.PreferencesAvatarView = Discourse.View.extend({ - templateName: "user/avatar", - classNames: ["user-preferences"], - - selectedChanged: function() { - var view = this; - Em.run.next(function() { - var value = view.get("controller.use_uploaded_avatar") ? "uploaded_avatar" : "gravatar"; - view.$('input:radio[name="avatar"]').val([value]); - }); - }.observes('controller.use_uploaded_avatar') - -}); diff --git a/app/assets/stylesheets/application/upload.scss b/app/assets/stylesheets/application/upload.css.scss similarity index 100% rename from app/assets/stylesheets/application/upload.scss rename to app/assets/stylesheets/application/upload.css.scss diff --git a/app/assets/stylesheets/application/user.css.scss b/app/assets/stylesheets/application/user.css.scss index a79ea581470..2fadb72b905 100644 --- a/app/assets/stylesheets/application/user.css.scss +++ b/app/assets/stylesheets/application/user.css.scss @@ -323,3 +323,18 @@ width: 680px; } } + +.avatar-selector { + label { + display: inline-block; + margin-right: 10px; + } + #avatar-input { + width: 0; + height: 0; + overflow: hidden; + } + .avatar { + margin: 5px 10px 5px 0; + } +} diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4137f24b311..681b4e9fef3 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -376,7 +376,7 @@ class UsersController < ApplicationController user.use_uploaded_avatar = params[:use_uploaded_avatar] user.save! - render json: { avatar_template: user.avatar_template } + render nothing: true end private diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 68a7acc2209..ccf8f58ea6b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -218,12 +218,11 @@ en: change_avatar: title: "Change your avatar" - upload_instructions: "Or you could upload an image" - upload: "Upload a picture" - uploading: "Uploading the picture..." - gravatar: "Gravatar" + gravatar: "Gravatar, based on" gravatar_title: "Change your avatar on Gravatar's website" - uploaded_avatar: "Uploaded picture" + uploaded_avatar: "Custom picture" + uploaded_avatar_empty: "Add a custom picture" + upload_title: "Upload your picture" email: title: "Email" From fb9f1ae3e8823d4cf8cacec22421ed5efeb5e06a Mon Sep 17 00:00:00 2001 From: Chris Hunt Date: Fri, 16 Aug 2013 15:01:02 -0700 Subject: [PATCH 17/19] Totally re-write favicon notifier - Black on white text - Larger font - Works with any size favicon (16, 32, 64) --- .../external/jquery.faviconNotify.js | 273 ++++-------------- 1 file changed, 50 insertions(+), 223 deletions(-) diff --git a/app/assets/javascripts/external/jquery.faviconNotify.js b/app/assets/javascripts/external/jquery.faviconNotify.js index e0c370b9d0c..984bd2dd0d3 100644 --- a/app/assets/javascripts/external/jquery.faviconNotify.js +++ b/app/assets/javascripts/external/jquery.faviconNotify.js @@ -1,226 +1,53 @@ /** - * jQuery Favicon Notify - * - * Updates the favicon to notify the user of changes. In the original tests I - * had an embedded font collection to allow any charachers - I decided that the - * ~130Kb and added complexity was overkill. As such it now uses a manual glyph - * set meaning that only numerical notifications are possible. - * - * Dual licensed under the MIT and GPL licenses: - * - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * @author David King - * @copyright Copyright (c) 2011 + - * @url oodavid.com - */ +* jQuery Favicon Notify +* +* Updates the favicon with a number to notify the user of changes. +* +* iconUrl: Url of favicon image or icon +* count: Integer count to place above favicon +* +* $.faviconNotify(iconUrl, count) +*/ (function($){ - var canvas; - var bg = '#000000'; - var fg = '#FFFFFF'; - var pos = 'br'; - $.faviconNotify = function(icon, num, myPos, myBg, myFg){ - // Default the positions - myPos = myPos || pos; - myFg = myFg || fg; - myBg = myBg || bg; - // Create a canvas if we need one - canvas = canvas || $('')[0]; - if(canvas.getContext){ - // Load the icon - $('').load(function(e){ - // Load the icon into the canvas - canvas.height = canvas.width = 16; - var ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(this, 0, 0); - // We gots num? - if(num !== undefined){ - num = parseFloat(num, 10); - // Convert the num into a glyphs array - var myGlyphs = []; - if(num > 99){ - myGlyphs.push(glyphs['LOTS']); - } else { - num = num.toString().split(''); - $.each(num, function(k,v){ - myGlyphs.push(glyphs[v]); - }); - } - if(num>0) { - // Merge the glyphs together - var combined = []; - var glyphHeight = myGlyphs[0].length; - $.each(myGlyphs, function(k,v){ - for(y=0; y').attr('href', canvas.toDataURL('image/png'))); - }).attr('src', icon) - } - }; - var glyphs = { - '0': [ - ' --- ', - ' -@@@- ', - '-@---@-', - '-@- -@-', - '-@- -@-', - '-@- -@-', - '-@---@-', - ' -@@@- ', - ' --- ' ], - '1': [ - ' - ', - ' -@- ', - '-@@- ', - ' -@- ', - ' -@- ', - ' -@- ', - ' -@- ', - '-@@@-', - ' --- ' ], - '2': [ - ' --- ', - ' -@@@- ', - '-@---@-', - ' - --@-', - ' -@@- ', - ' -@-- ', - '-@---- ', - '-@@@@@-', - ' ----- ' ], - '3': [ - ' --- ', - ' -@@@- ', - '-@---@-', - ' - --@-', - ' -@@- ', - ' - --@-', - '-@---@-', - ' -@@@- ', - ' --- ' ], - '4': [ - ' -- ', - ' -@@-', - ' -@-@-', - ' -@--@-', - '-@---@-', - '-@@@@@-', - ' ----@-', - ' -@-', - ' - ' ], - '5': [ - ' ----- ', - '-@@@@@-', - '-@---- ', - '-@--- ', - '-@@@@- ', - ' ----@-', - '-@---@-', - ' -@@@- ', - ' --- ' ], - '6': [ - ' --- ', - ' -@@@- ', - '-@---@-', - '-@---- ', - '-@@@@- ', - '-@---@-', - '-@---@-', - ' -@@@- ', - ' --- ' ], - '7': [ - ' ----- ', - '-@@@@@-', - ' ----@-', - ' -@- ', - ' -@- ', - ' -@- ', - ' -@- ', - ' -@- ', - ' - ' ], - '8': [ - ' --- ', - ' -@@@- ', - '-@---@-', - '-@---@-', - ' -@@@- ', - '-@---@-', - '-@---@-', - ' -@@@- ', - ' --- ' ], - '9': [ - ' --- ', - ' -@@@- ', - '-@---@-', - '-@---@-', - ' -@@@@-', - ' ----@-', - '-@---@-', - ' -@@@- ', - ' --- ' ], - '!': [ - ' - ', - '-@-', - '-@-', - '-@-', - '-@-', - '-@-', - ' - ', - '-@-', - ' - ' ], - '.': [ - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' - ', - '-@-', - ' - ' ], - 'LOTS': [ - ' - -- --- -- ', - '-@- -@@-@@@--@@-', - '-@--@--@-@--@- ', - '-@--@--@-@--@- ', - '-@--@--@-@- -@- ', - '-@--@--@-@- -@-', - '-@--@--@-@----@-', - '-@@@-@@--@-@@@- ', - ' --- -- - --- ' - ] - }; + $.faviconNotify = function(iconUrl, count){ + var canvas = canvas || $('')[0], + img = $('')[0], + multiplier, fontSize, context, xOffset, yOffset; + + if (canvas.getContext) { + if (count < 1) { count = '' } + else if (count < 10) { count = ' ' + count } + else if (count > 99) { count = '99' } + + img.onload = function () { + canvas.height = canvas.width = this.width; + multiplier = (this.width / 16); + + fontSize = multiplier * 11; + xOffset = multiplier; + yOffset = multiplier * 11; + + context = canvas.getContext('2d'); + context.drawImage(this, 0, 0); + context.font = 'bold ' + fontSize + 'px "helvetica", sans-serif'; + + context.fillStyle = '#FFF'; + context.fillText(count, xOffset, yOffset); + context.fillText(count, xOffset + 2, yOffset); + context.fillText(count, xOffset, yOffset + 2); + context.fillText(count, xOffset + 2, yOffset + 2); + + context.fillStyle = '#000'; + context.fillText(count, xOffset + 1, yOffset + 1); + + $('link[rel$=icon]').remove(); + $('head').append( + $('').attr( + 'href', canvas.toDataURL('image/png') + ) + ); + }; + img.src = iconUrl; + } + }; })(jQuery); From ef7231a66ff25b96bfd4230a670f7832f6250beb Mon Sep 17 00:00:00 2001 From: Chris Hunt Date: Fri, 16 Aug 2013 18:05:19 -0700 Subject: [PATCH 18/19] Add 32px image to default favicons (for retina) --- app/assets/images/default-favicon.ico | Bin 1342 -> 6518 bytes app/assets/images/favicon.ico | Bin 1150 -> 6518 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/app/assets/images/default-favicon.ico b/app/assets/images/default-favicon.ico index d20ae8eeded413f3de968472303a4dbc2a63409c..cab552a232024bfa1a894103544d0fdee3c377f1 100644 GIT binary patch literal 6518 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 CYoi|k literal 1342 zcmd^8ZBJ5R7=98VA)LGk2*<-opa2sX2uMIXK_&%QCI}&FTf`hq#7eUjAtPGNm+C_s zt@_lL-1=5Ou*sV)9?3-&<_atZy+oH357zj(AU?u1lbE*1MWkg!~LcH z{(iED2zX(qI|2>S_U`at`h9!TrMY1CX<{>rAmiFp$g7;2s_nAHLiI) zo_W->0hwyGjg5^x4hDlB$VUiy>P#lH5sSqd0|Nt8Ukp9YTP&6*kmG0igT&{1Y?p!DwX;Q`SM}-v8+%i)M%z~o&$WGAnbw9 zU^aR5bU&R=(+p3fdcD4czHKAm24Q5xz6-u6ld)N^*V~0Zb7C?awOVZz>;Z5cciwhg zaJ}g1>7g0;(3f|mQt4T<*`(DyLY+>>5Do`Aa8SRq*v~Bb7v?zb8+5BgBFRlm1U?|Y z-fp+gVt$g}hqPL)P^D6-LB9o1k7}6R!K@}HCtGs4{3UEk^t6io>8`A-v~gcyXXkYu zd~13-^B(>JUsTP4N&YApbFd_92}go*=(mFA4|b-IBU1tx6qemnM_7+q}`@| Ng)w*j*rfm1_!9uNhV}pe diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico index db5a23d7592ff5b62294d55b8fdba26fc5d1edbd..ac54b34b9fa7b6f5ad080a759aefda2c445e6dd5 100644 GIT binary patch literal 6518 zcmeHLc~n%_8GoipnpKmi#F@9un*{^`fx!)<2u0jLj95i!NK&LAE`S=0f=F>iQ;j)B zTq=SKSgBCZXktxjYZrUcp2qf^NQ13OB`5Kq0uF+J(OYR>DkRYgqtSxFfx#`4CQXgeX*lm&AdB8e%3q76yIw`wLMyU!`{Au} z5k(bGplr8*y?Ku#GE~6;g9RZ$Lmc_}`9qk7jxQ2!OxhtFAt4X8{RjK5evEys4fuIu zE%MJ6BJbF9_@KBK#tv0TCM(8`n{bkjKCIt2$wpZHUdNVy0!MBE)i;1&bvoxGH-Rk| zPb248f0V6!42opMAiu!Q?CgB|o%@mk-ywUj^8!%w6;RdDbuf*BPqw0Dzko=)0+Y!H zb5iFPIPaUG7>6JyHd1JsRNU<-DJekk~1S5>zP+CjawT^8O7RCB=BW-_d~VH_h-? zRG3w7q@<)4ELgB$p3ORtY;T5HszUI~?bz}8O^pWYkm)e*$B5?UvxhNkq!%R34?;jT z9C7^kajzF%2yc)j31)dbRP!gWC%0qyA8w%RDo{x@%3FYyzios)dL|?T>5gQMjv9B0 zNPeGvAaibJvj3pLP-Ky4Or&uPs{IRuOuvdzvoFIQa}tU<4zf|w?gj(|IkK{{H2>lI zMCYNnxEGfO1_VPPA6Cil%~BLh#_3QDW63sFm}FVI8yFbenx2wAoA0@Qm_}x1X7Z5G z;k7>AzU1#B*@bA4ZDh%Wfj)yUCOqOyYHE6y_v223*VWb4Pv_?5roZ}1a)(*9fpt4; zR$_fpQc`SfZSB+D&;P%F=Qyd-h@ipoRQpg2?f%i<={KF(7GuIAk+EPQ_U+qWw|Vp1 zQDpOHyW{lq{hQ*t-($a*M3Xi~hJ|D5)M>RlcNUj6G+Ybm30L2Dr}J&BWl5#?X4djT z;;ffiN=r*qNgqA>=eL3Bi3aOLG%6T9DgwE=E6RumEhgx4>$9i3yl0}ft_=@)l=nr^ zhUX17cn$E@#`6Ym7->(T-`r`kT{uU^$0w8$4|ip1_p|z%W2n{J1e?hKo2)`490Sug z(oDnGYQeNIB64Q+N8#+gSVto-sUH%ejTr2!YJAADix0Mk??8XNl=uqs&_xeA=AD`1 zwW@|<{*FD!ubPdFwH7SSdK}rwPhif}zQ{>>7Bz46!n+3rR8|S7sS$AUuz+(%1YD-? z=XXDhP4k{bke^xOL#N9;a2(zA#-;S)m`mg3_d@W)s=kBvAf(RpCs92qvc zt+KK*x@UZ{yClQUUV`nP0*5I-Yk8Y;RY`L_iW|@4Q2R|ScX9rz>bQx$P3KWqw*yJ< z1!E%dkzFC+-=vc-%f5$cQO0h1upo26qB;^yixE0+U1yPUVGdGu-O%b5J?Ao>6SHd` zuBQFNR9pPA&4Hi(V-;d69z_c2;vDht$*xB*Y%s-!+y1~h2pcg9+qP|+#5(ATtNRS) zQsY#F#$U$XE4Msi>O8s6%p3Eup{|&CcnlfDLnGN@*W%vVdvV7~OibF>GrkH{91cs+ zXIS^Qn_AuFj`MYEw%6*9H)8yOUMM~!;9~hh7-gq?D?5FG-{gx^;@g^=n?v1sa;+&p z8()V#{wnr1dak?f#RKc1vb_^&zllO3`NJ1vhlMk|w0OZ7xBU>=*~><|Fm>Hi-KO|c z-SuZoC}{Us^W6(u|BZF9<-)s&J@_z=6aJpp`aqOiZ-H@Fty;U`ZhV<)oEb3rT*UOE z>!>C=I?V@}SqBwut(bXqAhyt(Ji6*h?HzLLWK5$$#{T_fciyIp4-pn|1x&#X%-G)c zjq#aRt}_?>DG4jd2Wkl4Ot?Be`@phgxgPKpSd4E&4Rs)PW2;8vLF9HnTSNHEKFY!J zN&zKHdsAL?>0ifp*6F(Kc_w^uD=dM{2+ee$y!rm$pF1|M8Gq@S<#>beIX?`bJfiDa z*MG#AA87TfzOR}jSfuq-UpB!!vl(mt(lrKk$GF#b*8k$a&PP$1fS3_7Bsc$Kx!L~; z3pZ$a+J(=xdOB=k1C;TNi2YfsGyaf2-3!apZ_9z?X*6txI#}(OVE)ln+CB4IP0h0SsarYTJboY#Va)19cHe8@Sm zN4Y6pu&y@Nw!j`YgRrRwy{&hiWyy?TL&H!}QIV$OpZxX7A4iX!KsnF-EX5(^CuE1u zV6!zqozw{Hs}3aWXv3CsU!kg<_@I8Av3O{`Ibj!H1m^5-g>Pg&{dd5!a3AcnyN;il zI;XA?jeT@{y8hIXB}*%~r|PN)>OGkU<2l%rE3k%LhD^E`nCw8*s#YYIwhXwr$&er~c4+XlZE)NJ&Y%!oK0oFZYUOWfH7n zCF$YXo@yb(5LbdQH9}cSQ-wd#C== z@p&ejgiRP9bNOx_6ipVhGMQq-N?48Muo^xfo_HLfcvV0-BS!0wIevA=W}W(Y`(ftv znTH7bt{T{#hk}9)VX-lB#Dm(+7HmhR&HXd4>AwYS@S69WJa)%sxw%KnUcBUAgdOCL z)zkXKi4$RS)8{vFZ{GD?>VCwS?nB>q%`7wbAMESN$;sS*Jk=APr~PY9*X%htd8JgZ zwpuMdT5s3YH(glnGPArqSl^Rlr?jkGxtja0Cp}@@&t6ecQAk?a+|rmylUoD)gS5QO z^xZLe&3o)Oq4r^j2p?O!X3d7uy1Kfr?LY5_2R>6@Umvt*&z{_^Zx?}l6c)3$<{zvR lez()7&%~;N)pZ#e8Qg#O*l*r1-goD|)4+Ec_}|ySe*v{+prHT& literal 1150 zcma))&npCB7{}j@a&Ryq2ft=^XNyovoJegFii4Ab#FoT~i-T5b50ryQDdplq3GJvY zd$Qx^=np_iNhzTmkUXC6%)C2e2kYIQ_nCS3`F!8+`^+mufPac2c-F*Jp%4{9hYMOUawEn#^r~PhvwePqon~Kz3tV~$C~8fzp3;7M^<{;yftZ>&w`#f zoGJNG%^da2Jpai<2mSEyQinn{i=_8G6T|%-COVy{CBQ{ZEZqLm|(%gn!&C z!ui8hx70ZN^lJ}SFETVRkv%&+pnoy&2%I5YcV}t9$L`~2)=T{Ol^XhYa(!#g9e@v4 zgMHvEdiR4JZC4ZuF6FWbuo%oL9JrHCO@xBAG0P9KrbcqvZJTswN5krct0VQk<%CQ5 lpP0p+L|;sg4xX{5`G^u;DdH&L2;`3h*PU|xHFHEld;#sY*$Myv From 032bdceeef484a8d83fc8d63908597e7c1b4dd1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Sat, 17 Aug 2013 11:36:41 +0200 Subject: [PATCH 19/19] FIX: bench script is not working on OS X --- script/bench.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/script/bench.rb b/script/bench.rb index 305b628b2be..37dacb551a6 100644 --- a/script/bench.rb +++ b/script/bench.rb @@ -57,9 +57,8 @@ ENV["RUBY_HEAP_SLOTS_GROWTH_FACTOR"] = "1.25" ENV["RUBY_HEAP_MIN_SLOTS"] = "800000" ENV["RUBY_FREE_MIN"] = "600000" - def port_available? port - server = TCPServer.open port + server = TCPServer.open("0.0.0.0", port) server.close true rescue Errno::EADDRINUSE @@ -86,9 +85,9 @@ run("bundle exec ruby script/profile_db_generator.rb") def bench(path) puts "Running apache bench warmup" - `ab -n 100 http://localhost:#{@port}#{path}` + `ab -n 100 http://127.0.0.1:#{@port}#{path}` puts "Benchmarking #{path}" - `ab -n 100 -e tmp/ab.csv http://localhost:#{@port}#{path}` + `ab -n 100 -e tmp/ab.csv http://127.0.0.1:#{@port}#{path}` percentiles = Hash[*[50, 75, 90, 99].zip([]).flatten] CSV.foreach("tmp/ab.csv") do |percent, time| @@ -105,6 +104,8 @@ begin sleep 1 end + puts "Starting benchmark..." + home_page = bench("/") topic_page = bench("/t/oh-how-i-wish-i-could-shut-up-like-a-tunnel-for-so/69")