From 3e9230714fdd4da361e46addff94e1205ed21c52 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 13 Apr 2018 12:47:36 -0400 Subject: [PATCH 001/127] UX: moved posts message links to the first post at the destination topic --- app/models/post_mover.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/models/post_mover.rb b/app/models/post_mover.rb index 7544a87ef1..14a41879ec 100644 --- a/app/models/post_mover.rb +++ b/app/models/post_mover.rb @@ -182,9 +182,13 @@ class PostMover move_type_str = PostMover.move_types[@move_type].to_s message = I18n.with_locale(SiteSetting.default_locale) do - I18n.t("move_posts.#{move_type_str}_moderator_post", - count: posts.length, - topic_link: "[#{destination_topic.title}](#{destination_topic.relative_url})") + I18n.t( + "move_posts.#{move_type_str}_moderator_post", + count: posts.length, + topic_link: posts.first.is_first_post? ? + "[#{destination_topic.title}](#{destination_topic.relative_url})" : + "[#{destination_topic.title}](#{posts.first.url})" + ) end original_topic.add_moderator_post( From a4b8813a02fe2e915df4c1ac407b6997d688367e Mon Sep 17 00:00:00 2001 From: Kris Date: Fri, 13 Apr 2018 12:53:04 -0400 Subject: [PATCH 002/127] FIX: Header nav should be tabbable and have focus state --- .../discourse/widgets/hamburger-menu.js.es6 | 2 +- .../discourse/widgets/header.js.es6 | 1 + .../discourse/widgets/user-menu.js.es6 | 2 ++ .../stylesheets/common/base/header.scss | 3 +- .../stylesheets/common/base/menu-panel.scss | 30 +++++++++++++++---- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 index d221407768..b54c7caf4b 100644 --- a/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/hamburger-menu.js.es6 @@ -153,7 +153,7 @@ export default createWidget('hamburger-menu', { const { site } = this; if (!site.mobileView && !this.capabilities.touch) { - links.push({ action: 'showKeyboard', className: 'keyboard-shortcuts-link', label: 'keyboard_shortcuts_help.title' }); + links.push({ href: '', action: 'showKeyboard', className: 'keyboard-shortcuts-link', label: 'keyboard_shortcuts_help.title' }); } if (this.site.mobileView || (this.siteSettings.enable_mobile_theme && this.capabilities.touch)) { diff --git a/app/assets/javascripts/discourse/widgets/header.js.es6 b/app/assets/javascripts/discourse/widgets/header.js.es6 index 4d4a06c508..4d8876b619 100644 --- a/app/assets/javascripts/discourse/widgets/header.js.es6 +++ b/app/assets/javascripts/discourse/widgets/header.js.es6 @@ -138,6 +138,7 @@ createWidget('header-icons', { iconId: 'toggle-hamburger-menu', active: attrs.hamburgerVisible, action: 'toggleHamburger', + href: '', contents() { if (!attrs.flagCount) { return; } return h('div.badge-notification.flagged-posts', { attributes: { diff --git a/app/assets/javascripts/discourse/widgets/user-menu.js.es6 b/app/assets/javascripts/discourse/widgets/user-menu.js.es6 index 9b5502e91a..39313c78e6 100644 --- a/app/assets/javascripts/discourse/widgets/user-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/user-menu.js.es6 @@ -99,6 +99,7 @@ createWidget('user-menu-dismiss-link', { attrs=(hash action="dismissNotifications" className="dismiss" + tabindex="0" icon="check" label="user.dismiss" title="user.dismiss_notifications_tooltip")}} @@ -144,6 +145,7 @@ export default createWidget('user-menu', { action: 'logout', className: 'logout', icon: 'sign-out', + href: '', label: 'user.log_out' }) ) diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss index 9fdae27b0f..f3615a4d90 100644 --- a/app/assets/stylesheets/common/base/header.scss +++ b/app/assets/stylesheets/common/base/header.scss @@ -86,11 +86,12 @@ border-left: 1px solid transparent; border-right: 1px solid transparent; transition: all linear .15s; + outline: none; img.avatar { width: 2.2857em; height: 2.2857em; } - &:hover { + &:hover, &:focus { color: $primary; background-color: $primary-low; border-top: 1px solid transparent; diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss index 1d5c03a476..013a94da3a 100644 --- a/app/assets/stylesheets/common/base/menu-panel.scss +++ b/app/assets/stylesheets/common/base/menu-panel.scss @@ -59,8 +59,9 @@ a { padding: 0.25em 0.5em; display: block; - &:hover { + &:hover, &:focus { background-color: $highlight-medium; + outline: none; } } @@ -243,9 +244,19 @@ display: none; } - span { color: $primary; } - &:hover { background-color: $highlight-medium; } - a { padding: 0; } + span { + color: $primary; + } + + &:hover, &:focus { + background-color: $highlight-medium; + outline: none; + } + + a { + padding: 0; + } + p { margin: 0; overflow: hidden; @@ -318,9 +329,18 @@ div.menu-links-header { border-spacing: 0 0.5em; .menu-links-row { display: table-row; + li.glyphs { + text-align: right; + a { + display: inline-flex; + min-width: 15px; + justify-content: center; + } + } } - a:hover { + a:hover, a:focus { background-color: $highlight-medium; + outline: none; } a { padding: 0.5em; From fe32733a57220bbeb5313080b80719f3c4c7fed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Fri, 13 Apr 2018 19:04:27 +0200 Subject: [PATCH 003/127] extract signatures from emails sent using Zimbra --- lib/email/receiver.rb | 45 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 4c093de916..a390c2061d 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -274,7 +274,8 @@ module Email markdown, elided_markdown = if html.present? # use the first html extracter that matches if html_extracter = HTML_EXTRACTERS.select { |_, r| html[r] }.min_by { |_, r| html =~ r } - self.send(:"extract_from_#{html_extracter[0]}", html) + doc = Nokogiri::HTML.fragment(html) + self.send(:"extract_from_#{html_extracter[0]}", doc) else markdown = HtmlToMarkdown.new(html, keep_img_tags: true, keep_cid_imgs: true).to_markdown markdown = trim_discourse_markers(markdown) @@ -295,70 +296,70 @@ module Email end HTML_EXTRACTERS ||= [ - [:gmail, /class="gmail_/], - [:outlook, /id="(divRplyFwdMsg|Signature)"/], - [:word, /class="WordSection1"/], - [:exchange, /name="message(Body|Reply)Section"/], - [:apple_mail, /id="AppleMailSignature"/], - [:mozilla, /class="moz-/], - [:protonmail, /class="protonmail_/], + [:gmail, / class="gmail_/], + [:outlook, / id="(divRplyFwdMsg|Signature)"/], + [:word, / class="WordSection1"/], + [:exchange, / name="message(Body|Reply)Section"/], + [:apple_mail, / id="AppleMailSignature"/], + [:mozilla, / class="moz-/], + [:protonmail, / class="protonmail_/], + [:zimbra, / data-marker="__/], ] - def extract_from_gmail(html) - doc = Nokogiri::HTML.fragment(html) + def extract_from_gmail(doc) # GMail adds a bunch of 'gmail_' prefixed classes like: gmail_signature, gmail_extra, gmail_quote # Just elide them all elided = doc.css("*[class^='gmail_']").remove to_markdown(doc.to_html, elided.to_html) end - def extract_from_outlook(html) - doc = Nokogiri::HTML.fragment(html) + def extract_from_outlook(doc) # Outlook properly identifies the signature and any replied/forwarded email # Use their id to remove them and anything that comes after elided = doc.css("#Signature, #Signature ~ *, hr, #divRplyFwdMsg, #divRplyFwdMsg ~ *").remove to_markdown(doc.to_html, elided.to_html) end - def extract_from_word(html) - doc = Nokogiri::HTML.fragment(html) + def extract_from_word(doc) # Word (?) keeps the content in the 'WordSection1' class and uses

tags # When there's something else (,
, etc..) there's high chance it's a signature or forwarded email elided = doc.css(".WordSection1 > :not(p):not(ul):first-of-type, .WordSection1 > :not(p):not(ul):first-of-type ~ *").remove to_markdown(doc.at(".WordSection1").to_html, elided.to_html) end - def extract_from_exchange(html) - doc = Nokogiri::HTML.fragment(html) + def extract_from_exchange(doc) # Exchange is using the 'messageReplySection' class for forwarded emails # And 'messageBodySection' for the actual email elided = doc.css("div[name='messageReplySection']").remove to_markdown(doc.css("div[name='messageReplySection']").to_html, elided.to_html) end - def extract_from_apple_mail(html) - doc = Nokogiri::HTML.fragment(html) + def extract_from_apple_mail(doc) # AppleMail is the worst. It adds 'AppleMailSignature' ids (!) to several div/p with no deterministic rules # Our best guess is to elide whatever comes after that. elided = doc.css("#AppleMailSignature:last-of-type ~ *").remove to_markdown(doc.to_html, elided.to_html) end - def extract_from_mozilla(html) - doc = Nokogiri::HTML.fragment(html) + def extract_from_mozilla(doc) # Mozilla (Thunderbird ?) properly identifies signature and forwarded emails # Remove them and anything that comes after elided = doc.css("*[class^='moz-'], *[class^='moz-'] ~ *").remove to_markdown(doc.to_html, elided.to_html) end - def extract_from_protonmail(html) - doc = Nokogiri::HTML.fragment(html) + def extract_from_protonmail(doc) # Removes anything that has a class starting with "protonmail_" and everything after that elided = doc.css("*[class^='protonmail_'], *[class^='protonmail_'] ~ *").remove to_markdown(doc.to_html, elided.to_html) end + def extract_from_zimbra(doc) + # Removes anything that has a 'data-marker' attribute + elided = doc.css("*[data-marker]").remove + to_markdown(doc.to_html, elided.to_html) + end + def trim_reply_and_extract_elided(text) return [text, ""] if @opts[:skip_trimming] EmailReplyTrimmer.trim(text, true) From fa2c47461770614b73f50861f6e15017fb3dd86a Mon Sep 17 00:00:00 2001 From: Kris Date: Fri, 13 Apr 2018 13:58:30 -0400 Subject: [PATCH 004/127] adding slight gradient to lightbox background --- app/assets/stylesheets/common/base/magnific-popup.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/common/base/magnific-popup.scss b/app/assets/stylesheets/common/base/magnific-popup.scss index 078a8cf37b..ed4addcfda 100644 --- a/app/assets/stylesheets/common/base/magnific-popup.scss +++ b/app/assets/stylesheets/common/base/magnific-popup.scss @@ -53,7 +53,7 @@ $iframe-ratio: 9/16 !default; // Image-type options $include-image-type: true !default; -$image-background: #444 !default; +$image-background: linear-gradient(45deg, #111 0%,#333 100%) !default; $image-padding-top: 40px !default; $image-padding-bottom: 40px !default; $include-mobile-layout-for-image: true !default; // Removes paddings from top and bottom From 9d0ff0dc6855cef59e26e28655eebec31f53ad87 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 13 Apr 2018 14:38:16 -0400 Subject: [PATCH 005/127] FIX: Use `new-password` instead --- .../javascripts/discourse/templates/modal/create-account.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/modal/create-account.hbs b/app/assets/javascripts/discourse/templates/modal/create-account.hbs index c2cfea754d..033e31368c 100644 --- a/app/assets/javascripts/discourse/templates/modal/create-account.hbs +++ b/app/assets/javascripts/discourse/templates/modal/create-account.hbs @@ -79,7 +79,7 @@
From 637bef0c3cd7910d010dce075b8cfae1d98fd519 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Fri, 13 Apr 2018 15:10:06 -0400 Subject: [PATCH 006/127] UX: staff can see the delete button on a post that was marked for deletion by the author --- app/assets/javascripts/discourse/lib/transform-post.js.es6 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/lib/transform-post.js.es6 b/app/assets/javascripts/discourse/lib/transform-post.js.es6 index 3aa31c4714..e205dea1d0 100644 --- a/app/assets/javascripts/discourse/lib/transform-post.js.es6 +++ b/app/assets/javascripts/discourse/lib/transform-post.js.es6 @@ -27,7 +27,7 @@ export function transformBasicPost(post) { deleted: post.get('deleted'), deleted_at: post.deleted_at, user_deleted: post.user_deleted, - isDeleted: post.deleted_at || post.user_deleted, + isDeleted: post.deleted_at || post.user_deleted, // xxxxx deletedByAvatarTemplate: null, deletedByUsername: null, primary_group_name: post.primary_group_name, @@ -215,7 +215,8 @@ export default function transformPost(currentUser, site, post, prevPost, nextPos postAtts.expandablePost = topic.expandable_first_post; } else { postAtts.canRecover = postAtts.isDeleted && postAtts.canRecover; - postAtts.canDelete = !postAtts.isDeleted && postAtts.canDelete; + postAtts.canDelete = postAtts.canDelete && !post.deleted_at && + currentUser && (currentUser.staff || !post.user_deleted); } _additionalAttributes.forEach(a => postAtts[a] = post[a]); From 18f50ca01abfa5497b3dc423470c3b5e84e48771 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Sat, 14 Apr 2018 16:42:53 +0530 Subject: [PATCH 007/127] FIX: parameterize tag_id --- app/controllers/list_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index bf07fdc028..8339d8b2d3 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -368,7 +368,7 @@ class ListController < ApplicationController def build_topic_list_options options = {} params[:page] = params[:page].to_i rescue 1 - params[:tags] = [params[:tag_id]] if params[:tag_id].present? && guardian.can_tag_pms? + params[:tags] = [params[:tag_id].parameterize] if params[:tag_id].present? && guardian.can_tag_pms? TopicQuery.public_valid_options.each do |key| options[key] = params[key] From 0183656631e86ecb8eb6b443a15ea52a9d1acbd9 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Sat, 14 Apr 2018 23:20:43 +0530 Subject: [PATCH 008/127] FIX: verify filtered tags when checking for category minimum required tags --- lib/discourse_tagging.rb | 18 ++++++++++++------ spec/components/discourse_tagging_spec.rb | 6 ++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/discourse_tagging.rb b/lib/discourse_tagging.rb index c9b0e14e6e..bf635e7943 100644 --- a/lib/discourse_tagging.rb +++ b/lib/discourse_tagging.rb @@ -29,12 +29,6 @@ module DiscourseTagging category = topic.category tag_names = tag_names + old_tag_names if append - # validate minimum required tags for a category - if !guardian.is_staff? && category && category.minimum_required_tags > 0 && tag_names.length < category.minimum_required_tags - topic.errors[:base] << I18n.t("tags.minimum_required_tags", count: category.minimum_required_tags) - return false - end - if tag_names.present? # guardian is explicitly nil cause we don't want to strip all # staff tags that already passed validation @@ -54,8 +48,20 @@ module DiscourseTagging end end + # validate minimum required tags for a category + if !guardian.is_staff? && category && category.minimum_required_tags > 0 && tags.length < category.minimum_required_tags + topic.errors[:base] << I18n.t("tags.minimum_required_tags", count: category.minimum_required_tags) + return false + end + topic.tags = tags else + # validate minimum required tags for a category + if !guardian.is_staff? && category && category.minimum_required_tags > 0 + topic.errors[:base] << I18n.t("tags.minimum_required_tags", count: category.minimum_required_tags) + return false + end + topic.tags = [] end topic.tags_changed = true diff --git a/spec/components/discourse_tagging_spec.rb b/spec/components/discourse_tagging_spec.rb index d9660ef2d3..60ed771a93 100644 --- a/spec/components/discourse_tagging_spec.rb +++ b/spec/components/discourse_tagging_spec.rb @@ -101,6 +101,12 @@ describe DiscourseTagging do let(:category) { Fabricate(:category, minimum_required_tags: 2) } let(:topic) { Fabricate(:topic, category: category) } + it 'when tags are not present' do + valid = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user), []) + expect(valid).to eq(false) + expect(topic.errors[:base]&.first).to eq(I18n.t("tags.minimum_required_tags", count: category.minimum_required_tags)) + end + it 'when tags are less than minimum_required_tags' do valid = DiscourseTagging.tag_topic_by_names(topic, Guardian.new(user), [tag1.name]) expect(valid).to eq(false) From 9642240a18e0bc7c3142a17357f9d67ec79f9027 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Sun, 15 Apr 2018 03:38:53 -0700 Subject: [PATCH 009/127] very minor copyedit --- config/locales/client.en.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 21e7f3c7cc..8cb77a4832 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -680,7 +680,6 @@ en: delete_account: "Delete My Account" delete_account_confirm: "Are you sure you want to permanently delete your account? This action cannot be undone!" deleted_yourself: "Your account has been deleted successfully." - delete_yourself_not_allowed: "You cannot delete your account right now. Contact an admin to do delete your account for you." unread_message_count: "Messages" admin_delete: "Delete" users: "Users" From bf2574ee76b695422a9bf577a6d45a5dd9f604b0 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Sun, 15 Apr 2018 03:44:23 -0700 Subject: [PATCH 010/127] very minor copyedit (part deux) --- config/locales/client.en.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 8cb77a4832..0ce42828d3 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -680,6 +680,7 @@ en: delete_account: "Delete My Account" delete_account_confirm: "Are you sure you want to permanently delete your account? This action cannot be undone!" deleted_yourself: "Your account has been deleted successfully." + delete_yourself_not_allowed: "Please contact a staff member if you wish your account to be deleted." unread_message_count: "Messages" admin_delete: "Delete" users: "Users" From c28c5083e0e7db96ec27f27c0107292e7469b214 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Sun, 15 Apr 2018 17:24:04 +0530 Subject: [PATCH 011/127] SECURITY: santize tags when creating new topic via URL --- .../javascripts/discourse/controllers/composer.js.es6 | 9 +++++++-- .../select-kit/components/mini-tag-chooser.js.es6 | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 414e117897..b91e0d40da 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -682,7 +682,7 @@ export default Ember.Controller.extend({ } if (opts.topicTitle && opts.topicTitle.length <= this.siteSettings.max_topic_title_length) { - this.set('model.title', opts.topicTitle); + this.set('model.title', escapeExpression(opts.topicTitle)); } if (opts.topicCategoryId) { @@ -707,7 +707,12 @@ export default Ember.Controller.extend({ } if (opts.topicTags && !this.site.mobileView && this.site.get('can_tag_topics')) { - this.set('model.tags', opts.topicTags.split(",")); + const self = this; + let tags = escapeExpression(opts.topicTags).split(",").slice(0, self.siteSettings.max_tags_per_topic); + tags.forEach(function(tag, index, array) { + array[index] = tag.substring(0, self.siteSettings.max_tag_length); + }); + self.set('model.tags', tags); } if (opts.topicBody) { diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 index 740fd9e61b..dbcb5d0384 100644 --- a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 +++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 @@ -2,6 +2,7 @@ import ComboBox from "select-kit/components/combo-box"; import Tags from "select-kit/mixins/tags"; import { default as computed } from "ember-addons/ember-computed-decorators"; import renderTag from "discourse/lib/render-tag"; +import { escapeExpression } from 'discourse/lib/utilities'; const { get, isEmpty, run, makeArray } = Ember; export default ComboBox.extend(Tags, { @@ -110,6 +111,7 @@ export default ComboBox.extend(Tags, { } tags.map((tag) => { + tag = escapeExpression(tag); const isHighlighted = highlightedSelection.map(s => get(s, "value")).includes(tag); output += `
- {{input type="password" value=accountPasswordConfirm id="new-account-confirmation" autocomplete="false"}} + {{input type="password" value=accountPasswordConfirm id="new-account-confirmation" autocomplete="new-password"}} {{input value=accountChallenge id="new-account-challenge"}}
+ + + {{#each labels as |label|}} + + {{/each}} + + + + + {{#each dataset as |data|}} + + {{/each}} + + +
{{label}}
{{data}}
+ +{{/conditional-loading-spinner}} diff --git a/app/assets/javascripts/admin/templates/dashboard_next.hbs b/app/assets/javascripts/admin/templates/dashboard_next.hbs new file mode 100644 index 0000000000..4e1c243e60 --- /dev/null +++ b/app/assets/javascripts/admin/templates/dashboard_next.hbs @@ -0,0 +1,28 @@ +{{plugin-outlet name="admin-dashboard-top"}} + +{{#conditional-loading-spinner condition=loading}} +

+
+

Community health

+ {{period-chooser period=period action="changePeriod"}} +
+ +
+
+ {{mini-chart dataSourceName="signups" startDate=startDate endDate=endDate}} + {{mini-chart dataSourceName="topics" startDate=startDate endDate=endDate}} +
+
+
+ +
+
+ {{mini-table dataSourceName="users_by_trust_level"}} +
+ +
+
+
+ + +{{/conditional-loading-spinner}} diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss index 81d5eab682..d86e514499 100644 --- a/app/assets/stylesheets/common/admin/admin_base.scss +++ b/app/assets/stylesheets/common/admin/admin_base.scss @@ -5,6 +5,7 @@ @import "common/admin/customize"; @import "common/admin/flagging"; +@import "common/admin/dashboard_next"; @import "common/admin/moderation_history"; @import "common/admin/suspend"; diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss new file mode 100644 index 0000000000..c0331ea784 --- /dev/null +++ b/app/assets/stylesheets/common/admin/dashboard_next.scss @@ -0,0 +1,143 @@ +.dashboard-next { + + &.admin-contents { + margin: 0; + } + + .section-columns { + display: flex; + justify-content: space-between; + + .section-column { + flex: 1; + flex-grow: 1; + } + } + + .section { + .section-title { + h2 { + margin: 0 .5em 0 0; + } + + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid $primary-low-mid; + margin-bottom: .5em; + padding-bottom: .5em; + } + + .section-body { + padding: 1em 0; + } + } + + .mini-table { + .table-title { + align-items: center; + display: flex; + margin: .5em; + justify-content: space-between; + + h3 { + margin: 0 .5em 0 0; + } + } + + table { + border: 1px solid $primary-low-mid; + table-layout: fixed; + + thead { + tr { + background: $primary-low; + th { + border: 1px solid $primary-low-mid; + text-align: center; + } + } + } + + tbody { + tr { + td { + border: 1px solid $primary-low-mid; + text-align: center; + } + } + } + } + } + + .charts { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + + .mini-chart { + flex-grow: 1; + width: calc(100% * (1/3) - 1px); + margin-bottom: 1em; + + .chart-title { + align-items: center; + display: flex; + margin: .5em; + + h3 { + margin: 0 .5em 0 0; + } + } + + &.double-up, &.up { + .chart-trend, .data-point { + color: rgb(17, 141, 0); + } + } + + &.double-down, &.down { + .chart-trend, .data-point { + color: $danger; + } + } + + &.one-data-point { + .chart-container { + height: 100px; + justify-content: center; + align-items: center; + display: flex; + } + + .data-point { + font-size: $font-up-5; + font-weight: bold; + padding: 1em; + border-radius: 3px; + background: rgba(200,220,240,0.3); + } + } + } + + .chart-container { + position: relative; + } + + .chart-trend { + font-size: $font-up-5; + position: absolute; + left: 1.5em; + top: .5em; + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + } + + .chart-canvas { + width: 100%; + height: 100%; + } + } +} diff --git a/app/controllers/admin/dashboard_next_controller.rb b/app/controllers/admin/dashboard_next_controller.rb new file mode 100644 index 0000000000..173e2b7156 --- /dev/null +++ b/app/controllers/admin/dashboard_next_controller.rb @@ -0,0 +1,2 @@ +class Admin::DashboardNextController < Admin::AdminController +end diff --git a/config/routes.rb b/config/routes.rb index 8a96931a8e..a34e74a148 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -230,6 +230,8 @@ Discourse::Application.routes.draw do get "version_check" => "versions#show" + resources :dashboard_next, only: [:index] + resources :dashboard, only: [:index] do collection do get "problems" diff --git a/public/javascripts/Chart.min.js b/public/javascripts/Chart.min.js index 2130e2ab70..dbf22c4f84 100644 --- a/public/javascripts/Chart.min.js +++ b/public/javascripts/Chart.min.js @@ -1,10 +1,10 @@ /*! * Chart.js * http://chartjs.org/ - * Version: 2.7.1 + * Version: 2.7.2 * - * Copyright 2017 Nick Downie + * Copyright 2018 Chart.js Contributors * Released under the MIT license * https://github.com/chartjs/Chart.js/blob/master/LICENSE.md */ -!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Chart=t()}}(function(){return function t(e,n,i){function a(r,l){if(!n[r]){if(!e[r]){var s="function"==typeof require&&require;if(!l&&s)return s(r,!0);if(o)return o(r,!0);var u=new Error("Cannot find module '"+r+"'");throw u.code="MODULE_NOT_FOUND",u}var d=n[r]={exports:{}};e[r][0].call(d.exports,function(t){var n=e[r][1][t];return a(n||t)},d,d.exports,t,e,n,i)}return n[r].exports}for(var o="function"==typeof require&&require,r=0;rn?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=this,i=t,a=void 0===e?.5:e,o=2*a-1,r=n.alpha()-i.alpha(),l=((o*r==-1?o:(o+r)/(1+o*r))+1)/2,s=1-l;return this.rgb(l*n.red()+s*i.red(),l*n.green()+s*i.green(),l*n.blue()+s*i.blue()).alpha(n.alpha()*a+i.alpha()*(1-a))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new o,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},o.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},o.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},o.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]}function d(t){var e,n,i,a=u(t),o=a[0],r=a[1],l=a[2];return o/=95.047,r/=100,l/=108.883,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,l=l>.008856?Math.pow(l,1/3):7.787*l+16/116,e=116*r-16,n=500*(o-r),i=200*(r-l),[e,n,i]}function c(t){var e,n,i,a,o,r=t[0]/360,l=t[1]/100,s=t[2]/100;if(0==l)return o=255*s,[o,o,o];e=2*s-(n=s<.5?s*(1+l):s+l-s*l),a=[0,0,0];for(var u=0;u<3;u++)(i=r+1/3*-(u-1))<0&&i++,i>1&&i--,o=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*o;return a}function h(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,o=e-Math.floor(e),r=255*i*(1-n),l=255*i*(1-n*o),s=255*i*(1-n*(1-o)),i=255*i;switch(a){case 0:return[i,s,r];case 1:return[l,i,r];case 2:return[r,i,s];case 3:return[r,l,i];case 4:return[s,r,i];case 5:return[i,r,l]}}function f(t){var e,n,i,a,o=t[0]/360,l=t[1]/100,s=t[2]/100,u=l+s;switch(u>1&&(l/=u,s/=u),e=Math.floor(6*o),n=1-s,i=6*o-e,0!=(1&e)&&(i=1-i),a=l+i*(n-l),e){default:case 6:case 0:r=n,g=a,b=l;break;case 1:r=a,g=n,b=l;break;case 2:r=l,g=n,b=a;break;case 3:r=l,g=a,b=n;break;case 4:r=a,g=l,b=n;break;case 5:r=n,g=l,b=a}return[255*r,255*g,255*b]}function p(t){var e,n,i,a=t[0]/100,o=t[1]/100,r=t[2]/100,l=t[3]/100;return e=1-Math.min(1,a*(1-l)+l),n=1-Math.min(1,o*(1-l)+l),i=1-Math.min(1,r*(1-l)+l),[255*e,255*n,255*i]}function v(t){var e,n,i,a=t[0]/100,o=t[1]/100,r=t[2]/100;return e=3.2406*a+-1.5372*o+-.4986*r,n=-.9689*a+1.8758*o+.0415*r,i=.0557*a+-.204*o+1.057*r,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e*=12.92,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n*=12.92,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i*=12.92,e=Math.min(Math.max(0,e),1),n=Math.min(Math.max(0,n),1),i=Math.min(Math.max(0,i),1),[255*e,255*n,255*i]}function m(t){var e,n,i,a=t[0],o=t[1],r=t[2];return a/=95.047,o/=100,r/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*o-16,n=500*(a-o),i=200*(o-r),[e,n,i]}function x(t){var e,n,i,a,o=t[0],r=t[1],l=t[2];return o<=8?a=(n=100*o/903.3)/100*7.787+16/116:(n=100*Math.pow((o+16)/116,3),a=Math.pow(n/100,1/3)),e=e/95.047<=.008856?e=95.047*(r/500+a-16/116)/7.787:95.047*Math.pow(r/500+a,3),i=i/108.883<=.008859?i=108.883*(a-l/200-16/116)/7.787:108.883*Math.pow(a-l/200,3),[e,n,i]}function y(t){var e,n,i,a=t[0],o=t[1],r=t[2];return e=Math.atan2(r,o),(n=360*e/2/Math.PI)<0&&(n+=360),i=Math.sqrt(o*o+r*r),[a,i,n]}function k(t){return v(x(t))}function w(t){var e,n,i,a=t[0],o=t[1];return i=t[2]/360*2*Math.PI,e=o*Math.cos(i),n=o*Math.sin(i),[a,e,n]}function M(t){return S[t]}e.exports={rgb2hsl:i,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:l,rgb2keyword:s,rgb2xyz:u,rgb2lab:d,rgb2lch:function(t){return y(d(t))},hsl2rgb:c,hsl2hsv:function(t){var e,n,i=t[0],a=t[1]/100,o=t[2]/100;return 0===o?[0,0,0]:(o*=2,a*=o<=1?o:2-o,n=(o+a)/2,e=2*a/(o+a),[i,100*e,100*n])},hsl2hwb:function(t){return o(c(t))},hsl2cmyk:function(t){return l(c(t))},hsl2keyword:function(t){return s(c(t))},hsv2rgb:h,hsv2hsl:function(t){var e,n,i=t[0],a=t[1]/100,o=t[2]/100;return n=(2-a)*o,e=a*o,e/=n<=1?n:2-n,e=e||0,n/=2,[i,100*e,100*n]},hsv2hwb:function(t){return o(h(t))},hsv2cmyk:function(t){return l(h(t))},hsv2keyword:function(t){return s(h(t))},hwb2rgb:f,hwb2hsl:function(t){return i(f(t))},hwb2hsv:function(t){return a(f(t))},hwb2cmyk:function(t){return l(f(t))},hwb2keyword:function(t){return s(f(t))},cmyk2rgb:p,cmyk2hsl:function(t){return i(p(t))},cmyk2hsv:function(t){return a(p(t))},cmyk2hwb:function(t){return o(p(t))},cmyk2keyword:function(t){return s(p(t))},keyword2rgb:M,keyword2hsl:function(t){return i(M(t))},keyword2hsv:function(t){return a(M(t))},keyword2hwb:function(t){return o(M(t))},keyword2cmyk:function(t){return l(M(t))},keyword2lab:function(t){return d(M(t))},keyword2xyz:function(t){return u(M(t))},xyz2rgb:v,xyz2lab:m,xyz2lch:function(t){return y(m(t))},lab2xyz:x,lab2rgb:k,lab2lch:y,lch2lab:w,lch2xyz:function(t){return x(w(t))},lch2rgb:function(t){return k(w(t))}};var S={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},C={};for(var _ in S)C[JSON.stringify(S[_])]=_},{}],5:[function(t,e,n){var i=t(4),a=function(){return new u};for(var o in i){a[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),i[t](e)}}(o);var r=/(\w+)2(\w+)/.exec(o),l=r[1],s=r[2];(a[l]=a[l]||{})[s]=a[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var n=i[t](e);if("string"==typeof n||void 0===n)return n;for(var a=0;a0&&(t[0].yLabel?n=t[0].yLabel:e.labels.length>0&&t[0].index=0&&a>0)&&(v+=a));return o=c.getPixelForValue(v),r=c.getPixelForValue(v+f),l=(r-o)/2,{size:l,base:o,head:r,center:r+l/2}},calculateBarIndexPixels:function(t,e,n){var i,a,r,l,s,u,d=this,c=n.scale.options,h=d.getStackIndex(t),f=n.pixels,g=f[e],p=f.length,v=n.start,m=n.end;return 1===p?(i=g>v?g-v:m-g,a=g0&&(i=(g-f[e-1])/2,e===p-1&&(a=i)),e');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(n,i){var a=t.getDatasetMeta(0),r=e.datasets[0],l=a.data[i],s=l&&l.custom||{},u=o.valueAtIndexOrDefault,d=t.options.elements.arc;return{text:n,fillStyle:s.backgroundColor?s.backgroundColor:u(r.backgroundColor,i,d.backgroundColor),strokeStyle:s.borderColor?s.borderColor:u(r.borderColor,i,d.borderColor),lineWidth:s.borderWidth?s.borderWidth:u(r.borderWidth,i,d.borderWidth),hidden:isNaN(r.data[i])||a.data[i].hidden,index:i}}):[]}},onClick:function(t,e){var n,i,a,o=e.index,r=this.chart;for(n=0,i=(r.data.datasets||[]).length;n=Math.PI?-1:g<-Math.PI?1:0))+f,v={x:Math.cos(g),y:Math.sin(g)},m={x:Math.cos(p),y:Math.sin(p)},b=g<=0&&p>=0||g<=2*Math.PI&&2*Math.PI<=p,x=g<=.5*Math.PI&&.5*Math.PI<=p||g<=2.5*Math.PI&&2.5*Math.PI<=p,y=g<=-Math.PI&&-Math.PI<=p||g<=Math.PI&&Math.PI<=p,k=g<=.5*-Math.PI&&.5*-Math.PI<=p||g<=1.5*Math.PI&&1.5*Math.PI<=p,w=h/100,M={x:y?-1:Math.min(v.x*(v.x<0?1:w),m.x*(m.x<0?1:w)),y:k?-1:Math.min(v.y*(v.y<0?1:w),m.y*(m.y<0?1:w))},S={x:b?1:Math.max(v.x*(v.x>0?1:w),m.x*(m.x>0?1:w)),y:x?1:Math.max(v.y*(v.y>0?1:w),m.y*(m.y>0?1:w))},C={width:.5*(S.x-M.x),height:.5*(S.y-M.y)};u=Math.min(l/C.width,s/C.height),d={x:-.5*(S.x+M.x),y:-.5*(S.y+M.y)}}n.borderWidth=e.getMaxBorderWidth(c.data),n.outerRadius=Math.max((u-n.borderWidth)/2,0),n.innerRadius=Math.max(h?n.outerRadius/100*h:0,0),n.radiusLength=(n.outerRadius-n.innerRadius)/n.getVisibleDatasetCount(),n.offsetX=d.x*n.outerRadius,n.offsetY=d.y*n.outerRadius,c.total=e.calculateTotal(),e.outerRadius=n.outerRadius-n.radiusLength*e.getRingIndex(e.index),e.innerRadius=Math.max(e.outerRadius-n.radiusLength,0),o.each(c.data,function(n,i){e.updateElement(n,i,t)})},updateElement:function(t,e,n){var i=this,a=i.chart,r=a.chartArea,l=a.options,s=l.animation,u=(r.left+r.right)/2,d=(r.top+r.bottom)/2,c=l.rotation,h=l.rotation,f=i.getDataset(),g=n&&s.animateRotate?0:t.hidden?0:i.calculateCircumference(f.data[e])*(l.circumference/(2*Math.PI)),p=n&&s.animateScale?0:i.innerRadius,v=n&&s.animateScale?0:i.outerRadius,m=o.valueAtIndexOrDefault;o.extend(t,{_datasetIndex:i.index,_index:e,_model:{x:u+a.offsetX,y:d+a.offsetY,startAngle:c,endAngle:h,circumference:g,outerRadius:v,innerRadius:p,label:m(f.label,e,a.data.labels[e])}});var b=t._model;this.removeHoverStyle(t),n&&s.animateRotate||(b.startAngle=0===e?l.rotation:i.getMeta().data[e-1]._model.endAngle,b.endAngle=b.startAngle+b.circumference),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},calculateTotal:function(){var t,e=this.getDataset(),n=this.getMeta(),i=0;return o.each(n.data,function(n,a){t=e.data[a],isNaN(t)||n.hidden||(i+=Math.abs(t))}),i},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(t/e):0},getMaxBorderWidth:function(t){for(var e,n,i=0,a=this.index,o=t.length,r=0;r(i=e>i?e:i)?n:i;return i}})}},{25:25,40:40,45:45}],18:[function(t,e,n){"use strict";var i=t(25),a=t(40),o=t(45);i._set("line",{showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}}),e.exports=function(t){function e(t,e){return o.valueOrDefault(t.showLine,e.showLines)}t.controllers.line=t.DatasetController.extend({datasetElementType:a.Line,dataElementType:a.Point,update:function(t){var n,i,a,r=this,l=r.getMeta(),s=l.dataset,u=l.data||[],d=r.chart.options,c=d.elements.line,h=r.getScaleForId(l.yAxisID),f=r.getDataset(),g=e(f,d);for(g&&(a=s.custom||{},void 0!==f.tension&&void 0===f.lineTension&&(f.lineTension=f.tension),s._scale=h,s._datasetIndex=r.index,s._children=u,s._model={spanGaps:f.spanGaps?f.spanGaps:d.spanGaps,tension:a.tension?a.tension:o.valueOrDefault(f.lineTension,c.tension),backgroundColor:a.backgroundColor?a.backgroundColor:f.backgroundColor||c.backgroundColor,borderWidth:a.borderWidth?a.borderWidth:f.borderWidth||c.borderWidth,borderColor:a.borderColor?a.borderColor:f.borderColor||c.borderColor,borderCapStyle:a.borderCapStyle?a.borderCapStyle:f.borderCapStyle||c.borderCapStyle,borderDash:a.borderDash?a.borderDash:f.borderDash||c.borderDash,borderDashOffset:a.borderDashOffset?a.borderDashOffset:f.borderDashOffset||c.borderDashOffset,borderJoinStyle:a.borderJoinStyle?a.borderJoinStyle:f.borderJoinStyle||c.borderJoinStyle,fill:a.fill?a.fill:void 0!==f.fill?f.fill:c.fill,steppedLine:a.steppedLine?a.steppedLine:o.valueOrDefault(f.steppedLine,c.stepped),cubicInterpolationMode:a.cubicInterpolationMode?a.cubicInterpolationMode:o.valueOrDefault(f.cubicInterpolationMode,c.cubicInterpolationMode)},s.pivot()),n=0,i=u.length;n');var n=t.data,i=n.datasets,a=n.labels;if(i.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(n,i){var a=t.getDatasetMeta(0),r=e.datasets[0],l=a.data[i].custom||{},s=o.valueAtIndexOrDefault,u=t.options.elements.arc;return{text:n,fillStyle:l.backgroundColor?l.backgroundColor:s(r.backgroundColor,i,u.backgroundColor),strokeStyle:l.borderColor?l.borderColor:s(r.borderColor,i,u.borderColor),lineWidth:l.borderWidth?l.borderWidth:s(r.borderWidth,i,u.borderWidth),hidden:isNaN(r.data[i])||a.data[i].hidden,index:i}}):[]}},onClick:function(t,e){var n,i,a,o=e.index,r=this.chart;for(n=0,i=(r.data.datasets||[]).length;n0&&!isNaN(t)?2*Math.PI/e:0}})}},{25:25,40:40,45:45}],20:[function(t,e,n){"use strict";var i=t(25),a=t(40),o=t(45);i._set("radar",{scale:{type:"radialLinear"},elements:{line:{tension:0}}}),e.exports=function(t){t.controllers.radar=t.DatasetController.extend({datasetElementType:a.Line,dataElementType:a.Point,linkScales:o.noop,update:function(t){var e=this,n=e.getMeta(),i=n.dataset,a=n.data,r=i.custom||{},l=e.getDataset(),s=e.chart.options.elements.line,u=e.chart.scale;void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),o.extend(n.dataset,{_datasetIndex:e.index,_scale:u,_children:a,_loop:!0,_model:{tension:r.tension?r.tension:o.valueOrDefault(l.lineTension,s.tension),backgroundColor:r.backgroundColor?r.backgroundColor:l.backgroundColor||s.backgroundColor,borderWidth:r.borderWidth?r.borderWidth:l.borderWidth||s.borderWidth,borderColor:r.borderColor?r.borderColor:l.borderColor||s.borderColor,fill:r.fill?r.fill:void 0!==l.fill?l.fill:s.fill,borderCapStyle:r.borderCapStyle?r.borderCapStyle:l.borderCapStyle||s.borderCapStyle,borderDash:r.borderDash?r.borderDash:l.borderDash||s.borderDash,borderDashOffset:r.borderDashOffset?r.borderDashOffset:l.borderDashOffset||s.borderDashOffset,borderJoinStyle:r.borderJoinStyle?r.borderJoinStyle:l.borderJoinStyle||s.borderJoinStyle}}),n.dataset.pivot(),o.each(a,function(n,i){e.updateElement(n,i,t)},e),e.updateBezierControlPoints()},updateElement:function(t,e,n){var i=this,a=t.custom||{},r=i.getDataset(),l=i.chart.scale,s=i.chart.options.elements.point,u=l.getPointPositionForValue(e,r.data[e]);void 0!==r.radius&&void 0===r.pointRadius&&(r.pointRadius=r.radius),void 0!==r.hitRadius&&void 0===r.pointHitRadius&&(r.pointHitRadius=r.hitRadius),o.extend(t,{_datasetIndex:i.index,_index:e,_scale:l,_model:{x:n?l.xCenter:u.x,y:n?l.yCenter:u.y,tension:a.tension?a.tension:o.valueOrDefault(r.lineTension,i.chart.options.elements.line.tension),radius:a.radius?a.radius:o.valueAtIndexOrDefault(r.pointRadius,e,s.radius),backgroundColor:a.backgroundColor?a.backgroundColor:o.valueAtIndexOrDefault(r.pointBackgroundColor,e,s.backgroundColor),borderColor:a.borderColor?a.borderColor:o.valueAtIndexOrDefault(r.pointBorderColor,e,s.borderColor),borderWidth:a.borderWidth?a.borderWidth:o.valueAtIndexOrDefault(r.pointBorderWidth,e,s.borderWidth),pointStyle:a.pointStyle?a.pointStyle:o.valueAtIndexOrDefault(r.pointStyle,e,s.pointStyle),hitRadius:a.hitRadius?a.hitRadius:o.valueAtIndexOrDefault(r.pointHitRadius,e,s.hitRadius)}}),t._model.skip=a.skip?a.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.chart.chartArea,e=this.getMeta();o.each(e.data,function(n,i){var a=n._model,r=o.splineCurve(o.previousItem(e.data,i,!0)._model,a,o.nextItem(e.data,i,!0)._model,a.tension);a.controlPointPreviousX=Math.max(Math.min(r.previous.x,t.right),t.left),a.controlPointPreviousY=Math.max(Math.min(r.previous.y,t.bottom),t.top),a.controlPointNextX=Math.max(Math.min(r.next.x,t.right),t.left),a.controlPointNextY=Math.max(Math.min(r.next.y,t.bottom),t.top),n.pivot()})},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],n=t.custom||{},i=t._index,a=t._model;a.radius=n.hoverRadius?n.hoverRadius:o.valueAtIndexOrDefault(e.pointHoverRadius,i,this.chart.options.elements.point.hoverRadius),a.backgroundColor=n.hoverBackgroundColor?n.hoverBackgroundColor:o.valueAtIndexOrDefault(e.pointHoverBackgroundColor,i,o.getHoverColor(a.backgroundColor)),a.borderColor=n.hoverBorderColor?n.hoverBorderColor:o.valueAtIndexOrDefault(e.pointHoverBorderColor,i,o.getHoverColor(a.borderColor)),a.borderWidth=n.hoverBorderWidth?n.hoverBorderWidth:o.valueAtIndexOrDefault(e.pointHoverBorderWidth,i,a.borderWidth)},removeHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],n=t.custom||{},i=t._index,a=t._model,r=this.chart.options.elements.point;a.radius=n.radius?n.radius:o.valueAtIndexOrDefault(e.pointRadius,i,r.radius),a.backgroundColor=n.backgroundColor?n.backgroundColor:o.valueAtIndexOrDefault(e.pointBackgroundColor,i,r.backgroundColor),a.borderColor=n.borderColor?n.borderColor:o.valueAtIndexOrDefault(e.pointBorderColor,i,r.borderColor),a.borderWidth=n.borderWidth?n.borderWidth:o.valueAtIndexOrDefault(e.pointBorderWidth,i,r.borderWidth)}})}},{25:25,40:40,45:45}],21:[function(t,e,n){"use strict";t(25)._set("scatter",{hover:{mode:"single"},scales:{xAxes:[{id:"x-axis-1",type:"linear",position:"bottom"}],yAxes:[{id:"y-axis-1",type:"linear",position:"left"}]},showLines:!1,tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}}),e.exports=function(t){t.controllers.scatter=t.controllers.line}},{25:25}],22:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45);i._set("global",{animation:{duration:1e3,easing:"easeOutQuart",onProgress:o.noop,onComplete:o.noop}}),e.exports=function(t){t.Animation=a.extend({chart:null,currentStep:0,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,n,i){var a,o,r=this.animations;for(e.chart=t,i||(t.animating=!0),a=0,o=r.length;a1&&(n=Math.floor(t.dropFrames),t.dropFrames=t.dropFrames%1),t.advance(1+n);var i=Date.now();t.dropFrames+=(i-e)/t.frameDuration,t.animations.length>0&&t.requestAnimationFrame()},advance:function(t){for(var e,n,i=this.animations,a=0;a=e.numSteps?(o.callback(e.onAnimationComplete,[e],n),n.animating=!1,i.splice(a,1)):++a}},Object.defineProperty(t.Animation.prototype,"animationObject",{get:function(){return this}}),Object.defineProperty(t.Animation.prototype,"chartInstance",{get:function(){return this.chart},set:function(t){this.chart=t}})}},{25:25,26:26,45:45}],23:[function(t,e,n){"use strict";var i=t(25),a=t(45),o=t(28),r=t(48);e.exports=function(t){function e(t){var e=(t=t||{}).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=a.configMerge(i.global,i[t.type],t.options||{}),t}function n(t){var e=t.options;e.scale?t.scale.options=e.scale:e.scales&&e.scales.xAxes.concat(e.scales.yAxes).forEach(function(e){t.scales[e.id].options=e}),t.tooltip._options=e.tooltips}function l(t){return"top"===t||"bottom"===t}var s=t.plugins;t.types={},t.instances={},t.controllers={},a.extend(t.prototype,{construct:function(n,i){var o=this;i=e(i);var l=r.acquireContext(n,i),s=l&&l.canvas,u=s&&s.height,d=s&&s.width;o.id=a.uid(),o.ctx=l,o.canvas=s,o.config=i,o.width=d,o.height=u,o.aspectRatio=u?d/u:null,o.options=i.options,o._bufferedRender=!1,o.chart=o,o.controller=o,t.instances[o.id]=o,Object.defineProperty(o,"data",{get:function(){return o.config.data},set:function(t){o.config.data=t}}),l&&s?(o.initialize(),o.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return s.notify(t,"beforeInit"),a.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.ensureScalesHaveIDs(),t.buildScales(),t.initToolTip(),s.notify(t,"afterInit"),t},clear:function(){return a.canvas.clear(this),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,o=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(a.getMaximumWidth(i))),l=Math.max(0,Math.floor(o?r/o:a.getMaximumHeight(i)));if((e.width!==r||e.height!==l)&&(i.width=e.width=r,i.height=e.height=l,i.style.width=r+"px",i.style.height=l+"px",a.retinaScale(e,n.devicePixelRatio),!t)){var u={width:r,height:l};s.notify(e,"resize",[u]),e.options.onResize&&e.options.onResize(e,u),e.stop(),e.update(e.options.responsiveAnimationDuration)}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;a.each(e.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),a.each(e.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),n&&(n.id=n.id||"scale")},buildScales:function(){var e=this,n=e.options,i=e.scales={},o=[];n.scales&&(o=o.concat((n.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category",dposition:"bottom"}}),(n.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear",dposition:"left"}}))),n.scale&&o.push({options:n.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),a.each(o,function(n){var o=n.options,r=a.valueOrDefault(o.type,n.dtype),s=t.scaleService.getScaleConstructor(r);if(s){l(o.position)!==l(n.dposition)&&(o.position=n.dposition);var u=new s({id:o.id,options:o,ctx:e.ctx,chart:e});i[u.id]=u,u.mergeTicksOptions(),n.isDefault&&(e.scale=u)}}),t.scaleService.addScalesToLayout(this)},buildOrUpdateControllers:function(){var e=this,n=[],i=[];return a.each(e.data.datasets,function(a,o){var r=e.getDatasetMeta(o),l=a.type||e.config.type;if(r.type&&r.type!==l&&(e.destroyDatasetMeta(o),r=e.getDatasetMeta(o)),r.type=l,n.push(r.type),r.controller)r.controller.updateIndex(o);else{var s=t.controllers[r.type];if(void 0===s)throw new Error('"'+r.type+'" is not a chart type.');r.controller=new s(e,o),i.push(r.controller)}},e),i},resetElements:function(){var t=this;a.each(t.data.datasets,function(e,n){t.getDatasetMeta(n).controller.reset()},t)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(t){var e=this;if(t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]}),n(e),!1!==s.notify(e,"beforeUpdate")){e.tooltip._data=e.data;var i=e.buildOrUpdateControllers();a.each(e.data.datasets,function(t,n){e.getDatasetMeta(n).controller.buildOrUpdateElements()},e),e.updateLayout(),a.each(i,function(t){t.reset()}),e.updateDatasets(),e.tooltip.initialize(),e.lastActive=[],s.notify(e,"afterUpdate"),e._bufferedRender?e._bufferedRequest={duration:t.duration,easing:t.easing,lazy:t.lazy}:e.render(t)}},updateLayout:function(){var e=this;!1!==s.notify(e,"beforeLayout")&&(t.layoutService.update(this,this.width,this.height),s.notify(e,"afterScaleUpdate"),s.notify(e,"afterLayout"))},updateDatasets:function(){var t=this;if(!1!==s.notify(t,"beforeDatasetsUpdate")){for(var e=0,n=t.data.datasets.length;e=0;--n)e.isDatasetVisible(n)&&e.drawDataset(n,t);s.notify(e,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n=this,i=n.getDatasetMeta(t),a={meta:i,index:t,easingValue:e};!1!==s.notify(n,"beforeDatasetDraw",[a])&&(i.controller.draw(e),s.notify(n,"afterDatasetDraw",[a]))},_drawTooltip:function(t){var e=this,n=e.tooltip,i={tooltip:n,easingValue:t};!1!==s.notify(e,"beforeTooltipDraw",[i])&&(n.draw(),s.notify(e,"afterTooltipDraw",[i]))},getElementAtEvent:function(t){return o.modes.single(this,t)},getElementsAtEvent:function(t){return o.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return o.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=o.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return o.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this,n=e.data.datasets[t];n._meta||(n._meta={});var i=n._meta[e.id];return i||(i=n._meta[e.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e0||(a.forEach(function(e){delete t[e]}),delete t._chartjs)}}var a=["push","pop","shift","splice","unshift"];t.DatasetController=function(t,e){this.initialize(t,e)},i.extend(t.DatasetController.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),n=t.getDataset();null===e.xAxisID&&(e.xAxisID=n.xAxisID||t.chart.options.scales.xAxes[0].id),null===e.yAxisID&&(e.yAxisID=n.yAxisID||t.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},reset:function(){this.update(!0)},destroy:function(){this._data&&n(this._data,this)},createMetaDataset:function(){var t=this,e=t.datasetElementType;return e&&new e({_chart:t.chart,_datasetIndex:t.index})},createMetaData:function(t){var e=this,n=e.dataElementType;return n&&new n({_chart:e.chart,_datasetIndex:e.index,_index:t})},addElements:function(){var t,e,n=this,i=n.getMeta(),a=n.getDataset().data||[],o=i.data;for(t=0,e=a.length;ti&&t.insertElements(i,a-i)},insertElements:function(t,e){for(var n=0;n=n[e].length&&n[e].push({}),!n[e][r].type||s.type&&s.type!==n[e][r].type?o.merge(n[e][r],[t.scaleService.getScaleDefaults(l),s]):o.merge(n[e][r],s)}else o._merger(e,n,i,a)}})},o.where=function(t,e){if(o.isArray(t)&&Array.prototype.filter)return t.filter(e);var n=[];return o.each(t,function(t){e(t)&&n.push(t)}),n},o.findIndex=Array.prototype.findIndex?function(t,e,n){return t.findIndex(e,n)}:function(t,e,n){n=void 0===n?t:n;for(var i=0,a=t.length;i=0;i--){var a=t[i];if(e(a))return a}},o.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},o.almostEquals=function(t,e,n){return Math.abs(t-e)t},o.max=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.max(t,e)},Number.NEGATIVE_INFINITY)},o.min=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.min(t,e)},Number.POSITIVE_INFINITY)},o.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0==(t=+t)||isNaN(t)?t:t>0?1:-1},o.log10=Math.log10?function(t){return Math.log10(t)}:function(t){return Math.log(t)/Math.LN10},o.toRadians=function(t){return t*(Math.PI/180)},o.toDegrees=function(t){return t*(180/Math.PI)},o.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),o=Math.atan2(i,n);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:a}},o.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},o.aliasPixel=function(t){return t%2==0?0:.5},o.splineCurve=function(t,e,n,i){var a=t.skip?e:t,o=e,r=n.skip?e:n,l=Math.sqrt(Math.pow(o.x-a.x,2)+Math.pow(o.y-a.y,2)),s=Math.sqrt(Math.pow(r.x-o.x,2)+Math.pow(r.y-o.y,2)),u=l/(l+s),d=s/(l+s),c=i*(u=isNaN(u)?0:u),h=i*(d=isNaN(d)?0:d);return{previous:{x:o.x-c*(r.x-a.x),y:o.y-c*(r.y-a.y)},next:{x:o.x+h*(r.x-a.x),y:o.y+h*(r.y-a.y)}}},o.EPSILON=Number.EPSILON||1e-14,o.splineCurveMonotone=function(t){var e,n,i,a,r=(t||[]).map(function(t){return{model:t._model,deltaK:0,mK:0}}),l=r.length;for(e=0;e0?r[e-1]:null,(a=e0?r[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},o.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},o.niceNum=function(t,e){var n=Math.floor(o.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},o.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},o.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.currentTarget||t.srcElement,l=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var u=parseFloat(o.getStyle(r,"padding-left")),d=parseFloat(o.getStyle(r,"padding-top")),c=parseFloat(o.getStyle(r,"padding-right")),h=parseFloat(o.getStyle(r,"padding-bottom")),f=l.right-l.left-u-c,g=l.bottom-l.top-d-h;return n=Math.round((n-l.left-u)/f*r.width/e.currentDevicePixelRatio),i=Math.round((i-l.top-d)/g*r.height/e.currentDevicePixelRatio),{x:n,y:i}},o.getConstraintWidth=function(t){return r(t,"max-width","clientWidth")},o.getConstraintHeight=function(t){return r(t,"max-height","clientHeight")},o.getMaximumWidth=function(t){var e=t.parentNode;if(!e)return t.clientWidth;var n=parseInt(o.getStyle(e,"padding-left"),10),i=parseInt(o.getStyle(e,"padding-right"),10),a=e.clientWidth-n-i,r=o.getConstraintWidth(t);return isNaN(r)?a:Math.min(a,r)},o.getMaximumHeight=function(t){var e=t.parentNode;if(!e)return t.clientHeight;var n=parseInt(o.getStyle(e,"padding-top"),10),i=parseInt(o.getStyle(e,"padding-bottom"),10),a=e.clientHeight-n-i,r=o.getConstraintHeight(t);return isNaN(r)?a:Math.min(a,r)},o.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},o.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,o=t.width;i.height=a*n,i.width=o*n,t.ctx.scale(n,n),i.style.height=a+"px",i.style.width=o+"px"}},o.fontString=function(t,e,n){return e+" "+t+"px "+n},o.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var l=0;o.each(n,function(e){void 0!==e&&null!==e&&!0!==o.isArray(e)?l=o.measureText(t,a,r,l,e):o.isArray(e)&&o.each(e,function(e){void 0===e||null===e||o.isArray(e)||(l=o.measureText(t,a,r,l,e))})});var s=r.length/2;if(s>n.length){for(var u=0;ui&&(i=o),i},o.numberOfLabelLines=function(t){var e=1;return o.each(t,function(t){o.isArray(t)&&t.length>e&&(e=t.length)}),e},o.color=i?function(t){return t instanceof CanvasGradient&&(t=a.global.defaultColor),i(t)}:function(t){return console.error("Color.js not found!"),t},o.getHoverColor=function(t){return t instanceof CanvasPattern?t:o.color(t).saturate(.5).darken(.1).rgbString()}}},{25:25,3:3,45:45}],28:[function(t,e,n){"use strict";function i(t,e){return t.native?{x:t.x,y:t.y}:u.getRelativePosition(t,e)}function a(t,e){var n,i,a,o,r;for(i=0,o=t.data.datasets.length;i0&&(u=t.getDatasetMeta(u[0]._datasetIndex).data),u},"x-axis":function(t,e){return s(t,e,{intersect:!1})},point:function(t,e){return o(t,i(e,t))},nearest:function(t,e,n){var a=i(e,t);n.axis=n.axis||"xy";var o=l(n.axis),s=r(t,a,n.intersect,o);return s.length>1&&s.sort(function(t,e){var n=t.getArea()-e.getArea();return 0===n&&(n=t._datasetIndex-e._datasetIndex),n}),s.slice(0,1)},x:function(t,e,n){var o=i(e,t),r=[],l=!1;return a(t,function(t){t.inXRange(o.x)&&r.push(t),t.inRange(o.x,o.y)&&(l=!0)}),n.intersect&&!l&&(r=[]),r},y:function(t,e,n){var o=i(e,t),r=[],l=!1;return a(t,function(t){t.inYRange(o.y)&&r.push(t),t.inRange(o.x,o.y)&&(l=!0)}),n.intersect&&!l&&(r=[]),r}}}},{45:45}],29:[function(t,e,n){"use strict";t(25)._set("global",{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},layout:{padding:{top:0,right:0,bottom:0,left:0}}}),e.exports=function(){var t=function(t,e){return this.construct(t,e),this};return t.Chart=t,t}},{25:25}],30:[function(t,e,n){"use strict";var i=t(45);e.exports=function(t){function e(t,e){return i.where(t,function(t){return t.position===e})}function n(t,e){t.forEach(function(t,e){return t._tmpIndex_=e,t}),t.sort(function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i._tmpIndex_-a._tmpIndex_:i.weight-a.weight}),t.forEach(function(t){delete t._tmpIndex_})}t.layoutService={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),e.fullWidth=e.fullWidth||!1,e.position=e.position||"top",e.weight=e.weight||0,t.boxes.push(e)},removeBox:function(t,e){var n=t.boxes?t.boxes.indexOf(e):-1;-1!==n&&t.boxes.splice(n,1)},configure:function(t,e,n){for(var i,a=["fullWidth","position","weight"],o=a.length,r=0;rh&&st.maxHeight){s--;break}s++,c=u*d}t.labelRotation=s},afterCalculateTickRotation:function(){l.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){l.callback(this.options.beforeFit,[this])},fit:function(){var t=this,a=t.minSize={width:0,height:0},o=i(t._ticks),r=t.options,u=r.ticks,d=r.scaleLabel,c=r.gridLines,h=r.display,f=t.isHorizontal(),g=n(u),p=r.gridLines.tickMarkLength;if(a.width=f?t.isFullWidth()?t.maxWidth-t.margins.left-t.margins.right:t.maxWidth:h&&c.drawTicks?p:0,a.height=f?h&&c.drawTicks?p:0:t.maxHeight,d.display&&h){var v=s(d)+l.options.toPadding(d.padding).height;f?a.height+=v:a.width+=v}if(u.display&&h){var m=l.longestText(t.ctx,g.font,o,t.longestTextCache),b=l.numberOfLabelLines(o),x=.5*g.size,y=t.options.ticks.padding;if(f){t.longestLabelWidth=m;var k=l.toRadians(t.labelRotation),w=Math.cos(k),M=Math.sin(k)*m+g.size*b+x*(b-1)+x;a.height=Math.min(t.maxHeight,a.height+M+y),t.ctx.font=g.font;var S=e(t.ctx,o[0],g.font),C=e(t.ctx,o[o.length-1],g.font);0!==t.labelRotation?(t.paddingLeft="bottom"===r.position?w*S+3:w*x+3,t.paddingRight="bottom"===r.position?w*x+3:w*C+3):(t.paddingLeft=S/2+3,t.paddingRight=C/2+3)}else u.mirror?m=0:m+=y+x,a.width=Math.min(t.maxWidth,a.width+m),t.paddingTop=g.size/2,t.paddingBottom=g.size/2}t.handleMargins(),t.width=a.width,t.height=a.height},handleMargins:function(){var t=this;t.margins&&(t.paddingLeft=Math.max(t.paddingLeft-t.margins.left,0),t.paddingTop=Math.max(t.paddingTop-t.margins.top,0),t.paddingRight=Math.max(t.paddingRight-t.margins.right,0),t.paddingBottom=Math.max(t.paddingBottom-t.margins.bottom,0))},afterFit:function(){l.callback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(l.isNullOrUndef(t))return NaN;if("number"==typeof t&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},getLabelForIndex:l.noop,getPixelForValue:l.noop,getValueForPixel:l.noop,getPixelForTick:function(t){var e=this,n=e.options.offset;if(e.isHorizontal()){var i=(e.width-(e.paddingLeft+e.paddingRight))/Math.max(e._ticks.length-(n?0:1),1),a=i*t+e.paddingLeft;n&&(a+=i/2);var o=e.left+Math.round(a);return o+=e.isFullWidth()?e.margins.left:0}var r=e.height-(e.paddingTop+e.paddingBottom);return e.top+t*(r/(e._ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var n=(e.width-(e.paddingLeft+e.paddingRight))*t+e.paddingLeft,i=e.left+Math.round(n);return i+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this,e=t.min,n=t.max;return t.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0},_autoSkip:function(t){var e,n,i,a,o=this,r=o.isHorizontal(),s=o.options.ticks.minor,u=t.length,d=l.toRadians(o.labelRotation),c=Math.cos(d),h=o.longestLabelWidth*c,f=[];for(s.maxTicksLimit&&(a=s.maxTicksLimit),r&&(e=!1,(h+s.autoSkipPadding)*u>o.width-(o.paddingLeft+o.paddingRight)&&(e=1+Math.floor((h+s.autoSkipPadding)*u/(o.width-(o.paddingLeft+o.paddingRight)))),a&&u>a&&(e=Math.max(e,Math.floor(u/a)))),n=0;n1&&n%e>0||n%e==0&&n+e>=u)&&n!==u-1&&delete i.label,f.push(i);return f},draw:function(t){var e=this,i=e.options;if(i.display){var r=e.ctx,u=o.global,d=i.ticks.minor,c=i.ticks.major||d,h=i.gridLines,f=i.scaleLabel,g=0!==e.labelRotation,p=e.isHorizontal(),v=d.autoSkip?e._autoSkip(e.getTicks()):e.getTicks(),m=l.valueOrDefault(d.fontColor,u.defaultFontColor),b=n(d),x=l.valueOrDefault(c.fontColor,u.defaultFontColor),y=n(c),k=h.drawTicks?h.tickMarkLength:0,w=l.valueOrDefault(f.fontColor,u.defaultFontColor),M=n(f),S=l.options.toPadding(f.padding),C=l.toRadians(e.labelRotation),_=[],D="right"===i.position?e.left:e.right-k,I="right"===i.position?e.left+k:e.right,P="bottom"===i.position?e.top:e.bottom-k,A="bottom"===i.position?e.top+k:e.bottom;if(l.each(v,function(n,o){if(!l.isNullOrUndef(n.label)){var r,s,c,f,m=n.label;o===e.zeroLineIndex&&i.offset===h.offsetGridLines?(r=h.zeroLineWidth,s=h.zeroLineColor,c=h.zeroLineBorderDash,f=h.zeroLineBorderDashOffset):(r=l.valueAtIndexOrDefault(h.lineWidth,o),s=l.valueAtIndexOrDefault(h.color,o),c=l.valueOrDefault(h.borderDash,u.borderDash),f=l.valueOrDefault(h.borderDashOffset,u.borderDashOffset));var b,x,y,w,M,S,T,F,O,R,L="middle",z="middle",B=d.padding;if(p){var W=k+B;"bottom"===i.position?(z=g?"middle":"top",L=g?"right":"center",R=e.top+W):(z=g?"middle":"bottom",L=g?"left":"center",R=e.bottom-W);var N=a(e,o,h.offsetGridLines&&v.length>1);N1);H0)n=t.stepSize;else{var o=i.niceNum(e.max-e.min,!1);n=i.niceNum(o/(t.maxTicks-1),!0)}var r=Math.floor(e.min/n)*n,l=Math.ceil(e.max/n)*n;t.min&&t.max&&t.stepSize&&i.almostWhole((t.max-t.min)/t.stepSize,n/1e3)&&(r=t.min,l=t.max);var s=(l-r)/n;s=i.almostEquals(s,Math.round(s),n/1e3)?Math.round(s):Math.ceil(s),a.push(void 0!==t.min?t.min:r);for(var u=1;u3?n[2]-n[1]:n[1]-n[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var o=i.log10(Math.abs(a)),r="";if(0!==t){var l=-1*Math.floor(o);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var a=t/Math.pow(10,Math.floor(i.log10(t)));return 0===t?"0":1===a||2===a||5===a||0===e||e===n.length-1?t.toExponential():""}}}},{45:45}],35:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45);i._set("global",{tooltips:{enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretPadding:2,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,callbacks:{beforeTitle:o.noop,title:function(t,e){var n="",i=e.labels,a=i?i.length:0;if(t.length>0){var o=t[0];o.xLabel?n=o.xLabel:a>0&&o.indexi.height-e.height&&(r="bottom");var l,s,u,d,c,h=(a.left+a.right)/2,f=(a.top+a.bottom)/2;"center"===r?(l=function(t){return t<=h},s=function(t){return t>h}):(l=function(t){return t<=e.width/2},s=function(t){return t>=i.width-e.width/2}),u=function(t){return t+e.width>i.width},d=function(t){return t-e.width<0},c=function(t){return t<=f?"top":"bottom"},l(n.x)?(o="left",u(n.x)&&(o="center",r=c(n.y))):s(n.x)&&(o="right",d(n.x)&&(o="center",r=c(n.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:o,yAlign:g.yAlign?g.yAlign:r}}function d(t,e,n){var i=t.x,a=t.y,o=t.caretSize,r=t.caretPadding,l=t.cornerRadius,s=n.xAlign,u=n.yAlign,d=o+r,c=l+r;return"right"===s?i-=e.width:"center"===s&&(i-=e.width/2),"top"===u?a+=d:a-="bottom"===u?e.height+d:e.height/2,"center"===u?"left"===s?i+=d:"right"===s&&(i-=d):"left"===s?i-=c:"right"===s&&(i+=c),{x:i,y:a}}t.Tooltip=a.extend({initialize:function(){this._model=l(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options.callbacks,i=e.beforeTitle.apply(t,arguments),a=e.title.apply(t,arguments),o=e.afterTitle.apply(t,arguments),r=[];return r=n(r,i),r=n(r,a),r=n(r,o)},getBeforeBody:function(){var t=this._options.callbacks.beforeBody.apply(this,arguments);return o.isArray(t)?t:void 0!==t?[t]:[]},getBody:function(t,e){var i=this,a=i._options.callbacks,r=[];return o.each(t,function(t){var o={before:[],lines:[],after:[]};n(o.before,a.beforeLabel.call(i,t,e)),n(o.lines,a.label.call(i,t,e)),n(o.after,a.afterLabel.call(i,t,e)),r.push(o)}),r},getAfterBody:function(){var t=this._options.callbacks.afterBody.apply(this,arguments);return o.isArray(t)?t:void 0!==t?[t]:[]},getFooter:function(){var t=this,e=t._options.callbacks,i=e.beforeFooter.apply(t,arguments),a=e.footer.apply(t,arguments),o=e.afterFooter.apply(t,arguments),r=[];return r=n(r,i),r=n(r,a),r=n(r,o)},update:function(e){var n,i,a=this,c=a._options,h=a._model,f=a._model=l(c),g=a._active,p=a._data,v={xAlign:h.xAlign,yAlign:h.yAlign},m={x:h.x,y:h.y},b={width:h.width,height:h.height},x={x:h.caretX,y:h.caretY};if(g.length){f.opacity=1;var y=[],k=[];x=t.Tooltip.positioners[c.position].call(a,g,a._eventPosition);var w=[];for(n=0,i=g.length;n0&&i.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,o=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&o&&(this.drawBackground(i,e,t,n,a),i.x+=e.xPadding,i.y+=e.yPadding,this.drawTitle(i,e,t,a),this.drawBody(i,e,t,a),this.drawFooter(i,e,t,a))}},handleEvent:function(t){var e=this,n=e._options,i=!1;if(e._lastActive=e._lastActive||[],"mouseout"===t.type?e._active=[]:e._active=e._chart.getElementsAtEventForMode(t,n.mode,n),!(i=!o.arrayEquals(e._active,e._lastActive)))return!1;if(e._lastActive=e._active,n.enabled||n.custom){e._eventPosition={x:t.x,y:t.y};var a=e._model;e.update(!0),e.pivot(),i|=a.x!==e._model.x||a.y!==e._model.y}return i}}),t.Tooltip.positioners={average:function(t){if(!t.length)return!1;var e,n,i=0,a=0,o=0;for(e=0,n=t.length;es;)a-=2*Math.PI;for(;a=l&&a<=s,d=r>=n.innerRadius&&r<=n.outerRadius;return u&&d}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t=this._chart.ctx,e=this._view,n=e.startAngle,i=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,n,i),t.arc(e.x,e.y,e.innerRadius,i,n,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})},{25:25,26:26,45:45}],37:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45),r=i.global;i._set("global",{elements:{line:{tension:.4,backgroundColor:r.defaultColor,borderWidth:3,borderColor:r.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}}),e.exports=a.extend({draw:function(){var t,e,n,i,a=this,l=a._view,s=a._chart.ctx,u=l.spanGaps,d=a._children.slice(),c=r.elements.line,h=-1;for(a._loop&&d.length&&d.push(d[0]),s.save(),s.lineCap=l.borderCapStyle||c.borderCapStyle,s.setLineDash&&s.setLineDash(l.borderDash||c.borderDash),s.lineDashOffset=l.borderDashOffset||c.borderDashOffset,s.lineJoin=l.borderJoinStyle||c.borderJoinStyle,s.lineWidth=l.borderWidth||c.borderWidth,s.strokeStyle=l.borderColor||r.defaultColor,s.beginPath(),h=-1,t=0;te?1:-1,r=1,l=u.borderSkipped||"left"):(e=u.x-u.width/2,n=u.x+u.width/2,i=u.y,o=1,r=(a=u.base)>i?1:-1,l=u.borderSkipped||"bottom"),d){var c=Math.min(Math.abs(e-n),Math.abs(i-a)),h=(d=d>c?c:d)/2,f=e+("left"!==l?h*o:0),g=n+("right"!==l?-h*o:0),p=i+("top"!==l?h*r:0),v=a+("bottom"!==l?-h*r:0);f!==g&&(i=p,a=v),p!==v&&(e=f,n=g)}s.beginPath(),s.fillStyle=u.backgroundColor,s.strokeStyle=u.borderColor,s.lineWidth=d;var m=[[e,a],[e,i],[n,i],[n,a]],b=["bottom","left","top","right"].indexOf(l,0);-1===b&&(b=0);var x=t(0);s.moveTo(x[0],x[1]);for(var y=1;y<4;y++)x=t(y),s.lineTo(x[0],x[1]);s.fill(),d&&s.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var n=!1;if(this._view){var i=a(this);n=t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom}return n},inLabelRange:function(t,e){var n=this;if(!n._view)return!1;var o=a(n);return i(n)?t>=o.left&&t<=o.right:e>=o.top&&e<=o.bottom},inXRange:function(t){var e=a(this);return t>=e.left&&t<=e.right},inYRange:function(t){var e=a(this);return t>=e.top&&t<=e.bottom},getCenterPoint:function(){var t,e,n=this._view;return i(this)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return t.width*Math.abs(t.y-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})},{25:25,26:26}],40:[function(t,e,n){"use strict";e.exports={},e.exports.Arc=t(36),e.exports.Line=t(37),e.exports.Point=t(38),e.exports.Rectangle=t(39)},{36:36,37:37,38:38,39:39}],41:[function(t,e,n){"use strict";var i=t(42),n=e.exports={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,o){if(o){var r=Math.min(o,i/2),l=Math.min(o,a/2);t.moveTo(e+r,n),t.lineTo(e+i-r,n),t.quadraticCurveTo(e+i,n,e+i,n+l),t.lineTo(e+i,n+a-l),t.quadraticCurveTo(e+i,n+a,e+i-r,n+a),t.lineTo(e+r,n+a),t.quadraticCurveTo(e,n+a,e,n+a-l),t.lineTo(e,n+l),t.quadraticCurveTo(e,n,e+r,n)}else t.rect(e,n,i,a)},drawPoint:function(t,e,n,i,a){var o,r,l,s,u,d;if(!e||"object"!=typeof e||"[object HTMLImageElement]"!==(o=e.toString())&&"[object HTMLCanvasElement]"!==o){if(!(isNaN(n)||n<=0)){switch(e){default:t.beginPath(),t.arc(i,a,n,0,2*Math.PI),t.closePath(),t.fill();break;case"triangle":t.beginPath(),u=(r=3*n/Math.sqrt(3))*Math.sqrt(3)/2,t.moveTo(i-r/2,a+u/3),t.lineTo(i+r/2,a+u/3),t.lineTo(i,a-2*u/3),t.closePath(),t.fill();break;case"rect":d=1/Math.SQRT2*n,t.beginPath(),t.fillRect(i-d,a-d,2*d,2*d),t.strokeRect(i-d,a-d,2*d,2*d);break;case"rectRounded":var c=n/Math.SQRT2,h=i-c,f=a-c,g=Math.SQRT2*n;t.beginPath(),this.roundedRect(t,h,f,g,g,n/2),t.closePath(),t.fill();break;case"rectRot":d=1/Math.SQRT2*n,t.beginPath(),t.moveTo(i-d,a),t.lineTo(i,a+d),t.lineTo(i+d,a),t.lineTo(i,a-d),t.closePath(),t.fill();break;case"cross":t.beginPath(),t.moveTo(i,a+n),t.lineTo(i,a-n),t.moveTo(i-n,a),t.lineTo(i+n,a),t.closePath();break;case"crossRot":t.beginPath(),l=Math.cos(Math.PI/4)*n,s=Math.sin(Math.PI/4)*n,t.moveTo(i-l,a-s),t.lineTo(i+l,a+s),t.moveTo(i-l,a+s),t.lineTo(i+l,a-s),t.closePath();break;case"star":t.beginPath(),t.moveTo(i,a+n),t.lineTo(i,a-n),t.moveTo(i-n,a),t.lineTo(i+n,a),l=Math.cos(Math.PI/4)*n,s=Math.sin(Math.PI/4)*n,t.moveTo(i-l,a-s),t.lineTo(i+l,a+s),t.moveTo(i-l,a+s),t.lineTo(i+l,a-s),t.closePath();break;case"line":t.beginPath(),t.moveTo(i-n,a),t.lineTo(i+n,a),t.closePath();break;case"dash":t.beginPath(),t.moveTo(i,a),t.lineTo(i+n,a),t.closePath()}t.stroke()}}else t.drawImage(e,i-e.width/2,a-e.height/2,e.width,e.height)},clipArea:function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},unclipArea:function(t){t.restore()},lineTo:function(t,e,n,i){if(n.steppedLine)return"after"===n.steppedLine&&!i||"after"!==n.steppedLine&&i?t.lineTo(e.x,n.y):t.lineTo(n.x,e.y),void t.lineTo(n.x,n.y);n.tension?t.bezierCurveTo(i?e.controlPointPreviousX:e.controlPointNextX,i?e.controlPointPreviousY:e.controlPointNextY,i?n.controlPointNextX:n.controlPointPreviousX,i?n.controlPointNextY:n.controlPointPreviousY,n.x,n.y):t.lineTo(n.x,n.y)}};i.clear=n.clear,i.drawRoundedRectangle=function(t){t.beginPath(),n.roundedRect.apply(n,arguments),t.closePath()}},{42:42}],42:[function(t,e,n){"use strict";var i={noop:function(){},uid:function(){var t=0;return function(){return t++}}(),isNullOrUndef:function(t){return null===t||void 0===t},isArray:Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},isObject:function(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)},valueOrDefault:function(t,e){return void 0===t?e:t},valueAtIndexOrDefault:function(t,e,n){return i.valueOrDefault(i.isArray(t)?t[e]:t,n)},callback:function(t,e,n){if(t&&"function"==typeof t.call)return t.apply(n,e)},each:function(t,e,n,a){var o,r,l;if(i.isArray(t))if(r=t.length,a)for(o=r-1;o>=0;o--)e.call(n,t[o],o);else for(o=0;o=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-a.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*a.easeInBounce(2*t):.5*a.easeOutBounce(2*t-1)+.5}};e.exports={effects:a},i.easingEffects=a},{42:42}],44:[function(t,e,n){"use strict";var i=t(42);e.exports={toLineHeight:function(t,e){var n=(""+t).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);if(!n||"normal"===n[1])return 1.2*e;switch(t=+n[2],n[3]){case"px":return t;case"%":t/=100}return e*t},toPadding:function(t){var e,n,a,o;return i.isObject(t)?(e=+t.top||0,n=+t.right||0,a=+t.bottom||0,o=+t.left||0):e=n=a=o=+t||0,{top:e,right:n,bottom:a,left:o,height:e+a,width:o+n}},resolve:function(t,e,n){var a,o,r;for(a=0,o=t.length;a
';var a=e.childNodes[0],r=e.childNodes[1];e._reset=function(){a.scrollLeft=1e6,a.scrollTop=1e6,r.scrollLeft=1e6,r.scrollTop=1e6};var l=function(){e._reset(),t()};return o(a,"scroll",l.bind(a,"expand")),o(r,"scroll",l.bind(r,"shrink")),e}function c(t,e){var n=t[m]||(t[m]={}),i=n.renderProxy=function(t){t.animationName===y&&e()};v.each(k,function(e){o(t,e,i)}),n.reflow=!!t.offsetParent,t.classList.add(x)}function h(t){var e=t[m]||{},n=e.renderProxy;n&&(v.each(k,function(e){r(t,e,n)}),delete e.renderProxy),t.classList.remove(x)}function f(t,e,n){var i=t[m]||(t[m]={}),a=i.resizer=d(u(function(){if(i.resizer)return e(l("resize",n))}));c(t,function(){if(i.resizer){var e=t.parentNode;e&&e!==a.parentNode&&e.insertBefore(a,e.firstChild),a._reset()}})}function g(t){var e=t[m]||{},n=e.resizer;delete e.resizer,h(t),n&&n.parentNode&&n.parentNode.removeChild(n)}function p(t,e){var n=t._style||document.createElement("style");t._style||(t._style=n,e="/* Chart.js */\n"+e,n.setAttribute("type","text/css"),document.getElementsByTagName("head")[0].appendChild(n)),n.appendChild(document.createTextNode(e))}var v=t(45),m="$chartjs",b="chartjs-",x=b+"render-monitor",y=b+"render-animation",k=["animationstart","webkitAnimationStart"],w={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},M=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};e.exports={_enabled:"undefined"!=typeof window&&"undefined"!=typeof document,initialize:function(){var t="from{opacity:0.99}to{opacity:1}";p(this,"@-webkit-keyframes "+y+"{"+t+"}@keyframes "+y+"{"+t+"}."+x+"{-webkit-animation:"+y+" 0.001s;animation:"+y+" 0.001s;}")},acquireContext:function(t,e){"string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas);var n=t&&t.getContext&&t.getContext("2d");return n&&n.canvas===t?(a(t,e),n):null},releaseContext:function(t){var e=t.canvas;if(e[m]){var n=e[m].initial;["height","width"].forEach(function(t){var i=n[t];v.isNullOrUndef(i)?e.removeAttribute(t):e.setAttribute(t,i)}),v.each(n.style||{},function(t,n){e.style[n]=t}),e.width=e.width,delete e[m]}},addEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=n[m]||(n[m]={});o(i,e,(a.proxies||(a.proxies={}))[t.id+"_"+e]=function(e){n(s(e,t))})}else f(i,n,t)},removeEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=((n[m]||{}).proxies||{})[t.id+"_"+e];a&&r(i,e,a)}else g(i)}},v.addEvent=o,v.removeEvent=r},{45:45}],48:[function(t,e,n){"use strict";var i=t(45),a=t(46),o=t(47),r=o._enabled?o:a;e.exports=i.extend({initialize:function(){},acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},r)},{45:45,46:46,47:47}],49:[function(t,e,n){"use strict";var i=t(25),a=t(40),o=t(45);i._set("global",{plugins:{filler:{propagate:!0}}}),e.exports=function(){function t(t,e,n){var i,a=t._model||{},o=a.fill;if(void 0===o&&(o=!!a.backgroundColor),!1===o||null===o)return!1;if(!0===o)return"origin";if(i=parseFloat(o,10),isFinite(i)&&Math.floor(i)===i)return"-"!==o[0]&&"+"!==o[0]||(i=e+i),!(i===e||i<0||i>=n)&&i;switch(o){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return o;default:return!1}}function e(t){var e,n=t.el._model||{},i=t.el._scale||{},a=t.fill,o=null;if(isFinite(a))return null;if("start"===a?o=void 0===n.scaleBottom?i.bottom:n.scaleBottom:"end"===a?o=void 0===n.scaleTop?i.top:n.scaleTop:void 0!==n.scaleZero?o=n.scaleZero:i.getBasePosition?o=i.getBasePosition():i.getBasePixel&&(o=i.getBasePixel()),void 0!==o&&null!==o){if(void 0!==o.x&&void 0!==o.y)return o;if("number"==typeof o&&isFinite(o))return e=i.isHorizontal(),{x:e?o:null,y:e?null:o}}return null}function n(t,e,n){var i,a=t[e].fill,o=[e];if(!n)return a;for(;!1!==a&&-1===o.indexOf(a);){if(!isFinite(a))return a;if(!(i=t[a]))return!1;if(i.visible)return a;o.push(a),a=i.fill}return!1}function r(t){var e=t.fill,n="dataset";return!1===e?null:(isFinite(e)||(n="boundary"),d[n](t))}function l(t){return t&&!t.skip}function s(t,e,n,i,a){var r;if(i&&a){for(t.moveTo(e[0].x,e[0].y),r=1;r0;--r)o.canvas.lineTo(t,n[r],n[r-1],!0)}}function u(t,e,n,i,a,o){var r,u,d,c,h,f,g,p=e.length,v=i.spanGaps,m=[],b=[],x=0,y=0;for(t.beginPath(),r=0,u=p+!!o;r');for(var n=0;n'),t.data.datasets[n].label&&e.push(t.data.datasets[n].label),e.push("");return e.push(""),e.join("")}}),e.exports=function(t){function e(t,e){return t.usePointStyle?e*Math.SQRT2:t.boxWidth}function n(e,n){var i=new t.Legend({ctx:e.ctx,options:n,chart:e});r.configure(e,i,n),r.addBox(e,i),e.legend=i}var r=t.layoutService,l=o.noop;return t.Legend=a.extend({initialize:function(t){o.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:l,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:l,beforeSetDimensions:l,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:l,beforeBuildLabels:l,buildLabels:function(){var t=this,e=t.options.labels||{},n=o.callback(e.generateLabels,[t.chart],t)||[];e.filter&&(n=n.filter(function(n){return e.filter(n,t.chart.data)})),t.options.reverse&&n.reverse(),t.legendItems=n},afterBuildLabels:l,beforeFit:l,fit:function(){var t=this,n=t.options,a=n.labels,r=n.display,l=t.ctx,s=i.global,u=o.valueOrDefault,d=u(a.fontSize,s.defaultFontSize),c=u(a.fontStyle,s.defaultFontStyle),h=u(a.fontFamily,s.defaultFontFamily),f=o.fontString(d,c,h),g=t.legendHitBoxes=[],p=t.minSize,v=t.isHorizontal();if(v?(p.width=t.maxWidth,p.height=r?10:0):(p.width=r?10:0,p.height=t.maxHeight),r)if(l.font=f,v){var m=t.lineWidths=[0],b=t.legendItems.length?d+a.padding:0;l.textAlign="left",l.textBaseline="top",o.each(t.legendItems,function(n,i){var o=e(a,d)+d/2+l.measureText(n.text).width;m[m.length-1]+o+a.padding>=t.width&&(b+=d+a.padding,m[m.length]=t.left),g[i]={left:0,top:0,width:o,height:d},m[m.length-1]+=o+a.padding}),p.height+=b}else{var x=a.padding,y=t.columnWidths=[],k=a.padding,w=0,M=0,S=d+x;o.each(t.legendItems,function(t,n){var i=e(a,d)+d/2+l.measureText(t.text).width;M+S>p.height&&(k+=w+a.padding,y.push(w),w=0,M=0),w=Math.max(w,i),M+=S,g[n]={left:0,top:0,width:i,height:d}}),k+=w,y.push(w),p.width+=k}t.width=p.width,t.height=p.height},afterFit:l,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,n=t.options,a=n.labels,r=i.global,l=r.elements.line,s=t.width,u=t.lineWidths;if(n.display){var d,c=t.ctx,h=o.valueOrDefault,f=h(a.fontColor,r.defaultFontColor),g=h(a.fontSize,r.defaultFontSize),p=h(a.fontStyle,r.defaultFontStyle),v=h(a.fontFamily,r.defaultFontFamily),m=o.fontString(g,p,v);c.textAlign="left",c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=m;var b=e(a,g),x=t.legendHitBoxes,y=function(t,e,i){if(!(isNaN(b)||b<=0)){c.save(),c.fillStyle=h(i.fillStyle,r.defaultColor),c.lineCap=h(i.lineCap,l.borderCapStyle),c.lineDashOffset=h(i.lineDashOffset,l.borderDashOffset),c.lineJoin=h(i.lineJoin,l.borderJoinStyle),c.lineWidth=h(i.lineWidth,l.borderWidth),c.strokeStyle=h(i.strokeStyle,r.defaultColor);var a=0===h(i.lineWidth,l.borderWidth);if(c.setLineDash&&c.setLineDash(h(i.lineDash,l.borderDash)),n.labels&&n.labels.usePointStyle){var s=g*Math.SQRT2/2,u=s/Math.SQRT2,d=t+u,f=e+u;o.canvas.drawPoint(c,i.pointStyle,s,d,f)}else a||c.strokeRect(t,e,b,g),c.fillRect(t,e,b,g);c.restore()}},k=function(t,e,n,i){var a=g/2,o=b+a+t,r=e+a;c.fillText(n.text,o,r),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(o,r),c.lineTo(o+i,r),c.stroke())},w=t.isHorizontal();d=w?{x:t.left+(s-u[0])/2,y:t.top+a.padding,line:0}:{x:t.left+a.padding,y:t.top+a.padding,line:0};var M=g+a.padding;o.each(t.legendItems,function(e,n){var i=c.measureText(e.text).width,o=b+g/2+i,r=d.x,l=d.y;w?r+o>=s&&(l=d.y+=M,d.line++,r=d.x=t.left+(s-u[d.line])/2):l+M>t.bottom&&(r=d.x=r+t.columnWidths[d.line]+a.padding,l=d.y=t.top+a.padding,d.line++),y(r,l,e),x[n].left=r,x[n].top=l,k(r,l,e,i),w?d.x+=o+a.padding:d.y+=M})}},handleEvent:function(t){var e=this,n=e.options,i="mouseup"===t.type?"click":t.type,a=!1;if("mousemove"===i){if(!n.onHover)return}else{if("click"!==i)return;if(!n.onClick)return}var o=t.x,r=t.y;if(o>=e.left&&o<=e.right&&r>=e.top&&r<=e.bottom)for(var l=e.legendHitBoxes,s=0;s=u.left&&o<=u.left+u.width&&r>=u.top&&r<=u.top+u.height){if("click"===i){n.onClick.call(e,t.native,e.legendItems[s]),a=!0;break}if("mousemove"===i){n.onHover.call(e,t.native,e.legendItems[s]),a=!0;break}}}return a}}),{id:"legend",beforeInit:function(t){var e=t.options.legend;e&&n(t,e)},beforeUpdate:function(t){var e=t.options.legend,a=t.legend;e?(o.mergeIf(e,i.global.legend),a?(r.configure(t,a,e),a.options=e):n(t,e)):a&&(r.removeBox(t,a),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}}}},{25:25,26:26,45:45}],51:[function(t,e,n){"use strict";var i=t(25),a=t(26),o=t(45);i._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,lineHeight:1.2,padding:10,position:"top",text:"",weight:2e3}}),e.exports=function(t){function e(e,i){var a=new t.Title({ctx:e.ctx,options:i,chart:e});n.configure(e,a,i),n.addBox(e,a),e.titleBlock=a}var n=t.layoutService,r=o.noop;return t.Title=a.extend({initialize:function(t){var e=this;o.extend(e,t),e.legendHitBoxes=[]},beforeUpdate:r,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:r,beforeSetDimensions:r,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:r,beforeBuildLabels:r,buildLabels:r,afterBuildLabels:r,beforeFit:r,fit:function(){var t=this,e=o.valueOrDefault,n=t.options,a=n.display,r=e(n.fontSize,i.global.defaultFontSize),l=t.minSize,s=o.isArray(n.text)?n.text.length:1,u=o.options.toLineHeight(n.lineHeight,r),d=a?s*u+2*n.padding:0;t.isHorizontal()?(l.width=t.maxWidth,l.height=d):(l.width=d,l.height=t.maxHeight),t.width=l.width,t.height=l.height},afterFit:r,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=o.valueOrDefault,a=t.options,r=i.global;if(a.display){var l,s,u,d=n(a.fontSize,r.defaultFontSize),c=n(a.fontStyle,r.defaultFontStyle),h=n(a.fontFamily,r.defaultFontFamily),f=o.fontString(d,c,h),g=o.options.toLineHeight(a.lineHeight,d),p=g/2+a.padding,v=0,m=t.top,b=t.left,x=t.bottom,y=t.right;e.fillStyle=n(a.fontColor,r.defaultFontColor),e.font=f,t.isHorizontal()?(s=b+(y-b)/2,u=m+p,l=y-b):(s="left"===a.position?b+p:y-p,u=m+(x-m)/2,l=x-m,v=Math.PI*("left"===a.position?-.5:.5)),e.save(),e.translate(s,u),e.rotate(v),e.textAlign="center",e.textBaseline="middle";var k=a.text;if(o.isArray(k))for(var w=0,M=0;Me.max&&(e.max=i))})});e.min=isFinite(e.min)&&!isNaN(e.min)?e.min:0,e.max=isFinite(e.max)&&!isNaN(e.max)?e.max:1,this.handleTickRangeOptions()},getTickLimit:function(){var t,e=this,n=e.options.ticks;if(e.isHorizontal())t=Math.min(n.maxTicksLimit?n.maxTicksLimit:11,Math.ceil(e.width/50));else{var o=a.valueOrDefault(n.fontSize,i.global.defaultFontSize);t=Math.min(n.maxTicksLimit?n.maxTicksLimit:11,Math.ceil(e.height/(2*o)))}return t},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e,n=this,i=n.start,a=+n.getRightValue(t),o=n.end-i;return n.isHorizontal()?(e=n.left+n.width/o*(a-i),Math.round(e)):(e=n.bottom-n.height/o*(a-i),Math.round(e))},getValueForPixel:function(t){var e=this,n=e.isHorizontal(),i=n?e.width:e.height,a=(n?t-e.left:e.bottom-t)/i;return e.start+(e.end-e.start)*a},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}});t.scaleService.registerScaleType("linear",n,e)}},{25:25,34:34,45:45}],54:[function(t,e,n){"use strict";var i=t(45),a=t(34);e.exports=function(t){var e=i.noop;t.LinearScaleBase=t.Scale.extend({getRightValue:function(e){return"string"==typeof e?+e:t.Scale.prototype.getRightValue.call(this,e)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=i.sign(t.min),a=i.sign(t.max);n<0&&a<0?t.max=0:n>0&&a>0&&(t.min=0)}var o=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),o!==r&&t.min>=t.max&&(o?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:e,handleDirectionalChanges:e,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),o={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,stepSize:i.valueOrDefault(e.fixedStepSize,e.stepSize)},r=t.ticks=a.generators.linear(o,t);t.handleDirectionalChanges(),t.max=i.max(r),t.min=i.min(r),e.reverse?(r.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var e=this;e.ticksAsNumbers=e.ticks.slice(),e.zeroLineIndex=e.ticks.indexOf(0),t.Scale.prototype.convertTicksToLabels.call(e)}})}},{34:34,45:45}],55:[function(t,e,n){"use strict";var i=t(45),a=t(34);e.exports=function(t){var e={position:"left",ticks:{callback:a.formatters.logarithmic}},n=t.Scale.extend({determineDataLimits:function(){function t(t){return s?t.xAxisID===e.id:t.yAxisID===e.id}var e=this,n=e.options,a=n.ticks,o=e.chart,r=o.data.datasets,l=i.valueOrDefault,s=e.isHorizontal();e.min=null,e.max=null,e.minNotZero=null;var u=n.stacked;if(void 0===u&&i.each(r,function(e,n){if(!u){var i=o.getDatasetMeta(n);o.isDatasetVisible(n)&&t(i)&&void 0!==i.stack&&(u=!0)}}),n.stacked||u){var d={};i.each(r,function(a,r){var l=o.getDatasetMeta(r),s=[l.type,void 0===n.stacked&&void 0===l.stack?r:"",l.stack].join(".");o.isDatasetVisible(r)&&t(l)&&(void 0===d[s]&&(d[s]=[]),i.each(a.data,function(t,i){var a=d[s],o=+e.getRightValue(t);isNaN(o)||l.data[i].hidden||(a[i]=a[i]||0,n.relativePoints?a[i]=100:a[i]+=o)}))}),i.each(d,function(t){var n=i.min(t),a=i.max(t);e.min=null===e.min?n:Math.min(e.min,n),e.max=null===e.max?a:Math.max(e.max,a)})}else i.each(r,function(n,a){var r=o.getDatasetMeta(a);o.isDatasetVisible(a)&&t(r)&&i.each(n.data,function(t,n){var i=+e.getRightValue(t);isNaN(i)||r.data[n].hidden||(null===e.min?e.min=i:ie.max&&(e.max=i),0!==i&&(null===e.minNotZero||ia?{start:e-n-5,end:e}:{start:e,end:e+n+5}}function s(t){var i,o,s,u=n(t),d=Math.min(t.height/2,t.width/2),c={r:t.width,l:0,t:t.height,b:0},h={};t.ctx.font=u.font,t._pointLabelSizes=[];var f=e(t);for(i=0;ic.r&&(c.r=v.end,h.r=g),m.startc.b&&(c.b=m.end,h.b=g)}t.setReductions(d,c,h)}function u(t){var e=Math.min(t.height/2,t.width/2);t.drawingArea=Math.round(e),t.setCenterPoint(0,0,0,0)}function d(t){return 0===t||180===t?"center":t<180?"left":"right"}function c(t,e,n,i){if(a.isArray(e))for(var o=n.y,r=1.5*i,l=0;l270||t<90)&&(n.y-=e.h)}function f(t){var i=t.ctx,o=a.valueOrDefault,r=t.options,l=r.angleLines,s=r.pointLabels;i.lineWidth=l.lineWidth,i.strokeStyle=l.color;var u=t.getDistanceFromCenterForValue(r.ticks.reverse?t.min:t.max),f=n(t);i.textBaseline="top";for(var g=e(t)-1;g>=0;g--){if(l.display){var p=t.getPointPosition(g,u);i.beginPath(),i.moveTo(t.xCenter,t.yCenter),i.lineTo(p.x,p.y),i.stroke(),i.closePath()}if(s.display){var m=t.getPointPosition(g,u+5),b=o(s.fontColor,v.defaultFontColor);i.font=f.font,i.fillStyle=b;var x=t.getIndexAngle(g),y=a.toDegrees(x);i.textAlign=d(y),h(y,t._pointLabelSizes[g],m),c(i,t.pointLabels[g]||"",m,f.size)}}}function g(t,n,i,o){var r=t.ctx;if(r.strokeStyle=a.valueAtIndexOrDefault(n.color,o-1),r.lineWidth=a.valueAtIndexOrDefault(n.lineWidth,o-1),t.options.gridLines.circular)r.beginPath(),r.arc(t.xCenter,t.yCenter,i,0,2*Math.PI),r.closePath(),r.stroke();else{var l=e(t);if(0===l)return;r.beginPath();var s=t.getPointPosition(0,i);r.moveTo(s.x,s.y);for(var u=1;u0&&n>0?e:0)},draw:function(){var t=this,e=t.options,n=e.gridLines,i=e.ticks,o=a.valueOrDefault;if(e.display){var r=t.ctx,l=this.getIndexAngle(0),s=o(i.fontSize,v.defaultFontSize),u=o(i.fontStyle,v.defaultFontStyle),d=o(i.fontFamily,v.defaultFontFamily),c=a.fontString(s,u,d);a.each(t.ticks,function(e,a){if(a>0||i.reverse){var u=t.getDistanceFromCenterForValue(t.ticksAsNumbers[a]);if(n.display&&0!==a&&g(t,n,u,a),i.display){var d=o(i.fontColor,v.defaultFontColor);if(r.font=c,r.save(),r.translate(t.xCenter,t.yCenter),r.rotate(l),i.showLabelBackdrop){var h=r.measureText(e).width;r.fillStyle=i.backdropColor,r.fillRect(-h/2-i.backdropPaddingX,-u-s/2-i.backdropPaddingY,h+2*i.backdropPaddingX,s+2*i.backdropPaddingY)}r.textAlign="center",r.textBaseline="middle",r.fillStyle=d,r.fillText(e,0,-u),r.restore()}}}),(e.angleLines.display||e.pointLabels.display)&&f(t)}}});t.scaleService.registerScaleType("radialLinear",b,m)}},{25:25,34:34,45:45}],57:[function(t,e,n){"use strict";function i(t,e){return t-e}function a(t){var e,n,i,a={},o=[];for(e=0,n=t.length;ee&&l=0&&r<=l;){if(i=r+l>>1,a=t[i-1]||null,o=t[i],!a)return{lo:null,hi:o};if(o[e]n))return{lo:a,hi:o};l=i-1}}return{lo:o,hi:null}}function l(t,e,n,i){var a=r(t,e,n),o=a.lo?a.hi?a.lo:t[t.length-2]:t[0],l=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=l[e]-o[e],u=s?(n-o[e])/s:0,d=(l[i]-o[i])*u;return o[i]+d}function s(t,e){var n=e.parser,i=e.parser||e.format;return"function"==typeof n?n(t):"string"==typeof t&&"string"==typeof i?m(t,i):(t instanceof m||(t=m(t)),t.isValid()?t:"function"==typeof i?i(t):t)}function u(t,e){if(x.isNullOrUndef(t))return null;var n=e.options.time,i=s(e.getRightValue(t),n);return i.isValid()?(n.round&&i.startOf(n.round),i.valueOf()):null}function d(t,e,n,i){var a,o,r,l=e-t,s=w[n],u=s.size,d=s.steps;if(!d)return Math.ceil(l/((i||1)*u));for(a=0,o=d.length;a=M.indexOf(e);a--)if(o=M[a],w[o].common&&r.as(o)>=t.length)return o;return M[e?M.indexOf(e):0]}function f(t){for(var e=M.indexOf(t)+1,n=M.length;e1?e[1]:i,r=e[0],s=(l(t,"time",o,"pos")-l(t,"time",r,"pos"))/2),a.time.max||(o=e[e.length-1],r=e.length>1?e[e.length-2]:n,u=(l(t,"time",o,"pos")-l(t,"time",r,"pos"))/2)),{left:s,right:u}}function v(t,e){var n,i,a,o,r=[];for(n=0,i=t.length;n=a&&n<=r&&c.push(n);return i.min=a,i.max=r,i._unit=s.unit||h(c,s.minUnit,i.min,i.max),i._majorUnit=f(i._unit),i._table=o(i._timestamps.data,a,r,l.distribution),i._offsets=p(i._table,c,a,r,l),v(c,i._majorUnit)},getLabelForIndex:function(t,e){var n=this,i=n.chart.data,a=n.options.time,o=i.labels&&t=0&&ti?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,i=(e[0]+t)%360;return e[0]=i<0?360+i:i,this.setValues("hsl",e),this},mix:function(t,e){var i=this,n=t,a=void 0===e?.5:e,o=2*a-1,r=i.alpha()-n.alpha(),s=((o*r==-1?o:(o+r)/(1+o*r))+1)/2,l=1-s;return this.rgb(s*i.red()+l*n.red(),s*i.green()+l*n.green(),s*i.blue()+l*n.blue()).alpha(i.alpha()*a+n.alpha()*(1-a))},toJSON:function(){return this.rgb()},clone:function(){var t,e,i=new o,n=this.values,a=i.values;for(var r in n)n.hasOwnProperty(r)&&(t=n[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return i}},o.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},o.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},o.prototype.getValues=function(t){for(var e=this.values,i={},n=0;n.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)+.1805*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)),100*(.2126*e+.7152*i+.0722*n),100*(.0193*e+.1192*i+.9505*n)]}function d(t){var e=u(t),i=e[0],n=e[1],a=e[2];return n/=100,a/=108.883,i=(i/=95.047)>.008856?Math.pow(i,1/3):7.787*i+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(i-n),200*(n-(a=a>.008856?Math.pow(a,1/3):7.787*a+16/116))]}function c(t){var e,i,n,a,o,r=t[0]/360,s=t[1]/100,l=t[2]/100;if(0==s)return[o=255*l,o,o];e=2*l-(i=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(n=r+1/3*-(u-1))<0&&n++,n>1&&n--,o=6*n<1?e+6*(i-e)*n:2*n<1?i:3*n<2?e+(i-e)*(2/3-n)*6:e,a[u]=255*o;return a}function h(t){var e=t[0]/60,i=t[1]/100,n=t[2]/100,a=Math.floor(e)%6,o=e-Math.floor(e),r=255*n*(1-i),s=255*n*(1-i*o),l=255*n*(1-i*(1-o));n*=255;switch(a){case 0:return[n,l,r];case 1:return[s,n,r];case 2:return[r,n,l];case 3:return[r,s,n];case 4:return[l,r,n];case 5:return[n,r,s]}}function f(t){var e,i,n,a,o=t[0]/360,s=t[1]/100,l=t[2]/100,u=s+l;switch(u>1&&(s/=u,l/=u),n=6*o-(e=Math.floor(6*o)),0!=(1&e)&&(n=1-n),a=s+n*((i=1-l)-s),e){default:case 6:case 0:r=i,g=a,b=s;break;case 1:r=a,g=i,b=s;break;case 2:r=s,g=i,b=a;break;case 3:r=s,g=a,b=i;break;case 4:r=a,g=s,b=i;break;case 5:r=i,g=s,b=a}return[255*r,255*g,255*b]}function p(t){var e=t[0]/100,i=t[1]/100,n=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a))]}function m(t){var e,i,n,a=t[0]/100,o=t[1]/100,r=t[2]/100;return i=-.9689*a+1.8758*o+.0415*r,n=.0557*a+-.204*o+1.057*r,e=(e=3.2406*a+-1.5372*o+-.4986*r)>.0031308?1.055*Math.pow(e,1/2.4)-.055:e*=12.92,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i*=12.92,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n*=12.92,[255*(e=Math.min(Math.max(0,e),1)),255*(i=Math.min(Math.max(0,i),1)),255*(n=Math.min(Math.max(0,n),1))]}function v(t){var e=t[0],i=t[1],n=t[2];return i/=100,n/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(e-i),200*(i-(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116))]}function x(t){var e,i,n,a,o=t[0],r=t[1],s=t[2];return o<=8?a=(i=100*o/903.3)/100*7.787+16/116:(i=100*Math.pow((o+16)/116,3),a=Math.pow(i/100,1/3)),[e=e/95.047<=.008856?e=95.047*(r/500+a-16/116)/7.787:95.047*Math.pow(r/500+a,3),i,n=n/108.883<=.008859?n=108.883*(a-s/200-16/116)/7.787:108.883*Math.pow(a-s/200,3)]}function y(t){var e,i=t[0],n=t[1],a=t[2];return(e=360*Math.atan2(a,n)/2/Math.PI)<0&&(e+=360),[i,Math.sqrt(n*n+a*a),e]}function k(t){return m(x(t))}function M(t){var e,i=t[0],n=t[1];return e=t[2]/360*2*Math.PI,[i,n*Math.cos(e),n*Math.sin(e)]}function w(t){return S[t]}e.exports={rgb2hsl:n,rgb2hsv:a,rgb2hwb:o,rgb2cmyk:s,rgb2keyword:l,rgb2xyz:u,rgb2lab:d,rgb2lch:function(t){return y(d(t))},hsl2rgb:c,hsl2hsv:function(t){var e=t[0],i=t[1]/100,n=t[2]/100;if(0===n)return[0,0,0];return[e,100*(2*(i*=(n*=2)<=1?n:2-n)/(n+i)),100*((n+i)/2)]},hsl2hwb:function(t){return o(c(t))},hsl2cmyk:function(t){return s(c(t))},hsl2keyword:function(t){return l(c(t))},hsv2rgb:h,hsv2hsl:function(t){var e,i,n=t[0],a=t[1]/100,o=t[2]/100;return e=a*o,[n,100*(e=(e/=(i=(2-a)*o)<=1?i:2-i)||0),100*(i/=2)]},hsv2hwb:function(t){return o(h(t))},hsv2cmyk:function(t){return s(h(t))},hsv2keyword:function(t){return l(h(t))},hwb2rgb:f,hwb2hsl:function(t){return n(f(t))},hwb2hsv:function(t){return a(f(t))},hwb2cmyk:function(t){return s(f(t))},hwb2keyword:function(t){return l(f(t))},cmyk2rgb:p,cmyk2hsl:function(t){return n(p(t))},cmyk2hsv:function(t){return a(p(t))},cmyk2hwb:function(t){return o(p(t))},cmyk2keyword:function(t){return l(p(t))},keyword2rgb:w,keyword2hsl:function(t){return n(w(t))},keyword2hsv:function(t){return a(w(t))},keyword2hwb:function(t){return o(w(t))},keyword2cmyk:function(t){return s(w(t))},keyword2lab:function(t){return d(w(t))},keyword2xyz:function(t){return u(w(t))},xyz2rgb:m,xyz2lab:v,xyz2lch:function(t){return y(v(t))},lab2xyz:x,lab2rgb:k,lab2lch:y,lch2lab:M,lch2xyz:function(t){return x(M(t))},lch2rgb:function(t){return k(M(t))}};var S={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},C={};for(var _ in S)C[JSON.stringify(S[_])]=_},{}],5:[function(t,e,i){var n=t(4),a=function(){return new u};for(var o in n){a[o+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),n[t](e)}}(o);var r=/(\w+)2(\w+)/.exec(o),s=r[1],l=r[2];(a[s]=a[s]||{})[l]=a[o]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=n[t](e);if("string"==typeof i||void 0===i)return i;for(var a=0;a0&&(t[0].yLabel?i=t[0].yLabel:e.labels.length>0&&t[0].index0?Math.min(r,n-i):r,i=n;return r}(i,u):-1,pixels:u,start:s,end:l,stackCount:n,scale:i}},calculateBarValuePixels:function(t,e){var i,n,a,o,r,s,l=this.chart,u=this.getMeta(),d=this.getValueScale(),c=l.data.datasets,h=d.getRightValue(c[t].data[e]),f=d.options.stacked,g=u.stack,p=0;if(f||void 0===f&&void 0!==g)for(i=0;i=0&&a>0)&&(p+=a));return o=d.getPixelForValue(p),{size:s=((r=d.getPixelForValue(p+h))-o)/2,base:o,head:r,center:r+s/2}},calculateBarIndexPixels:function(t,e,i){var n,a,r,s,l,u,d,c,h,f,g,p,m,v,b,x,y,k=i.scale.options,M="flex"===k.barThickness?(h=e,g=k,m=(f=i).pixels,v=m[h],b=h>0?m[h-1]:null,x=h');var i=t.data,n=i.datasets,a=i.labels;if(n.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(i,n){var a=t.getDatasetMeta(0),r=e.datasets[0],s=a.data[n],l=s&&s.custom||{},u=o.valueAtIndexOrDefault,d=t.options.elements.arc;return{text:i,fillStyle:l.backgroundColor?l.backgroundColor:u(r.backgroundColor,n,d.backgroundColor),strokeStyle:l.borderColor?l.borderColor:u(r.borderColor,n,d.borderColor),lineWidth:l.borderWidth?l.borderWidth:u(r.borderWidth,n,d.borderWidth),hidden:isNaN(r.data[n])||a.data[n].hidden,index:n}}):[]}},onClick:function(t,e){var i,n,a,o=e.index,r=this.chart;for(i=0,n=(r.data.datasets||[]).length;i=Math.PI?-1:g<-Math.PI?1:0))+f,m=Math.cos(g),v=Math.sin(g),b=Math.cos(p),x=Math.sin(p),y=g<=0&&p>=0||g<=2*Math.PI&&2*Math.PI<=p,k=g<=.5*Math.PI&&.5*Math.PI<=p||g<=2.5*Math.PI&&2.5*Math.PI<=p,M=g<=-Math.PI&&-Math.PI<=p||g<=Math.PI&&Math.PI<=p,w=g<=.5*-Math.PI&&.5*-Math.PI<=p||g<=1.5*Math.PI&&1.5*Math.PI<=p,S=h/100,C=M?-1:Math.min(m*(m<0?1:S),b*(b<0?1:S)),_=w?-1:Math.min(v*(v<0?1:S),x*(x<0?1:S)),D=y?1:Math.max(m*(m>0?1:S),b*(b>0?1:S)),I=k?1:Math.max(v*(v>0?1:S),x*(x>0?1:S)),P=.5*(D-C),A=.5*(I-_);u=Math.min(s/P,l/A),d={x:-.5*(D+C),y:-.5*(I+_)}}i.borderWidth=e.getMaxBorderWidth(c.data),i.outerRadius=Math.max((u-i.borderWidth)/2,0),i.innerRadius=Math.max(h?i.outerRadius/100*h:0,0),i.radiusLength=(i.outerRadius-i.innerRadius)/i.getVisibleDatasetCount(),i.offsetX=d.x*i.outerRadius,i.offsetY=d.y*i.outerRadius,c.total=e.calculateTotal(),e.outerRadius=i.outerRadius-i.radiusLength*e.getRingIndex(e.index),e.innerRadius=Math.max(e.outerRadius-i.radiusLength,0),o.each(c.data,function(i,n){e.updateElement(i,n,t)})},updateElement:function(t,e,i){var n=this,a=n.chart,r=a.chartArea,s=a.options,l=s.animation,u=(r.left+r.right)/2,d=(r.top+r.bottom)/2,c=s.rotation,h=s.rotation,f=n.getDataset(),g=i&&l.animateRotate?0:t.hidden?0:n.calculateCircumference(f.data[e])*(s.circumference/(2*Math.PI)),p=i&&l.animateScale?0:n.innerRadius,m=i&&l.animateScale?0:n.outerRadius,v=o.valueAtIndexOrDefault;o.extend(t,{_datasetIndex:n.index,_index:e,_model:{x:u+a.offsetX,y:d+a.offsetY,startAngle:c,endAngle:h,circumference:g,outerRadius:m,innerRadius:p,label:v(f.label,e,a.data.labels[e])}});var b=t._model;this.removeHoverStyle(t),i&&l.animateRotate||(b.startAngle=0===e?s.rotation:n.getMeta().data[e-1]._model.endAngle,b.endAngle=b.startAngle+b.circumference),t.pivot()},removeHoverStyle:function(e){t.DatasetController.prototype.removeHoverStyle.call(this,e,this.chart.options.elements.arc)},calculateTotal:function(){var t,e=this.getDataset(),i=this.getMeta(),n=0;return o.each(i.data,function(i,a){t=e.data[a],isNaN(t)||i.hidden||(n+=Math.abs(t))}),n},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?2*Math.PI*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){for(var e,i,n=0,a=this.index,o=t.length,r=0;r(n=e>n?e:n)?i:n;return n}})}},{25:25,40:40,45:45}],18:[function(t,e,i){"use strict";var n=t(25),a=t(40),o=t(45);n._set("line",{showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}}),e.exports=function(t){function e(t,e){return o.valueOrDefault(t.showLine,e.showLines)}t.controllers.line=t.DatasetController.extend({datasetElementType:a.Line,dataElementType:a.Point,update:function(t){var i,n,a,r=this,s=r.getMeta(),l=s.dataset,u=s.data||[],d=r.chart.options,c=d.elements.line,h=r.getScaleForId(s.yAxisID),f=r.getDataset(),g=e(f,d);for(g&&(a=l.custom||{},void 0!==f.tension&&void 0===f.lineTension&&(f.lineTension=f.tension),l._scale=h,l._datasetIndex=r.index,l._children=u,l._model={spanGaps:f.spanGaps?f.spanGaps:d.spanGaps,tension:a.tension?a.tension:o.valueOrDefault(f.lineTension,c.tension),backgroundColor:a.backgroundColor?a.backgroundColor:f.backgroundColor||c.backgroundColor,borderWidth:a.borderWidth?a.borderWidth:f.borderWidth||c.borderWidth,borderColor:a.borderColor?a.borderColor:f.borderColor||c.borderColor,borderCapStyle:a.borderCapStyle?a.borderCapStyle:f.borderCapStyle||c.borderCapStyle,borderDash:a.borderDash?a.borderDash:f.borderDash||c.borderDash,borderDashOffset:a.borderDashOffset?a.borderDashOffset:f.borderDashOffset||c.borderDashOffset,borderJoinStyle:a.borderJoinStyle?a.borderJoinStyle:f.borderJoinStyle||c.borderJoinStyle,fill:a.fill?a.fill:void 0!==f.fill?f.fill:c.fill,steppedLine:a.steppedLine?a.steppedLine:o.valueOrDefault(f.steppedLine,c.stepped),cubicInterpolationMode:a.cubicInterpolationMode?a.cubicInterpolationMode:o.valueOrDefault(f.cubicInterpolationMode,c.cubicInterpolationMode)},l.pivot()),i=0,n=u.length;i');var i=t.data,n=i.datasets,a=i.labels;if(n.length)for(var o=0;o'),a[o]&&e.push(a[o]),e.push("");return e.push(""),e.join("")},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map(function(i,n){var a=t.getDatasetMeta(0),r=e.datasets[0],s=a.data[n].custom||{},l=o.valueAtIndexOrDefault,u=t.options.elements.arc;return{text:i,fillStyle:s.backgroundColor?s.backgroundColor:l(r.backgroundColor,n,u.backgroundColor),strokeStyle:s.borderColor?s.borderColor:l(r.borderColor,n,u.borderColor),lineWidth:s.borderWidth?s.borderWidth:l(r.borderWidth,n,u.borderWidth),hidden:isNaN(r.data[n])||a.data[n].hidden,index:n}}):[]}},onClick:function(t,e){var i,n,a,o=e.index,r=this.chart;for(i=0,n=(r.data.datasets||[]).length;i0&&!isNaN(t)?2*Math.PI/e:0}})}},{25:25,40:40,45:45}],20:[function(t,e,i){"use strict";var n=t(25),a=t(40),o=t(45);n._set("radar",{scale:{type:"radialLinear"},elements:{line:{tension:0}}}),e.exports=function(t){t.controllers.radar=t.DatasetController.extend({datasetElementType:a.Line,dataElementType:a.Point,linkScales:o.noop,update:function(t){var e=this,i=e.getMeta(),n=i.dataset,a=i.data,r=n.custom||{},s=e.getDataset(),l=e.chart.options.elements.line,u=e.chart.scale;void 0!==s.tension&&void 0===s.lineTension&&(s.lineTension=s.tension),o.extend(i.dataset,{_datasetIndex:e.index,_scale:u,_children:a,_loop:!0,_model:{tension:r.tension?r.tension:o.valueOrDefault(s.lineTension,l.tension),backgroundColor:r.backgroundColor?r.backgroundColor:s.backgroundColor||l.backgroundColor,borderWidth:r.borderWidth?r.borderWidth:s.borderWidth||l.borderWidth,borderColor:r.borderColor?r.borderColor:s.borderColor||l.borderColor,fill:r.fill?r.fill:void 0!==s.fill?s.fill:l.fill,borderCapStyle:r.borderCapStyle?r.borderCapStyle:s.borderCapStyle||l.borderCapStyle,borderDash:r.borderDash?r.borderDash:s.borderDash||l.borderDash,borderDashOffset:r.borderDashOffset?r.borderDashOffset:s.borderDashOffset||l.borderDashOffset,borderJoinStyle:r.borderJoinStyle?r.borderJoinStyle:s.borderJoinStyle||l.borderJoinStyle}}),i.dataset.pivot(),o.each(a,function(i,n){e.updateElement(i,n,t)},e),e.updateBezierControlPoints()},updateElement:function(t,e,i){var n=this,a=t.custom||{},r=n.getDataset(),s=n.chart.scale,l=n.chart.options.elements.point,u=s.getPointPositionForValue(e,r.data[e]);void 0!==r.radius&&void 0===r.pointRadius&&(r.pointRadius=r.radius),void 0!==r.hitRadius&&void 0===r.pointHitRadius&&(r.pointHitRadius=r.hitRadius),o.extend(t,{_datasetIndex:n.index,_index:e,_scale:s,_model:{x:i?s.xCenter:u.x,y:i?s.yCenter:u.y,tension:a.tension?a.tension:o.valueOrDefault(r.lineTension,n.chart.options.elements.line.tension),radius:a.radius?a.radius:o.valueAtIndexOrDefault(r.pointRadius,e,l.radius),backgroundColor:a.backgroundColor?a.backgroundColor:o.valueAtIndexOrDefault(r.pointBackgroundColor,e,l.backgroundColor),borderColor:a.borderColor?a.borderColor:o.valueAtIndexOrDefault(r.pointBorderColor,e,l.borderColor),borderWidth:a.borderWidth?a.borderWidth:o.valueAtIndexOrDefault(r.pointBorderWidth,e,l.borderWidth),pointStyle:a.pointStyle?a.pointStyle:o.valueAtIndexOrDefault(r.pointStyle,e,l.pointStyle),hitRadius:a.hitRadius?a.hitRadius:o.valueAtIndexOrDefault(r.pointHitRadius,e,l.hitRadius)}}),t._model.skip=a.skip?a.skip:isNaN(t._model.x)||isNaN(t._model.y)},updateBezierControlPoints:function(){var t=this.chart.chartArea,e=this.getMeta();o.each(e.data,function(i,n){var a=i._model,r=o.splineCurve(o.previousItem(e.data,n,!0)._model,a,o.nextItem(e.data,n,!0)._model,a.tension);a.controlPointPreviousX=Math.max(Math.min(r.previous.x,t.right),t.left),a.controlPointPreviousY=Math.max(Math.min(r.previous.y,t.bottom),t.top),a.controlPointNextX=Math.max(Math.min(r.next.x,t.right),t.left),a.controlPointNextY=Math.max(Math.min(r.next.y,t.bottom),t.top),i.pivot()})},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},n=t._index,a=t._model;a.radius=i.hoverRadius?i.hoverRadius:o.valueAtIndexOrDefault(e.pointHoverRadius,n,this.chart.options.elements.point.hoverRadius),a.backgroundColor=i.hoverBackgroundColor?i.hoverBackgroundColor:o.valueAtIndexOrDefault(e.pointHoverBackgroundColor,n,o.getHoverColor(a.backgroundColor)),a.borderColor=i.hoverBorderColor?i.hoverBorderColor:o.valueAtIndexOrDefault(e.pointHoverBorderColor,n,o.getHoverColor(a.borderColor)),a.borderWidth=i.hoverBorderWidth?i.hoverBorderWidth:o.valueAtIndexOrDefault(e.pointHoverBorderWidth,n,a.borderWidth)},removeHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],i=t.custom||{},n=t._index,a=t._model,r=this.chart.options.elements.point;a.radius=i.radius?i.radius:o.valueAtIndexOrDefault(e.pointRadius,n,r.radius),a.backgroundColor=i.backgroundColor?i.backgroundColor:o.valueAtIndexOrDefault(e.pointBackgroundColor,n,r.backgroundColor),a.borderColor=i.borderColor?i.borderColor:o.valueAtIndexOrDefault(e.pointBorderColor,n,r.borderColor),a.borderWidth=i.borderWidth?i.borderWidth:o.valueAtIndexOrDefault(e.pointBorderWidth,n,r.borderWidth)}})}},{25:25,40:40,45:45}],21:[function(t,e,i){"use strict";t(25)._set("scatter",{hover:{mode:"single"},scales:{xAxes:[{id:"x-axis-1",type:"linear",position:"bottom"}],yAxes:[{id:"y-axis-1",type:"linear",position:"left"}]},showLines:!1,tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}}),e.exports=function(t){t.controllers.scatter=t.controllers.line}},{25:25}],22:[function(t,e,i){"use strict";var n=t(25),a=t(26),o=t(45);n._set("global",{animation:{duration:1e3,easing:"easeOutQuart",onProgress:o.noop,onComplete:o.noop}}),e.exports=function(t){t.Animation=a.extend({chart:null,currentStep:0,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),t.animationService={frameDuration:17,animations:[],dropFrames:0,request:null,addAnimation:function(t,e,i,n){var a,o,r=this.animations;for(e.chart=t,n||(t.animating=!0),a=0,o=r.length;a1&&(i=Math.floor(t.dropFrames),t.dropFrames=t.dropFrames%1),t.advance(1+i);var n=Date.now();t.dropFrames+=(n-e)/t.frameDuration,t.animations.length>0&&t.requestAnimationFrame()},advance:function(t){for(var e,i,n=this.animations,a=0;a=e.numSteps?(o.callback(e.onAnimationComplete,[e],i),i.animating=!1,n.splice(a,1)):++a}},Object.defineProperty(t.Animation.prototype,"animationObject",{get:function(){return this}}),Object.defineProperty(t.Animation.prototype,"chartInstance",{get:function(){return this.chart},set:function(t){this.chart=t}})}},{25:25,26:26,45:45}],23:[function(t,e,i){"use strict";var n=t(25),a=t(45),o=t(28),r=t(30),s=t(48),l=t(31);e.exports=function(t){function e(t){return"top"===t||"bottom"===t}t.types={},t.instances={},t.controllers={},a.extend(t.prototype,{construct:function(e,i){var o,r,l=this;(r=(o=(o=i)||{}).data=o.data||{}).datasets=r.datasets||[],r.labels=r.labels||[],o.options=a.configMerge(n.global,n[o.type],o.options||{}),i=o;var u=s.acquireContext(e,i),d=u&&u.canvas,c=d&&d.height,h=d&&d.width;l.id=a.uid(),l.ctx=u,l.canvas=d,l.config=i,l.width=h,l.height=c,l.aspectRatio=c?h/c:null,l.options=i.options,l._bufferedRender=!1,l.chart=l,l.controller=l,t.instances[l.id]=l,Object.defineProperty(l,"data",{get:function(){return l.config.data},set:function(t){l.config.data=t}}),u&&d?(l.initialize(),l.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return l.notify(t,"beforeInit"),a.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.initToolTip(),l.notify(t,"afterInit"),t},clear:function(){return a.canvas.clear(this),this},stop:function(){return t.animationService.cancelAnimation(this),this},resize:function(t){var e=this,i=e.options,n=e.canvas,o=i.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(a.getMaximumWidth(n))),s=Math.max(0,Math.floor(o?r/o:a.getMaximumHeight(n)));if((e.width!==r||e.height!==s)&&(n.width=e.width=r,n.height=e.height=s,n.style.width=r+"px",n.style.height=s+"px",a.retinaScale(e,i.devicePixelRatio),!t)){var u={width:r,height:s};l.notify(e,"resize",[u]),e.options.onResize&&e.options.onResize(e,u),e.stop(),e.update(e.options.responsiveAnimationDuration)}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},i=t.scale;a.each(e.xAxes,function(t,e){t.id=t.id||"x-axis-"+e}),a.each(e.yAxes,function(t,e){t.id=t.id||"y-axis-"+e}),i&&(i.id=i.id||"scale")},buildOrUpdateScales:function(){var i=this,n=i.options,o=i.scales||{},r=[],s=Object.keys(o).reduce(function(t,e){return t[e]=!1,t},{});n.scales&&(r=r.concat((n.scales.xAxes||[]).map(function(t){return{options:t,dtype:"category",dposition:"bottom"}}),(n.scales.yAxes||[]).map(function(t){return{options:t,dtype:"linear",dposition:"left"}}))),n.scale&&r.push({options:n.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),a.each(r,function(n){var r=n.options,l=r.id,u=a.valueOrDefault(r.type,n.dtype);e(r.position)!==e(n.dposition)&&(r.position=n.dposition),s[l]=!0;var d=null;if(l in o&&o[l].type===u)(d=o[l]).options=r,d.ctx=i.ctx,d.chart=i;else{var c=t.scaleService.getScaleConstructor(u);if(!c)return;d=new c({id:l,type:u,options:r,ctx:i.ctx,chart:i}),o[d.id]=d}d.mergeTicksOptions(),n.isDefault&&(i.scale=d)}),a.each(s,function(t,e){t||delete o[e]}),i.scales=o,t.scaleService.addScalesToLayout(this)},buildOrUpdateControllers:function(){var e=this,i=[],n=[];return a.each(e.data.datasets,function(a,o){var r=e.getDatasetMeta(o),s=a.type||e.config.type;if(r.type&&r.type!==s&&(e.destroyDatasetMeta(o),r=e.getDatasetMeta(o)),r.type=s,i.push(r.type),r.controller)r.controller.updateIndex(o),r.controller.linkScales();else{var l=t.controllers[r.type];if(void 0===l)throw new Error('"'+r.type+'" is not a chart type.');r.controller=new l(e,o),n.push(r.controller)}},e),n},resetElements:function(){var t=this;a.each(t.data.datasets,function(e,i){t.getDatasetMeta(i).controller.reset()},t)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(e){var i,n,o=this;if(e&&"object"==typeof e||(e={duration:e,lazy:arguments[1]}),n=(i=o).options,a.each(i.scales,function(t){r.removeBox(i,t)}),n=a.configMerge(t.defaults.global,t.defaults[i.config.type],n),i.options=i.config.options=n,i.ensureScalesHaveIDs(),i.buildOrUpdateScales(),i.tooltip._options=n.tooltips,i.tooltip.initialize(),l._invalidate(o),!1!==l.notify(o,"beforeUpdate")){o.tooltip._data=o.data;var s=o.buildOrUpdateControllers();a.each(o.data.datasets,function(t,e){o.getDatasetMeta(e).controller.buildOrUpdateElements()},o),o.updateLayout(),o.options.animation&&o.options.animation.duration&&a.each(s,function(t){t.reset()}),o.updateDatasets(),o.tooltip.initialize(),o.lastActive=[],l.notify(o,"afterUpdate"),o._bufferedRender?o._bufferedRequest={duration:e.duration,easing:e.easing,lazy:e.lazy}:o.render(e)}},updateLayout:function(){!1!==l.notify(this,"beforeLayout")&&(r.update(this,this.width,this.height),l.notify(this,"afterScaleUpdate"),l.notify(this,"afterLayout"))},updateDatasets:function(){if(!1!==l.notify(this,"beforeDatasetsUpdate")){for(var t=0,e=this.data.datasets.length;t=0;--i)e.isDatasetVisible(i)&&e.drawDataset(i,t);l.notify(e,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var i=this.getDatasetMeta(t),n={meta:i,index:t,easingValue:e};!1!==l.notify(this,"beforeDatasetDraw",[n])&&(i.controller.draw(e),l.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,i={tooltip:e,easingValue:t};!1!==l.notify(this,"beforeTooltipDraw",[i])&&(e.draw(),l.notify(this,"afterTooltipDraw",[i]))},getElementAtEvent:function(t){return o.modes.single(this,t)},getElementsAtEvent:function(t){return o.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return o.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,i){var n=o.modes[e];return"function"==typeof n?n(this,t,i):[]},getDatasetAtEvent:function(t){return o.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var i=e._meta[this.id];return i||(i=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null}),i},getVisibleDatasetCount:function(){for(var t=0,e=0,i=this.data.datasets.length;e0||(e.forEach(function(e){delete t[e]}),delete t._chartjs)}}t.DatasetController=function(t,e){this.initialize(t,e)},n.extend(t.DatasetController.prototype,{datasetElementType:null,dataElementType:null,initialize:function(t,e){this.chart=t,this.index=e,this.linkScales(),this.addElements()},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),i=t.getDataset();null!==e.xAxisID&&e.xAxisID in t.chart.scales||(e.xAxisID=i.xAxisID||t.chart.options.scales.xAxes[0].id),null!==e.yAxisID&&e.yAxisID in t.chart.scales||(e.yAxisID=i.yAxisID||t.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},reset:function(){this.update(!0)},destroy:function(){this._data&&i(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,i=this.getMeta(),n=this.getDataset().data||[],a=i.data;for(t=0,e=n.length;ti&&this.insertElements(i,n-i)},insertElements:function(t,e){for(var i=0;i=i[e].length&&i[e].push({}),!i[e][r].type||l.type&&l.type!==i[e][r].type?o.merge(i[e][r],[t.scaleService.getScaleDefaults(s),l]):o.merge(i[e][r],l)}else o._merger(e,i,n,a)}})},o.where=function(t,e){if(o.isArray(t)&&Array.prototype.filter)return t.filter(e);var i=[];return o.each(t,function(t){e(t)&&i.push(t)}),i},o.findIndex=Array.prototype.findIndex?function(t,e,i){return t.findIndex(e,i)}:function(t,e,i){i=void 0===i?t:i;for(var n=0,a=t.length;n=0;n--){var a=t[n];if(e(a))return a}},o.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},o.almostEquals=function(t,e,i){return Math.abs(t-e)t},o.max=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.max(t,e)},Number.NEGATIVE_INFINITY)},o.min=function(t){return t.reduce(function(t,e){return isNaN(e)?t:Math.min(t,e)},Number.POSITIVE_INFINITY)},o.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},o.log10=Math.log10?function(t){return Math.log10(t)}:function(t){var e=Math.log(t)*Math.LOG10E,i=Math.round(e);return t===Math.pow(10,i)?i:e},o.toRadians=function(t){return t*(Math.PI/180)},o.toDegrees=function(t){return t*(180/Math.PI)},o.getAngleFromPoint=function(t,e){var i=e.x-t.x,n=e.y-t.y,a=Math.sqrt(i*i+n*n),o=Math.atan2(n,i);return o<-.5*Math.PI&&(o+=2*Math.PI),{angle:o,distance:a}},o.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},o.aliasPixel=function(t){return t%2==0?0:.5},o.splineCurve=function(t,e,i,n){var a=t.skip?e:t,o=e,r=i.skip?e:i,s=Math.sqrt(Math.pow(o.x-a.x,2)+Math.pow(o.y-a.y,2)),l=Math.sqrt(Math.pow(r.x-o.x,2)+Math.pow(r.y-o.y,2)),u=s/(s+l),d=l/(s+l),c=n*(u=isNaN(u)?0:u),h=n*(d=isNaN(d)?0:d);return{previous:{x:o.x-c*(r.x-a.x),y:o.y-c*(r.y-a.y)},next:{x:o.x+h*(r.x-a.x),y:o.y+h*(r.y-a.y)}}},o.EPSILON=Number.EPSILON||1e-14,o.splineCurveMonotone=function(t){var e,i,n,a,r,s,l,u,d,c=(t||[]).map(function(t){return{model:t._model,deltaK:0,mK:0}}),h=c.length;for(e=0;e0?c[e-1]:null,(a=e0?c[e-1]:null,a=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},o.previousItem=function(t,e,i){return i?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},o.niceNum=function(t,e){var i=Math.floor(o.log10(t)),n=t/Math.pow(10,i);return(e?n<1.5?1:n<3?2:n<7?5:10:n<=1?1:n<=2?2:n<=5?5:10)*Math.pow(10,i)},o.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},o.getRelativePosition=function(t,e){var i,n,a=t.originalEvent||t,r=t.currentTarget||t.srcElement,s=r.getBoundingClientRect(),l=a.touches;l&&l.length>0?(i=l[0].clientX,n=l[0].clientY):(i=a.clientX,n=a.clientY);var u=parseFloat(o.getStyle(r,"padding-left")),d=parseFloat(o.getStyle(r,"padding-top")),c=parseFloat(o.getStyle(r,"padding-right")),h=parseFloat(o.getStyle(r,"padding-bottom")),f=s.right-s.left-u-c,g=s.bottom-s.top-d-h;return{x:i=Math.round((i-s.left-u)/f*r.width/e.currentDevicePixelRatio),y:n=Math.round((n-s.top-d)/g*r.height/e.currentDevicePixelRatio)}},o.getConstraintWidth=function(t){return r(t,"max-width","clientWidth")},o.getConstraintHeight=function(t){return r(t,"max-height","clientHeight")},o.getMaximumWidth=function(t){var e=t.parentNode;if(!e)return t.clientWidth;var i=parseInt(o.getStyle(e,"padding-left"),10),n=parseInt(o.getStyle(e,"padding-right"),10),a=e.clientWidth-i-n,r=o.getConstraintWidth(t);return isNaN(r)?a:Math.min(a,r)},o.getMaximumHeight=function(t){var e=t.parentNode;if(!e)return t.clientHeight;var i=parseInt(o.getStyle(e,"padding-top"),10),n=parseInt(o.getStyle(e,"padding-bottom"),10),a=e.clientHeight-i-n,r=o.getConstraintHeight(t);return isNaN(r)?a:Math.min(a,r)},o.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},o.retinaScale=function(t,e){var i=t.currentDevicePixelRatio=e||window.devicePixelRatio||1;if(1!==i){var n=t.canvas,a=t.height,o=t.width;n.height=a*i,n.width=o*i,t.ctx.scale(i,i),n.style.height||n.style.width||(n.style.height=a+"px",n.style.width=o+"px")}},o.fontString=function(t,e,i){return e+" "+t+"px "+i},o.longestText=function(t,e,i,n){var a=(n=n||{}).data=n.data||{},r=n.garbageCollect=n.garbageCollect||[];n.font!==e&&(a=n.data={},r=n.garbageCollect=[],n.font=e),t.font=e;var s=0;o.each(i,function(e){null!=e&&!0!==o.isArray(e)?s=o.measureText(t,a,r,s,e):o.isArray(e)&&o.each(e,function(e){null==e||o.isArray(e)||(s=o.measureText(t,a,r,s,e))})});var l=r.length/2;if(l>i.length){for(var u=0;un&&(n=o),n},o.numberOfLabelLines=function(t){var e=1;return o.each(t,function(t){o.isArray(t)&&t.length>e&&(e=t.length)}),e},o.color=n?function(t){return t instanceof CanvasGradient&&(t=a.global.defaultColor),n(t)}:function(t){return console.error("Color.js not found!"),t},o.getHoverColor=function(t){return t instanceof CanvasPattern?t:o.color(t).saturate(.5).darken(.1).rgbString()}}},{25:25,3:3,45:45}],28:[function(t,e,i){"use strict";var n=t(45);function a(t,e){return t.native?{x:t.x,y:t.y}:n.getRelativePosition(t,e)}function o(t,e){var i,n,a,o,r;for(n=0,o=t.data.datasets.length;n0&&(u=t.getDatasetMeta(u[0]._datasetIndex).data),u},"x-axis":function(t,e){return u(t,e,{intersect:!1})},point:function(t,e){return r(t,a(e,t))},nearest:function(t,e,i){var n=a(e,t);i.axis=i.axis||"xy";var o=l(i.axis),r=s(t,n,i.intersect,o);return r.length>1&&r.sort(function(t,e){var i=t.getArea()-e.getArea();return 0===i&&(i=t._datasetIndex-e._datasetIndex),i}),r.slice(0,1)},x:function(t,e,i){var n=a(e,t),r=[],s=!1;return o(t,function(t){t.inXRange(n.x)&&r.push(t),t.inRange(n.x,n.y)&&(s=!0)}),i.intersect&&!s&&(r=[]),r},y:function(t,e,i){var n=a(e,t),r=[],s=!1;return o(t,function(t){t.inYRange(n.y)&&r.push(t),t.inRange(n.x,n.y)&&(s=!0)}),i.intersect&&!s&&(r=[]),r}}}},{45:45}],29:[function(t,e,i){"use strict";t(25)._set("global",{responsive:!0,responsiveAnimationDuration:0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",showLines:!0,elements:{},layout:{padding:{top:0,right:0,bottom:0,left:0}}}),e.exports=function(){var t=function(t,e){return this.construct(t,e),this};return t.Chart=t,t}},{25:25}],30:[function(t,e,i){"use strict";var n=t(45);function a(t,e){return n.where(t,function(t){return t.position===e})}function o(t,e){t.forEach(function(t,e){return t._tmpIndex_=e,t}),t.sort(function(t,i){var n=e?i:t,a=e?t:i;return n.weight===a.weight?n._tmpIndex_-a._tmpIndex_:n.weight-a.weight}),t.forEach(function(t){delete t._tmpIndex_})}e.exports={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),e.fullWidth=e.fullWidth||!1,e.position=e.position||"top",e.weight=e.weight||0,t.boxes.push(e)},removeBox:function(t,e){var i=t.boxes?t.boxes.indexOf(e):-1;-1!==i&&t.boxes.splice(i,1)},configure:function(t,e,i){for(var n,a=["fullWidth","position","weight"],o=a.length,r=0;rh&&lt.maxHeight){l--;break}l++,c=u*d}t.labelRotation=l},afterCalculateTickRotation:function(){o.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){o.callback(this.options.beforeFit,[this])},fit:function(){var t=this,n=t.minSize={width:0,height:0},a=s(t._ticks),l=t.options,u=l.ticks,d=l.scaleLabel,c=l.gridLines,h=l.display,f=t.isHorizontal(),g=i(u),p=l.gridLines.tickMarkLength;if(n.width=f?t.isFullWidth()?t.maxWidth-t.margins.left-t.margins.right:t.maxWidth:h&&c.drawTicks?p:0,n.height=f?h&&c.drawTicks?p:0:t.maxHeight,d.display&&h){var m=r(d)+o.options.toPadding(d.padding).height;f?n.height+=m:n.width+=m}if(u.display&&h){var v=o.longestText(t.ctx,g.font,a,t.longestTextCache),b=o.numberOfLabelLines(a),x=.5*g.size,y=t.options.ticks.padding;if(f){t.longestLabelWidth=v;var k=o.toRadians(t.labelRotation),M=Math.cos(k),w=Math.sin(k)*v+g.size*b+x*(b-1)+x;n.height=Math.min(t.maxHeight,n.height+w+y),t.ctx.font=g.font;var S=e(t.ctx,a[0],g.font),C=e(t.ctx,a[a.length-1],g.font);0!==t.labelRotation?(t.paddingLeft="bottom"===l.position?M*S+3:M*x+3,t.paddingRight="bottom"===l.position?M*x+3:M*C+3):(t.paddingLeft=S/2+3,t.paddingRight=C/2+3)}else u.mirror?v=0:v+=y+x,n.width=Math.min(t.maxWidth,n.width+v),t.paddingTop=g.size/2,t.paddingBottom=g.size/2}t.handleMargins(),t.width=n.width,t.height=n.height},handleMargins:function(){var t=this;t.margins&&(t.paddingLeft=Math.max(t.paddingLeft-t.margins.left,0),t.paddingTop=Math.max(t.paddingTop-t.margins.top,0),t.paddingRight=Math.max(t.paddingRight-t.margins.right,0),t.paddingBottom=Math.max(t.paddingBottom-t.margins.bottom,0))},afterFit:function(){o.callback(this.options.afterFit,[this])},isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(o.isNullOrUndef(t))return NaN;if("number"==typeof t&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},getLabelForIndex:o.noop,getPixelForValue:o.noop,getValueForPixel:o.noop,getPixelForTick:function(t){var e=this,i=e.options.offset;if(e.isHorizontal()){var n=(e.width-(e.paddingLeft+e.paddingRight))/Math.max(e._ticks.length-(i?0:1),1),a=n*t+e.paddingLeft;i&&(a+=n/2);var o=e.left+Math.round(a);return o+=e.isFullWidth()?e.margins.left:0}var r=e.height-(e.paddingTop+e.paddingBottom);return e.top+t*(r/(e._ticks.length-1))},getPixelForDecimal:function(t){var e=this;if(e.isHorizontal()){var i=(e.width-(e.paddingLeft+e.paddingRight))*t+e.paddingLeft,n=e.left+Math.round(i);return n+=e.isFullWidth()?e.margins.left:0}return e.top+t*e.height},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,i,n,a,r=this,s=r.isHorizontal(),l=r.options.ticks.minor,u=t.length,d=o.toRadians(r.labelRotation),c=Math.cos(d),h=r.longestLabelWidth*c,f=[];for(l.maxTicksLimit&&(a=l.maxTicksLimit),s&&(e=!1,(h+l.autoSkipPadding)*u>r.width-(r.paddingLeft+r.paddingRight)&&(e=1+Math.floor((h+l.autoSkipPadding)*u/(r.width-(r.paddingLeft+r.paddingRight)))),a&&u>a&&(e=Math.max(e,Math.floor(u/a)))),i=0;i1&&i%e>0||i%e==0&&i+e>=u)&&i!==u-1&&delete n.label,f.push(n);return f},draw:function(t){var e=this,a=e.options;if(a.display){var s=e.ctx,u=n.global,d=a.ticks.minor,c=a.ticks.major||d,h=a.gridLines,f=a.scaleLabel,g=0!==e.labelRotation,p=e.isHorizontal(),m=d.autoSkip?e._autoSkip(e.getTicks()):e.getTicks(),v=o.valueOrDefault(d.fontColor,u.defaultFontColor),b=i(d),x=o.valueOrDefault(c.fontColor,u.defaultFontColor),y=i(c),k=h.drawTicks?h.tickMarkLength:0,M=o.valueOrDefault(f.fontColor,u.defaultFontColor),w=i(f),S=o.options.toPadding(f.padding),C=o.toRadians(e.labelRotation),_=[],D=e.options.gridLines.lineWidth,I="right"===a.position?e.right:e.right-D-k,P="right"===a.position?e.right+k:e.right,A="bottom"===a.position?e.top+D:e.bottom-k-D,T="bottom"===a.position?e.top+D+k:e.bottom+D;if(o.each(m,function(i,n){if(!o.isNullOrUndef(i.label)){var r,s,c,f,v,b,x,y,M,w,S,F,O,R,L=i.label;n===e.zeroLineIndex&&a.offset===h.offsetGridLines?(r=h.zeroLineWidth,s=h.zeroLineColor,c=h.zeroLineBorderDash,f=h.zeroLineBorderDashOffset):(r=o.valueAtIndexOrDefault(h.lineWidth,n),s=o.valueAtIndexOrDefault(h.color,n),c=o.valueOrDefault(h.borderDash,u.borderDash),f=o.valueOrDefault(h.borderDashOffset,u.borderDashOffset));var z="middle",B="middle",W=d.padding;if(p){var N=k+W;"bottom"===a.position?(B=g?"middle":"top",z=g?"right":"center",R=e.top+N):(B=g?"middle":"bottom",z=g?"left":"center",R=e.bottom-N);var V=l(e,n,h.offsetGridLines&&m.length>1);V1);j3?i[2]-i[1]:i[1]-i[0];Math.abs(a)>1&&t!==Math.floor(t)&&(a=t-Math.floor(t));var o=n.log10(Math.abs(a)),r="";if(0!==t){var s=-1*Math.floor(o);s=Math.max(Math.min(s,20),0),r=t.toFixed(s)}else r="0";return r},logarithmic:function(t,e,i){var a=t/Math.pow(10,Math.floor(n.log10(t)));return 0===t?"0":1===a||2===a||5===a||0===e||e===i.length-1?t.toExponential():""}}}},{45:45}],35:[function(t,e,i){"use strict";var n=t(25),a=t(26),o=t(45);n._set("global",{tooltips:{enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretPadding:2,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,callbacks:{beforeTitle:o.noop,title:function(t,e){var i="",n=e.labels,a=n?n.length:0;if(t.length>0){var o=t[0];o.xLabel?i=o.xLabel:a>0&&o.indexl.height-e.height&&(c="bottom");var h=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===c?(i=function(t){return t<=h},n=function(t){return t>h}):(i=function(t){return t<=e.width/2},n=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},o=function(t){return t-e.width-s.caretSize-s.caretPadding<0},r=function(t){return t<=f?"top":"bottom"},i(s.x)?(d="left",a(s.x)&&(d="center",c=r(s.y))):n(s.x)&&(d="right",o(s.x)&&(d="center",c=r(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:c}}(this,F=function(t,e){var i=t._chart.ctx,n=2*e.yPadding,a=0,r=e.body,s=r.reduce(function(t,e){return t+e.before.length+e.lines.length+e.after.length},0);s+=e.beforeBody.length+e.afterBody.length;var l=e.title.length,u=e.footer.length,d=e.titleFontSize,c=e.bodyFontSize,h=e.footerFontSize;n+=l*d,n+=l?(l-1)*e.titleSpacing:0,n+=l?e.titleMarginBottom:0,n+=s*c,n+=s?(s-1)*e.bodySpacing:0,n+=u?e.footerMarginTop:0,n+=u*h,n+=u?(u-1)*e.footerSpacing:0;var f=0,g=function(t){a=Math.max(a,i.measureText(t).width+f)};return i.font=o.fontString(d,e._titleFontStyle,e._titleFontFamily),o.each(e.title,g),i.font=o.fontString(c,e._bodyFontStyle,e._bodyFontFamily),o.each(e.beforeBody.concat(e.afterBody),g),f=e.displayColors?c+2:0,o.each(r,function(t){o.each(t.before,g),o.each(t.lines,g),o.each(t.after,g)}),f=0,i.font=o.fontString(h,e._footerFontStyle,e._footerFontFamily),o.each(e.footer,g),{width:a+=2*e.xPadding,height:n}}(this,D)),a=D,s=F,l=A,u=S._chart,d=a.x,c=a.y,h=a.caretSize,f=a.caretPadding,g=a.cornerRadius,p=l.xAlign,m=l.yAlign,v=h+f,b=g+f,"right"===p?d-=s.width:"center"===p&&((d-=s.width/2)+s.width>u.width&&(d=u.width-s.width),d<0&&(d=0)),"top"===m?c+=v:c-="bottom"===m?s.height+v:s.height/2,"center"===m?"left"===p?d+=v:"right"===p&&(d-=v):"left"===p?d-=b:"right"===p&&(d+=b),T={x:d,y:c}}else D.opacity=0;return D.xAlign=A.xAlign,D.yAlign=A.yAlign,D.x=T.x,D.y=T.y,D.width=F.width,D.height=F.height,D.caretX=O.x,D.caretY=O.y,S._model=D,e&&C.custom&&C.custom.call(S,D),S},drawCaret:function(t,e){var i=this._chart.ctx,n=this._view,a=this.getCaretPosition(t,e,n);i.lineTo(a.x1,a.y1),i.lineTo(a.x2,a.y2),i.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,i){var n,a,o,r,s,l,u=i.caretSize,d=i.cornerRadius,c=i.xAlign,h=i.yAlign,f=t.x,g=t.y,p=e.width,m=e.height;if("center"===h)s=g+m/2,"left"===c?(a=(n=f)-u,o=n,r=s+u,l=s-u):(a=(n=f+p)+u,o=n,r=s-u,l=s+u);else if("left"===c?(n=(a=f+d+u)-u,o=a+u):"right"===c?(n=(a=f+p-d-u)-u,o=a+u):(n=(a=i.caretX)-u,o=a+u),"top"===h)s=(r=g)-u,l=r;else{s=(r=g+m)+u,l=r;var v=o;o=n,n=v}return{x1:n,x2:a,x3:o,y1:r,y2:s,y3:l}},drawTitle:function(t,i,n,a){var r=i.title;if(r.length){n.textAlign=i._titleAlign,n.textBaseline="top";var s,l,u=i.titleFontSize,d=i.titleSpacing;for(n.fillStyle=e(i.titleFontColor,a),n.font=o.fontString(u,i._titleFontStyle,i._titleFontFamily),s=0,l=r.length;s0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var i={width:e.width,height:e.height},n={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,o=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&o&&(this.drawBackground(n,e,t,i,a),n.x+=e.xPadding,n.y+=e.yPadding,this.drawTitle(n,e,t,a),this.drawBody(n,e,t,a),this.drawFooter(n,e,t,a))}},handleEvent:function(t){var e,i=this,n=i._options;return i._lastActive=i._lastActive||[],"mouseout"===t.type?i._active=[]:i._active=i._chart.getElementsAtEventForMode(t,n.mode,n),(e=!o.arrayEquals(i._active,i._lastActive))&&(i._lastActive=i._active,(n.enabled||n.custom)&&(i._eventPosition={x:t.x,y:t.y},i.update(!0),i.pivot())),e}}),t.Tooltip.positioners={average:function(t){if(!t.length)return!1;var e,i,n=0,a=0,o=0;for(e=0,i=t.length;el;)a-=2*Math.PI;for(;a=s&&a<=l,d=r>=i.innerRadius&&r<=i.outerRadius;return u&&d}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,i=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t=this._chart.ctx,e=this._view,i=e.startAngle,n=e.endAngle;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,i,n),t.arc(e.x,e.y,e.innerRadius,n,i,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}})},{25:25,26:26,45:45}],37:[function(t,e,i){"use strict";var n=t(25),a=t(26),o=t(45),r=n.global;n._set("global",{elements:{line:{tension:.4,backgroundColor:r.defaultColor,borderWidth:3,borderColor:r.defaultColor,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}}),e.exports=a.extend({draw:function(){var t,e,i,n,a=this._view,s=this._chart.ctx,l=a.spanGaps,u=this._children.slice(),d=r.elements.line,c=-1;for(this._loop&&u.length&&u.push(u[0]),s.save(),s.lineCap=a.borderCapStyle||d.borderCapStyle,s.setLineDash&&s.setLineDash(a.borderDash||d.borderDash),s.lineDashOffset=a.borderDashOffset||d.borderDashOffset,s.lineJoin=a.borderJoinStyle||d.borderJoinStyle,s.lineWidth=a.borderWidth||d.borderWidth,s.strokeStyle=a.borderColor||r.defaultColor,s.beginPath(),c=-1,t=0;tt?1:-1,o=1,r=l.borderSkipped||"left"):(t=l.x-l.width/2,e=l.x+l.width/2,i=l.y,a=1,o=(n=l.base)>i?1:-1,r=l.borderSkipped||"bottom"),u){var d=Math.min(Math.abs(t-e),Math.abs(i-n)),c=(u=u>d?d:u)/2,h=t+("left"!==r?c*a:0),f=e+("right"!==r?-c*a:0),g=i+("top"!==r?c*o:0),p=n+("bottom"!==r?-c*o:0);h!==f&&(i=g,n=p),g!==p&&(t=h,e=f)}s.beginPath(),s.fillStyle=l.backgroundColor,s.strokeStyle=l.borderColor,s.lineWidth=u;var m=[[t,n],[t,i],[e,i],[e,n]],v=["bottom","left","top","right"].indexOf(r,0);function b(t){return m[(v+t)%4]}-1===v&&(v=0);var x=b(0);s.moveTo(x[0],x[1]);for(var y=1;y<4;y++)x=b(y),s.lineTo(x[0],x[1]);s.fill(),u&&s.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=!1;if(this._view){var n=r(this);i=t>=n.left&&t<=n.right&&e>=n.top&&e<=n.bottom}return i},inLabelRange:function(t,e){if(!this._view)return!1;var i=r(this);return o(this)?t>=i.left&&t<=i.right:e>=i.top&&e<=i.bottom},inXRange:function(t){var e=r(this);return t>=e.left&&t<=e.right},inYRange:function(t){var e=r(this);return t>=e.top&&t<=e.bottom},getCenterPoint:function(){var t,e,i=this._view;return o(this)?(t=i.x,e=(i.y+i.base)/2):(t=(i.x+i.base)/2,e=i.y),{x:t,y:e}},getArea:function(){var t=this._view;return t.width*Math.abs(t.y-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}})},{25:25,26:26}],40:[function(t,e,i){"use strict";e.exports={},e.exports.Arc=t(36),e.exports.Line=t(37),e.exports.Point=t(38),e.exports.Rectangle=t(39)},{36:36,37:37,38:38,39:39}],41:[function(t,e,i){"use strict";var n=t(42);i=e.exports={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,i,n,a,o){if(o){var r=Math.min(o,n/2),s=Math.min(o,a/2);t.moveTo(e+r,i),t.lineTo(e+n-r,i),t.quadraticCurveTo(e+n,i,e+n,i+s),t.lineTo(e+n,i+a-s),t.quadraticCurveTo(e+n,i+a,e+n-r,i+a),t.lineTo(e+r,i+a),t.quadraticCurveTo(e,i+a,e,i+a-s),t.lineTo(e,i+s),t.quadraticCurveTo(e,i,e+r,i)}else t.rect(e,i,n,a)},drawPoint:function(t,e,i,n,a){var o,r,s,l,u,d;if(!e||"object"!=typeof e||"[object HTMLImageElement]"!==(o=e.toString())&&"[object HTMLCanvasElement]"!==o){if(!(isNaN(i)||i<=0)){switch(e){default:t.beginPath(),t.arc(n,a,i,0,2*Math.PI),t.closePath(),t.fill();break;case"triangle":t.beginPath(),u=(r=3*i/Math.sqrt(3))*Math.sqrt(3)/2,t.moveTo(n-r/2,a+u/3),t.lineTo(n+r/2,a+u/3),t.lineTo(n,a-2*u/3),t.closePath(),t.fill();break;case"rect":d=1/Math.SQRT2*i,t.beginPath(),t.fillRect(n-d,a-d,2*d,2*d),t.strokeRect(n-d,a-d,2*d,2*d);break;case"rectRounded":var c=i/Math.SQRT2,h=n-c,f=a-c,g=Math.SQRT2*i;t.beginPath(),this.roundedRect(t,h,f,g,g,i/2),t.closePath(),t.fill();break;case"rectRot":d=1/Math.SQRT2*i,t.beginPath(),t.moveTo(n-d,a),t.lineTo(n,a+d),t.lineTo(n+d,a),t.lineTo(n,a-d),t.closePath(),t.fill();break;case"cross":t.beginPath(),t.moveTo(n,a+i),t.lineTo(n,a-i),t.moveTo(n-i,a),t.lineTo(n+i,a),t.closePath();break;case"crossRot":t.beginPath(),s=Math.cos(Math.PI/4)*i,l=Math.sin(Math.PI/4)*i,t.moveTo(n-s,a-l),t.lineTo(n+s,a+l),t.moveTo(n-s,a+l),t.lineTo(n+s,a-l),t.closePath();break;case"star":t.beginPath(),t.moveTo(n,a+i),t.lineTo(n,a-i),t.moveTo(n-i,a),t.lineTo(n+i,a),s=Math.cos(Math.PI/4)*i,l=Math.sin(Math.PI/4)*i,t.moveTo(n-s,a-l),t.lineTo(n+s,a+l),t.moveTo(n-s,a+l),t.lineTo(n+s,a-l),t.closePath();break;case"line":t.beginPath(),t.moveTo(n-i,a),t.lineTo(n+i,a),t.closePath();break;case"dash":t.beginPath(),t.moveTo(n,a),t.lineTo(n+i,a),t.closePath()}t.stroke()}}else t.drawImage(e,n-e.width/2,a-e.height/2,e.width,e.height)},clipArea:function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},unclipArea:function(t){t.restore()},lineTo:function(t,e,i,n){if(i.steppedLine)return"after"===i.steppedLine&&!n||"after"!==i.steppedLine&&n?t.lineTo(e.x,i.y):t.lineTo(i.x,e.y),void t.lineTo(i.x,i.y);i.tension?t.bezierCurveTo(n?e.controlPointPreviousX:e.controlPointNextX,n?e.controlPointPreviousY:e.controlPointNextY,n?i.controlPointNextX:i.controlPointPreviousX,n?i.controlPointNextY:i.controlPointPreviousY,i.x,i.y):t.lineTo(i.x,i.y)}};n.clear=i.clear,n.drawRoundedRectangle=function(t){t.beginPath(),i.roundedRect.apply(i,arguments),t.closePath()}},{42:42}],42:[function(t,e,i){"use strict";var n,a={noop:function(){},uid:(n=0,function(){return n++}),isNullOrUndef:function(t){return null==t},isArray:Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)},isObject:function(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)},valueOrDefault:function(t,e){return void 0===t?e:t},valueAtIndexOrDefault:function(t,e,i){return a.valueOrDefault(a.isArray(t)?t[e]:t,i)},callback:function(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)},each:function(t,e,i,n){var o,r,s;if(a.isArray(t))if(r=t.length,n)for(o=r-1;o>=0;o--)e.call(i,t[o],o);else for(o=0;o=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,n=1;return 0===t?0:1===t?1:(i||(i=.3),n<1?(n=1,e=i/4):e=i/(2*Math.PI)*Math.asin(1/n),-n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i))},easeOutElastic:function(t){var e=1.70158,i=0,n=1;return 0===t?0:1===t?1:(i||(i=.3),n<1?(n=1,e=i/4):e=i/(2*Math.PI)*Math.asin(1/n),n*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/i)+1)},easeInOutElastic:function(t){var e=1.70158,i=0,n=1;return 0===t?0:2==(t/=.5)?1:(i||(i=.45),n<1?(n=1,e=i/4):e=i/(2*Math.PI)*Math.asin(1/n),t<1?n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*-.5:n*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*.5+1)},easeInBack:function(t){return t*t*(2.70158*t-1.70158)},easeOutBack:function(t){return(t-=1)*t*(2.70158*t+1.70158)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-a.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*a.easeInBounce(2*t):.5*a.easeOutBounce(2*t-1)+.5}};e.exports={effects:a},n.easingEffects=a},{42:42}],44:[function(t,e,i){"use strict";var n=t(42);e.exports={toLineHeight:function(t,e){var i=(""+t).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t},toPadding:function(t){var e,i,a,o;return n.isObject(t)?(e=+t.top||0,i=+t.right||0,a=+t.bottom||0,o=+t.left||0):e=i=a=o=+t||0,{top:e,right:i,bottom:a,left:o,height:e+a,width:o+i}},resolve:function(t,e,i){var a,o,r;for(a=0,o=t.length;a
';var a=e.childNodes[0],r=e.childNodes[1];e._reset=function(){a.scrollLeft=1e6,a.scrollTop=1e6,r.scrollLeft=1e6,r.scrollTop=1e6};var s=function(){e._reset(),t()};return h(a,"scroll",s.bind(a,"expand")),h(r,"scroll",s.bind(r,"shrink")),e}((u=function(){if(x.resizer)return e(g("resize",i))},c=!1,f=[],function(){f=Array.prototype.slice.call(arguments),d=d||this,c||(c=!0,n.requestAnimFrame.call(window,function(){c=!1,u.apply(d,f)}))}));m=function(){if(x.resizer){var e=t.parentNode;e&&e!==y.parentNode&&e.insertBefore(y,e.firstChild),y._reset()}},v=(p=t)[a]||(p[a]={}),b=v.renderProxy=function(t){t.animationName===s&&m()},n.each(l,function(t){h(p,t,b)}),v.reflow=!!p.offsetParent,p.classList.add(r)}function m(t){var e,i,o,s=t[a]||{},u=s.resizer;delete s.resizer,i=(e=t)[a]||{},(o=i.renderProxy)&&(n.each(l,function(t){f(e,t,o)}),delete i.renderProxy),e.classList.remove(r),u&&u.parentNode&&u.parentNode.removeChild(u)}e.exports={_enabled:"undefined"!=typeof window&&"undefined"!=typeof document,initialize:function(){var t,e,i,n="from{opacity:0.99}to{opacity:1}";e="@-webkit-keyframes "+s+"{"+n+"}@keyframes "+s+"{"+n+"}."+r+"{-webkit-animation:"+s+" 0.001s;animation:"+s+" 0.001s;}",i=(t=this)._style||document.createElement("style"),t._style||(t._style=i,e="/* Chart.js */\n"+e,i.setAttribute("type","text/css"),document.getElementsByTagName("head")[0].appendChild(i)),i.appendChild(document.createTextNode(e))},acquireContext:function(t,e){"string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas);var i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){var i=t.style,n=t.getAttribute("height"),o=t.getAttribute("width");if(t[a]={initial:{height:n,width:o,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",null===o||""===o){var r=d(t,"width");void 0!==r&&(t.width=r)}if(null===n||""===n)if(""===t.style.height)t.height=t.width/(e.options.aspectRatio||2);else{var s=d(t,"height");void 0!==r&&(t.height=s)}}(t,e),i):null},releaseContext:function(t){var e=t.canvas;if(e[a]){var i=e[a].initial;["height","width"].forEach(function(t){var a=i[t];n.isNullOrUndef(a)?e.removeAttribute(t):e.setAttribute(t,a)}),n.each(i.style||{},function(t,i){e.style[i]=t}),e.width=e.width,delete e[a]}},addEventListener:function(t,e,i){var o=t.canvas;if("resize"!==e){var r=i[a]||(i[a]={});h(o,e,(r.proxies||(r.proxies={}))[t.id+"_"+e]=function(e){var a,o,r,s;i((o=t,r=u[(a=e).type]||a.type,s=n.getRelativePosition(a,o),g(r,o,s.x,s.y,a)))})}else p(o,i,t)},removeEventListener:function(t,e,i){var n=t.canvas;if("resize"!==e){var o=((i[a]||{}).proxies||{})[t.id+"_"+e];o&&f(n,e,o)}else m(n)}},n.addEvent=h,n.removeEvent=f},{45:45}],48:[function(t,e,i){"use strict";var n=t(45),a=t(46),o=t(47),r=o._enabled?o:a;e.exports=n.extend({initialize:function(){},acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},r)},{45:45,46:46,47:47}],49:[function(t,e,i){"use strict";e.exports={},e.exports.filler=t(50),e.exports.legend=t(51),e.exports.title=t(52)},{50:50,51:51,52:52}],50:[function(t,e,i){"use strict";var n=t(25),a=t(40),o=t(45);n._set("global",{plugins:{filler:{propagate:!0}}});var r={dataset:function(t){var e=t.fill,i=t.chart,n=i.getDatasetMeta(e),a=n&&i.isDatasetVisible(e)&&n.dataset._children||[],o=a.length||0;return o?function(t,e){return e=i)&&n;switch(o){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return o;default:return!1}}function l(t){var e,i=t.el._model||{},n=t.el._scale||{},a=t.fill,o=null;if(isFinite(a))return null;if("start"===a?o=void 0===i.scaleBottom?n.bottom:i.scaleBottom:"end"===a?o=void 0===i.scaleTop?n.top:i.scaleTop:void 0!==i.scaleZero?o=i.scaleZero:n.getBasePosition?o=n.getBasePosition():n.getBasePixel&&(o=n.getBasePixel()),null!=o){if(void 0!==o.x&&void 0!==o.y)return o;if("number"==typeof o&&isFinite(o))return{x:(e=n.isHorizontal())?o:null,y:e?null:o}}return null}function u(t,e,i){var n,a=t[e].fill,o=[e];if(!i)return a;for(;!1!==a&&-1===o.indexOf(a);){if(!isFinite(a))return a;if(!(n=t[a]))return!1;if(n.visible)return a;o.push(a),a=n.fill}return!1}function d(t){return t&&!t.skip}function c(t,e,i,n,a){var r;if(n&&a){for(t.moveTo(e[0].x,e[0].y),r=1;r0;--r)o.canvas.lineTo(t,i[r],i[r-1],!0)}}e.exports={id:"filler",afterDatasetsUpdate:function(t,e){var i,n,o,d,c,h,f,g=(t.data.datasets||[]).length,p=e.propagate,m=[];for(n=0;n');for(var i=0;i'),t.data.datasets[i].label&&e.push(t.data.datasets[i].label),e.push("");return e.push(""),e.join("")}});var u=a.extend({initialize:function(t){o.extend(this,t),this.legendHitBoxes=[],this.doughnutMode=!1},beforeUpdate:s,update:function(t,e,i){var n=this;return n.beforeUpdate(),n.maxWidth=t,n.maxHeight=e,n.margins=i,n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n.beforeBuildLabels(),n.buildLabels(),n.afterBuildLabels(),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate(),n.minSize},afterUpdate:s,beforeSetDimensions:s,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:s,beforeBuildLabels:s,buildLabels:function(){var t=this,e=t.options.labels||{},i=o.callback(e.generateLabels,[t.chart],t)||[];e.filter&&(i=i.filter(function(i){return e.filter(i,t.chart.data)})),t.options.reverse&&i.reverse(),t.legendItems=i},afterBuildLabels:s,beforeFit:s,fit:function(){var t=this,e=t.options,i=e.labels,a=e.display,r=t.ctx,s=n.global,u=o.valueOrDefault,d=u(i.fontSize,s.defaultFontSize),c=u(i.fontStyle,s.defaultFontStyle),h=u(i.fontFamily,s.defaultFontFamily),f=o.fontString(d,c,h),g=t.legendHitBoxes=[],p=t.minSize,m=t.isHorizontal();if(m?(p.width=t.maxWidth,p.height=a?10:0):(p.width=a?10:0,p.height=t.maxHeight),a)if(r.font=f,m){var v=t.lineWidths=[0],b=t.legendItems.length?d+i.padding:0;r.textAlign="left",r.textBaseline="top",o.each(t.legendItems,function(e,n){var a=l(i,d)+d/2+r.measureText(e.text).width;v[v.length-1]+a+i.padding>=t.width&&(b+=d+i.padding,v[v.length]=t.left),g[n]={left:0,top:0,width:a,height:d},v[v.length-1]+=a+i.padding}),p.height+=b}else{var x=i.padding,y=t.columnWidths=[],k=i.padding,M=0,w=0,S=d+x;o.each(t.legendItems,function(t,e){var n=l(i,d)+d/2+r.measureText(t.text).width;w+S>p.height&&(k+=M+i.padding,y.push(M),M=0,w=0),M=Math.max(M,n),w+=S,g[e]={left:0,top:0,width:n,height:d}}),k+=M,y.push(M),p.width+=k}t.width=p.width,t.height=p.height},afterFit:s,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,i=e.labels,a=n.global,r=a.elements.line,s=t.width,u=t.lineWidths;if(e.display){var d,c=t.ctx,h=o.valueOrDefault,f=h(i.fontColor,a.defaultFontColor),g=h(i.fontSize,a.defaultFontSize),p=h(i.fontStyle,a.defaultFontStyle),m=h(i.fontFamily,a.defaultFontFamily),v=o.fontString(g,p,m);c.textAlign="left",c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=v;var b=l(i,g),x=t.legendHitBoxes,y=t.isHorizontal();d=y?{x:t.left+(s-u[0])/2,y:t.top+i.padding,line:0}:{x:t.left+i.padding,y:t.top+i.padding,line:0};var k=g+i.padding;o.each(t.legendItems,function(n,l){var f,p,m,v,M,w=c.measureText(n.text).width,S=b+g/2+w,C=d.x,_=d.y;y?C+S>=s&&(_=d.y+=k,d.line++,C=d.x=t.left+(s-u[d.line])/2):_+k>t.bottom&&(C=d.x=C+t.columnWidths[d.line]+i.padding,_=d.y=t.top+i.padding,d.line++),function(t,i,n){if(!(isNaN(b)||b<=0)){c.save(),c.fillStyle=h(n.fillStyle,a.defaultColor),c.lineCap=h(n.lineCap,r.borderCapStyle),c.lineDashOffset=h(n.lineDashOffset,r.borderDashOffset),c.lineJoin=h(n.lineJoin,r.borderJoinStyle),c.lineWidth=h(n.lineWidth,r.borderWidth),c.strokeStyle=h(n.strokeStyle,a.defaultColor);var s=0===h(n.lineWidth,r.borderWidth);if(c.setLineDash&&c.setLineDash(h(n.lineDash,r.borderDash)),e.labels&&e.labels.usePointStyle){var l=g*Math.SQRT2/2,u=l/Math.SQRT2,d=t+u,f=i+u;o.canvas.drawPoint(c,n.pointStyle,l,d,f)}else s||c.strokeRect(t,i,b,g),c.fillRect(t,i,b,g);c.restore()}}(C,_,n),x[l].left=C,x[l].top=_,f=n,p=w,v=b+(m=g/2)+C,M=_+m,c.fillText(f.text,v,M),f.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(v,M),c.lineTo(v+p,M),c.stroke()),y?d.x+=S+i.padding:d.y+=k})}},handleEvent:function(t){var e=this,i=e.options,n="mouseup"===t.type?"click":t.type,a=!1;if("mousemove"===n){if(!i.onHover)return}else{if("click"!==n)return;if(!i.onClick)return}var o=t.x,r=t.y;if(o>=e.left&&o<=e.right&&r>=e.top&&r<=e.bottom)for(var s=e.legendHitBoxes,l=0;l=u.left&&o<=u.left+u.width&&r>=u.top&&r<=u.top+u.height){if("click"===n){i.onClick.call(e,t.native,e.legendItems[l]),a=!0;break}if("mousemove"===n){i.onHover.call(e,t.native,e.legendItems[l]),a=!0;break}}}return a}});function d(t,e){var i=new u({ctx:t.ctx,options:e,chart:t});r.configure(t,i,e),r.addBox(t,i),t.legend=i}e.exports={id:"legend",_element:u,beforeInit:function(t){var e=t.options.legend;e&&d(t,e)},beforeUpdate:function(t){var e=t.options.legend,i=t.legend;e?(o.mergeIf(e,n.global.legend),i?(r.configure(t,i,e),i.options=e):d(t,e)):i&&(r.removeBox(t,i),delete t.legend)},afterEvent:function(t,e){var i=t.legend;i&&i.handleEvent(e)}}},{25:25,26:26,30:30,45:45}],52:[function(t,e,i){"use strict";var n=t(25),a=t(26),o=t(45),r=t(30),s=o.noop;n._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,lineHeight:1.2,padding:10,position:"top",text:"",weight:2e3}});var l=a.extend({initialize:function(t){o.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:s,update:function(t,e,i){var n=this;return n.beforeUpdate(),n.maxWidth=t,n.maxHeight=e,n.margins=i,n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n.beforeBuildLabels(),n.buildLabels(),n.afterBuildLabels(),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate(),n.minSize},afterUpdate:s,beforeSetDimensions:s,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:s,beforeBuildLabels:s,buildLabels:s,afterBuildLabels:s,beforeFit:s,fit:function(){var t=this,e=o.valueOrDefault,i=t.options,a=i.display,r=e(i.fontSize,n.global.defaultFontSize),s=t.minSize,l=o.isArray(i.text)?i.text.length:1,u=o.options.toLineHeight(i.lineHeight,r),d=a?l*u+2*i.padding:0;t.isHorizontal()?(s.width=t.maxWidth,s.height=d):(s.width=d,s.height=t.maxHeight),t.width=s.width,t.height=s.height},afterFit:s,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,i=o.valueOrDefault,a=t.options,r=n.global;if(a.display){var s,l,u,d=i(a.fontSize,r.defaultFontSize),c=i(a.fontStyle,r.defaultFontStyle),h=i(a.fontFamily,r.defaultFontFamily),f=o.fontString(d,c,h),g=o.options.toLineHeight(a.lineHeight,d),p=g/2+a.padding,m=0,v=t.top,b=t.left,x=t.bottom,y=t.right;e.fillStyle=i(a.fontColor,r.defaultFontColor),e.font=f,t.isHorizontal()?(l=b+(y-b)/2,u=v+p,s=y-b):(l="left"===a.position?b+p:y-p,u=v+(x-v)/2,s=x-v,m=Math.PI*("left"===a.position?-.5:.5)),e.save(),e.translate(l,u),e.rotate(m),e.textAlign="center",e.textBaseline="middle";var k=a.text;if(o.isArray(k))for(var M=0,w=0;wt.max&&(t.max=n))})});t.min=isFinite(t.min)&&!isNaN(t.min)?t.min:0,t.max=isFinite(t.max)&&!isNaN(t.max)?t.max:1,this.handleTickRangeOptions()},getTickLimit:function(){var t,e=this.options.ticks;if(this.isHorizontal())t=Math.min(e.maxTicksLimit?e.maxTicksLimit:11,Math.ceil(this.width/50));else{var i=a.valueOrDefault(e.fontSize,n.global.defaultFontSize);t=Math.min(e.maxTicksLimit?e.maxTicksLimit:11,Math.ceil(this.height/(2*i)))}return t},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){var e=this,i=e.start,n=+e.getRightValue(t),a=e.end-i;return e.isHorizontal()?e.left+e.width/a*(n-i):e.bottom-e.height/a*(n-i)},getValueForPixel:function(t){var e=this,i=e.isHorizontal(),n=i?e.width:e.height,a=(i?t-e.left:e.bottom-t)/n;return e.start+(e.end-e.start)*a},getPixelForTick:function(t){return this.getPixelForValue(this.ticksAsNumbers[t])}});t.scaleService.registerScaleType("linear",i,e)}},{25:25,34:34,45:45}],55:[function(t,e,i){"use strict";var n=t(45);e.exports=function(t){var e=n.noop;t.LinearScaleBase=t.Scale.extend({getRightValue:function(e){return"string"==typeof e?+e:t.Scale.prototype.getRightValue.call(this,e)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var i=n.sign(t.min),a=n.sign(t.max);i<0&&a<0?t.max=0:i>0&&a>0&&(t.min=0)}var o=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),o!==r&&t.min>=t.max&&(o?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:e,handleDirectionalChanges:e,buildTicks:function(){var t=this,e=t.options.ticks,i=t.getTickLimit(),a={maxTicks:i=Math.max(2,i),min:e.min,max:e.max,stepSize:n.valueOrDefault(e.fixedStepSize,e.stepSize)},o=t.ticks=function(t,e){var i,a=[];if(t.stepSize&&t.stepSize>0)i=t.stepSize;else{var o=n.niceNum(e.max-e.min,!1);i=n.niceNum(o/(t.maxTicks-1),!0)}var r=Math.floor(e.min/i)*i,s=Math.ceil(e.max/i)*i;t.min&&t.max&&t.stepSize&&n.almostWhole((t.max-t.min)/t.stepSize,i/1e3)&&(r=t.min,s=t.max);var l=(s-r)/i;l=n.almostEquals(l,Math.round(l),i/1e3)?Math.round(l):Math.ceil(l);var u=1;i<1&&(u=Math.pow(10,i.toString().length-2),r=Math.round(r*u)/u,s=Math.round(s*u)/u),a.push(void 0!==t.min?t.min:r);for(var d=1;d0){var i=n.min(e),a=n.max(e);t.min=null===t.min?i:Math.min(t.min,i),t.max=null===t.max?a:Math.max(t.max,a)}})}else n.each(a,function(e,a){var o=i.getDatasetMeta(a);i.isDatasetVisible(a)&&r(o)&&n.each(e.data,function(e,i){var n=+t.getRightValue(e);isNaN(n)||o.data[i].hidden||n<0||(null===t.min?t.min=n:nt.max&&(t.max=n),0!==n&&(null===t.minNotZero||n0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(n.log10(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,i=!t.isHorizontal(),a={min:e.min,max:e.max},o=t.ticks=function(t,e){var i,a,o=[],r=n.valueOrDefault,s=r(t.min,Math.pow(10,Math.floor(n.log10(e.min)))),l=Math.floor(n.log10(e.max)),u=Math.ceil(e.max/Math.pow(10,l));0===s?(i=Math.floor(n.log10(e.minNotZero)),a=Math.floor(e.minNotZero/Math.pow(10,i)),o.push(s),s=a*Math.pow(10,i)):(i=Math.floor(n.log10(s)),a=Math.floor(s/Math.pow(10,i)));for(var d=i<0?Math.pow(10,Math.abs(i)):1;o.push(s),10==++a&&(a=1,d=++i>=0?1:d),s=Math.round(a*Math.pow(10,i)*d)/d,ia?{start:e-i-5,end:e}:{start:e,end:e+i+5}}function u(t,e,i,n){if(a.isArray(e))for(var o=i.y,r=1.5*n,s=0;sd.r&&(d.r=b.end,c.r=m),x.startd.b&&(d.b=x.end,c.b=m)}t.setReductions(u,d,c)}(this):(t=this,e=Math.min(t.height/2,t.width/2),t.drawingArea=Math.round(e),t.setCenterPoint(0,0,0,0))},setReductions:function(t,e,i){var n=e.l/Math.sin(i.l),a=Math.max(e.r-this.width,0)/Math.sin(i.r),o=-e.t/Math.cos(i.t),r=-Math.max(e.b-this.height,0)/Math.cos(i.b);n=d(n),a=d(a),o=d(o),r=d(r),this.drawingArea=Math.min(Math.round(t-(n+a)/2),Math.round(t-(o+r)/2)),this.setCenterPoint(n,a,o,r)},setCenterPoint:function(t,e,i,n){var a=this,o=a.width-e-a.drawingArea,r=t+a.drawingArea,s=i+a.drawingArea,l=a.height-n-a.drawingArea;a.xCenter=Math.round((r+o)/2+a.left),a.yCenter=Math.round((s+l)/2+a.top)},getIndexAngle:function(t){return t*(2*Math.PI/r(this))+(this.chart.options&&this.chart.options.startAngle?this.chart.options.startAngle:0)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(null===t)return 0;var i=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*i:(t-e.min)*i},getPointPosition:function(t,e){var i=this.getIndexAngle(t)-Math.PI/2;return{x:Math.round(Math.cos(i)*e)+this.xCenter,y:Math.round(Math.sin(i)*e)+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(){var t=this.min,e=this.max;return this.getPointPositionForValue(0,this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0)},draw:function(){var t=this,i=t.options,n=i.gridLines,o=i.ticks,l=a.valueOrDefault;if(i.display){var d=t.ctx,c=this.getIndexAngle(0),h=l(o.fontSize,e.defaultFontSize),f=l(o.fontStyle,e.defaultFontStyle),g=l(o.fontFamily,e.defaultFontFamily),p=a.fontString(h,f,g);a.each(t.ticks,function(i,s){if(s>0||o.reverse){var u=t.getDistanceFromCenterForValue(t.ticksAsNumbers[s]);if(n.display&&0!==s&&function(t,e,i,n){var o=t.ctx;if(o.strokeStyle=a.valueAtIndexOrDefault(e.color,n-1),o.lineWidth=a.valueAtIndexOrDefault(e.lineWidth,n-1),t.options.gridLines.circular)o.beginPath(),o.arc(t.xCenter,t.yCenter,i,0,2*Math.PI),o.closePath(),o.stroke();else{var s=r(t);if(0===s)return;o.beginPath();var l=t.getPointPosition(0,i);o.moveTo(l.x,l.y);for(var u=1;u=0;m--){if(o.display){var v=t.getPointPosition(m,g);i.beginPath(),i.moveTo(t.xCenter,t.yCenter),i.lineTo(v.x,v.y),i.stroke(),i.closePath()}if(l.display){var b=t.getPointPosition(m,g+5),x=a.valueAtIndexOrDefault(l.fontColor,m,e.defaultFontColor);i.font=p.font,i.fillStyle=x;var y=t.getIndexAngle(m),k=a.toDegrees(y);i.textAlign=0===(f=k)||180===f?"center":f<180?"left":"right",d=k,c=t._pointLabelSizes[m],h=b,90===d||270===d?h.y-=c.h/2:(d>270||d<90)&&(h.y-=c.h),u(i,t.pointLabels[m]||"",b,p.size)}}}(t)}}});t.scaleService.registerScaleType("radialLinear",c,i)}},{25:25,34:34,45:45}],58:[function(t,e,i){"use strict";var n=t(1);n="function"==typeof n?n:window.moment;var a=t(25),o=t(45),r=Number.MIN_SAFE_INTEGER||-9007199254740991,s=Number.MAX_SAFE_INTEGER||9007199254740991,l={millisecond:{common:!0,size:1,steps:[1,2,5,10,20,50,100,250,500]},second:{common:!0,size:1e3,steps:[1,2,5,10,30]},minute:{common:!0,size:6e4,steps:[1,2,5,10,30]},hour:{common:!0,size:36e5,steps:[1,2,3,6,12]},day:{common:!0,size:864e5,steps:[1,2,5]},week:{common:!1,size:6048e5,steps:[1,2,3,4]},month:{common:!0,size:2628e6,steps:[1,2,3]},quarter:{common:!1,size:7884e6,steps:[1,2,3,4]},year:{common:!0,size:3154e7}},u=Object.keys(l);function d(t,e){return t-e}function c(t){var e,i,n,a={},o=[];for(e=0,i=t.length;e=0&&r<=s;){if(a=t[(n=r+s>>1)-1]||null,o=t[n],!a)return{lo:null,hi:o};if(o[e]i))return{lo:a,hi:o};s=n-1}}return{lo:o,hi:null}}(t,e,i),o=a.lo?a.hi?a.lo:t[t.length-2]:t[0],r=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=r[e]-o[e],l=s?(i-o[e])/s:0,u=(r[n]-o[n])*l;return o[n]+u}function f(t,e){var i=e.parser,a=e.parser||e.format;return"function"==typeof i?i(t):"string"==typeof t&&"string"==typeof a?n(t,a):(t instanceof n||(t=n(t)),t.isValid()?t:"function"==typeof a?a(t):t)}function g(t,e){if(o.isNullOrUndef(t))return null;var i=e.options.time,n=f(e.getRightValue(t),i);return n.isValid()?(i.round&&n.startOf(i.round),n.valueOf()):null}function p(t){for(var e=u.indexOf(t)+1,i=u.length;e=k&&i<=M&&_.push(i);return y.min=k,y.max=M,y._unit=S.unit||function(t,e,i,a){var o,r,s=n.duration(n(a).diff(n(i)));for(o=u.length-1;o>=u.indexOf(e);o--)if(r=u[o],l[r].common&&s.as(r)>=t.length)return r;return u[e?u.indexOf(e):0]}(_,S.minUnit,y.min,y.max),y._majorUnit=p(y._unit),y._table=function(t,e,i,n){if("linear"===n||!t.length)return[{time:e,pos:0},{time:i,pos:1}];var a,o,r,s,l,u=[],d=[e];for(a=0,o=t.length;ae&&s1?o[1]:s,v=o[0],b=(h(a,"time",c,"pos")-h(a,"time",v,"pos"))/2),d.time.max||(c=o[o.length-1],v=o.length>1?o[o.length-2]:r,x=(h(a,"time",c,"pos")-h(a,"time",v,"pos"))/2)),{left:b,right:x}),y._labelFormat=function(t,e){var i,n,a,o=t.length;for(i=0;i=0&&t0?s:1}});t.scaleService.registerScaleType("time",e,{position:"bottom",distribution:"linear",bounds:"data",time:{parser:!1,format:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}})}},{1:1,25:25,45:45}]},{},[7])(7)}); From 8a40a1d50aadca98c179510cd3cf8d7f931da958 Mon Sep 17 00:00:00 2001 From: Sudaraka Jayathilaka Date: Mon, 16 Apr 2018 14:23:31 +0530 Subject: [PATCH 018/127] Add users-top plugin outlet (#5761) --- app/assets/javascripts/discourse/templates/mobile/users.hbs | 1 + app/assets/javascripts/discourse/templates/users.hbs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/mobile/users.hbs b/app/assets/javascripts/discourse/templates/mobile/users.hbs index 56417c588c..605ce97fca 100644 --- a/app/assets/javascripts/discourse/templates/mobile/users.hbs +++ b/app/assets/javascripts/discourse/templates/mobile/users.hbs @@ -1,6 +1,7 @@ {{#load-more selector=".directory .user" action="loadMore"}}
+ {{plugin-outlet name="users-top" connectorTagName='div' args=(hash model=model)}}
{{period-chooser period=period}} diff --git a/app/assets/javascripts/discourse/templates/users.hbs b/app/assets/javascripts/discourse/templates/users.hbs index 0fe4408dd7..bfe29fb340 100644 --- a/app/assets/javascripts/discourse/templates/users.hbs +++ b/app/assets/javascripts/discourse/templates/users.hbs @@ -2,7 +2,7 @@ {{#load-more selector=".directory tbody tr" action="loadMore"}}
- + {{plugin-outlet name="users-top" connectorTagName='div' args=(hash model=model)}}
{{period-chooser period=period}} {{text-field value=nameInput placeholderKey="directory.filter_name" class="filter-name no-blur"}} From 3e7638e3f5671796642c0dcc1087827afa195e0c Mon Sep 17 00:00:00 2001 From: Kyle Zhao Date: Mon, 16 Apr 2018 04:56:35 -0400 Subject: [PATCH 019/127] Improve docker performance with `delegated` mount flag (#5760) --- bin/docker/boot_dev | 2 +- bin/docker/shutdown_dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/docker/boot_dev b/bin/docker/boot_dev index 0a80ac638a..781f0567a3 100755 --- a/bin/docker/boot_dev +++ b/bin/docker/boot_dev @@ -38,7 +38,7 @@ echo "Using data in: ${DATA_DIR}" mkdir -p "${DATA_DIR}" -docker run -d -p 1080:1080 -p 3000:3000 -v "$DATA_DIR:/shared/postgres_data" -v "$SOURCE_DIR:/src" --hostname=discourse --name=discourse_dev --restart=always discourse/discourse_dev:release /sbin/boot +docker run -d -p 1080:1080 -p 3000:3000 -v "$DATA_DIR:/shared/postgres_data:delegated" -v "$SOURCE_DIR:/src:delegated" --hostname=discourse --name=discourse_dev --restart=always discourse/discourse_dev:release /sbin/boot if [ "${initialize}" = "initialize" ]; then echo "Installing gems..." diff --git a/bin/docker/shutdown_dev b/bin/docker/shutdown_dev index 557ccfe9df..672299efb0 100755 --- a/bin/docker/shutdown_dev +++ b/bin/docker/shutdown_dev @@ -1,3 +1,3 @@ #!/bin/bash -docker rm -f discourse_dev +docker stop discourse_dev && docker rm discourse_dev From b602bab74178179d0f103dc8de3accbec59649ce Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 16 Apr 2018 10:57:32 +0200 Subject: [PATCH 020/127] linting --- app/assets/javascripts/admin/components/mini-table.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/components/mini-table.js.es6 b/app/assets/javascripts/admin/components/mini-table.js.es6 index 8be0731477..7a1b0552d9 100644 --- a/app/assets/javascripts/admin/components/mini-table.js.es6 +++ b/app/assets/javascripts/admin/components/mini-table.js.es6 @@ -38,6 +38,6 @@ export default Ember.Component.extend({ }); }).finally(() => { this.set("isLoading", false); - }) + }); } }); From 3d99726981daa0048157c502dbcb45ab36c3c86d Mon Sep 17 00:00:00 2001 From: Maja Komel Date: Mon, 16 Apr 2018 11:48:06 +0200 Subject: [PATCH 021/127] FIX: set notification level when changing post owner (#5616) FIX: do not notify last post editor if they mention themself --- app/services/post_alerter.rb | 2 +- app/services/post_owner_changer.rb | 6 ++++++ spec/services/post_alerter_spec.rb | 7 +++++++ spec/services/post_owner_changer_spec.rb | 16 ++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index 44973d7adf..2c30ada818 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -47,7 +47,7 @@ class PostAlerter end def after_save_post(post, new_record = false) - notified = [post.user] + notified = [post.user, post.last_editor].uniq # mentions (users/groups) mentioned_groups, mentioned_users = extract_mentions(post) diff --git a/app/services/post_owner_changer.rb b/app/services/post_owner_changer.rb index 4e2d894cbe..8d4e261e8b 100644 --- a/app/services/post_owner_changer.rb +++ b/app/services/post_owner_changer.rb @@ -23,6 +23,12 @@ class PostOwnerChanger post.set_owner(@new_owner, @acting_user, @skip_revision) PostAction.remove_act(@new_owner, post, PostActionType.types[:like]) + if post.post_number == 1 + TopicUser.change(@new_owner.id, @topic.id, notification_level: NotificationLevels.topic_levels[:watching]) + else + TopicUser.change(@new_owner.id, @topic.id, notification_level: NotificationLevels.topic_levels[:tracking]) + end + @topic.update_statistics @new_owner.user_stat.update( diff --git a/spec/services/post_alerter_spec.rb b/spec/services/post_alerter_spec.rb index e36d603df0..20b6e8a778 100644 --- a/spec/services/post_alerter_spec.rb +++ b/spec/services/post_alerter_spec.rb @@ -410,6 +410,13 @@ describe PostAlerter do expect(n.data_hash["original_username"]).to eq(admin.username) end + it "doesn't notify the last post editor if they mention themself" do + post = create_post_with_alerts(user: user, raw: 'Post without a mention.') + expect { + post.revise(evil_trout, raw: "O hai, @eviltrout!") + }.not_to change(evil_trout.notifications, :count) + end + let(:alice) { Fabricate(:user, username: 'alice') } let(:bob) { Fabricate(:user, username: 'bob') } let(:carol) { Fabricate(:admin, username: 'carol') } diff --git a/spec/services/post_owner_changer_spec.rb b/spec/services/post_owner_changer_spec.rb index e462bbe9bd..5ad9d8af57 100644 --- a/spec/services/post_owner_changer_spec.rb +++ b/spec/services/post_owner_changer_spec.rb @@ -75,6 +75,22 @@ describe PostOwnerChanger do expect(p1.reload.user).to eq(user_a) end + context "sets topic notification level for the new owner" do + let(:p4) { Fabricate(:post, post_number: 2, topic_id: topic.id) } + + it "'watching' if the first post gets a new owner" do + described_class.new(post_ids: [p1.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner! + tu = TopicUser.find_by(user_id: user_a.id, topic_id: topic.id) + expect(tu.notification_level).to eq(3) + end + + it "'tracking' if other than the first post gets a new owner" do + described_class.new(post_ids: [p4.id], topic_id: topic.id, new_owner: user_a, acting_user: editor).change_owner! + tu = TopicUser.find_by(user_id: user_a.id, topic_id: topic.id) + expect(tu.notification_level).to eq(2) + end + end + context "integration tests" do let(:p1user) { p1.user } let(:p2user) { p2.user } From 035d92d2e128f2aaeffe67c3213f19754bfb2d96 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 16 Apr 2018 12:00:49 +0200 Subject: [PATCH 022/127] dashboard next: minor tweaks * help texts * renaming of components to dashboard-* * use number formatter * adjust trend position --- ...i-chart.js.es6 => dashboard-mini-chart.js.es6} | 2 +- ...i-table.js.es6 => dashboard-mini-table.js.es6} | 2 +- .../{mini-chart.hbs => dashboard-mini-chart.hbs} | 7 ++++--- .../{mini-table.hbs => dashboard-mini-table.hbs} | 2 +- .../admin/templates/dashboard_next.hbs | 15 ++++++++++++--- .../stylesheets/common/admin/dashboard_next.scss | 10 +++++++--- config/locales/client.en.yml | 6 ++++++ 7 files changed, 32 insertions(+), 12 deletions(-) rename app/assets/javascripts/admin/components/{mini-chart.js.es6 => dashboard-mini-chart.js.es6} (98%) rename app/assets/javascripts/admin/components/{mini-table.js.es6 => dashboard-mini-table.js.es6} (95%) rename app/assets/javascripts/admin/templates/components/{mini-chart.hbs => dashboard-mini-chart.hbs} (81%) rename app/assets/javascripts/admin/templates/components/{mini-table.hbs => dashboard-mini-table.hbs} (93%) diff --git a/app/assets/javascripts/admin/components/mini-chart.js.es6 b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 similarity index 98% rename from app/assets/javascripts/admin/components/mini-chart.js.es6 rename to app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 index e55e684c73..bd2087be79 100644 --- a/app/assets/javascripts/admin/components/mini-chart.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 @@ -3,7 +3,7 @@ import computed from 'ember-addons/ember-computed-decorators'; import loadScript from 'discourse/lib/load-script'; export default Ember.Component.extend({ - classNames: ["mini-chart"], + classNames: ["dashboard-mini-chart"], classNameBindings: ["trend", "oneDataPoint"], diff --git a/app/assets/javascripts/admin/components/mini-table.js.es6 b/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 similarity index 95% rename from app/assets/javascripts/admin/components/mini-table.js.es6 rename to app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 index 7a1b0552d9..3ac28b5bae 100644 --- a/app/assets/javascripts/admin/components/mini-table.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 @@ -2,7 +2,7 @@ import { ajax } from 'discourse/lib/ajax'; import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ - classNames: ["mini-table"], + classNames: ["dashboard-mini-table"], total: null, labels: null, diff --git a/app/assets/javascripts/admin/templates/components/mini-chart.hbs b/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs similarity index 81% rename from app/assets/javascripts/admin/templates/components/mini-chart.hbs rename to app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs index 5b30980bbd..f2728c6d6d 100644 --- a/app/assets/javascripts/admin/templates/components/mini-chart.hbs +++ b/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs @@ -2,18 +2,19 @@

{{title}}

- {{d-icon "question-circle"}} + {{#if help}} + {{d-icon "question-circle" title=help}} + {{/if}}
- {{#if oneDataPoint}} {{chartData.lastObject.y}} {{else}}
- {{total}} + {{number total}} {{#if trendIcon}} {{d-icon trendIcon}} diff --git a/app/assets/javascripts/admin/templates/components/mini-table.hbs b/app/assets/javascripts/admin/templates/components/dashboard-mini-table.hbs similarity index 93% rename from app/assets/javascripts/admin/templates/components/mini-table.hbs rename to app/assets/javascripts/admin/templates/components/dashboard-mini-table.hbs index 4c75f25902..b7b0bdc61e 100644 --- a/app/assets/javascripts/admin/templates/components/mini-table.hbs +++ b/app/assets/javascripts/admin/templates/components/dashboard-mini-table.hbs @@ -19,7 +19,7 @@ {{#each dataset as |data|}} - {{data}} + {{number data}} {{/each}} diff --git a/app/assets/javascripts/admin/templates/dashboard_next.hbs b/app/assets/javascripts/admin/templates/dashboard_next.hbs index 4e1c243e60..a683ba27db 100644 --- a/app/assets/javascripts/admin/templates/dashboard_next.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_next.hbs @@ -9,15 +9,24 @@
- {{mini-chart dataSourceName="signups" startDate=startDate endDate=endDate}} - {{mini-chart dataSourceName="topics" startDate=startDate endDate=endDate}} + {{dashboard-mini-chart + dataSourceName="signups" + startDate=startDate + endDate=endDate + help="admin.dashboard.charts.signups.help"}} + + {{dashboard-mini-chart + dataSourceName="topics" + startDate=startDate + endDate=endDate + help="admin.dashboard.charts.topics.help"}}
- {{mini-table dataSourceName="users_by_trust_level"}} + {{dashboard-mini-table dataSourceName="users_by_trust_level"}}
diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss index c0331ea784..138085885f 100644 --- a/app/assets/stylesheets/common/admin/dashboard_next.scss +++ b/app/assets/stylesheets/common/admin/dashboard_next.scss @@ -33,7 +33,7 @@ } } - .mini-table { + .dashboard-mini-table { .table-title { align-items: center; display: flex; @@ -75,11 +75,15 @@ justify-content: space-between; flex-wrap: wrap; - .mini-chart { + .dashboard-mini-chart { flex-grow: 1; width: calc(100% * (1/3) - 1px); margin-bottom: 1em; + .d-icon-question-circle { + cursor: pointer; + } + .chart-title { align-items: center; display: flex; @@ -127,7 +131,7 @@ .chart-trend { font-size: $font-up-5; position: absolute; - left: 1.5em; + left: 2em; top: .5em; display: flex; justify-content: space-between; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0ce42828d3..592af7dacb 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2737,6 +2737,12 @@ en: page_views_short: "Pageviews" show_traffic_report: "Show Detailed Traffic Report" + charts: + signups: + help: Users created for this period + topics: + help: Topics created for this period + reports: today: "Today" yesterday: "Yesterday" From 9353ae4b5dacc2d63ebda02de2f9e378c7af4b38 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Mon, 16 Apr 2018 16:02:23 +0530 Subject: [PATCH 023/127] Remove obsolete per topic unsubscribe page. --- .../controllers/topic-unsubscribe.js.es6 | 7 ----- .../discourse/routes/app-route-map.js.es6 | 1 - .../discourse/routes/topic-unsubscribe.js.es6 | 19 -------------- .../discourse/templates/topic/unsubscribe.hbs | 13 ---------- app/assets/stylesheets/common/base/topic.scss | 14 ---------- app/controllers/topics_controller.rb | 26 +------------------ app/models/topic.rb | 4 --- config/locales/client.en.yml | 3 --- config/routes.rb | 2 -- 9 files changed, 1 insertion(+), 88 deletions(-) delete mode 100644 app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6 delete mode 100644 app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6 delete mode 100644 app/assets/javascripts/discourse/templates/topic/unsubscribe.hbs diff --git a/app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6 b/app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6 deleted file mode 100644 index a2611ebdcb..0000000000 --- a/app/assets/javascripts/discourse/controllers/topic-unsubscribe.js.es6 +++ /dev/null @@ -1,7 +0,0 @@ -export default Ember.Controller.extend({ - - stopNotificiationsText: function() { - return I18n.t("topic.unsubscribe.stop_notifications", { title: this.get("model.fancyTitle") }); - }.property("model.fancyTitle"), - -}); diff --git a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 index f489faf4bb..da873ecf9d 100644 --- a/app/assets/javascripts/discourse/routes/app-route-map.js.es6 +++ b/app/assets/javascripts/discourse/routes/app-route-map.js.es6 @@ -14,7 +14,6 @@ export default function() { }); this.route('topicBySlugOrId', { path: '/t/:slugOrId', resetNamespace: true }); - this.route('topicUnsubscribe', { path: '/t/:slug/:id/unsubscribe' }); this.route('discovery', { path: '/', resetNamespace: true }, function() { // top diff --git a/app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6 b/app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6 deleted file mode 100644 index 2faf69d0fb..0000000000 --- a/app/assets/javascripts/discourse/routes/topic-unsubscribe.js.es6 +++ /dev/null @@ -1,19 +0,0 @@ -import { loadTopicView } from 'discourse/models/topic'; - -export default Discourse.Route.extend({ - model(params) { - const topic = this.store.createRecord("topic", { id: params.id }); - return loadTopicView(topic).then(() => topic); - }, - - afterModel(topic) { - topic.set("details.notificationReasonText", null); - }, - - actions: { - didTransition() { - this.controllerFor("application").set("showFooter", true); - return true; - } - } -}); diff --git a/app/assets/javascripts/discourse/templates/topic/unsubscribe.hbs b/app/assets/javascripts/discourse/templates/topic/unsubscribe.hbs deleted file mode 100644 index 17a4df882e..0000000000 --- a/app/assets/javascripts/discourse/templates/topic/unsubscribe.hbs +++ /dev/null @@ -1,13 +0,0 @@ -
-
-

- {{{stopNotificiationsText}}} -

- -

- {{i18n "topic.unsubscribe.change_notification_state"}} -

- - {{topic-notifications-button notificationLevel=model.details.notification_level topic=model}} -
-
diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss index fba521fcba..8bf9f29261 100644 --- a/app/assets/stylesheets/common/base/topic.scss +++ b/app/assets/stylesheets/common/base/topic.scss @@ -192,20 +192,6 @@ a.badge-category { align-items: center; } -.topic-unsubscribe { - .notifications-button { - display: inline-block; - float: none; - line-height: $line-height-large; - .dropdown-toggle { - float: none; - } - .dropdown-menu { - bottom: initial; - } - } -} - .post-links-container { @include unselectable; clear: both; diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index 9f4fe20b35..b9334d490d 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -38,7 +38,7 @@ class TopicsController < ApplicationController before_action :consider_user_for_promotion, only: :show - skip_before_action :check_xhr, only: [:show, :unsubscribe, :feed] + skip_before_action :check_xhr, only: [:show, :feed] def id_for_slug topic = Topic.find_by(slug: params[:slug].downcase) @@ -154,30 +154,6 @@ class TopicsController < ApplicationController render_serialized(topic.reload, BasicTopicSerializer) end - def unsubscribe - if current_user.blank? - cookies[:destination_url] = request.fullpath - return redirect_to "/login-preferences" - end - - @topic_view = TopicView.new(params[:topic_id], current_user) - - if slugs_do_not_match || (!request.format.json? && params[:slug].blank?) - return redirect_to @topic_view.topic.unsubscribe_url, status: 301 - end - - tu = TopicUser.find_by(user_id: current_user.id, topic_id: params[:topic_id]) - - if tu && tu.notification_level > TopicUser.notification_levels[:regular] - tu.notification_level = TopicUser.notification_levels[:regular] - tu.save! - else - TopicUser.change(current_user.id, params[:topic_id].to_i, notification_level: TopicUser.notification_levels[:muted]) - end - - perform_show_response - end - def wordpress params.require(:best) params.require(:topic_id) diff --git a/app/models/topic.rb b/app/models/topic.rb index abc0f5f270..efc4cf1ae5 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -1007,10 +1007,6 @@ SQL Topic.relative_url(id, slug, post_number) end - def unsubscribe_url - "#{url}/unsubscribe" - end - def clear_pin_for(user) return unless user.present? TopicUser.change(user.id, id, cleared_pinned_at: Time.now) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 592af7dacb..f904bebf8a 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1595,9 +1595,6 @@ en: search: "There are no more search results." topic: - unsubscribe: - stop_notifications: "You will now receive less notifications for {{title}}" - change_notification_state: "Your current notification state is " filter_to: one: "1 post in topic" other: "{{count}} posts in topic" diff --git a/config/routes.rb b/config/routes.rb index a34e74a148..cc191d986a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -650,8 +650,6 @@ Discourse::Application.routes.draw do get "t/:topic_id/wordpress" => "topics#wordpress", constraints: { topic_id: /\d+/ } get "t/:slug/:topic_id/moderator-liked" => "topics#moderator_liked", constraints: { topic_id: /\d+/ } get "t/:slug/:topic_id/summary" => "topics#show", defaults: { summary: true }, constraints: { topic_id: /\d+/ } - get "t/:slug/:topic_id/unsubscribe" => "topics#unsubscribe", constraints: { topic_id: /\d+/ } - get "t/:topic_id/unsubscribe" => "topics#unsubscribe", constraints: { topic_id: /\d+/ } get "t/:topic_id/summary" => "topics#show", constraints: { topic_id: /\d+/ } put "t/:slug/:topic_id" => "topics#update", constraints: { topic_id: /\d+/ } put "t/:slug/:topic_id/star" => "topics#star", constraints: { topic_id: /\d+/ } From 06b6c805d58d88b2b90ffb6b644053c1e64d36d5 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 16 Apr 2018 13:03:43 +0200 Subject: [PATCH 024/127] dashboard next: adds report for user types --- .../admin/templates/dashboard_next.hbs | 1 + .../common/admin/dashboard_next.scss | 2 ++ app/models/report.rb | 18 +++++++++++++ config/locales/server.en.yml | 9 +++++++ spec/models/report_spec.rb | 27 +++++++++++++++++++ 5 files changed, 57 insertions(+) diff --git a/app/assets/javascripts/admin/templates/dashboard_next.hbs b/app/assets/javascripts/admin/templates/dashboard_next.hbs index a683ba27db..be33186c52 100644 --- a/app/assets/javascripts/admin/templates/dashboard_next.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_next.hbs @@ -26,6 +26,7 @@
+ {{dashboard-mini-table dataSourceName="users_by_types"}} {{dashboard-mini-table dataSourceName="users_by_trust_level"}}
diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss index 138085885f..ff29f13878 100644 --- a/app/assets/stylesheets/common/admin/dashboard_next.scss +++ b/app/assets/stylesheets/common/admin/dashboard_next.scss @@ -34,6 +34,8 @@ } .dashboard-mini-table { + margin-bottom: 1em; + .table-title { align-items: center; display: flex; diff --git a/app/models/report.rb b/app/models/report.rb index 2d6c8a3624..521a74fb9a 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -243,4 +243,22 @@ class Report .group(:user_agent).sum(:count) .map { |ua, count| { x: ua, y: count } } end + + def self.report_users_by_types(report) + report.data = [] + + label = Proc.new { |key| I18n.t("reports.users_by_types.xaxis_labels.#{key}") } + + admins = User.real.where(admin: true).count + report.data << { x: label.call("admin"), y: admins } if admins > 0 + + moderators = User.real.where(moderator: true).count + report.data << { x: label.call("moderator"), y: moderators } if moderators > 0 + + suspended = User.suspended.count + report.data << { x: label.call("suspended"), y: suspended } if suspended > 0 + + silenced = User.silenced.count + report.data << { x: label.call("silenced"), y: silenced } if silenced > 0 + end end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2c4ac31006..1d3169ab80 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -869,6 +869,15 @@ en: title: "Users per Trust Level" xaxis: "Trust Level" yaxis: "Number of Users" + users_by_types: + title: "Users per types" + xaxis: "Type" + yaxis: "Number of Users" + xaxis_labels: + admin: Admin + moderator: Moderator + suspended: Suspended + silenced: Silenced emails: title: "Emails Sent" xaxis: "Day" diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index 0d0d8201b0..8ef61d74b4 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -250,6 +250,33 @@ describe Report do end end + describe 'users by types level report' do + let(:report) { Report.find('users_by_types') } + + context "no users" do + it "returns an empty report" do + expect(report.data).to be_blank + end + end + + context "with users at different trust levels" do + before do + 3.times { Fabricate(:user, admin: true) } + 2.times { Fabricate(:user, moderator: true) } + UserSilencer.silence(Fabricate(:user), Fabricate.build(:admin)) + Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) + end + + it "returns a report with data" do + expect(report.data).to be_present + expect(report.data.find { |d| d[:x] == "admin" }[:y]).to eq 3 + expect(report.data.find { |d| d[:x] == "moderator" }[:y]).to eq 2 + expect(report.data.find { |d| d[:x] == "silenced" }[:y]).to eq 1 + expect(report.data.find { |d| d[:x] == "suspended" }[:y]).to eq 1 + end + end + end + describe 'posts counts' do it "only counts regular posts" do post = Fabricate(:post) From a4a0b8e922d83d6c0167091cc0cf06cb24a0bd43 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 16 Apr 2018 13:40:44 +0200 Subject: [PATCH 025/127] fix spec --- spec/models/report_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index 8ef61d74b4..0cafcdbbd2 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -269,10 +269,12 @@ describe Report do it "returns a report with data" do expect(report.data).to be_present - expect(report.data.find { |d| d[:x] == "admin" }[:y]).to eq 3 - expect(report.data.find { |d| d[:x] == "moderator" }[:y]).to eq 2 - expect(report.data.find { |d| d[:x] == "silenced" }[:y]).to eq 1 - expect(report.data.find { |d| d[:x] == "suspended" }[:y]).to eq 1 + + label = Proc.new { |key| I18n.t("reports.users_by_types.xaxis_labels.#{key}") } + expect(report.data.find { |d| d[:x] == label.call("admin") }[:y]).to eq 3 + expect(report.data.find { |d| d[:x] == label.call("moderator") }[:y]).to eq 2 + expect(report.data.find { |d| d[:x] == label.call("silenced") }[:y]).to eq 1 + expect(report.data.find { |d| d[:x] == label.call("suspended") }[:y]).to eq 1 end end end From 001b0710c7433300c141459bf25aecf57e7c6434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 16 Apr 2018 15:41:45 +0200 Subject: [PATCH 026/127] FIX: don't add diff classes more than once --- lib/discourse_diff.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/discourse_diff.rb b/lib/discourse_diff.rb index c1971ed710..89d293e431 100644 --- a/lib/discourse_diff.rb +++ b/lib/discourse_diff.rb @@ -184,8 +184,13 @@ class DiscourseDiff # add it right before the ">" html_or_text.insert(index_of_next_chevron, " class=\"diff-#{klass}\"") else - # we have a class, insert it at the beginning - html_or_text.insert(index_of_class + "class=".length + 1, "diff-#{klass} ") + # we have a class, insert it at the beginning if not already present + classes = html_or_text[/class=(["'])([^\1]*)\1/, 2] + if classes.include?("diff-#{klass}") + html_or_text + else + html_or_text.insert(index_of_class + "class=".length + 1, "diff-#{klass} ") + end end else "<#{klass}>#{html_or_text}" From 4fb41663b3c7071dc1ef7d92eb3e5a6516dfe3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Mon, 16 Apr 2018 15:46:32 +0200 Subject: [PATCH 027/127] SECURITY: prevent XSS when showing diffs --- .../javascripts/discourse/controllers/history.js.es6 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/history.js.es6 b/app/assets/javascripts/discourse/controllers/history.js.es6 index 95db043e02..2cc7927f86 100644 --- a/app/assets/javascripts/discourse/controllers/history.js.es6 +++ b/app/assets/javascripts/discourse/controllers/history.js.es6 @@ -3,6 +3,8 @@ import { categoryBadgeHTML } from 'discourse/helpers/category-link'; import computed from 'ember-addons/ember-computed-decorators'; import { propertyGreaterThan, propertyLessThan } from 'discourse/lib/computed'; import { on } from 'ember-addons/ember-computed-decorators'; +import { default as WhiteLister } from 'pretty-text/white-lister'; +import { sanitize } from 'pretty-text/sanitizer'; function customTagArray(fieldName) { return function() { @@ -187,7 +189,14 @@ export default Ember.Controller.extend(ModalFunctionality, { @computed('viewMode', 'model.body_changes') bodyDiff(viewMode) { - return this.get("model.body_changes." + viewMode); + const html = this.get(`model.body_changes.${viewMode}`); + if (viewMode === "side_by_side_markdown") { + return html; + } else { + const whiteLister = new WhiteLister({ features: { editHistory: true }}); + whiteLister.whiteListFeature("editHistory", { custom: () => true }); + return sanitize(html, whiteLister); + } }, actions: { From cfe88a67e131f36c6b8d350f62e9f45507739215 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Mon, 16 Apr 2018 16:01:29 +0200 Subject: [PATCH 028/127] dashboard next: minor quality improvements * locale for title * minimum chart/table while loading * sort users by type * more spacing in the UI * minor refactoring --- .../components/dashboard-mini-chart.js.es6 | 51 +++++++++---------- .../components/dashboard-mini-table.js.es6 | 26 ++++++---- .../controllers/admin-dashboard-next.js.es6 | 8 ++- .../admin/templates/dashboard_next.hbs | 4 +- .../common/admin/dashboard_next.scss | 14 +++-- config/locales/client.en.yml | 1 + 6 files changed, 53 insertions(+), 51 deletions(-) diff --git a/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 index bd2087be79..676917e8c2 100644 --- a/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 @@ -5,7 +5,7 @@ import loadScript from 'discourse/lib/load-script'; export default Ember.Component.extend({ classNames: ["dashboard-mini-chart"], - classNameBindings: ["trend", "oneDataPoint"], + classNameBindings: ["trend", "oneDataPoint", "isLoading"], isLoading: false, total: null, @@ -13,11 +13,9 @@ export default Ember.Component.extend({ title: null, chartData: null, oneDataPoint: false, - backgroundColor: "rgba(200,220,240,0.3)", borderColor: "#08C", - didInsertElement() { this._super(); @@ -46,17 +44,9 @@ export default Ember.Component.extend({ } }, - _computeTrend(total, prevTotal) { - const percentChange = ((total - prevTotal) / prevTotal) * 100; - - if (percentChange > 50) return "double-up"; - if (percentChange > 0) return "up"; - if (percentChange === 0) return "stable"; - if (percentChange < 50) return "double-down"; - if (percentChange < 0) return "down"; - }, - fetchReport() { + this.set("isLoading", true); + let payload = {data: {}}; if (this.get("startDate")) { @@ -67,8 +57,6 @@ export default Ember.Component.extend({ payload.data.end_date = this.get("endDate").toISOString(); } - this.set("isLoading", true); - ajax(this.get("dataSource"), payload) .then((response) => { const report = response.report; @@ -94,9 +82,9 @@ export default Ember.Component.extend({ }, drawChart() { - const ctx = this.$(".chart-canvas")[0].getContext("2d"); + const context = this.$(".chart-canvas")[0].getContext("2d"); - let data = { + const data = { labels: this.get("chartData").map(r => r.x), datasets: [{ data: this.get("chartData").map(r => r.y), @@ -105,25 +93,32 @@ export default Ember.Component.extend({ }] }; - const config = { + this._chart = new window.Chart(context, this._buildChartConfig(data)); + }, + + _buildChartConfig(data) { + return { type: "line", - data: data, + data, options: { legend: { display: false }, responsive: true, - layout: { - padding: { left: 0, top: 0, right: 0, bottom: 0 } - }, + layout: { padding: { left: 0, top: 0, right: 0, bottom: 0 } }, scales: { - yAxes: [{ - display: true, - ticks: { suggestedMin: 0 } - }], + yAxes: [{ display: true, ticks: { suggestedMin: 0 } }], xAxes: [{ display: true }], } }, }; + }, - this._chart = new window.Chart(ctx, config); - } + _computeTrend(total, prevTotal) { + const percentChange = ((total - prevTotal) / prevTotal) * 100; + + if (percentChange > 50) return "double-up"; + if (percentChange > 0) return "up"; + if (percentChange === 0) return "stable"; + if (percentChange < 50) return "double-down"; + if (percentChange < 0) return "down"; + }, }); diff --git a/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 b/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 index 3ac28b5bae..9b5f96e2f1 100644 --- a/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 @@ -4,6 +4,8 @@ import computed from 'ember-addons/ember-computed-decorators'; export default Ember.Component.extend({ classNames: ["dashboard-mini-table"], + classNameBindings: ["isLoading"], + total: null, labels: null, title: null, @@ -26,18 +28,20 @@ export default Ember.Component.extend({ fetchReport() { this.set("isLoading", true); - ajax(this.get("dataSource")).then((response) => { - const report = response.report; + ajax(this.get("dataSource")) + .then((response) => { + const report = response.report; + const data = report.data.sort((a, b) => a.x >= b.x); - this.setProperties({ - labels: report.data.map(r => r.x), - dataset: report.data.map(r => r.y), - total: report.total, - title: report.title, - chartData: report.data + this.setProperties({ + labels: data.map(r => r.x), + dataset: data.map(r => r.y), + total: report.total, + title: report.title, + chartData: data + }); + }).finally(() => { + this.set("isLoading", false); }); - }).finally(() => { - this.set("isLoading", false); - }); } }); diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 index 2d009fe285..8094b04209 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 @@ -8,8 +8,6 @@ export default Ember.Controller.extend({ @computed("period") startDate(period) { - if (period === "all") return null; - switch (period) { case "yearly": return moment().subtract(1, "year").startOf("day"); @@ -26,14 +24,14 @@ export default Ember.Controller.extend({ case "daily": return moment().startOf("day"); break; + default: + return null; } }, @computed("period") endDate(period) { - if (period === "all") return null; - - return moment().endOf("day"); + return period === "all" ? null : moment().endOf("day"); }, actions: { diff --git a/app/assets/javascripts/admin/templates/dashboard_next.hbs b/app/assets/javascripts/admin/templates/dashboard_next.hbs index be33186c52..9a481e4451 100644 --- a/app/assets/javascripts/admin/templates/dashboard_next.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_next.hbs @@ -3,7 +3,7 @@ {{#conditional-loading-spinner condition=loading}}
-

Community health

+

{{i18n "admin.dashboard.community_health"}}

{{period-chooser period=period action="changePeriod"}}
@@ -33,6 +33,4 @@
- - {{/conditional-loading-spinner}} diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss index ff29f13878..44a95a465b 100644 --- a/app/assets/stylesheets/common/admin/dashboard_next.scss +++ b/app/assets/stylesheets/common/admin/dashboard_next.scss @@ -36,14 +36,17 @@ .dashboard-mini-table { margin-bottom: 1em; + &.is-loading { + height: 150px; + } + .table-title { align-items: center; display: flex; - margin: .5em; justify-content: space-between; h3 { - margin: 0 .5em 0 0; + margin: .5em 0; } } @@ -82,6 +85,10 @@ width: calc(100% * (1/3) - 1px); margin-bottom: 1em; + &.is-loading { + height: 150px; + } + .d-icon-question-circle { cursor: pointer; } @@ -89,10 +96,9 @@ .chart-title { align-items: center; display: flex; - margin: .5em; h3 { - margin: 0 .5em 0 0; + margin: .5em 0; } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f904bebf8a..69cd0f1a79 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2733,6 +2733,7 @@ en: page_views: "Pageviews" page_views_short: "Pageviews" show_traffic_report: "Show Detailed Traffic Report" + community_health: Community health charts: signups: From 3d7dbdedc0427612b20df068a8c6c315e3f08ae1 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 16 Apr 2018 15:43:20 -0400 Subject: [PATCH 029/127] FEATURE: An API to help sites build robots.txt files programatically This is mainly useful for subfolder sites, who need to expose their robots.txt contents to a parent site. --- app/controllers/robots_txt_controller.rb | 63 ++++++++++++++++----- app/views/robots_txt/index.erb | 28 +++------ config/routes.rb | 1 + spec/requests/robots_txt_controller_spec.rb | 10 ++++ 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/app/controllers/robots_txt_controller.rb b/app/controllers/robots_txt_controller.rb index b5dfdb3e62..9815355632 100644 --- a/app/controllers/robots_txt_controller.rb +++ b/app/controllers/robots_txt_controller.rb @@ -36,26 +36,59 @@ class RobotsTxtController < ApplicationController } def index - if SiteSetting.allow_index_in_robots_txt - path = :index + if SiteSetting.allow_index_in_robots_txt? + @robots_info = fetch_robots_info + render :index, content_type: 'text/plain' + else + render :no_index, content_type: 'text/plain' + end + end - @crawler_delayed_agents = SiteSetting.slow_down_crawler_user_agents.split('|').map { |agent| - [agent, SiteSetting.slow_down_crawler_rate] - } + # If you are hosting Discourse in a subfolder, you will need to create your robots.txt + # in the root of your web server with the appropriate paths. This method will return + # JSON that can be used by a script to create a robots.txt that works well with your + # existing site. + def builder + render json: fetch_robots_info + end - if SiteSetting.whitelisted_crawler_user_agents.present? - @allowed_user_agents = SiteSetting.whitelisted_crawler_user_agents.split('|') - @disallowed_user_agents = ['*'] - elsif SiteSetting.blacklisted_crawler_user_agents.present? - @allowed_user_agents = ['*'] - @disallowed_user_agents = SiteSetting.blacklisted_crawler_user_agents.split('|') - else - @allowed_user_agents = ['*'] +protected + + def fetch_robots_info + deny_paths = DISALLOWED_PATHS.map { |p| Discourse.base_uri + p } + deny_all = [ "#{Discourse.base_uri}/" ] + + result = { + header: "# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file", + agents: [] + } + + if SiteSetting.whitelisted_crawler_user_agents.present? + SiteSetting.whitelisted_crawler_user_agents.split('|').each do |agent| + result[:agents] << { name: agent, disallow: deny_paths } + end + + result[:agents] << { name: '*', disallow: deny_all } + elsif SiteSetting.blacklisted_crawler_user_agents.present? + result[:agents] << { name: '*', disallow: deny_paths } + SiteSetting.blacklisted_crawler_user_agents.split('|').each do |agent| + result[:agents] << { name: agent, disallow: deny_all } end else - path = :no_index + result[:agents] << { name: '*', disallow: deny_paths } end - render path, content_type: 'text/plain' + if SiteSetting.slow_down_crawler_user_agents.present? + SiteSetting.slow_down_crawler_user_agents.split('|').each do |agent| + result[:agents] << { + name: agent, + delay: SiteSetting.slow_down_crawler_rate, + disallow: deny_paths + } + end + end + + result end + end diff --git a/app/views/robots_txt/index.erb b/app/views/robots_txt/index.erb index 2e9105c902..71ca94baa7 100644 --- a/app/views/robots_txt/index.erb +++ b/app/views/robots_txt/index.erb @@ -1,30 +1,18 @@ -# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +<%= @robots_info[:header] %> <% if Discourse.base_uri.present? %> # This robots.txt file is not used. Please append the content below in the robots.txt file located at the root <% end %> # -<% @allowed_user_agents.each do |user_agent| %> -User-agent: <%= user_agent %> -<% end %> -<% RobotsTxtController::DISALLOWED_PATHS.each do |path| %> -Disallow: <%= Discourse.base_uri + path %> +<% @robots_info[:agents].each do |agent| %> +User-agent: <%= agent[:name] %> +<%- if agent[:delay] -%> +Crawl-delay: <%= agent[:delay] %> +<%- end -%> +<% agent[:disallow].each do |path| %> +Disallow: <%= path %> <% end %> -<% if @disallowed_user_agents %> - <% @disallowed_user_agents.each do |user_agent| %> -User-agent: <%= user_agent %> -Disallow: <%= Discourse.base_uri + "/" %> - <% end %> <% end %> <%= server_plugin_outlet "robots_txt_index" %> - -<% @crawler_delayed_agents.each do |agent, delay| %> -User-agent: <%= agent %> -Crawl-delay: <%= delay %> -<% RobotsTxtController::DISALLOWED_PATHS.each do |path| %> -Disallow: <%= Discourse.base_uri + path %> -<% end %> - -<% end %> diff --git a/config/routes.rb b/config/routes.rb index cc191d986a..d64c0f28c4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -744,6 +744,7 @@ Discourse::Application.routes.draw do get "favicon/proxied" => "static#favicon", format: false get "robots.txt" => "robots_txt#index" + get "robots-builder.json" => "robots_txt#builder" get "offline.html" => "offline#index" get "manifest.json" => "metadata#manifest", as: :manifest get "opensearch" => "metadata#opensearch", format: :xml diff --git a/spec/requests/robots_txt_controller_spec.rb b/spec/requests/robots_txt_controller_spec.rb index 87232518cb..c5627c0f19 100644 --- a/spec/requests/robots_txt_controller_spec.rb +++ b/spec/requests/robots_txt_controller_spec.rb @@ -1,6 +1,16 @@ require 'rails_helper' RSpec.describe RobotsTxtController do + describe '#builder' do + it "returns json information for building a robots.txt" do + get "/robots-builder.json" + json = ::JSON.parse(response.body) + expect(json).to be_present + expect(json['header']).to be_present + expect(json['agents']).to be_present + end + end + describe '#index' do context 'subfolder' do From 75e5f686fb818556412f47a8f26b76c1a1c7dd84 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Fri, 13 Apr 2018 17:43:18 -0700 Subject: [PATCH 030/127] FEATURE: group cards popup on mention clicks --- .../components/user-card-contents.js.es6 | 154 ++++++-------- .../user-card-group-contents.js.es6 | 32 +++ .../components/user-card-user-contents.js.es6 | 116 ++++++++++ .../components/user-card-contents.hbs | 198 +++--------------- .../components/user-card-group-contents.hbs | 50 +++++ .../components/user-card-user-contents.hbs | 172 +++++++++++++++ .../discourse/templates/user-card.hbs | 1 + app/assets/stylesheets/desktop/user-card.scss | 28 +++ 8 files changed, 490 insertions(+), 261 deletions(-) create mode 100644 app/assets/javascripts/discourse/components/user-card-group-contents.js.es6 create mode 100644 app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 create mode 100644 app/assets/javascripts/discourse/templates/components/user-card-group-contents.hbs create mode 100644 app/assets/javascripts/discourse/templates/components/user-card-user-contents.hbs diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index b7d3b4d31f..990eeb0aa4 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -1,115 +1,76 @@ import { wantsNewWindow } from 'discourse/lib/intercept-click'; -import { propertyNotEqual, setting } from 'discourse/lib/computed'; import CleansUp from 'discourse/mixins/cleans-up'; import afterTransition from 'discourse/lib/after-transition'; -import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { default as computed } from 'ember-addons/ember-computed-decorators'; import DiscourseURL from 'discourse/lib/url'; import User from 'discourse/models/user'; +import Group from 'discourse/models/group'; import { userPath } from 'discourse/lib/url'; -import { durationTiny } from 'discourse/lib/formatter'; -import CanCheckEmails from 'discourse/mixins/can-check-emails'; const clickOutsideEventName = "mousedown.outside-user-card"; const clickDataExpand = "click.discourse-user-card"; const clickMention = "click.discourse-user-mention"; +const groupClickMention = "click.discourse-group-mention"; -export default Ember.Component.extend(CleansUp, CanCheckEmails, { +const maxMembersToDisplay = 10; + +export default Ember.Component.extend(CleansUp, { elementId: 'user-card', classNameBindings: ['visible:show', 'showBadges', 'hasCardBadgeImage', 'user.card_background::no-bg'], - allowBackgrounds: setting('allow_profile_backgrounds'), postStream: Ember.computed.alias('topic.postStream'), - enoughPostsForFiltering: Ember.computed.gte('topicPostCount', 2), viewingTopic: Ember.computed.match('currentPath', /^topic\./), - viewingAdmin: Ember.computed.match('currentPath', /^admin\./), - showFilter: Ember.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'), - showName: propertyNotEqual('user.name', 'user.username'), - hasUserFilters: Ember.computed.gt('postStream.userFilters.length', 0), - isSuspended: Ember.computed.notEmpty('user.suspend_reason'), - showBadges: setting('enable_badges'), - showMoreBadges: Ember.computed.gt('moreBadgesCount', 0), - showDelete: Ember.computed.and("viewingAdmin", "showName", "user.canBeDeleted"), - linkWebsite: Ember.computed.not('user.isBasic'), - hasLocationOrWebsite: Ember.computed.or('user.location', 'user.website_name'), - showCheckEmail: Ember.computed.and('user.staged', 'canCheckEmails'), visible: false, user: null, + group: null, username: null, avatar: null, userLoading: null, cardTarget: null, post: null, + cardType: null, // If inside a topic topicPostCount: null, - @computed('user.name') - nameFirst(name) { - return !this.siteSettings.prioritize_username_in_ux && name && name.trim().length > 0; + @computed('cardType') + isUserShown(cardType) { + return cardType == 'user'; }, - @computed('username', 'topicPostCount') - togglePostsLabel(username, count) { - return I18n.t("topic.filter_to", { username, count }); + @computed('cardType') + isGroupShown(cardType) { + return cardType == 'group'; }, - @computed('user.user_fields.@each.value') - publicUserFields() { - const siteUserFields = this.site.get('user_fields'); - if (!Ember.isEmpty(siteUserFields)) { - const userFields = this.get('user.user_fields'); - return siteUserFields.filterBy('show_on_user_card', true).sortBy('position').map(field => { - Ember.set(field, 'dasherized_name', field.get('name').dasherize()); - const value = userFields ? userFields[field.get('id')] : null; - return Ember.isEmpty(value) ? null : Ember.Object.create({ value, field }); - }).compact(); - } + _showUser(username, $target) { + const args = { stats: false }; + args.include_post_count_for = this.get('topic.id'); + + User.findByUsername(username, args).then(user => { + if (user.topic_post_count) { + this.set('topicPostCount', user.topic_post_count[args.include_post_count_for]); + } + this.setProperties({ user, avatar: user, visible: true, cardType: 'user' }); + + this._positionCard($target); + }).catch(() => this._close()).finally(() => this.set('userLoading', null)); }, - @computed("user.trust_level") - removeNoFollow(trustLevel) { - return trustLevel > 2 && !this.siteSettings.tl3_links_no_follow; + _showGroup(groupname, $target) { + this.store.find("group", groupname).then(group => { + this.setProperties({ group, avatar: group, visible: true, cardType: 'group' }); + this._positionCard($target); + if(!group.flair_url && !group.flair_bg_color) { + group.set('flair_url', 'fa-users'); + } + group.set('limit', maxMembersToDisplay); + return group.findMembers(); + }).catch(() => this._close()).finally(() => this.set('userLoading', null)); }, - @computed('user.badge_count', 'user.featured_user_badges.length') - moreBadgesCount: (badgeCount, badgeLength) => badgeCount - badgeLength, - - @computed('user.card_badge.image') - hasCardBadgeImage: image => image && image.indexOf('fa-') !== 0, - - @observes('user.card_background') - addBackground() { - if (!this.get('allowBackgrounds')) { return; } - - const $this = this.$(); - if (!$this) { return; } - - const url = this.get('user.card_background'); - const bg = Ember.isEmpty(url) ? '' : `url(${Discourse.getURLWithCDN(url)})`; - $this.css('background-image', bg); - }, - - @computed('user.time_read', 'user.recent_time_read') - showRecentTimeRead(timeRead, recentTimeRead) { - return timeRead !== recentTimeRead && recentTimeRead !== 0; - }, - - @computed('user.recent_time_read') - recentTimeRead(recentTimeReadSeconds) { - return durationTiny(recentTimeReadSeconds); - }, - - @computed('showRecentTimeRead', 'user.time_read', 'recentTimeRead') - timeReadTooltip(showRecent, timeRead, recentTimeRead) { - if (showRecent) { - return I18n.t('time_read_recently_tooltip', {time_read: durationTiny(timeRead), recent_time_read: recentTimeRead}); - } else { - return I18n.t('time_read_tooltip', {time_read: durationTiny(timeRead)}); - } - }, - - _show(username, $target) { + _show(username, $target, userCardType) { // No user card for anon if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { return false; @@ -141,17 +102,13 @@ export default Ember.Component.extend(CleansUp, CanCheckEmails, { const post = this.get('viewingTopic') && postId ? this.get('postStream').findLoadedPost(postId) : null; this.setProperties({ username, userLoading: username, cardTarget: target, post }); - const args = { stats: false }; - args.include_post_count_for = this.get('topic.id'); + if(userCardType == 'group') { + this._showGroup(username, $target); + } + else if(userCardType == 'user') { + this._showUser(username, $target); + } - User.findByUsername(username, args).then(user => { - if (user.topic_post_count) { - this.set('topicPostCount', user.topic_post_count[args.include_post_count_for]); - } - this.setProperties({ user, avatar: user, visible: true }); - - this._positionCard($target); - }).catch(() => this._close()).finally(() => this.set('userLoading', null)); return false; }, @@ -179,14 +136,20 @@ export default Ember.Component.extend(CleansUp, CanCheckEmails, { $('#main-outlet').on(clickDataExpand, '[data-user-card]', (e) => { if (wantsNewWindow(e)) { return; } const $target = $(e.currentTarget); - return this._show($target.data('user-card'), $target); + return this._show($target.data('user-card'), $target, 'user'); }); $('#main-outlet').on(clickMention, 'a.mention', (e) => { if (wantsNewWindow(e)) { return; } const $target = $(e.target); - return this._show($target.text().replace(/^@/, ''), $target); + return this._show($target.text().replace(/^@/, ''), $target, 'user'); }); + + $('#main-outlet').on(groupClickMention, 'a.mention-group', (e) => { + if (wantsNewWindow(e)) { return; } + const $target = $(e.target); + return this._show($target.text().replace(/^@/, ''), $target, 'group'); + }) }, _positionCard(target) { @@ -241,12 +204,14 @@ export default Ember.Component.extend(CleansUp, CanCheckEmails, { this.setProperties({ visible: false, user: null, + group: null, username: null, avatar: null, userLoading: null, cardTarget: null, post: null, - topicPostCount: null + topicPostCount: null, + cardType: null }); }, @@ -265,7 +230,7 @@ export default Ember.Component.extend(CleansUp, CanCheckEmails, { willDestroyElement() { this._super(); $('html').off(clickOutsideEventName); - $('#main').off(clickDataExpand).off(clickMention); + $('#main').off(clickDataExpand).off(clickMention).off(groupClickMention); }, actions: { @@ -284,6 +249,10 @@ export default Ember.Component.extend(CleansUp, CanCheckEmails, { this.sendAction('composePrivateMessage', ...args); }, + messageGroup() { + this.sendAction('createNewMessageViaParams', this.get('group.name')); + }, + togglePosts() { this.sendAction('togglePosts', this.get('user')); this._close(); @@ -298,6 +267,11 @@ export default Ember.Component.extend(CleansUp, CanCheckEmails, { this._close(); }, + showGroup() { + this.sendAction('showGroup', this.get('group')); + this._close(); + }, + checkEmail(user) { user.checkEmail(); } diff --git a/app/assets/javascripts/discourse/components/user-card-group-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-group-contents.js.es6 new file mode 100644 index 0000000000..7f085d301f --- /dev/null +++ b/app/assets/javascripts/discourse/components/user-card-group-contents.js.es6 @@ -0,0 +1,32 @@ +import { default as computed } from 'ember-addons/ember-computed-decorators'; + +export default Ember.Component.extend({ + group: null, + + showMoreMembers: Ember.computed.gt('moreMembersCount', 0), + + @computed('group.user_count', 'group.members.length') + moreMembersCount: (memberCount, maxMemberDisplay) => memberCount - maxMemberDisplay, + + @computed('group') + groupPath(group) { + return `${Discourse.BaseUri}/groups/${group.name}`; + }, + + actions: { + close() { + this.sendAction('close'); + }, + + messageGroup() { + this.sendAction('messageGroup'); + }, + + showGroup() { + this.sendAction('showGroup'); + }, + showUser(user) { + this.sendAction('showUser', user); + }, + } +}); diff --git a/app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 new file mode 100644 index 0000000000..7585c07e96 --- /dev/null +++ b/app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 @@ -0,0 +1,116 @@ +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { propertyNotEqual, setting } from 'discourse/lib/computed'; +import { durationTiny } from 'discourse/lib/formatter'; +import CanCheckEmails from 'discourse/mixins/can-check-emails'; + +export default Ember.Component.extend(CanCheckEmails, { + + allowBackgrounds: setting('allow_profile_backgrounds'), + + enoughPostsForFiltering: Ember.computed.gte('topicPostCount', 2), + showFilter: Ember.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'), + showName: propertyNotEqual('user.name', 'user.username'), + hasUserFilters: Ember.computed.gt('postStream.userFilters.length', 0), + isSuspended: Ember.computed.notEmpty('user.suspend_reason'), + showBadges: setting('enable_badges'), + showMoreBadges: Ember.computed.gt('moreBadgesCount', 0), + showDelete: Ember.computed.and("viewingAdmin", "showName", "user.canBeDeleted"), + linkWebsite: Ember.computed.not('user.isBasic'), + hasLocationOrWebsite: Ember.computed.or('user.location', 'user.website_name'), + showCheckEmail: Ember.computed.and('user.staged', 'canCheckEmails'), + + @computed('user.name') + nameFirst(name) { + return !this.siteSettings.prioritize_username_in_ux && name && name.trim().length > 0; + }, + + @computed('username', 'topicPostCount') + togglePostsLabel(username, count) { + return I18n.t("topic.filter_to", { username, count }); + }, + + @computed('user.user_fields.@each.value') + publicUserFields() { + const siteUserFields = this.site.get('user_fields'); + if (!Ember.isEmpty(siteUserFields)) { + const userFields = this.get('user.user_fields'); + return siteUserFields.filterBy('show_on_user_card', true).sortBy('position').map(field => { + Ember.set(field, 'dasherized_name', field.get('name').dasherize()); + const value = userFields ? userFields[field.get('id')] : null; + return Ember.isEmpty(value) ? null : Ember.Object.create({ value, field }); + }).compact(); + } + }, + + @computed("user.trust_level") + removeNoFollow(trustLevel) { + return trustLevel > 2 && !this.siteSettings.tl3_links_no_follow; + }, + + @computed('user.badge_count', 'user.featured_user_badges.length') + moreBadgesCount: (badgeCount, badgeLength) => badgeCount - badgeLength, + + @computed('user.card_badge.image') + hasCardBadgeImage: image => image && image.indexOf('fa-') !== 0, + + @observes('user.card_background') + addBackground() { + if (!this.get('allowBackgrounds')) { return; } + + const $this = this.$(); + if (!$this) { return; } + + const url = this.get('user.card_background'); + const bg = Ember.isEmpty(url) ? '' : `url(${Discourse.getURLWithCDN(url)})`; + $this.css('background-image', bg); + }, + + @computed('user.time_read', 'user.recent_time_read') + showRecentTimeRead(timeRead, recentTimeRead) { + return timeRead !== recentTimeRead && recentTimeRead !== 0; + }, + + @computed('user.recent_time_read') + recentTimeRead(recentTimeReadSeconds) { + return durationTiny(recentTimeReadSeconds); + }, + + @computed('showRecentTimeRead', 'user.time_read', 'recentTimeRead') + timeReadTooltip(showRecent, timeRead, recentTimeRead) { + if (showRecent) { + return I18n.t('time_read_recently_tooltip', {time_read: durationTiny(timeRead), recent_time_read: recentTimeRead}); + } else { + return I18n.t('time_read_tooltip', {time_read: durationTiny(timeRead)}); + } + }, + + actions: { + close() { + this.sendAction('close'); + }, + + cancelFilter() { + this.sendAction('cancelFilter'); + }, + + composePrivateMessage(...args) { + this.sendAction('composePrivateMessage', ...args); + }, + + togglePosts() { + this.sendAction('togglePosts'); + }, + + deleteUser() { + this.sendAction('deleteUser'); + }, + + showUser() { + this.sendAction('showUser'); + }, + + checkEmail(user) { + this.sendAction('showUser', user); + } + } +}); diff --git a/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs b/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs index 614c4808d3..989801e9dc 100644 --- a/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs +++ b/app/assets/javascripts/discourse/templates/components/user-card-contents.hbs @@ -1,177 +1,33 @@ {{#if visible}}
- -
- {{bound-avatar avatar "huge"}} - {{#if user.primary_group_name}} - {{avatar-flair - flairURL=user.primary_group_flair_url - flairBgColor=user.primary_group_flair_bg_color - flairColor=user.primary_group_flair_color - groupName=user.primary_group_name}} - {{/if}} - {{plugin-outlet name="user-card-avatar-flair" args=(hash user=user) tagName='div'}} -
- -
- -

- {{if nameFirst user.name (format-username username)}} {{user-status user currentUser=currentUser}} -

- {{plugin-outlet name="user-card-after-username" args=(hash user=user) tagName=''}} - - {{#unless nameFirst}} - {{#if user.name}} -

{{user.name}}

- {{/if}} - {{else}} -

{{username}}

- {{/unless}} - - {{#if user.title}} -

{{user.title}}

- {{/if}} - - {{#if user.staged}} -

{{i18n 'user.staged'}}

- {{/if}} - - {{plugin-outlet name="user-card-post-names" args=(hash user=user) tagName='div'}} -
-
- -
    - {{#if user.can_send_private_message_to_user}} -
  • - {{d-button - class="btn-primary" - action=(action "composePrivateMessage" user post) - icon="envelope" - label="user.private_message"}} -
  • - {{/if}} - - {{#if showFilter}} -
  • - {{d-button - action=(action "togglePosts" user) - icon="filter" - translatedLabel=togglePostsLabel}} -
  • - {{/if}} - - {{#if hasUserFilters}} -
  • - {{d-button - action="cancelFilter" - icon="times" - label="topic.filters.cancel"}} -
  • - {{/if}} - - {{#if showDelete}} -
  • - {{d-button - class="btn-danger" - action=(action "deleteUser" user) - icon="exclamation-triangle" - label="admin.user.delete"}} -
  • - {{/if}} -
- {{plugin-outlet - name="user-card-additional-controls" - args=(hash user=user close=(action "close")) - tagName=""}} - - {{#if isSuspended}} -
- {{d-icon "ban"}} - {{i18n 'user.suspended_notice' date=user.suspendedTillDate}}
- {{i18n 'user.suspended_reason'}} {{user.suspend_reason}} -
- {{else}} - {{#if user.bio_cooked}}
{{text-overflow class="overflow" text=user.bio_excerpt}}
{{/if}} + {{#if isUserShown}} + {{user-card-user-contents + model=model + viewingTopic=viewingTopic + topicPostCount=topicPostCount + user=user + username=username + userLoading=userLoading + cardTarget=cardTarget + avatar=avatar + postStream=postStream + close="close" + cancelFilter="cancelFilter" + composePrivateMessage="composePrivateMessage" + togglePosts="togglePosts" + deleteUser="deleteUser" + showUser="showUser" + checkEmail="checkEmail" + }} {{/if}} - - {{#if user.card_badge}} - {{#link-to 'badges.show' user.card_badge class="card-badge" title=user.card_badge.name}} - {{icon-or-image user.card_badge.image title=user.card_badge.name}} - {{/link-to}} - {{/if}} - - {{#if hasLocationOrWebsite}} -
- {{#if user.location}} - {{d-icon "map-marker"}} {{user.location}} - {{/if}} - - {{#if user.website_name}} - - {{d-icon "globe"}} - {{#if linkWebsite}} - {{user.website_name}} - {{else}} - {{user.website_name}} - {{/if}} - - {{/if}} - - {{plugin-outlet name="user-card-location-and-website" args=(hash user=user)}} -
- {{/if}} - - {{#if user}} - - {{/if}} - - {{#if publicUserFields}} -
- {{#each publicUserFields as |uf|}} - {{#if uf.value}} -
- {{uf.field.name}}: - {{uf.value}} -
- {{/if}} - {{/each}} -
- {{/if}} - - {{#if showBadges}} -
- {{#each user.featured_user_badges as |ub|}} - {{user-badge badge=ub.badge user=user}} - {{/each}} - {{#if showMoreBadges}} - {{#link-to 'user.badges' user class="btn more-user-badges"}} - {{i18n 'badges.more_badges' count=moreBadgesCount}} - {{/link-to}} - {{/if}} -
+ {{#if isGroupShown}} + {{user-card-group-contents + group=group + close="close" + messageGroup="messageGroup" + showGroup="showGroup" + showUser="showUser" + }} {{/if}}
{{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/user-card-group-contents.hbs b/app/assets/javascripts/discourse/templates/components/user-card-group-contents.hbs new file mode 100644 index 0000000000..50062615f5 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/user-card-group-contents.hbs @@ -0,0 +1,50 @@ + + +
+ +

+ {{ group.name }} +

+ {{#if group.full_name}} +

{{group.full_name}}

+ {{else}} +

{{group.name}}

+ {{/if}} +
+
+ +
+ {{group-membership-button + model=group + showLogin='showLogin'}} + + {{#if group.messageable}} + {{d-button + action="messageGroup" + class="btn-primary group-message-button inline" + icon="envelope" + label="groups.message"}} + {{/if}} +
+ + + diff --git a/app/assets/javascripts/discourse/templates/components/user-card-user-contents.hbs b/app/assets/javascripts/discourse/templates/components/user-card-user-contents.hbs new file mode 100644 index 0000000000..b103563eb6 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/components/user-card-user-contents.hbs @@ -0,0 +1,172 @@ +
+ {{bound-avatar avatar "huge"}} + {{#if user.primary_group_name}} + {{avatar-flair + flairURL=user.primary_group_flair_url + flairBgColor=user.primary_group_flair_bg_color + flairColor=user.primary_group_flair_color + groupName=user.primary_group_name}} + {{/if}} + {{plugin-outlet name="user-card-avatar-flair" args=(hash user=user) tagName='div'}} +
+ +
+ +

+ {{if nameFirst user.name (format-username username)}} {{user-status user currentUser=currentUser}} +

+ {{plugin-outlet name="user-card-after-username" args=(hash user=user) tagName=''}} + + {{#unless nameFirst}} + {{#if user.name}} +

{{user.name}}

+ {{/if}} + {{else}} +

{{username}}

+ {{/unless}} + + {{#if user.title}} +

{{user.title}}

+ {{/if}} + + {{#if user.staged}} +

{{i18n 'user.staged'}}

+ {{/if}} + + {{plugin-outlet name="user-card-post-names" args=(hash user=user) tagName='div'}} +
+
+ +
    + {{#if user.can_send_private_message_to_user}} +
  • + {{d-button + class="btn-primary" + action=(action "composePrivateMessage" user post) + icon="envelope" + label="user.private_message"}} +
  • + {{/if}} + + {{#if showFilter}} +
  • + {{d-button + action=(action "togglePosts" user) + icon="filter" + translatedLabel=togglePostsLabel}} +
  • + {{/if}} + + {{#if hasUserFilters}} +
  • + {{d-button + action="cancelFilter" + icon="times" + label="topic.filters.cancel"}} +
  • + {{/if}} + + {{#if showDelete}} +
  • + {{d-button + class="btn-danger" + action=(action "deleteUser" user) + icon="exclamation-triangle" + label="admin.user.delete"}} +
  • + {{/if}} +
+{{plugin-outlet + name="user-card-additional-controls" + args=(hash user=user close=(action "close")) + tagName=""}} + +{{#if isSuspended}} +
+ {{d-icon "ban"}} + {{i18n 'user.suspended_notice' date=user.suspendedTillDate}}
+ {{i18n 'user.suspended_reason'}} {{user.suspend_reason}} +
+{{else}} + {{#if user.bio_cooked}}
{{text-overflow class="overflow" text=user.bio_excerpt}}
{{/if}} +{{/if}} + +{{#if user.card_badge}} + {{#link-to 'badges.show' user.card_badge class="card-badge" title=user.card_badge.name}} + {{icon-or-image user.card_badge.image title=user.card_badge.name}} + {{/link-to}} +{{/if}} + +{{#if hasLocationOrWebsite}} +
+ {{#if user.location}} + {{d-icon "map-marker"}} {{user.location}} + {{/if}} + + {{#if user.website_name}} + + {{d-icon "globe"}} + {{#if linkWebsite}} + {{user.website_name}} + {{else}} + {{user.website_name}} + {{/if}} + + {{/if}} + + {{plugin-outlet name="user-card-location-and-website" args=(hash user=user)}} +
+{{/if}} + +{{#if user}} + +{{/if}} + +{{#if publicUserFields}} +
+ {{#each publicUserFields as |uf|}} + {{#if uf.value}} +
+ {{uf.field.name}}: + {{uf.value}} +
+ {{/if}} + {{/each}} +
+{{/if}} + +{{#if showBadges}} +
+ {{#each user.featured_user_badges as |ub|}} + {{user-badge badge=ub.badge user=user}} + {{/each}} + {{#if showMoreBadges}} + {{#link-to 'user.badges' user class="btn more-user-badges"}} + {{i18n 'badges.more_badges' count=moreBadgesCount}} + {{/link-to}} + {{/if}} +
+{{/if}} diff --git a/app/assets/javascripts/discourse/templates/user-card.hbs b/app/assets/javascripts/discourse/templates/user-card.hbs index 6a0b2ade38..9136a63713 100644 --- a/app/assets/javascripts/discourse/templates/user-card.hbs +++ b/app/assets/javascripts/discourse/templates/user-card.hbs @@ -4,4 +4,5 @@ showUser="showUser" togglePosts="togglePosts" composePrivateMessage="composePrivateMessage" + createNewMessageViaParams="createNewMessageViaParams" deleteUser="deleteUser"}} diff --git a/app/assets/stylesheets/desktop/user-card.scss b/app/assets/stylesheets/desktop/user-card.scss index 2450fd71dc..c38ad51b85 100644 --- a/app/assets/stylesheets/desktop/user-card.scss +++ b/app/assets/stylesheets/desktop/user-card.scss @@ -198,6 +198,34 @@ $user_card_background: $secondary; margin-top: -53px; } + .group-card-avatar { + float: left; + margin-right: 10px; + margin-top: 0px; + .avatar-flair { + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + background-repeat: no-repeat; + background-position: center; + color: $primary; + i { + margin: auto; + font-size: $font-up-4; + } + } + } + .members { + a { + color: lighten($primary, 40%); + &:hover { + color: $primary; + } + } + } + p { margin: 0 0 5px 0; } From e570043af94a446e79a28ff425634166cdb01425 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Mon, 16 Apr 2018 16:58:05 -0700 Subject: [PATCH 031/127] make linter happy --- .../discourse/components/user-card-contents.js.es6 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index 990eeb0aa4..dc2f3342e4 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -4,7 +4,6 @@ import afterTransition from 'discourse/lib/after-transition'; import { default as computed } from 'ember-addons/ember-computed-decorators'; import DiscourseURL from 'discourse/lib/url'; import User from 'discourse/models/user'; -import Group from 'discourse/models/group'; import { userPath } from 'discourse/lib/url'; const clickOutsideEventName = "mousedown.outside-user-card"; @@ -36,12 +35,12 @@ export default Ember.Component.extend(CleansUp, { @computed('cardType') isUserShown(cardType) { - return cardType == 'user'; + return cardType === 'user'; }, @computed('cardType') isGroupShown(cardType) { - return cardType == 'group'; + return cardType === 'group'; }, _showUser(username, $target) { @@ -102,10 +101,10 @@ export default Ember.Component.extend(CleansUp, { const post = this.get('viewingTopic') && postId ? this.get('postStream').findLoadedPost(postId) : null; this.setProperties({ username, userLoading: username, cardTarget: target, post }); - if(userCardType == 'group') { + if(userCardType === 'group') { this._showGroup(username, $target); } - else if(userCardType == 'user') { + else if(userCardType === 'user') { this._showUser(username, $target); } @@ -149,7 +148,7 @@ export default Ember.Component.extend(CleansUp, { if (wantsNewWindow(e)) { return; } const $target = $(e.target); return this._show($target.text().replace(/^@/, ''), $target, 'group'); - }) + }); }, _positionCard(target) { From 828bfd9d27e87e3ab24f0b92292f804b1844a3fc Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 17 Apr 2018 10:08:21 +0800 Subject: [PATCH 032/127] Add specs for https://github.com/discourse/discourse/commit/c74c933996e2775ca7765a82916adc6ca7055226. --- .../guardian/category_guardian_spec.rb | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 spec/components/guardian/category_guardian_spec.rb diff --git a/spec/components/guardian/category_guardian_spec.rb b/spec/components/guardian/category_guardian_spec.rb new file mode 100644 index 0000000000..3dccbb9e07 --- /dev/null +++ b/spec/components/guardian/category_guardian_spec.rb @@ -0,0 +1,47 @@ +require 'rails_helper' + +RSpec.describe CategoryGuardian do + let(:admin) { Fabricate(:admin) } + let(:guardian) { Guardian.new(admin) } + let(:category) { Fabricate(:category) } + + describe '#cannot_delete_category_reason' do + describe 'when category is uncategorized' do + it 'should return the reason' do + category = Category.find(SiteSetting.uncategorized_category_id) + + expect(guardian.cannot_delete_category_reason(category)).to eq( + I18n.t('category.cannot_delete.uncategorized') + ) + end + end + + describe 'when category has subcategories' do + it 'should return the right reason' do + category.subcategories << Fabricate(:category) + + expect(guardian.cannot_delete_category_reason(category)).to eq( + I18n.t('category.cannot_delete.has_subcategories') + ) + end + end + + describe 'when category has topics' do + it 'should return the right reason' do + topic = Fabricate(:topic, + title: '', + category: category + ) + + category.reload + + expect(guardian.cannot_delete_category_reason(category)).to eq( + I18n.t('category.cannot_delete.topic_exists', + count: 1, + topic_link: "</a><script>alert(document.cookie);</script><a>" + ) + ) + end + end + end +end From 12e321da18d78c210731503cc86cb0140362ed97 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Mon, 16 Apr 2018 21:00:59 -0700 Subject: [PATCH 033/127] fix: display user card background image --- .../components/user-card-contents.js.es6 | 20 ++++++++++++++++++- .../components/user-card-user-contents.js.es6 | 19 ++---------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index dc2f3342e4..a703de698b 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -1,7 +1,8 @@ import { wantsNewWindow } from 'discourse/lib/intercept-click'; import CleansUp from 'discourse/mixins/cleans-up'; import afterTransition from 'discourse/lib/after-transition'; -import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { propertyNotEqual, setting } from 'discourse/lib/computed'; +import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import DiscourseURL from 'discourse/lib/url'; import User from 'discourse/models/user'; import { userPath } from 'discourse/lib/url'; @@ -16,6 +17,8 @@ const maxMembersToDisplay = 10; export default Ember.Component.extend(CleansUp, { elementId: 'user-card', classNameBindings: ['visible:show', 'showBadges', 'hasCardBadgeImage', 'user.card_background::no-bg'], + allowBackgrounds: setting('allow_profile_backgrounds'), + showBadges: setting('enable_badges'), postStream: Ember.computed.alias('topic.postStream'), viewingTopic: Ember.computed.match('currentPath', /^topic\./), @@ -43,6 +46,21 @@ export default Ember.Component.extend(CleansUp, { return cardType === 'group'; }, + @observes('user.card_background') + addBackground() { + if (!this.get('allowBackgrounds')) { return; } + + const $this = this.$(); + if (!$this) { return; } + + const url = this.get('user.card_background'); + const bg = Ember.isEmpty(url) ? '' : `url(${Discourse.getURLWithCDN(url)})`; + $this.css('background-image', bg); + }, + + @computed('user.card_badge.image') + hasCardBadgeImage: image => image && image.indexOf('fa-') !== 0, + _showUser(username, $target) { const args = { stats: false }; args.include_post_count_for = this.get('topic.id'); diff --git a/app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 index 7585c07e96..a4cbec5f28 100644 --- a/app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-user-contents.js.es6 @@ -1,4 +1,4 @@ -import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { default as computed } from 'ember-addons/ember-computed-decorators'; import { propertyNotEqual, setting } from 'discourse/lib/computed'; import { durationTiny } from 'discourse/lib/formatter'; import CanCheckEmails from 'discourse/mixins/can-check-emails'; @@ -6,13 +6,13 @@ import CanCheckEmails from 'discourse/mixins/can-check-emails'; export default Ember.Component.extend(CanCheckEmails, { allowBackgrounds: setting('allow_profile_backgrounds'), + showBadges: setting('enable_badges'), enoughPostsForFiltering: Ember.computed.gte('topicPostCount', 2), showFilter: Ember.computed.and('viewingTopic', 'postStream.hasNoFilters', 'enoughPostsForFiltering'), showName: propertyNotEqual('user.name', 'user.username'), hasUserFilters: Ember.computed.gt('postStream.userFilters.length', 0), isSuspended: Ember.computed.notEmpty('user.suspend_reason'), - showBadges: setting('enable_badges'), showMoreBadges: Ember.computed.gt('moreBadgesCount', 0), showDelete: Ember.computed.and("viewingAdmin", "showName", "user.canBeDeleted"), linkWebsite: Ember.computed.not('user.isBasic'), @@ -50,21 +50,6 @@ export default Ember.Component.extend(CanCheckEmails, { @computed('user.badge_count', 'user.featured_user_badges.length') moreBadgesCount: (badgeCount, badgeLength) => badgeCount - badgeLength, - @computed('user.card_badge.image') - hasCardBadgeImage: image => image && image.indexOf('fa-') !== 0, - - @observes('user.card_background') - addBackground() { - if (!this.get('allowBackgrounds')) { return; } - - const $this = this.$(); - if (!$this) { return; } - - const url = this.get('user.card_background'); - const bg = Ember.isEmpty(url) ? '' : `url(${Discourse.getURLWithCDN(url)})`; - $this.css('background-image', bg); - }, - @computed('user.time_read', 'user.recent_time_read') showRecentTimeRead(timeRead, recentTimeRead) { return timeRead !== recentTimeRead && recentTimeRead !== 0; From 1b9a38c5e2fd9105f8b77a0625bcecec9a825c27 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 17 Apr 2018 12:07:13 +0800 Subject: [PATCH 034/127] FIX: Incorrect formatter used when logstash formatter is enabled. --- config/initializers/{100-lograge.rb => 101-lograge.rb} | 4 +--- lib/discourse_logstash_logger.rb | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) rename config/initializers/{100-lograge.rb => 101-lograge.rb} (96%) diff --git a/config/initializers/100-lograge.rb b/config/initializers/101-lograge.rb similarity index 96% rename from config/initializers/100-lograge.rb rename to config/initializers/101-lograge.rb index 7b4b5e4e46..650a046039 100644 --- a/config/initializers/100-lograge.rb +++ b/config/initializers/101-lograge.rb @@ -33,9 +33,7 @@ if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || ENV[" { ip: ip, - username: username, - hostname: `hostname`, - pid: Process.pid + username: username } rescue => e Rails.logger.warn("Failed to append custom payload: #{e.message}\n#{e.backtrace.join("\n")}") diff --git a/lib/discourse_logstash_logger.rb b/lib/discourse_logstash_logger.rb index 369e7e3c05..32376b4960 100644 --- a/lib/discourse_logstash_logger.rb +++ b/lib/discourse_logstash_logger.rb @@ -10,6 +10,7 @@ class DiscourseLogstashLogger event['severity_name'] = event['severity'] event['severity'] = Object.const_get("Logger::Severity::#{event['severity']}") event['type'] = type + event['pid'] = Process.pid }, ) end From 2024ea5bd385b7aeefa8209b431f5bf73bfb38ad Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 17 Apr 2018 12:15:13 +0800 Subject: [PATCH 035/127] Make Eslint happy. --- .../javascripts/discourse/components/user-card-contents.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index a703de698b..da9f8dfdda 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -1,7 +1,7 @@ import { wantsNewWindow } from 'discourse/lib/intercept-click'; import CleansUp from 'discourse/mixins/cleans-up'; import afterTransition from 'discourse/lib/after-transition'; -import { propertyNotEqual, setting } from 'discourse/lib/computed'; +import { setting } from 'discourse/lib/computed'; import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import DiscourseURL from 'discourse/lib/url'; import User from 'discourse/models/user'; From 56cbfa2611bbc9dc0a34ba62936dd68a5a1d8eb5 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 17 Apr 2018 14:45:01 +1000 Subject: [PATCH 036/127] UX: Require an extra click to open topic draft When there is a topic draft in place instead of showing "+ New Topic" we will show "+ Open Draft", this allows topic drafts to be much less intrusive. Also, fixes draft handling of tags --- .../components/create-topic-button.js.es6 | 5 ++- .../discourse/components/d-navigation.js.es6 | 6 +++ .../discourse/controllers/composer.js.es6 | 26 ++++++------ .../controllers/navigation/categories.js.es6 | 9 +++- .../controllers/navigation/default.js.es6 | 5 +++ .../discourse/controllers/tags-show.js.es6 | 4 ++ .../discourse/models/composer.js.es6 | 6 ++- .../routes/build-category-route.js.es6 | 2 - .../discourse/routes/build-topic-route.js.es6 | 2 - .../discourse/routes/discourse.js.es6 | 41 ++++++++++++++----- .../routes/discovery-categories.js.es6 | 9 ++-- .../discourse/routes/discovery.js.es6 | 7 +++- .../discourse/routes/tags-show.js.es6 | 28 +++++++------ .../components/create-topic-button.hbs | 2 +- .../templates/components/d-navigation.hbs | 4 +- .../templates/navigation/categories.hbs | 1 + .../templates/navigation/category.hbs | 1 + .../templates/navigation/default.hbs | 1 + .../discourse/templates/tags/show.hbs | 1 + config/locales/client.en.yml | 1 + 20 files changed, 112 insertions(+), 49 deletions(-) diff --git a/app/assets/javascripts/discourse/components/create-topic-button.js.es6 b/app/assets/javascripts/discourse/components/create-topic-button.js.es6 index a792b83a29..c450bf7206 100644 --- a/app/assets/javascripts/discourse/components/create-topic-button.js.es6 +++ b/app/assets/javascripts/discourse/components/create-topic-button.js.es6 @@ -1 +1,4 @@ -export default Ember.Component.extend({ tagName: '' }); +export default Ember.Component.extend({ + tagName: '', + label: 'topic.create' +}); diff --git a/app/assets/javascripts/discourse/components/d-navigation.js.es6 b/app/assets/javascripts/discourse/components/d-navigation.js.es6 index ff1e6ef40a..313386c621 100644 --- a/app/assets/javascripts/discourse/components/d-navigation.js.es6 +++ b/app/assets/javascripts/discourse/components/d-navigation.js.es6 @@ -13,6 +13,12 @@ export default Ember.Component.extend({ return this.site.get('categoriesList'); }, + @computed('hasDraft') + createTopicLabel(hasDraft) + { + return hasDraft ? 'topic.open_draft': 'topic.create'; + }, + @computed('category.can_edit') showCategoryEdit: canEdit => canEdit, diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index b91e0d40da..bc64c7e3bf 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -41,7 +41,8 @@ function loadDraft(store, opts) { composerState: Composer.DRAFT, composerTime: draft.composerTime, typingTime: draft.typingTime, - whisper: draft.whisper + whisper: draft.whisper, + tags: draft.tags }); return composer; } @@ -730,25 +731,26 @@ export default Ember.Controller.extend({ destroyDraft() { const key = this.get('model.draftKey'); if (key) { + if (key === 'new_topic') { + this.send('clearTopicDraft'); + } Draft.clear(key, this.get('model.draftSequence')); } }, cancelComposer() { - const self = this; - - return new Ember.RSVP.Promise(function (resolve) { - if (self.get('model.hasMetaData') || self.get('model.replyDirty')) { + return new Ember.RSVP.Promise((resolve) => { + if (this.get('model.hasMetaData') || this.get('model.replyDirty')) { bootbox.dialog(I18n.t("post.abandon.confirm"), [ { label: I18n.t("post.abandon.no_value") }, { label: I18n.t("post.abandon.yes_value"), 'class': 'btn-danger', - callback(result) { + callback: (result) => { if (result) { - self.destroyDraft(); - self.get('model').clearState(); - self.close(); + this.destroyDraft(); + this.get('model').clearState(); + this.close(); resolve(); } } @@ -756,9 +758,9 @@ export default Ember.Controller.extend({ ]); } else { // it is possible there is some sort of crazy draft with no body ... just give up on it - self.destroyDraft(); - self.get('model').clearState(); - self.close(); + this.destroyDraft(); + this.get('model').clearState(); + this.close(); resolve(); } }); diff --git a/app/assets/javascripts/discourse/controllers/navigation/categories.js.es6 b/app/assets/javascripts/discourse/controllers/navigation/categories.js.es6 index 8276b094c1..5a8a32c5d3 100644 --- a/app/assets/javascripts/discourse/controllers/navigation/categories.js.es6 +++ b/app/assets/javascripts/discourse/controllers/navigation/categories.js.es6 @@ -1,3 +1,10 @@ import NavigationDefaultController from 'discourse/controllers/navigation/default'; -export default NavigationDefaultController.extend(); +export default NavigationDefaultController.extend({ + + discoveryCategories: Ember.inject.controller('discovery/categories'), + + draft: function() { + return this.get('discoveryCategories.model.draft'); + }.property('discoveryCategories.model', 'discoveryCategories.model.draft') +}); diff --git a/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 b/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 index 1fa8eedcc7..e56e4dd5f1 100644 --- a/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 +++ b/app/assets/javascripts/discourse/controllers/navigation/default.js.es6 @@ -1,4 +1,9 @@ + export default Ember.Controller.extend({ discovery: Ember.inject.controller(), discoveryTopics: Ember.inject.controller('discovery/topics'), + + draft: function() { + return this.get('discoveryTopics.model.draft'); + }.property('discoveryTopics.model', 'discoveryTopics.model.draft') }); diff --git a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 index dbf9251736..643d1095b2 100644 --- a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/tags-show.js.es6 @@ -61,6 +61,10 @@ export default Ember.Controller.extend(BulkTopicSelection, { categories: Ember.computed.alias('site.categoriesList'), + createTopicLabel: function() { + return this.get('list.draft') ? 'topic.open_draft' : 'topic.create'; + }.property('list', 'list.draft'), + @computed('canCreateTopic', 'category', 'canCreateTopicOnCategory') createTopicDisabled(canCreateTopic, category, canCreateTopicOnCategory) { return !canCreateTopic || (category && !canCreateTopicOnCategory); diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6 index 8e4c6b60ae..cefd6df7dd 100644 --- a/app/assets/javascripts/discourse/models/composer.js.es6 +++ b/app/assets/javascripts/discourse/models/composer.js.es6 @@ -518,7 +518,8 @@ const Composer = RestModel.extend({ targetUsernames: opts.usernames, composerTotalOpened: opts.composerTime, typingTime: opts.typingTime, - whisper: opts.whisper + whisper: opts.whisper, + tags: opts.tags }); if (opts.post) { @@ -836,7 +837,8 @@ const Composer = RestModel.extend({ metaData: this.get('metaData'), usernames: this.get('targetUsernames'), composerTime: this.get('composerTime'), - typingTime: this.get('typingTime') + typingTime: this.get('typingTime'), + tags: this.get('tags') }; this.set('draftStatus', I18n.t('composer.saving_draft_tip')); diff --git a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 index 4b29783e56..fb4f1b7fe8 100644 --- a/app/assets/javascripts/discourse/routes/build-category-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-category-route.js.es6 @@ -118,8 +118,6 @@ export default (filterArg, params) => { this.controllerFor('discovery/topics').setProperties(topicOpts); this.searchService.set('searchContext', category.get('searchContext')); this.set('topics', null); - - this.openTopicDraft(topics); }, renderTemplate() { diff --git a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 index a7c1dc00ee..198ab74e49 100644 --- a/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 +++ b/app/assets/javascripts/discourse/routes/build-topic-route.js.es6 @@ -106,8 +106,6 @@ export default function(filter, extras) { } } this.controllerFor('discovery/topics').setProperties(topicOpts); - - this.openTopicDraft(model); this.controllerFor('navigation/default').set('canCreateTopic', model.get('can_create_topic')); }, diff --git a/app/assets/javascripts/discourse/routes/discourse.js.es6 b/app/assets/javascripts/discourse/routes/discourse.js.es6 index 42595a0956..f726cb5d8f 100644 --- a/app/assets/javascripts/discourse/routes/discourse.js.es6 +++ b/app/assets/javascripts/discourse/routes/discourse.js.es6 @@ -52,7 +52,25 @@ const DiscourseRoute = Ember.Route.extend({ refreshTitle() { Ember.run.once(this, this._refreshTitleOnce); + }, + + clearTopicDraft() { + // perhaps re-delegate this to root controller in all cases? + // TODO also poison the store so it does not come back from the + // dead + if (this.get('controller.list.draft')) { + this.set('controller.list.draft', null); + } + + if (this.controllerFor("discovery/categories").get('model.draft')) { + this.controllerFor("discovery/categories").set('model.draft', null); + } + + if (this.controllerFor("discovery/topics").get('model.draft')) { + this.controllerFor("discovery/topics").set('model.draft', null); + } } + }, redirectIfLoginRequired() { @@ -63,17 +81,18 @@ const DiscourseRoute = Ember.Route.extend({ }, openTopicDraft(model){ - // If there's a draft, open the create topic composer - if (model.draft) { - const composer = this.controllerFor('composer'); - if (!composer.get('model.viewOpen')) { - composer.open({ - action: Composer.CREATE_TOPIC, - draft: model.draft, - draftKey: model.draft_key, - draftSequence: model.draft_sequence - }); - } + const composer = this.controllerFor('composer'); + + if (composer.get('model.action') === Composer.CREATE_TOPIC && + composer.get('model.draftKey') === model.draft_key) { + composer.set('model.composeState', Composer.OPEN); + } else { + composer.open({ + action: Composer.CREATE_TOPIC, + draft: model.draft, + draftKey: model.draft_key, + draftSequence: model.draft_sequence + }); } }, diff --git a/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 b/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 index 9753a776eb..a85c0743f6 100644 --- a/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery-categories.js.es6 @@ -89,8 +89,6 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, { showCategoryAdmin: model.get("can_create_category"), canCreateTopic: model.get("can_create_topic"), }); - - this.openTopicDraft(model); }, actions: { @@ -133,7 +131,12 @@ const DiscoveryCategoriesRoute = Discourse.Route.extend(OpenComposer, { }, createTopic() { - this.openComposer(this.controllerFor("discovery/categories")); + const model = this.controllerFor("discovery/categories").get('model'); + if (model.draft) { + this.openTopicDraft(model); + } else { + this.openComposer(this.controllerFor("discovery/categories")); + } }, didTransition() { diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index a77806d1a4..b8b05fe35e 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -45,7 +45,12 @@ export default Discourse.Route.extend(OpenComposer, { }, createTopic() { - this.openComposer(this.controllerFor("discovery/topics")); + const model = this.controllerFor("discovery/topics").get('model'); + if (model.draft) { + this.openTopicDraft(model); + } else { + this.openComposer(this.controllerFor("discovery/topics")); + } }, dismissReadTopics(dismissTopics) { diff --git a/app/assets/javascripts/discourse/routes/tags-show.js.es6 b/app/assets/javascripts/discourse/routes/tags-show.js.es6 index eec3a6b362..68e37e44ef 100644 --- a/app/assets/javascripts/discourse/routes/tags-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/tags-show.js.es6 @@ -129,18 +129,22 @@ export default Discourse.Route.extend({ var controller = this.controllerFor("tags.show"), self = this; - this.controllerFor('composer').open({ - categoryId: controller.get('category.id'), - action: Composer.CREATE_TOPIC, - draftKey: controller.get('list.draft_key'), - draftSequence: controller.get('list.draft_sequence') - }).then(function() { - // Pre-fill the tags input field - if (controller.get('model.id')) { - var c = self.controllerFor('composer').get('model'); - c.set('tags', _.flatten([controller.get('model.id')], controller.get('additionalTags'))); - } - }); + if (controller.get('list.draft')) { + this.openTopicDraft(controller.get('list')); + } else { + this.controllerFor('composer').open({ + categoryId: controller.get('category.id'), + action: Composer.CREATE_TOPIC, + draftKey: controller.get('list.draft_key'), + draftSequence: controller.get('list.draft_sequence') + }).then(function() { + // Pre-fill the tags input field + if (controller.get('model.id')) { + var c = self.controllerFor('composer').get('model'); + c.set('tags', _.flatten([controller.get('model.id')], controller.get('additionalTags'))); + } + }); + } }, didTransition() { diff --git a/app/assets/javascripts/discourse/templates/components/create-topic-button.hbs b/app/assets/javascripts/discourse/templates/components/create-topic-button.hbs index ee1172ffe2..e99673f9cb 100644 --- a/app/assets/javascripts/discourse/templates/components/create-topic-button.hbs +++ b/app/assets/javascripts/discourse/templates/components/create-topic-button.hbs @@ -5,5 +5,5 @@ action=action icon="plus" disabled=disabled - label="topic.create"}} + label=label}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/components/d-navigation.hbs b/app/assets/javascripts/discourse/templates/components/d-navigation.hbs index 6f8c71f524..2d1b956b61 100644 --- a/app/assets/javascripts/discourse/templates/components/d-navigation.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-navigation.hbs @@ -15,7 +15,9 @@ {{create-topic-button canCreateTopic=canCreateTopic action=createTopic - disabled=createTopicDisabled}} + disabled=createTopicDisabled + label=createTopicLabel +}} {{#if showCategoryEdit}} {{d-button diff --git a/app/assets/javascripts/discourse/templates/navigation/categories.hbs b/app/assets/javascripts/discourse/templates/navigation/categories.hbs index 30495333df..4dba68da5c 100644 --- a/app/assets/javascripts/discourse/templates/navigation/categories.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/categories.hbs @@ -5,5 +5,6 @@ createCategory=(route-action "createCategory") reorderCategories=(route-action "reorderCategories") canCreateTopic=canCreateTopic + hasDraft=draft createTopic=(route-action "createTopic")}} {{/d-section}} diff --git a/app/assets/javascripts/discourse/templates/navigation/category.hbs b/app/assets/javascripts/discourse/templates/navigation/category.hbs index 55c7e7ef84..9bffcd46dc 100644 --- a/app/assets/javascripts/discourse/templates/navigation/category.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/category.hbs @@ -18,6 +18,7 @@ canCreateTopic=canCreateTopic createTopic=(route-action "createTopic") createTopicDisabled=cannotCreateTopicOnCategory + hasDraft=draft editCategory=(route-action "editCategory" category)}} {{plugin-outlet name="category-navigation" args=(hash category=category)}} diff --git a/app/assets/javascripts/discourse/templates/navigation/default.hbs b/app/assets/javascripts/discourse/templates/navigation/default.hbs index b9d9854295..d1bf784c4f 100644 --- a/app/assets/javascripts/discourse/templates/navigation/default.hbs +++ b/app/assets/javascripts/discourse/templates/navigation/default.hbs @@ -2,5 +2,6 @@ {{d-navigation filterMode=filterMode canCreateTopic=canCreateTopic + hasDraft=draft createTopic=(route-action "createTopic")}} {{/d-section}} diff --git a/app/assets/javascripts/discourse/templates/tags/show.hbs b/app/assets/javascripts/discourse/templates/tags/show.hbs index 33b3508a8a..95865c66f3 100644 --- a/app/assets/javascripts/discourse/templates/tags/show.hbs +++ b/app/assets/javascripts/discourse/templates/tags/show.hbs @@ -19,6 +19,7 @@ {{create-topic-button canCreateTopic=canCreateTopic disabled=createTopicDisabled + label=createTopicLabel action=(route-action "createTopic")}} {{#if showTagFilter}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 69cd0f1a79..829650b154 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1600,6 +1600,7 @@ en: other: "{{count}} posts in topic" create: 'New Topic' create_long: 'Create a new Topic' + open_draft: "Open Draft" private_message: 'Start a message' archive_message: help: 'Move message to your archive' From 28fbee04df9aa084aad04bf235e45c2f35fce259 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 17 Apr 2018 14:23:10 +0800 Subject: [PATCH 037/127] Allow auto close site settings to be shadowed by global. --- config/site_settings.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/site_settings.yml b/config/site_settings.yml index 00c23780c3..80b326cde6 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -651,8 +651,12 @@ posting: default: false notify_about_queued_posts_after: default: 24 - auto_close_messages_post_count: 500 - auto_close_topics_post_count: 10000 + auto_close_messages_post_count: + default: 500 + shadowed_by_global: true + auto_close_topics_post_count: + default: 10000 + shadowed_by_global: true code_formatting_style: client: true type: enum From 45cfb61af1771c92d1405bae1853a4e6b19bb504 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 17 Apr 2018 12:34:38 +0530 Subject: [PATCH 038/127] FIX: sanitize click track links --- app/views/clicks/track.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/clicks/track.html.erb b/app/views/clicks/track.html.erb index c67a6f6a1d..b0b1de890b 100644 --- a/app/views/clicks/track.html.erb +++ b/app/views/clicks/track.html.erb @@ -1,4 +1,4 @@

<%= I18n.t("redirect_warning") %>

-

<%= link_to params[:url], params[:url] %>

+

<%= sanitize link_to params[:url], params[:url] %>

From 64a45b0980f89d4356f272dbf606db193a436bce Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 17 Apr 2018 15:56:55 +0800 Subject: [PATCH 039/127] FIX: Missing `Group#bio_raw` attribute for group owners. https://meta.discourse.org/t/group-description-does-not-load-in-editor-for-owners-who-are-not-staff/85345 --- app/serializers/basic_group_serializer.rb | 2 +- .../basic_group_serializer_spec.rb | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index 095324ca33..6283dfa12e 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -47,7 +47,7 @@ class BasicGroupSerializer < ApplicationSerializer end def include_bio_raw? - staff? + staff? || is_group_owner end def include_is_group_user? diff --git a/spec/serializers/basic_group_serializer_spec.rb b/spec/serializers/basic_group_serializer_spec.rb index 712f06fa7f..4d1801ff92 100644 --- a/spec/serializers/basic_group_serializer_spec.rb +++ b/spec/serializers/basic_group_serializer_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe BasicGroupSerializer do + let(:guardian) { Guardian.new } + let(:group) { Fabricate(:group) } subject { described_class.new(group, scope: Guardian.new, root: false) } describe '#display_name' do @@ -20,4 +22,22 @@ describe BasicGroupSerializer do end end end + + describe '#bio_raw' do + let(:group) { Fabricate(:group, bio_raw: 'testing') } + + let(:user) do + user = Fabricate(:user) + group.add_owner(user) + user + end + + let(:guardian) { Guardian.new(user) } + + describe 'group owner' do + it 'should include bio_raw' do + expect(subject.bio_raw).to eq('testing') + end + end + end end From 9980f18d8664ee0e9bb95d80f1af89dd1575b369 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 17 Apr 2018 18:05:51 +1000 Subject: [PATCH 040/127] FEATURE: track request queueing as early as possible --- lib/middleware/request_tracker.rb | 12 +++++++++++- spec/components/middleware/request_tracker_spec.rb | 9 ++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb index e7ffb8869a..0f2a126e66 100644 --- a/lib/middleware/request_tracker.rb +++ b/lib/middleware/request_tracker.rb @@ -129,7 +129,8 @@ class Middleware::RequestTracker is_background: !!(request.path =~ /^\/message-bus\// || request.path =~ /\/topics\/timings/), is_mobile: helper.is_mobile?, track_view: track_view, - timing: timing + timing: timing, + queue_seconds: env['REQUEST_QUEUE_SECONDS'] }.tap do |h| h[:user_agent] = env['HTTP_USER_AGENT'] if h[:is_crawler] end @@ -158,6 +159,15 @@ class Middleware::RequestTracker def call(env) result = nil log_request = true + + # doing this as early as possible so we have an + # accurate counter + if queue_start = env['HTTP_X_REQUEST_START'] + queue_start = queue_start.split("t=")[1].to_f + queue_time = (Time.now.to_f - queue_start) + env['REQUEST_QUEUE_SECONDS'] = queue_time + end + request = Rack::Request.new(env) if rate_limit(request) diff --git a/spec/components/middleware/request_tracker_spec.rb b/spec/components/middleware/request_tracker_spec.rb index 96262e78ed..a36a3f307b 100644 --- a/spec/components/middleware/request_tracker_spec.rb +++ b/spec/components/middleware/request_tracker_spec.rb @@ -265,8 +265,15 @@ describe Middleware::RequestTracker do # ensure pg is warmed up with the select 1 query User.where(id: -100).pluck(:id) + freeze_time + start = Time.now.to_f + + freeze_time 1.minute.from_now + tracker = Middleware::RequestTracker.new(app([200, {}, []], sql_calls: 2, redis_calls: 2)) - tracker.call(env) + tracker.call(env("HTTP_X_REQUEST_START" => "t=#{start}")) + + expect(@data[:queue_seconds]).to eq(60) timing = @data[:timing] expect(timing[:total_duration]).to be > 0 From 2b8307c6c37c82f00274c835e24cfb34ff9d1a90 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Tue, 17 Apr 2018 11:01:06 +0200 Subject: [PATCH 041/127] dashboard next: minor improvements * rename route to dashboard-next * better scaling of charts for large data sets * adjust trend position to avoid overlap * makes sure silenced/suspended is made on real users * correctly format data when only one data point * minor refactoring --- .../components/dashboard-mini-chart.js.es6 | 41 +++++++++++++++++-- .../components/dashboard-mini-table.js.es6 | 4 +- .../controllers/admin-dashboard-next.js.es6 | 2 +- .../admin/routes/admin-route-map.js.es6 | 2 +- .../components/dashboard-mini-chart.hbs | 2 +- .../common/admin/dashboard_next.scss | 3 +- app/models/report.rb | 4 +- config/routes.rb | 2 +- 8 files changed, 48 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 index 676917e8c2..6e53028cbf 100644 --- a/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-mini-chart.js.es6 @@ -20,14 +20,14 @@ export default Ember.Component.extend({ this._super(); loadScript("/javascripts/Chart.min.js").then(() => { - this.fetchReport.apply(this); + this.fetchReport(); }); }, didUpdateAttrs() { this._super(); - this.fetchReport.apply(this); + this.fetchReport(); }, @computed("dataSourceName") @@ -45,6 +45,8 @@ export default Ember.Component.extend({ }, fetchReport() { + if (this.get("isLoading")) return; + this.set("isLoading", true); let payload = {data: {}}; @@ -97,6 +99,23 @@ export default Ember.Component.extend({ }, _buildChartConfig(data) { + const values = this.get("chartData").map(d => d.y); + const max = Math.max(...values); + const min = Math.min(...values); + const stepSize = Math.max(...[Math.ceil((max - min)/5), 20]); + + const startDate = this.get("startDate") || moment(); + const endDate = this.get("endDate") || moment(); + const datesDifference = startDate.diff(endDate, "days"); + let unit = "day"; + if (datesDifference >= 366) { + unit = "quarter"; + } else if (datesDifference >= 61) { + unit = "month"; + } else if (datesDifference >= 14) { + unit = "week"; + } + return { type: "line", data, @@ -105,8 +124,22 @@ export default Ember.Component.extend({ responsive: true, layout: { padding: { left: 0, top: 0, right: 0, bottom: 0 } }, scales: { - yAxes: [{ display: true, ticks: { suggestedMin: 0 } }], - xAxes: [{ display: true }], + yAxes: [ + { + display: true, + ticks: { suggestedMin: 0, stepSize, suggestedMax: max + stepSize } + } + ], + xAxes: [ + { + display: true, + type: "time", + time: { + parser: "YYYY-MM-DD", + unit + } + } + ], } }, }; diff --git a/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 b/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 index 9b5f96e2f1..30f22642e7 100644 --- a/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 +++ b/app/assets/javascripts/admin/components/dashboard-mini-table.js.es6 @@ -17,7 +17,7 @@ export default Ember.Component.extend({ didInsertElement() { this._super(); - this.fetchReport.apply(this); + this.fetchReport(); }, @computed("dataSourceName") @@ -26,6 +26,8 @@ export default Ember.Component.extend({ }, fetchReport() { + if (this.get("isLoading")) return; + this.set("isLoading", true); ajax(this.get("dataSource")) diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 index 8094b04209..d030c25658 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-next.js.es6 @@ -41,6 +41,6 @@ export default Ember.Controller.extend({ }, _reportsForPeriodURL(period) { - return `/admin/dashboard_next?period=${period}`; + return `/admin/dashboard-next?period=${period}`; } }); diff --git a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 index 1a82a9d1f8..5b42f045e1 100644 --- a/app/assets/javascripts/admin/routes/admin-route-map.js.es6 +++ b/app/assets/javascripts/admin/routes/admin-route-map.js.es6 @@ -1,7 +1,7 @@ export default function() { this.route('admin', { resetNamespace: true }, function() { this.route('dashboard', { path: '/' }); - this.route('dashboard_next', { path: '/dashboard_next' }); + this.route('dashboardNext', { path: '/dashboard-next' }); this.route('adminSiteSettings', { path: '/site_settings', resetNamespace: true }, function() { this.route('adminSiteSettingsCategory', { path: 'category/:category_id', resetNamespace: true} ); }); diff --git a/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs b/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs index f2728c6d6d..7f12611bc9 100644 --- a/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs +++ b/app/assets/javascripts/admin/templates/components/dashboard-mini-chart.hbs @@ -10,7 +10,7 @@
{{#if oneDataPoint}} - {{chartData.lastObject.y}} + {{number chartData.lastObject.y}} {{else}}
diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss index 44a95a465b..19f2b1a248 100644 --- a/app/assets/stylesheets/common/admin/dashboard_next.scss +++ b/app/assets/stylesheets/common/admin/dashboard_next.scss @@ -91,6 +91,7 @@ .d-icon-question-circle { cursor: pointer; + margin-left: .25em; } .chart-title { @@ -139,7 +140,7 @@ .chart-trend { font-size: $font-up-5; position: absolute; - left: 2em; + right: 1.5em; top: .5em; display: flex; justify-content: space-between; diff --git a/app/models/report.rb b/app/models/report.rb index 521a74fb9a..12b98d8a37 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -255,10 +255,10 @@ class Report moderators = User.real.where(moderator: true).count report.data << { x: label.call("moderator"), y: moderators } if moderators > 0 - suspended = User.suspended.count + suspended = User.real.suspended.count report.data << { x: label.call("suspended"), y: suspended } if suspended > 0 - silenced = User.silenced.count + silenced = User.real.silenced.count report.data << { x: label.call("silenced"), y: silenced } if silenced > 0 end end diff --git a/config/routes.rb b/config/routes.rb index d64c0f28c4..53db9bcece 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -230,7 +230,7 @@ Discourse::Application.routes.draw do get "version_check" => "versions#show" - resources :dashboard_next, only: [:index] + get "dashboard-next" => "dashboard_next#index" resources :dashboard, only: [:index] do collection do From 2d9d77d390f136cf6fad806560f758a5f75afee7 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 17 Apr 2018 18:14:19 +0800 Subject: [PATCH 042/127] Fix the build. --- app/serializers/basic_group_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index 6283dfa12e..aec7f7cf85 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -47,7 +47,7 @@ class BasicGroupSerializer < ApplicationSerializer end def include_bio_raw? - staff? || is_group_owner + staff? || (include_is_group_owner? && is_group_owner) end def include_is_group_user? From 7c1eefddf909b82c316013bee4a52a9f4800f2c0 Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Tue, 17 Apr 2018 18:19:10 +0530 Subject: [PATCH 043/127] update Copyright year --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51b8314318..6e04e399e9 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ The original Discourse code contributors can be found in [**AUTHORS.MD**](docs/A ## Copyright / License -Copyright 2014 - 2017 Civilized Discourse Construction Kit, Inc. +Copyright 2014 - 2018 Civilized Discourse Construction Kit, Inc. Licensed under the GNU General Public License Version 2.0 (or later); you may not use this work except in compliance with the License. From ad4c25e00448f4dbf7abcfdc9978041d4c296d67 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Tue, 17 Apr 2018 21:06:40 +0800 Subject: [PATCH 044/127] PERF: Only save site setting if values have been changed. --- lib/site_settings/db_provider.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/site_settings/db_provider.rb b/lib/site_settings/db_provider.rb index 8aef485c93..3ad32164e9 100644 --- a/lib/site_settings/db_provider.rb +++ b/lib/site_settings/db_provider.rb @@ -37,7 +37,7 @@ class SiteSettings::DbProvider model.data_type = data_type # save! used to ensure after_commit is called - model.save! + model.save! if model.changed? true end From 2585ada5ca368d88a4d11ab9a8f51d74961f8ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Tue, 17 Apr 2018 17:08:12 +0200 Subject: [PATCH 045/127] FIX: don't allow spaces in 'reply_by_email_address' site setting --- lib/validators/reply_by_email_address_validator.rb | 13 +++++++++---- .../reply_by_email_address_validator_spec.rb | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/validators/reply_by_email_address_validator.rb b/lib/validators/reply_by_email_address_validator.rb index ff668bf858..a9110e0f85 100644 --- a/lib/validators/reply_by_email_address_validator.rb +++ b/lib/validators/reply_by_email_address_validator.rb @@ -4,14 +4,19 @@ class ReplyByEmailAddressValidator end def valid_value?(val) + val&.strip! + return true if val.blank? - return false if val["@"].nil? + return false if !val.include?("@") + + value = val.dup if SiteSetting.find_related_post_with_key - !!val["%{reply_key}"] && val.sub(/\+?%{reply_key}/, "") != SiteSetting.notification_email - else - val != SiteSetting.notification_email + return false if !value.include?("%{reply_key}") + value.sub!(/\+?%{reply_key}/, "") end + + value != SiteSetting.notification_email && !value.include?(" ") end def error_message diff --git a/spec/components/validators/reply_by_email_address_validator_spec.rb b/spec/components/validators/reply_by_email_address_validator_spec.rb index cf7a889a4a..c64a4a62a5 100644 --- a/spec/components/validators/reply_by_email_address_validator_spec.rb +++ b/spec/components/validators/reply_by_email_address_validator_spec.rb @@ -12,6 +12,7 @@ describe ReplyByEmailAddressValidator do it "returns false if value is not an email address" do expect(validator.valid_value?('WAT%{reply_key}.com')).to eq(false) + expect(validator.valid_value?('word +%{reply_key}@example.com')).to eq(false) end it "returns false if value does not contain '%{reply_key}'" do From 059f1d8df4bcbf90a6025c465258243682cab9a4 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 17 Apr 2018 09:27:48 -0700 Subject: [PATCH 046/127] minor tweaks for css group card image --- app/assets/stylesheets/desktop/user-card.scss | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/desktop/user-card.scss b/app/assets/stylesheets/desktop/user-card.scss index c38ad51b85..ef4f05868b 100644 --- a/app/assets/stylesheets/desktop/user-card.scss +++ b/app/assets/stylesheets/desktop/user-card.scss @@ -201,22 +201,24 @@ $user_card_background: $secondary; .group-card-avatar { float: left; margin-right: 10px; - margin-top: 0px; + margin-top: -5px; + $size: 50px; .avatar-flair { - width: 40px; - height: 40px; - border-radius: 50%; + width: $size; + height: $size; display: flex; align-items: center; background-repeat: no-repeat; background-position: center; + background-size: $size; color: $primary; i { margin: auto; - font-size: $font-up-4; + font-size: $size !important; } } } + .members { a { color: lighten($primary, 40%); From b87fa6d7495d28a21d2445f44d8ad414b900a416 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Tue, 17 Apr 2018 12:39:21 -0400 Subject: [PATCH 047/127] FIX: blacklisted crawlers could get through by omitting the accept header --- lib/middleware/request_tracker.rb | 1 - spec/components/middleware/request_tracker_spec.rb | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb index 0f2a126e66..f98e328c1f 100644 --- a/lib/middleware/request_tracker.rb +++ b/lib/middleware/request_tracker.rb @@ -289,7 +289,6 @@ class Middleware::RequestTracker def block_crawler(request) request.get? && !request.xhr? && - request.env['HTTP_ACCEPT'] =~ /text\/html/ && !request.path.ends_with?('robots.txt') && CrawlerDetection.is_blocked_crawler?(request.env['HTTP_USER_AGENT']) end diff --git a/spec/components/middleware/request_tracker_spec.rb b/spec/components/middleware/request_tracker_spec.rb index a36a3f307b..d93e4085db 100644 --- a/spec/components/middleware/request_tracker_spec.rb +++ b/spec/components/middleware/request_tracker_spec.rb @@ -330,9 +330,9 @@ describe Middleware::RequestTracker do }.to_not change { ApplicationRequest.count } end - it "allows json requests" do + it "blocks json requests" do SiteSetting.blacklisted_crawler_user_agents = 'Googlebot' - expect_success_response(*middleware.call(env( + expect_blocked_response(*middleware.call(env( 'HTTP_USER_AGENT' => 'Googlebot/2.1 (+http://www.google.com/bot.html)', 'HTTP_ACCEPT' => 'application/json' ))) From 99138b171a71bcab3cbd0e957e43e70cf314f045 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 17 Apr 2018 10:30:34 -0700 Subject: [PATCH 048/127] Fix: trigger group card on a post's primary group click --- .../discourse/components/user-card-contents.js.es6 | 9 ++++++++- .../javascripts/discourse/widgets/poster-name.js.es6 | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index da9f8dfdda..61d34e36a6 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -11,6 +11,7 @@ const clickOutsideEventName = "mousedown.outside-user-card"; const clickDataExpand = "click.discourse-user-card"; const clickMention = "click.discourse-user-mention"; const groupClickMention = "click.discourse-group-mention"; +const groupClickDataExpand = "click.discourse-group-card" const maxMembersToDisplay = 10; @@ -162,6 +163,12 @@ export default Ember.Component.extend(CleansUp, { return this._show($target.text().replace(/^@/, ''), $target, 'user'); }); + $('#main-outlet').on(groupClickDataExpand, '[data-group-card]', (e) => { + if (wantsNewWindow(e)) { return; } + const $target = $(e.currentTarget); + return this._show($target.data('group-card'), $target, 'group'); + }); + $('#main-outlet').on(groupClickMention, 'a.mention-group', (e) => { if (wantsNewWindow(e)) { return; } const $target = $(e.target); @@ -247,7 +254,7 @@ export default Ember.Component.extend(CleansUp, { willDestroyElement() { this._super(); $('html').off(clickOutsideEventName); - $('#main').off(clickDataExpand).off(clickMention).off(groupClickMention); + $('#main').off(clickDataExpand).off(clickMention).off(groupClickMention).off(groupClickDataExpand); }, actions: { diff --git a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 index 5a93ab9fd1..3fc5c0ee2a 100644 --- a/app/assets/javascripts/discourse/widgets/poster-name.js.es6 +++ b/app/assets/javascripts/discourse/widgets/poster-name.js.es6 @@ -14,7 +14,7 @@ createWidget('poster-name-title', { let titleContents = attrs.title; if (attrs.primaryGroupName) { const href = Discourse.getURL(`/groups/${attrs.primaryGroupName}`); - titleContents = h('a.user-group', { className: attrs.extraClasses, attributes: { href } }, attrs.title); + titleContents = h('a.user-group', { className: attrs.extraClasses, attributes: { href, 'data-group-card': attrs.primaryGroupName } }, attrs.title); } return titleContents; } From 3e2ba081d7252e10ea9fbce590bc72d663b15f55 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 17 Apr 2018 10:45:46 -0700 Subject: [PATCH 049/127] fix linter --- .../javascripts/discourse/components/user-card-contents.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 index 61d34e36a6..c8c869e160 100644 --- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6 @@ -11,7 +11,7 @@ const clickOutsideEventName = "mousedown.outside-user-card"; const clickDataExpand = "click.discourse-user-card"; const clickMention = "click.discourse-user-mention"; const groupClickMention = "click.discourse-group-mention"; -const groupClickDataExpand = "click.discourse-group-card" +const groupClickDataExpand = "click.discourse-group-card"; const maxMembersToDisplay = 10; From 8fc1289172142fdca17d8cfcabc0901928b4d668 Mon Sep 17 00:00:00 2001 From: Neil Lalonde Date: Tue, 17 Apr 2018 15:08:13 -0400 Subject: [PATCH 050/127] move topic excerpt code to one method to DRY it up and for extensibility --- app/models/post.rb | 4 ++++ lib/post_creator.rb | 2 +- lib/post_revisor.rb | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index 7d8da4859e..4eb309c7c6 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -406,6 +406,10 @@ class Post < ActiveRecord::Base Post.excerpt(cooked, maxlength, options) end + def excerpt_for_topic + Post.excerpt(cooked, 220, strip_links: true) + end + def is_first_post? post_number.blank? ? topic.try(:highest_post_number) == 0 : diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 522dc288da..1773adecfa 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -397,7 +397,7 @@ class PostCreator attrs[:last_posted_at] = @post.created_at attrs[:last_post_user_id] = @post.user_id attrs[:word_count] = (@topic.word_count || 0) + @post.word_count - attrs[:excerpt] = @post.excerpt(220, strip_links: true) if new_topic? + attrs[:excerpt] = @post.excerpt_for_topic if new_topic? attrs[:bumped_at] = @post.created_at unless @post.no_bump attrs[:updated_at] = 'now()' @topic.update_columns(attrs) diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index 3a1a55d3f1..efa718787d 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -533,7 +533,7 @@ class PostRevisor end def update_topic_excerpt - excerpt = @post.excerpt(220, strip_links: true) + excerpt = @post.excerpt_for_topic @topic.update_column(:excerpt, excerpt) if @topic.archetype == "banner" ApplicationController.banner_json_cache.clear From ce9fa724b938d388c9ec0757553d11147704144d Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Tue, 17 Apr 2018 13:48:08 -0700 Subject: [PATCH 051/127] Feature: ctrl click on links allowed in preview --- app/assets/javascripts/discourse/components/d-editor.js.es6 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6 index c2811e5e5a..498dca8b39 100644 --- a/app/assets/javascripts/discourse/components/d-editor.js.es6 +++ b/app/assets/javascripts/discourse/components/d-editor.js.es6 @@ -12,6 +12,7 @@ import { siteDir } from 'discourse/lib/text-direction'; import { determinePostReplaceSelection, clipboardData } from 'discourse/lib/utilities'; import toMarkdown from 'discourse/lib/to-markdown'; import deprecated from 'discourse-common/lib/deprecated'; +import { wantsNewWindow } from 'discourse/lib/intercept-click'; // Our head can be a static string or a function that returns a string // based on input (like for numbered lists). @@ -258,6 +259,7 @@ export default Ember.Component.extend({ // disable clicking on links in the preview this.$('.d-editor-preview').on('click.preview', e => { + if (wantsNewWindow(e)) { return; } if ($(e.target).is("a")) { e.preventDefault(); return false; From ec7448bd1b84f3dc8ab1c00dc846350128c2af0a Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Tue, 17 Apr 2018 17:12:45 -0700 Subject: [PATCH 052/127] shorten copy from "is replying..." to "replying..." --- plugins/discourse-presence/config/locales/client.en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/discourse-presence/config/locales/client.en.yml b/plugins/discourse-presence/config/locales/client.en.yml index ea1af30f81..b4dc7cdd86 100644 --- a/plugins/discourse-presence/config/locales/client.en.yml +++ b/plugins/discourse-presence/config/locales/client.en.yml @@ -4,5 +4,5 @@ en: replying: "replying" editing: "editing" replying_to_topic: - one: "is replying" - other: "are replying" + one: "replying" + other: "replying" From 3566c6f02b2d1f3e84ff5cc565a8cbc676124a6b Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 18 Apr 2018 00:14:43 +0530 Subject: [PATCH 053/127] FIX: strip emoji string from slug --- lib/slug.rb | 2 ++ spec/components/slug_spec.rb | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/lib/slug.rb b/lib/slug.rb index dc95095b7b..669006c2e8 100644 --- a/lib/slug.rb +++ b/lib/slug.rb @@ -6,6 +6,8 @@ module Slug MAX_LENGTH = 255 def self.for(string, default = 'topic', max_length = MAX_LENGTH) + string = string.gsub(/:([\w\-+]+(?::t\d)?):/, '') if string.present? # strip emoji strings + slug = case (SiteSetting.slug_generation_method || :ascii).to_sym when :ascii then self.ascii_generator(string) diff --git a/spec/components/slug_spec.rb b/spec/components/slug_spec.rb index ad436f6b52..991ff235e1 100644 --- a/spec/components/slug_spec.rb +++ b/spec/components/slug_spec.rb @@ -24,6 +24,10 @@ describe Slug do expect(Slug.for("o_o_o")).to eq("o-o-o") end + it 'strips emoji string' do + expect(Slug.for(":smile: To Infinity and beyond! 🚀 :woman:t5:")).to eq("to-infinity-and-beyond") + end + context 'ascii generator' do before { SiteSetting.slug_generation_method = 'ascii' } From 7bf9650e965119aab017a08a1ab8ad419bbe5f4a Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Wed, 18 Apr 2018 14:21:51 +0800 Subject: [PATCH 054/127] Remove comment that is no longer accurate. --- app/jobs/scheduled/enqueue_digest_emails.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/jobs/scheduled/enqueue_digest_emails.rb b/app/jobs/scheduled/enqueue_digest_emails.rb index 23db70d4e5..98a7887894 100644 --- a/app/jobs/scheduled/enqueue_digest_emails.rb +++ b/app/jobs/scheduled/enqueue_digest_emails.rb @@ -1,6 +1,5 @@ module Jobs - # A daily job that will enqueue digest emails to be sent to users class EnqueueDigestEmails < Jobs::Scheduled every 30.minutes From 59cd7894d91cdda98f3897d231e4889b11517fc8 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 18 Apr 2018 16:58:40 +1000 Subject: [PATCH 055/127] FEATURE: if site is under extreme load show anon view If a particular path is being hit extremely hard by logged on users, revert to anonymous cached view. This will only come into effect if 3 requests queue for longer than 2 seconds on a *single* path. This can happen if a URL is shared with the entire forum base and everyone is logged on --- .../discourse/components/global-notice.js.es6 | 5 ++ config/discourse_defaults.conf | 8 +++ config/locales/client.en.yml | 2 + lib/middleware/anonymous_cache.rb | 64 ++++++++++++++++--- lib/rate_limiter.rb | 7 +- .../middleware/anonymous_cache_spec.rb | 49 ++++++++++++++ 6 files changed, 125 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/discourse/components/global-notice.js.es6 b/app/assets/javascripts/discourse/components/global-notice.js.es6 index 06f7e68a16..9ea7f4104b 100644 --- a/app/assets/javascripts/discourse/components/global-notice.js.es6 +++ b/app/assets/javascripts/discourse/components/global-notice.js.es6 @@ -9,6 +9,11 @@ export default Ember.Component.extend(bufferedRender({ buildBuffer(buffer) { let notices = []; + if ($.cookie("dosp") === "1") { + $.cookie("dosp", null, { path: '/' }); + notices.push([I18n.t("forced_anonymous"), 'forced-anonymous']); + } + if (this.session.get('safe_mode')) { notices.push([I18n.t("safe_mode.enabled"), 'safe-mode']); } diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf index 6e3cf55ed0..9c3cf3c38a 100644 --- a/config/discourse_defaults.conf +++ b/config/discourse_defaults.conf @@ -194,3 +194,11 @@ max_reqs_per_ip_mode = none # bypass rate limiting any IP resolved as a private IP max_reqs_rate_limit_on_private = false + +# logged in DoS protection + +# protection will only trigger for requests that queue longer than this amount +force_anonymous_min_queue_seconds = 2 +# only trigger anon if we see more than N requests for this path in last 10 seconds +force_anonymous_min_per_10_seconds = 3 + diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 829650b154..5498ba32e7 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2690,6 +2690,8 @@ en: custom_message_template_forum: "Hey, you should join this forum!" custom_message_template_topic: "Hey, I thought you might enjoy this topic!" + forced_anonymous: "Site is under heavy load, we are temporarily presenting you with a cached anonymous view" + safe_mode: enabled: "Safe mode is enabled, to exit safe mode close this browser window" diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb index ad5f0eeff2..217f294a85 100644 --- a/lib/middleware/anonymous_cache.rb +++ b/lib/middleware/anonymous_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_dependency "mobile_detection" require_dependency "crawler_detection" require_dependency "guardian" @@ -10,9 +12,9 @@ module Middleware end class Helper - USER_AGENT = "HTTP_USER_AGENT".freeze - RACK_SESSION = "rack.session".freeze - ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING".freeze + USER_AGENT = "HTTP_USER_AGENT" + RACK_SESSION = "rack.session" + ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING" def initialize(env) @env = env @@ -86,7 +88,40 @@ module Middleware def no_cache_bypass request = Rack::Request.new(@env) - request.cookies['_bypass_cache'].nil? + request.cookies['_bypass_cache'].nil? && + request[Auth::DefaultCurrentUserProvider::API_KEY].nil? && + @env[Auth::DefaultCurrentUserProvider::USER_API_KEY].nil? + end + + def force_anonymous! + @env[Auth::DefaultCurrentUserProvider::USER_API_KEY] = nil + @env['HTTP_COOKIE'] = nil + @env['rack.request.cookie.hash'] = {} + @env['rack.request.cookie.string'] = '' + @env['_bypass_cache'] = nil + request = Rack::Request.new(@env) + request.delete_param('api_username') + request.delete_param('api_key') + end + + def check_logged_in_rate_limit! + limiter = RateLimiter.new( + nil, + "logged_in_anon_cache_#{@env["HOST"]}/#{@env["REQUEST_URI"]}", + GlobalSetting.force_anonymous_min_per_10_seconds, + 10 + ) + !limiter.performed!(raise_error: false) + end + + def should_force_anonymous? + if queue_time = @env['REQUEST_QUEUE_SECONDS'] + if queue_time > GlobalSetting.force_anonymous_min_queue_seconds && get? + return check_logged_in_rate_limit! + end + end + + false end def cacheable? @@ -142,13 +177,26 @@ module Middleware def call(env) helper = Helper.new(env) + force_anon = false - if helper.cacheable? - helper.cached || helper.cache(@app.call(env)) - else - @app.call(env) + if helper.should_force_anonymous? + force_anon = env["DISCOURSE_FORCE_ANON"] = true + helper.force_anonymous! end + result = + if helper.cacheable? + helper.cached || helper.cache(@app.call(env)) + else + @app.call(env) + end + + if force_anon + result[1]["Set-Cookie"] = "dosp=1" + end + + result + end end diff --git a/lib/rate_limiter.rb b/lib/rate_limiter.rb index 8645bb003d..2582b9587a 100644 --- a/lib/rate_limiter.rb +++ b/lib/rate_limiter.rb @@ -80,14 +80,17 @@ class RateLimiter PERFORM_LUA_SHA = Digest::SHA1.hexdigest(PERFORM_LUA) end - def performed! + def performed!(raise_error: true) return if rate_unlimited? now = Time.now.to_i if ((max || 0) <= 0) || (eval_lua(PERFORM_LUA, PERFORM_LUA_SHA, [prefixed_key], [now, @secs, @max]) == 0) - raise RateLimiter::LimitExceeded.new(seconds_to_wait, @type) + raise RateLimiter::LimitExceeded.new(seconds_to_wait, @type) if raise_error + false + else + true end rescue Redis::CommandError => e if e.message =~ /READONLY/ diff --git a/spec/components/middleware/anonymous_cache_spec.rb b/spec/components/middleware/anonymous_cache_spec.rb index 50d608c870..800f4d825a 100644 --- a/spec/components/middleware/anonymous_cache_spec.rb +++ b/spec/components/middleware/anonymous_cache_spec.rb @@ -45,6 +45,55 @@ describe Middleware::AnonymousCache::Helper do end end + context 'force_anonymous!' do + before do + RateLimiter.enable + end + + after do + RateLimiter.disable + end + + it 'will revert to anonymous once we reach the limit' do + + RateLimiter.clear_all! + + is_anon = false + + app = Middleware::AnonymousCache.new( + lambda do |env| + is_anon = env["HTTP_COOKIE"].nil? + [200, {}, ["ok"]] + end + ) + + global_setting :force_anonymous_min_per_10_seconds, 2 + global_setting :force_anonymous_min_queue_seconds, 1 + + env = { + "HTTP_COOKIE" => "_t=#{SecureRandom.hex}", + "HOST" => "site.com", + "REQUEST_METHOD" => "GET", + "REQUEST_URI" => "/somewhere/rainbow", + "REQUEST_QUEUE_SECONDS" => 2.1, + "rack.input" => StringIO.new + } + + app.call(env) + expect(is_anon).to eq(false) + + app.call(env) + expect(is_anon).to eq(false) + + app.call(env) + expect(is_anon).to eq(true) + + _status, headers, _body = app.call(env) + expect(is_anon).to eq(true) + expect(headers['Set-Cookie']).to eq('dosp=1') + end + end + context "cached" do let!(:helper) do new_helper("ANON_CACHE_DURATION" => 10) From 1b9647d124c8c3aeec96ffb6e975799fc7e23112 Mon Sep 17 00:00:00 2001 From: Jeff Atwood Date: Wed, 18 Apr 2018 01:04:14 -0700 Subject: [PATCH 056/127] minor copyedit --- config/locales/client.en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 5498ba32e7..8435828599 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2690,7 +2690,7 @@ en: custom_message_template_forum: "Hey, you should join this forum!" custom_message_template_topic: "Hey, I thought you might enjoy this topic!" - forced_anonymous: "Site is under heavy load, we are temporarily presenting you with a cached anonymous view" + forced_anonymous: "Due to extreme load, this topic is temporarily being shown to everyone as a logged out user would see it." safe_mode: enabled: "Safe mode is enabled, to exit safe mode close this browser window" From c61ce6641161c7c506b30c95ceea757eb8b57f0e Mon Sep 17 00:00:00 2001 From: Arpit Jalan Date: Wed, 18 Apr 2018 13:37:54 +0530 Subject: [PATCH 057/127] fix the build --- spec/components/pretty_text_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 7496f32801..702b4582d7 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -35,7 +35,7 @@ describe PrettyText do