From c490da31457e5200306c128f47531c713a35ed56 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 18 Jun 2021 10:25:01 -0700 Subject: [PATCH 001/556] UX: Add wizard message component to submissions page --- .../discourse/controllers/admin-wizards-submissions.js.es6 | 6 ++++++ .../discourse/templates/admin-wizards-submissions.hbs | 5 +++++ config/locales/client.en.yml | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 new file mode 100644 index 00000000..b5b9365a --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 @@ -0,0 +1,6 @@ +import Controller from "@ember/controller"; + +export default Controller.extend({ + messageKey: "select", + documentationUrl: "https://thepavilion.io/t/2818", +}); diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs index d843485a..5a660790 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs @@ -8,6 +8,11 @@ )}} +{{wizard-message + key=messageKey + url=documentationUrl + component="submissions"}} +
{{outlet}}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 43b86698..fa3a6c5d 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -91,6 +91,9 @@ en: destroying: Destroying wizards... import_complete: Import complete destroy_complete: Destruction complete + submissions: + select: "Select a wizard to see its submissions" + documentation: "Check out the submissions documentation" editor: show: "Show" From 10c7c8bcc09721ae917dba6ef9f97524449da8de Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 18 Jun 2021 11:25:51 -0700 Subject: [PATCH 002/556] UX: Change info message when selecting a wizard in the submissions pane --- .../admin-wizards-submissions.js.es6 | 30 ++++++++++++++++++- .../templates/admin-wizards-submissions.hbs | 3 +- config/locales/client.en.yml | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 index b5b9365a..7388a8d6 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions.js.es6 @@ -1,6 +1,34 @@ import Controller from "@ember/controller"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; export default Controller.extend({ - messageKey: "select", documentationUrl: "https://thepavilion.io/t/2818", + + @discourseComputed("wizardId") + wizardName(wizardId) { + let currentWizard = this.wizardList.find( + (wizard) => wizard.id === wizardId + ); + if (currentWizard) { + return currentWizard.name; + } + }, + + @discourseComputed("wizardName") + messageOpts(wizardName) { + return { + wizardName, + }; + }, + + @discourseComputed("wizardId") + messageKey(wizardId) { + let key = "select"; + + if (wizardId) { + key = "viewing"; + } + + return key; + }, }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs index 5a660790..07dd1682 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs @@ -1,4 +1,4 @@ -
+
{{combo-box value=wizardId content=wizardList @@ -10,6 +10,7 @@ {{wizard-message key=messageKey + opts=messageOpts url=documentationUrl component="submissions"}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index fa3a6c5d..31f10924 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -93,6 +93,7 @@ en: destroy_complete: Destruction complete submissions: select: "Select a wizard to see its submissions" + viewing: "You're viewing the logs of the %{wizardName}. Click 'Download' on the right to download them." documentation: "Check out the submissions documentation" editor: From dd8513a56390f5b254441783fccf23a72c748575 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 18 Jun 2021 11:47:24 -0700 Subject: [PATCH 003/556] UX: Add wizard-message component to logs pane --- .../discourse/controllers/admin-wizards-logs.js.es6 | 2 ++ .../javascripts/discourse/templates/admin-wizards-logs.hbs | 6 ++++++ config/locales/client.en.yml | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 index 9559b01b..f45013d7 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 @@ -9,6 +9,8 @@ export default Controller.extend({ page: 0, canLoadMore: true, logs: [], + documentationUrl: "https://thepavilion.io/t/2818", + messageKey: "viewing", loadLogs() { if (!this.canLoadMore) { diff --git a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs index 18fd3fdb..33402cce 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs @@ -8,6 +8,12 @@ class="refresh"}}
+{{wizard-message + key=messageKey + opts=messageOpts + url=documentationUrl + component="logs"}} + {{#load-more selector=".log-list tr" action=(action "loadMore") class="wizard-logs"}} {{#if noResults}}

{{i18n "search.no_results"}}

diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 31f10924..569f252f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -95,6 +95,10 @@ en: select: "Select a wizard to see its submissions" viewing: "You're viewing the logs of the %{wizardName}. Click 'Download' on the right to download them." documentation: "Check out the submissions documentation" + logs: + viewing: "View recent logs for wizards on the forum" + documentation: "Check out the logs documentation" + editor: show: "Show" From 34fee3729cb4e5c62a94e5875436eabee7099777 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 14 Jul 2021 14:04:19 +0800 Subject: [PATCH 004/556] Add pagination to submissions --- .../admin-wizards-submissions-show.js.es6 | 28 ++++++++++++ .../discourse/models/custom-wizard.js.es6 | 44 ++++++++++++++++++- .../admin-wizards-submissions-show.js.es6 | 38 +++------------- .../admin-wizards-submissions-show.hbs | 44 +++++++++++-------- .../custom_wizard/admin/submissions.rb | 12 +++-- lib/custom_wizard/submission.rb | 30 +++++++++---- lib/custom_wizard/wizard.rb | 2 +- .../custom_wizard/submission_serializer.rb | 5 +-- .../custom_wizard/submission_spec.rb | 15 +++++-- 9 files changed, 146 insertions(+), 72 deletions(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index f5f9926d..bc38648d 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -1,6 +1,34 @@ import Controller from "@ember/controller"; import { fmt } from "discourse/lib/computed"; +import { empty } from '@ember/object/computed'; +import CustomWizard from "../models/custom-wizard"; export default Controller.extend({ downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"), + noResults: empty('submissions'), + page: 0, + total: 0, + + loadMoreSubmissions() { + const page = this.get('page'); + const wizardId = this.get('wizard.id'); + + this.set('loadingMore', true); + CustomWizard.submissions(wizardId, page).then(result => { + if (result.submissions) { + this.get('submissions').pushObjects(result.submissions); + } + }).finally(() => { + this.set('loadingMore', false); + }); + }, + + actions: { + loadMore() { + if (!this.loadingMore && (this.submissions.length < this.total)) { + this.set('page', this.get('page') + 1); + this.loadMoreSubmissions(); + } + } + } }); diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index e6a8408d..e4b0a530 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -211,9 +211,51 @@ CustomWizard.reopenClass({ .catch(popupAjaxError); }, - submissions(wizardId) { + submissions(wizardId, page = null) { + let data = {}; + + if (page) { + data.page = page; + } + return ajax(`/admin/wizards/submissions/${wizardId}`, { type: "GET", + data + }).then(result => { + if (result.wizard) { + let fields = ["username"]; + let submissions = []; + let wizard = result.wizard; + let total = result.total; + + result.submissions.forEach((s) => { + let submission = { + username: s.username, + }; + + Object.keys(s.fields).forEach((f) => { + if (fields.indexOf(f) < 0) { + fields.push(f); + } + + if (fields.includes(f)) { + submission[f] = s.fields[f]; + } + }); + + submission['submitted_at'] = s.submitted_at; + submissions.push(submission); + }); + + fields.push("submitted_at"); + + return { + wizard, + fields, + submissions, + total + }; + } }).catch(popupAjaxError); }, diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 index 73168ff3..509816da 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 @@ -1,7 +1,6 @@ import CustomWizard from "../models/custom-wizard"; import DiscourseRoute from "discourse/routes/discourse"; - -const excludedMetaFields = ["route_to", "redirect_on_complete", "redirect_to"]; +import { A } from "@ember/array"; export default DiscourseRoute.extend({ model(params) { @@ -9,34 +8,11 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - if (model && model.submissions) { - let fields = ["username"]; - model.submissions.forEach((s) => { - Object.keys(s.fields).forEach((k) => { - if (!excludedMetaFields.includes(k) && fields.indexOf(k) < 0) { - fields.push(k); - } - }); - }); - - let submissions = []; - model.submissions.forEach((s) => { - let submission = { - username: s.username, - }; - Object.keys(s.fields).forEach((f) => { - if (fields.includes(f)) { - submission[f] = s.fields[f]; - } - }); - submissions.push(submission); - }); - - controller.setProperties({ - wizard: model.wizard, - submissions, - fields, - }); - } + controller.setProperties({ + wizard: model.wizard, + fields: model.fields, + submissions: A(model.submissions), + total: model.total + }); }, }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs index 6d1f255b..9e8e10c8 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs @@ -11,23 +11,31 @@
- - - - {{#each fields as |f|}} - - {{/each}} - - - - {{#each submissions as |s|}} - - {{#each-in s as |k v|}} - - {{/each-in}} - - {{/each}} - -
{{f}}
{{v}}
+ {{#load-more selector=".wizard-submissions tr" action=(action "loadMore")}} + {{#if noResults}} +

{{i18n "search.no_results"}}

+ {{else}} + + + + {{#each fields as |f|}} + + {{/each}} + + + + {{#each submissions as |s|}} + + {{#each-in s as |k v|}} + + {{/each-in}} + + {{/each}} + +
{{f}}
{{v}}
+ {{/if}} + + {{conditional-loading-spinner condition=loadingMore}} + {{/load-more}}
{{/if}} diff --git a/controllers/custom_wizard/admin/submissions.rb b/controllers/custom_wizard/admin/submissions.rb index 4cb2a0e4..c3bf809f 100644 --- a/controllers/custom_wizard/admin/submissions.rb +++ b/controllers/custom_wizard/admin/submissions.rb @@ -13,12 +13,16 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController def show render_json_dump( wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), - submissions: ActiveModel::ArraySerializer.new(ordered_submissions, each_serializer: CustomWizard::SubmissionSerializer) + submissions: ActiveModel::ArraySerializer.new( + submission_list.submissions, + each_serializer: CustomWizard::SubmissionSerializer + ), + total: submission_list.total ) end def download - send_data ordered_submissions.to_json, + send_data submission_list.submissions.to_json, filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json", content_type: "application/json", disposition: "attachment" @@ -26,7 +30,7 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController protected - def ordered_submissions - CustomWizard::Submission.list(@wizard, order_by: 'id') + def submission_list + CustomWizard::Submission.list(@wizard, page: params[:page].to_i) end end diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb index e50cb259..95b4f7fa 100644 --- a/lib/custom_wizard/submission.rb +++ b/lib/custom_wizard/submission.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true class CustomWizard::Submission include ActiveModel::SerializerSupport - + + PAGE_LIMIT = 50 KEY ||= "submissions" META ||= %w(submitted_at route_to redirect_on_complete redirect_to) @@ -44,7 +45,7 @@ class CustomWizard::Submission validate submission_list = self.class.list(wizard, user_id: user.id) - submissions = submission_list.select { |submission| submission.id != self.id } + submissions = submission_list.submissions.select { |submission| submission.id != self.id } submissions.push(self) submission_data = submissions.map { |submission| data_to_save(submission) } @@ -92,27 +93,38 @@ class CustomWizard::Submission end def self.get(wizard, user_id) - data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).first + data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).last new(wizard, data, user_id) end - def self.list(wizard, user_id: nil, order_by: nil) + def self.list(wizard, user_id: nil, page: nil) params = { plugin_name: "#{wizard.id}_#{KEY}" } params[:key] = user_id if user_id.present? query = PluginStoreRow.where(params) - query = query.order("#{order_by} DESC") if order_by.present? - - result = [] + result = OpenStruct.new(submissions: [], total: nil) query.each do |record| - if (submission_data = ::JSON.parse(record.value)).any? + if (submission_data = ::JSON.parse(record.value)).any? submission_data.each do |data| - result.push(new(wizard, data, record.key)) + result.submissions.push(new(wizard, data, record.key)) end end end + result.total = result.submissions.size + + if !page.nil? + start = page * PAGE_LIMIT + length = PAGE_LIMIT + + if result.submissions.length > start + result.submissions = result.submissions[start, length] + else + result.submissions = [] + end + end + result end end diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index 8f5a897f..e8427334 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -272,7 +272,7 @@ class CustomWizard::Wizard def submissions return nil unless user.present? - @submissions ||= CustomWizard::Submission.list(self, user_id: user.id) + @submissions ||= CustomWizard::Submission.list(self, user_id: user.id).submissions end def current_submission diff --git a/serializers/custom_wizard/submission_serializer.rb b/serializers/custom_wizard/submission_serializer.rb index 52f0cb32..992deacb 100644 --- a/serializers/custom_wizard/submission_serializer.rb +++ b/serializers/custom_wizard/submission_serializer.rb @@ -3,10 +3,7 @@ class CustomWizard::SubmissionSerializer < ApplicationSerializer attributes :id, :username, :fields, - :submitted_at, - :route_to, - :redirect_on_complete, - :redirect_to + :submitted_at def username object.user.present? ? diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index a8c33861..ce9756d1 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -21,8 +21,11 @@ describe CustomWizard::Submission do @wizard = CustomWizard::Wizard.create(template_json["id"], user) @wizard2 = CustomWizard::Wizard.create(template_json["id"], user2) @wizard3 = CustomWizard::Wizard.create(template_json_2["id"], user) + @count = CustomWizard::Submission::PAGE_LIMIT + 20 - described_class.new(@wizard, step_1_field_1: "I am a user submission").save + @count.times do |index| + described_class.new(@wizard, step_1_field_1: "I am user submission #{index+1}").save + end described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save end @@ -30,14 +33,18 @@ describe CustomWizard::Submission do it "saves a user's submission" do expect( described_class.get(@wizard, user.id).fields["step_1_field_1"] - ).to eq("I am a user submission") + ).to eq("I am user submission #{@count}") end it "list submissions by wizard" do - expect(described_class.list(@wizard).size).to eq(2) + expect(described_class.list(@wizard).total).to eq(@count + 1) end it "list submissions by wizard and user" do - expect(described_class.list(@wizard, user_id: user.id).size).to eq(1) + expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count) + end + + it "paginates submission lists" do + expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 1) - CustomWizard::Submission::PAGE_LIMIT) end end From 56a146341335d4ef768012b55821f3048dbda3c5 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 14 Jul 2021 14:05:13 +0800 Subject: [PATCH 005/556] Apply prettier --- .../admin-wizards-submissions-show.js.es6 | 34 +++++----- .../discourse/models/custom-wizard.js.es6 | 64 ++++++++++--------- .../admin-wizards-submissions-show.js.es6 | 2 +- 3 files changed, 52 insertions(+), 48 deletions(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index bc38648d..6352b3b2 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -1,34 +1,36 @@ import Controller from "@ember/controller"; import { fmt } from "discourse/lib/computed"; -import { empty } from '@ember/object/computed'; +import { empty } from "@ember/object/computed"; import CustomWizard from "../models/custom-wizard"; export default Controller.extend({ downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"), - noResults: empty('submissions'), + noResults: empty("submissions"), page: 0, total: 0, loadMoreSubmissions() { - const page = this.get('page'); - const wizardId = this.get('wizard.id'); + const page = this.get("page"); + const wizardId = this.get("wizard.id"); - this.set('loadingMore', true); - CustomWizard.submissions(wizardId, page).then(result => { - if (result.submissions) { - this.get('submissions').pushObjects(result.submissions); - } - }).finally(() => { - this.set('loadingMore', false); - }); + this.set("loadingMore", true); + CustomWizard.submissions(wizardId, page) + .then((result) => { + if (result.submissions) { + this.get("submissions").pushObjects(result.submissions); + } + }) + .finally(() => { + this.set("loadingMore", false); + }); }, actions: { loadMore() { - if (!this.loadingMore && (this.submissions.length < this.total)) { - this.set('page', this.get('page') + 1); + if (!this.loadingMore && this.submissions.length < this.total) { + this.set("page", this.get("page") + 1); this.loadMoreSubmissions(); } - } - } + }, + }, }); diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index e4b0a530..5e94e31e 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -220,43 +220,45 @@ CustomWizard.reopenClass({ return ajax(`/admin/wizards/submissions/${wizardId}`, { type: "GET", - data - }).then(result => { - if (result.wizard) { - let fields = ["username"]; - let submissions = []; - let wizard = result.wizard; - let total = result.total; + data, + }) + .then((result) => { + if (result.wizard) { + let fields = ["username"]; + let submissions = []; + let wizard = result.wizard; + let total = result.total; - result.submissions.forEach((s) => { - let submission = { - username: s.username, - }; + result.submissions.forEach((s) => { + let submission = { + username: s.username, + }; - Object.keys(s.fields).forEach((f) => { - if (fields.indexOf(f) < 0) { - fields.push(f); - } + Object.keys(s.fields).forEach((f) => { + if (fields.indexOf(f) < 0) { + fields.push(f); + } - if (fields.includes(f)) { - submission[f] = s.fields[f]; - } + if (fields.includes(f)) { + submission[f] = s.fields[f]; + } + }); + + submission["submitted_at"] = s.submitted_at; + submissions.push(submission); }); - - submission['submitted_at'] = s.submitted_at; - submissions.push(submission); - }); - fields.push("submitted_at"); + fields.push("submitted_at"); - return { - wizard, - fields, - submissions, - total - }; - } - }).catch(popupAjaxError); + return { + wizard, + fields, + submissions, + total, + }; + } + }) + .catch(popupAjaxError); }, create(wizardJson = {}) { diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 index 509816da..2ff9fbf9 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 @@ -12,7 +12,7 @@ export default DiscourseRoute.extend({ wizard: model.wizard, fields: model.fields, submissions: A(model.submissions), - total: model.total + total: model.total, }); }, }); From 1a78b44d35fa35b0192d5c72093d95817e172003 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 14 Jul 2021 14:06:54 +0800 Subject: [PATCH 006/556] Apply rubocop --- lib/custom_wizard/submission.rb | 4 ++-- spec/components/custom_wizard/submission_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb index 95b4f7fa..618a9a67 100644 --- a/lib/custom_wizard/submission.rb +++ b/lib/custom_wizard/submission.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class CustomWizard::Submission include ActiveModel::SerializerSupport - + PAGE_LIMIT = 50 KEY ||= "submissions" META ||= %w(submitted_at route_to redirect_on_complete redirect_to) @@ -105,7 +105,7 @@ class CustomWizard::Submission result = OpenStruct.new(submissions: [], total: nil) query.each do |record| - if (submission_data = ::JSON.parse(record.value)).any? + if (submission_data = ::JSON.parse(record.value)).any? submission_data.each do |data| result.submissions.push(new(wizard, data, record.key)) end diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index ce9756d1..fd7e8984 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -24,7 +24,7 @@ describe CustomWizard::Submission do @count = CustomWizard::Submission::PAGE_LIMIT + 20 @count.times do |index| - described_class.new(@wizard, step_1_field_1: "I am user submission #{index+1}").save + described_class.new(@wizard, step_1_field_1: "I am user submission #{index + 1}").save end described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save From 55b92f4256745a6be2e60c7940f31303c918186f Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 21 Jul 2021 11:31:03 +0800 Subject: [PATCH 007/556] IMPROVE: Add additional data to submission serialiser --- lib/custom_wizard/wizard.rb | 4 ++- .../custom_wizard/submission_serializer.rb | 30 +++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index e8427334..e52feec4 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -32,7 +32,8 @@ class CustomWizard::Wizard :actions, :action_ids, :user, - :submissions + :submissions, + :template attr_reader :all_step_ids @@ -79,6 +80,7 @@ class CustomWizard::Wizard @actions = attrs['actions'] || [] @action_ids = @actions.map { |a| a['id'] } + @template = attrs end def cast_bool(val) diff --git a/serializers/custom_wizard/submission_serializer.rb b/serializers/custom_wizard/submission_serializer.rb index 992deacb..f9cc7230 100644 --- a/serializers/custom_wizard/submission_serializer.rb +++ b/serializers/custom_wizard/submission_serializer.rb @@ -1,13 +1,33 @@ # frozen_string_literal: true class CustomWizard::SubmissionSerializer < ApplicationSerializer attributes :id, - :username, + :user, :fields, :submitted_at - def username - object.user.present? ? - object.user.username : - I18n.t('admin.wizard.submission.no_user', user_id: object.user_id) + has_one :user, serializer: ::BasicUserSerializer, embed: :objects + + def include_user? + object.user.present? + end + + def fields + @fields ||= begin + result = {} + + object.wizard.template['steps'].each do |step| + step['fields'].each do |field| + if value = object.fields[field['id']] + result[field['id']] = { + value: value, + type: field['type'], + label: field['label'] + } + end + end + end + + result + end end end From 51038ade8a1caefe663b4c9a8cf63ee9f7684beb Mon Sep 17 00:00:00 2001 From: Keegan George Date: Mon, 26 Jul 2021 10:44:08 -0700 Subject: [PATCH 008/556] FIX: Update help text in wizard message bar --- 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 e7c10b52..ce253455 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -93,7 +93,7 @@ en: destroy_complete: Destruction complete submissions: select: "Select a wizard to see its submissions" - viewing: "You're viewing the logs of the %{wizardName}. Click 'Download' on the right to download them." + viewing: "You're viewing the submissions of the %{wizardName}. Click 'Download' on the right to download them." documentation: "Check out the submissions documentation" logs: viewing: "View recent logs for wizards on the forum" From ae271ce647fcfb321000755aa91b98a30a0ffe89 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Mon, 9 Aug 2021 14:44:44 -0700 Subject: [PATCH 009/556] UX: Add support button to admin-nav (#118) * Add support button to admin-nav * FIX: Security vulnerabilities with _blank anchor link * Update pro support url * UX: Create pro button custom styling * UX: Merge support button focus styling with hover * DEV: Move pro support url to setting * UX: Change support button name to Pro Support * DEV: Format stylesheet code * DEV: Use variables and change selector specificity for pro button * DEV: Hardcode pro-support url in button * DEV: Remove support url localization * DEV: Undo formatting fixes and add pro support button strings * DEV: Undo formatting fixes auto applied * DEV: Add space between selectors * DEV: Convert scss variables to CSS Custom properties * DEV: Fix linting * FIX: Use SCSS variables for color manipulation functions * DEV: Fix space before i18n * DEV: Add new line at end of file * DEV: Add new line at end of file * DEV: Remove name attribute in localizations * DEV: Remove padding from new line --- .../discourse/templates/admin-wizards.hbs | 4 ++++ assets/stylesheets/common/wizard-admin.scss | 20 +++++++++++++++++++ .../stylesheets/common/wizard-variables.scss | 7 +++++++ config/locales/client.en.yml | 3 +++ plugin.rb | 1 + 5 files changed, 35 insertions(+) create mode 100644 assets/stylesheets/common/wizard-variables.scss diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index bd575aae..5baa9f52 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -7,6 +7,10 @@ {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} + + {{/admin-nav}}
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 66cc6b43..3c4f9ccf 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -2,6 +2,7 @@ @import "wizard-manager"; @import "wizard-api"; @import "common/components/buttons"; +@import "wizard-variables"; .admin-wizard-controls { display: flex; @@ -715,3 +716,22 @@ width: 80px; vertical-align: middle; } + +.btn.btn-pavilion-pro { + background: var(--pavilion-primary); + color: var(--pavilion-secondary); + + .d-icon { + color: var(--pavilion-secondary); + } + + &:hover, + &:focus { + background: darken($pavilionPrimary, 5%); + + &[href], + svg.d-icon { + color: darken($pavilionSecondary, 10%); + } + } +} diff --git a/assets/stylesheets/common/wizard-variables.scss b/assets/stylesheets/common/wizard-variables.scss new file mode 100644 index 00000000..68f02b6b --- /dev/null +++ b/assets/stylesheets/common/wizard-variables.scss @@ -0,0 +1,7 @@ +$pavilionPrimary: #3c1c8c; +$pavilionSecondary: #ffffff; + +:root { + --pavilion-primary: #3c1c8c; + --pavilion-secondary: #ffffff; +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ef826cab..16cbe883 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -58,6 +58,9 @@ en: select_type: "Select a type" condition: "Condition" index: "Index" + pro_support_button: + title: "Request Pro Support" + label: "Pro Support" message: wizard: diff --git a/plugin.rb b/plugin.rb index 449d0237..615ad81e 100644 --- a/plugin.rb +++ b/plugin.rb @@ -33,6 +33,7 @@ if respond_to?(:register_svg_icon) register_svg_icon "chevron-right" register_svg_icon "chevron-left" register_svg_icon "save" + register_svg_icon "far-life-ring" end class ::Sprockets::DirectiveProcessor From a27c222dc66bc16e2efb996ef2d8b39ced3b8b3e Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 10 Aug 2021 14:45:23 +0800 Subject: [PATCH 010/556] Update authentication and subscription handling --- .../components/wizard-message.js.es6 | 1 + .../components/wizard-pro-subscription.js.es6 | 47 ++++++ .../controllers/admin-wizards-pro.js.es6 | 56 +++++++ .../custom-wizard-admin-route-map.js.es6 | 10 +- .../discourse/models/custom-wizard-pro.js.es6 | 34 ++++ .../discourse/models/custom-wizard.js.es6 | 2 +- .../discourse/routes/admin-wizards-pro.js.es6 | 20 +++ .../discourse/templates/admin-wizards-pro.hbs | 31 ++++ .../discourse/templates/admin-wizards.hbs | 1 + .../components/wizard-pro-subscription.hbs | 30 ++++ assets/stylesheets/common/wizard-admin.scss | 60 ++++++- config/locales/client.en.yml | 31 +++- config/routes.rb | 6 + controllers/custom_wizard/admin/pro.rb | 46 ++++++ .../{ => regular}/refresh_api_access_token.rb | 0 jobs/{ => regular}/set_after_time_wizard.rb | 0 jobs/scheduled/update_pro_status.rb | 11 ++ lib/custom_wizard/pro.rb | 21 +++ lib/custom_wizard/pro/authentication.rb | 155 ++++++++++++++++++ lib/custom_wizard/pro/subscription.rb | 74 +++++++++ plugin.rb | 12 +- .../pro/authentication_serializer.rb | 11 ++ .../pro/subscription_serializer.rb | 10 ++ serializers/custom_wizard/pro_serializer.rb | 26 +++ 24 files changed, 687 insertions(+), 8 deletions(-) create mode 100644 assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 create mode 100644 assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 create mode 100644 assets/javascripts/discourse/models/custom-wizard-pro.js.es6 create mode 100644 assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 create mode 100644 assets/javascripts/discourse/templates/admin-wizards-pro.hbs create mode 100644 assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs create mode 100644 controllers/custom_wizard/admin/pro.rb rename jobs/{ => regular}/refresh_api_access_token.rb (100%) rename jobs/{ => regular}/set_after_time_wizard.rb (100%) create mode 100644 jobs/scheduled/update_pro_status.rb create mode 100644 lib/custom_wizard/pro.rb create mode 100644 lib/custom_wizard/pro/authentication.rb create mode 100644 lib/custom_wizard/pro/subscription.rb create mode 100644 serializers/custom_wizard/pro/authentication_serializer.rb create mode 100644 serializers/custom_wizard/pro/subscription_serializer.rb create mode 100644 serializers/custom_wizard/pro_serializer.rb diff --git a/assets/javascripts/discourse/components/wizard-message.js.es6 b/assets/javascripts/discourse/components/wizard-message.js.es6 index b273e78b..686a7254 100644 --- a/assets/javascripts/discourse/components/wizard-message.js.es6 +++ b/assets/javascripts/discourse/components/wizard-message.js.es6 @@ -6,6 +6,7 @@ import I18n from "I18n"; const icons = { error: "times-circle", success: "check-circle", + warn: "exclamation-circle", info: "info-circle", }; diff --git a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 new file mode 100644 index 00000000..8ea56699 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 @@ -0,0 +1,47 @@ +import Component from "@ember/component"; +import CustomWizardPro from "../models/custom-wizard-pro"; +import { notEmpty } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [':custom-wizard-pro-subscription', 'subscription.active:active:inactive'], + subscribed: notEmpty('subscription'), + + @discourseComputed('subscription.type') + title(type) { + return type ? + I18n.t(`admin.wizard.pro.subscription.title.${type}`) : + I18n.t("admin.wizard.pro.not_subscribed"); + }, + + @discourseComputed('subscription.active') + stateClass(active) { + return active ? 'active' : 'inactive'; + }, + + @discourseComputed('stateClass') + stateLabel(stateClass) { + return I18n.t(`admin.wizard.pro.subscription.status.${stateClass}`); + }, + + actions: { + update() { + this.set('updating', true); + CustomWizardPro.update_subscription().then(result => { + if (result.success) { + this.setProperties({ + updateIcon: 'check', + subscription: result.subscription + }); + } else { + this.set('updateIcon', 'times'); + } + }).finally(() => { + this.set('updating', false); + setTimeout(() => { + this.set('updateIcon', null); + }, 7000); + }) + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 new file mode 100644 index 00000000..7c873c66 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 @@ -0,0 +1,56 @@ +import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; +import CustomWizardPro from "../models/custom-wizard-pro"; +import { alias } from "@ember/object/computed"; + +export default Controller.extend({ + messageUrl: "https://thepavilion.io/t/3652", + messageType: 'info', + messageKey: null, + showSubscription: alias('model.authentication.active'), + + setup() { + const authentication = this.get('model.authentication'); + const subscription = this.get('model.subscription'); + const subscribed = subscription && subscription.active; + const authenticated = authentication && authentication.active; + + if (!subscribed) { + this.set('messageKey', authenticated ? 'not_subscribed' : 'authorize'); + } else { + this.set('messageKey', !authenticated ? + 'subscription_expiring' : + subscribed ? 'subscription_active' : 'subscription_inactive' + ); + } + }, + + @discourseComputed('model.server') + messageOpts(server) { + return { server }; + }, + + actions: { + unauthorize() { + this.set('unauthorizing', true); + + CustomWizardPro.unauthorize().then(result => { + if (result.success) { + this.setProperties({ + messageKey: 'unauthorized', + messageType: 'warn', + "model.authentication": null, + "model.subscription": null + }); + } else { + this.setProperties({ + messageKey: 'unauthorize_failed', + messageType: 'error' + }); + } + }).finally(() => { + this.set('unauthorizing', false); + }) + } + } +}); diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 90ab5359..ec2f1b98 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -43,12 +43,20 @@ export default { } ); - this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true }); + this.route("adminWizardsLogs", { + path: "/logs", + resetNamespace: true + }); this.route("adminWizardsManager", { path: "/manager", resetNamespace: true, }); + + this.route("adminWizardsPro", { + path: "/pro", + resetNamespace: true, + }); } ); }, diff --git a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 new file mode 100644 index 00000000..66d80572 --- /dev/null +++ b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 @@ -0,0 +1,34 @@ +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import EmberObject from "@ember/object"; +import DiscourseURL from "discourse/lib/url"; + +const CustomWizardPro = EmberObject.extend(); + +const basePath = "/admin/wizards/pro"; + +CustomWizardPro.reopenClass({ + status() { + return ajax(basePath, { + type: "GET", + }).catch(popupAjaxError); + }, + + authorize() { + window.location.href = `${basePath}/authorize`; + }, + + unauthorize() { + return ajax(`${basePath}/authorize`, { + type: "DELETE", + }).catch(popupAjaxError); + }, + + update_subscription() { + return ajax(`${basePath}/subscription`, { + type: "POST", + }).catch(popupAjaxError); + } +}); + +export default CustomWizardPro; \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index e6a8408d..80c4d86a 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -221,7 +221,7 @@ CustomWizard.reopenClass({ const wizard = this._super.apply(this); wizard.setProperties(buildProperties(wizardJson)); return wizard; - }, + } }); export default CustomWizard; diff --git a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 new file mode 100644 index 00000000..b6fdcb10 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 @@ -0,0 +1,20 @@ +import CustomWizardPro from "../models/custom-wizard-pro"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model() { + return CustomWizardPro.status(); + }, + + setupController(controller, model) { + console.log(model) + controller.set('model', model); + controller.setup(); + }, + + actions: { + authorize() { + CustomWizardPro.authorize(); + } + } +}); diff --git a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs new file mode 100644 index 00000000..67a48a8f --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs @@ -0,0 +1,31 @@ +
+

{{i18n "admin.wizard.pro.title"}}

+ +
+ {{#if model.authentication.active}} + {{conditional-loading-spinner size="small" condition=unauthorizing}} + + {{i18n "admin.wizard.pro.unauthorize"}} + + + {{else}} + {{d-button + icon="id-card" + label="admin.wizard.pro.authorize" + action=(route-action "authorize")}} + {{/if}} +
+
+ +{{wizard-message + key=messageKey + url=messageUrl + type=messageType + opts=messageOpts + component="pro"}} + +
+ {{#if showSubscription}} + {{wizard-pro-subscription subscription=model.subscription}} + {{/if}} +
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index bd575aae..a2e104f7 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -7,6 +7,7 @@ {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} + {{nav-item route="adminWizardsPro" label="admin.wizard.pro.nav_label"}} {{/admin-nav}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs new file mode 100644 index 00000000..3d360220 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs @@ -0,0 +1,30 @@ +
+

{{title}}

+ +
+ + {{#if updating}} + {{loading-spinner size="small"}} + {{else if updateIcon}} + {{d-icon updateIcon}} + {{/if}} + + {{d-button + icon="sync" + action=(action "update") + disabled=updating + label="admin.wizard.pro.subscription.update"}} +
+
+ +{{#if subscribed}} +
+
{{stateLabel}}
+
+ {{{i18n + 'admin.wizard.pro.subscription.last_updated' + updated_at=(format-date subscription.updated_at leaveAgo="true") + }}} +
+
+{{/if}} \ No newline at end of file diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 66cc6b43..b887dace 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -7,7 +7,7 @@ display: flex; align-items: center; justify-content: space-between; - margin-bottom: 20px; + margin-bottom: 10px; & + .wizard-message + div { margin-top: 20px; @@ -715,3 +715,61 @@ width: 80px; vertical-align: middle; } + +.admin-wizards-pro { + .admin-wizard-controls { + h3, label { + margin: 0; + } + + label { + padding: .4em .5em; + margin-left: .75em; + background-color: $success; + color: $secondary; + } + + .buttons { + display: flex; + align-items: center; + + .loading-container { + margin-right: 1em; + } + } + } + + .custom-wizard-pro-subscription { + .title-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: .5em; + + h3 { + margin: 0; + } + + .buttons > span { + margin-right: .5em; + } + } + + .detail-container { + display: flex; + align-items: center; + padding: 1em; + background-color: $primary-very-low; + + .subscription-state { + padding: .25em .5em; + margin-right: .75em; + + &.active { + background-color: $success; + color: $secondary; + } + } + } + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ef826cab..e6a0e849 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -86,12 +86,20 @@ en: no_file: Please choose a file to import file_size_error: The file size must be 512kb or less file_format_error: The file must be a .json file - server_error: "Error: {{message}}" importing: Importing wizards... destroying: Destroying wizards... import_complete: Import complete destroy_complete: Destruction complete - + pro: + documentation: Check out the PRO documentation + authorize: "Authorize this forum to use your PRO subscription plan on %{server}." + not_subscribed: "You've authorized, but are not currently subscribed to a PRO plan on %{server}." + subscription_expiring: "Your subscription is active, but will expire in the next 48 hours." + subscription_active: "Your subscription is active." + subscription_inactive: "Your subscription is inactive on this forum. Read more in the documentation." + unauthorized: "You're unauthorized. If you have a subscription, it will become inactive in the next 48 hours." + unauthorize_failed: Failed to unauthorize. + editor: show: "Show" hide: "Hide" @@ -424,7 +432,24 @@ en: imported: imported upload: Select wizards.json destroy: Destroy - destroyed: destroyed + destroyed: destroyed + + pro: + nav_label: PRO + title: Custom Wizard PRO + authorize: Authorize + authorized: Authorized + unauthorize: cancel + not_subscribed: You're not currently subscribed + subscription: + title: + community: Community Subscription + business: Business Subscription + status: + active: Active + inactive: Inactive + update: Update + last_updated: Last updated {{updated_at}} wizard_js: group: diff --git a/config/routes.rb b/config/routes.rb index 28fcbb82..94cc5858 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -43,5 +43,11 @@ Discourse::Application.routes.append do get 'admin/wizards/manager/export' => 'admin_manager#export' post 'admin/wizards/manager/import' => 'admin_manager#import' delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy' + + get 'admin/wizards/pro' => 'admin_pro#index' + get 'admin/wizards/pro/authorize' => 'admin_pro#authorize' + get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback' + delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy' + post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription' end end diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb new file mode 100644 index 00000000..b0686af2 --- /dev/null +++ b/controllers/custom_wizard/admin/pro.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class CustomWizard::AdminProController < CustomWizard::AdminController + skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback] + + def index + render_serialized(CustomWizard::Pro.new, CustomWizard::ProSerializer, root: false) + end + + def authorize + request_id = SecureRandom.hex(32) + cookies[:user_api_request_id] = request_id + redirect_to CustomWizard::ProAuthentication.generate_request(current_user.id, request_id).to_s + end + + def authorize_callback + payload = params[:payload] + request_id = cookies[:user_api_request_id] + + CustomWizard::ProAuthentication.handle_response(request_id, payload) + CustomWizard::ProSubscription.update + + redirect_to '/admin/wizards/pro' + end + + def destroy + if CustomWizard::ProAuthentication.destroy + render json: success_json + else + render json: failed_json + end + end + + def update_subscription + if CustomWizard::ProSubscription.update + render json: success_json.merge( + subscription: CustomWizard::ProSubscriptionSerializer.new( + CustomWizard::ProSubscription.new, + root: false + ) + ) + else + render json: failed_json + end + end +end \ No newline at end of file diff --git a/jobs/refresh_api_access_token.rb b/jobs/regular/refresh_api_access_token.rb similarity index 100% rename from jobs/refresh_api_access_token.rb rename to jobs/regular/refresh_api_access_token.rb diff --git a/jobs/set_after_time_wizard.rb b/jobs/regular/set_after_time_wizard.rb similarity index 100% rename from jobs/set_after_time_wizard.rb rename to jobs/regular/set_after_time_wizard.rb diff --git a/jobs/scheduled/update_pro_status.rb b/jobs/scheduled/update_pro_status.rb new file mode 100644 index 00000000..962f3ba9 --- /dev/null +++ b/jobs/scheduled/update_pro_status.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Jobs + class UpdateProSubscription < ::Jobs::Scheduled + every 1.days + + def execute(args) + CustomWizard::ProSubscription.update + end + end +end \ No newline at end of file diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb new file mode 100644 index 00000000..c280c09d --- /dev/null +++ b/lib/custom_wizard/pro.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class CustomWizard::Pro + NAMESPACE ||= "#{CustomWizard::PLUGIN_NAME}_pro" + + attr_reader :authentication, + :subscription + + def initialize + @authentication = CustomWizard::ProAuthentication.new + @subscription = CustomWizard::ProSubscription.new + end + + def authorized? + @authentication.active? + end + + def subscribed? + @subscription.active? + end +end \ No newline at end of file diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/pro/authentication.rb new file mode 100644 index 00000000..c92710bc --- /dev/null +++ b/lib/custom_wizard/pro/authentication.rb @@ -0,0 +1,155 @@ +class CustomWizard::ProAuthentication + include ActiveModel::Serialization + + API_KEY ||= "api_key" + API_CLIENT_ID ||= 'api_client_id' + KEYS ||= "keys" + + attr_reader :client_id, + :auth_by, + :auth_at, + :api_key + + def initialize + api = get_api_key + + @api_key = api.key + @auth_at = api.auth_at + @auth_by = api.auth_by + @client_id = get_client_id || set_client_id + end + + def active? + @api_key.present? + end + + def update(data) + api_key = data[:key] + user_id = data[:user_id] + user = User.find(user_id) + + if user&.admin + set_api_key(api_key, user.id) + else + false + end + end + + def destroy + remove + end + + def self.destroy + self.new.destroy + end + + def generate_keys(user_id, request_id) + rsa = OpenSSL::PKey::RSA.generate(2048) + nonce = SecureRandom.hex(32) + set_keys(request_id, user_id, rsa, nonce) + + OpenStruct.new(nonce: nonce, public_key: rsa.public_key) + end + + def decrypt_payload(request_id, payload) + keys = get_keys(request_id) + return false unless keys.present? && keys.pem + delete_keys(request_id) + + rsa = OpenSSL::PKey::RSA.new(keys.pem) + decrypted_payload = rsa.private_decrypt(Base64.decode64(payload)) + return false unless decrypted_payload.present? + + begin + data = JSON.parse(decrypted_payload).symbolize_keys + rescue JSON::ParserError + return false + end + + return false unless data[:nonce] == keys.nonce + data[:user_id] = keys.user_id + + data + end + + def self.generate_request(user_id, request_id) + authentication = self.new + keys = authentication.generate_keys(user_id, request_id) + + params = { + public_key: keys.public_key, + nonce: keys.nonce, + client_id: authentication.client_id, + auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback", + application_name: SiteSetting.title, + scopes: CustomWizard::ProSubscription::SCOPE + } + + uri = URI.parse("https://#{CustomWizard::ProSubscription::SUBSCRIPTION_SERVER}/user-api-key/new") + uri.query = URI.encode_www_form(params) + uri.to_s + end + + def self.handle_response(request_id, payload) + authentication = self.new + + data = authentication.decrypt_payload(request_id, payload) + return unless data.is_a?(Hash) && data[:key] && data[:user_id] + + authentication.update(data) + end + + private + + def get_api_key + raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, API_KEY) + OpenStruct.new( + key: raw && raw['key'], + auth_by: raw && raw['auth_by'], + auth_at: raw && raw['auth_at'] + ) + end + + def set_api_key(key, user_id) + PluginStore.set(CustomWizard::Pro::NAMESPACE, API_KEY, + key: key, + auth_by: user_id, + auth_at: Time.now + ) + end + + def remove + PluginStore.remove(CustomWizard::Pro::NAMESPACE, API_KEY) + end + + def get_client_id + PluginStore.get(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID) + end + + def set_client_id + client_id = SecureRandom.hex(32) + PluginStore.set(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID, client_id) + client_id + end + + def set_keys(request_id, user_id, rsa, nonce) + PluginStore.set(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}", + user_id: user_id, + pem: rsa.export, + nonce: nonce + ) + end + + def get_keys(request_id) + raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}") + OpenStruct.new( + user_id: raw && raw['user_id'], + pem: raw && raw['pem'], + nonce: raw && raw['nonce'] + ) + end + + def delete_keys(request_id) + PluginStore.remove(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}") + end +end \ No newline at end of file diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/pro/subscription.rb new file mode 100644 index 00000000..2e0c8542 --- /dev/null +++ b/lib/custom_wizard/pro/subscription.rb @@ -0,0 +1,74 @@ +class CustomWizard::ProSubscription + include ActiveModel::Serialization + + SUBSCRIPTION_SERVER ||= "test.thepavilion.io" + SUBSCRIPTION_TYPE ||= "stripe" + SCOPE ||= "discourse-subscription-server:user_subscription" + CLIENT_NAME ||= "custom-wizard" + SUBSCRIPTION_KEY ||= "custom_wizard_pro_subscription" + UPDATE_DAY_BUFFER ||= 2 + TYPES ||= %w(community business) + + attr_reader :type, + :updated_at + + def initialize + raw = get + + if raw + @type = raw['type'] + @updated_at = raw['updated_at'] + end + end + + def active? + TYPES.include?(type) && updated_at.to_datetime > (Date.today - UPDATE_DAY_BUFFER.days).to_datetime + end + + def update(data) + return false unless data && data.is_a?(Hash) + subscriptions = data[:subscriptions] + + if subscriptions.present? + subscription = subscriptions.first + type = subscription[:price_nickname] + + set(type) + end + end + + def self.update + @subscribed = nil + auth = CustomWizard::ProAuthentication.new + subscription = self.new + + if auth.active? + response = Excon.get( + "https://#{SUBSCRIPTION_SERVER}/subscription-server/user-subscriptions/#{SUBSCRIPTION_TYPE}/#{CLIENT_NAME}", + headers: { "User-Api-Key" => auth.api_key } + ) + + if response.status == 200 + begin + data = JSON.parse(response.body).deep_symbolize_keys + rescue JSON::ParserError + return false + end + + return subscription.update(data) + end + end + + false + end + + private + + def set(type) + PluginStore.set(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY, type: type, updated_at: Time.now) + end + + def get + PluginStore.get(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY) + end +end \ No newline at end of file diff --git a/plugin.rb b/plugin.rb index 449d0237..97ed8e5a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -62,11 +62,13 @@ after_initialize do ../controllers/custom_wizard/admin/logs.rb ../controllers/custom_wizard/admin/manager.rb ../controllers/custom_wizard/admin/custom_fields.rb + ../controllers/custom_wizard/admin/pro.rb ../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/steps.rb ../controllers/custom_wizard/realtime_validations.rb - ../jobs/refresh_api_access_token.rb - ../jobs/set_after_time_wizard.rb + ../jobs/regular/refresh_api_access_token.rb + ../jobs/regular/set_after_time_wizard.rb + ../jobs/scheduled/update_pro_status.rb ../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/action_result.rb @@ -85,6 +87,9 @@ after_initialize do ../lib/custom_wizard/submission.rb ../lib/custom_wizard/template.rb ../lib/custom_wizard/wizard.rb + ../lib/custom_wizard/pro.rb + ../lib/custom_wizard/pro/subscription.rb + ../lib/custom_wizard/pro/authentication.rb ../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/endpoint.rb @@ -105,6 +110,9 @@ after_initialize do ../serializers/custom_wizard/log_serializer.rb ../serializers/custom_wizard/submission_serializer.rb ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb + ../serializers/custom_wizard/pro_serializer.rb + ../serializers/custom_wizard/pro/authentication_serializer.rb + ../serializers/custom_wizard/pro/subscription_serializer.rb ../extensions/extra_locales_controller.rb ../extensions/invites_controller.rb ../extensions/users_controller.rb diff --git a/serializers/custom_wizard/pro/authentication_serializer.rb b/serializers/custom_wizard/pro/authentication_serializer.rb new file mode 100644 index 00000000..b54f428f --- /dev/null +++ b/serializers/custom_wizard/pro/authentication_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +class CustomWizard::ProAuthenticationSerializer < ApplicationSerializer + attributes :active, + :client_id, + :auth_by, + :auth_at + + def active + object.active? + end +end \ No newline at end of file diff --git a/serializers/custom_wizard/pro/subscription_serializer.rb b/serializers/custom_wizard/pro/subscription_serializer.rb new file mode 100644 index 00000000..6be5ec6f --- /dev/null +++ b/serializers/custom_wizard/pro/subscription_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +class CustomWizard::ProSubscriptionSerializer < ApplicationSerializer + attributes :type, + :active, + :updated_at + + def active + object.active? + end +end \ No newline at end of file diff --git a/serializers/custom_wizard/pro_serializer.rb b/serializers/custom_wizard/pro_serializer.rb new file mode 100644 index 00000000..5b351f29 --- /dev/null +++ b/serializers/custom_wizard/pro_serializer.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +class CustomWizard::ProSerializer < ApplicationSerializer + attributes :server, + :authentication, + :subscription + + def server + CustomWizard::ProSubscription::SUBSCRIPTION_SERVER + end + + def authentication + if object.authentication + CustomWizard::ProAuthenticationSerializer.new(object.authentication, root: false) + else + nil + end + end + + def subscription + if object.subscription + CustomWizard::ProSubscriptionSerializer.new(object.subscription, root: false) + else + nil + end + end +end \ No newline at end of file From a7904a28af923b1c4b2f369b64289c97793404d0 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 10 Aug 2021 15:32:21 +0800 Subject: [PATCH 011/556] Update pro admin title attributes --- .../discourse/routes/admin-wizards-pro.js.es6 | 1 - .../discourse/templates/admin-wizards-pro.hbs | 5 ++++- .../templates/components/wizard-pro-subscription.hbs | 10 ++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 index b6fdcb10..2fa091c7 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 @@ -7,7 +7,6 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - console.log(model) controller.set('model', model); controller.setup(); }, diff --git a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs index 67a48a8f..a0658ccb 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs @@ -7,11 +7,14 @@ {{i18n "admin.wizard.pro.unauthorize"}} - + {{else}} {{d-button icon="id-card" label="admin.wizard.pro.authorize" + title="admin.wizard.pro.authorize" action=(route-action "authorize")}} {{/if}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs index 3d360220..72631bfd 100644 --- a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs @@ -13,18 +13,16 @@ icon="sync" action=(action "update") disabled=updating + title="admin.wizard.pro.subscription.update" label="admin.wizard.pro.subscription.update"}}
{{#if subscribed}}
-
{{stateLabel}}
-
- {{{i18n - 'admin.wizard.pro.subscription.last_updated' - updated_at=(format-date subscription.updated_at leaveAgo="true") - }}} +
{{stateLabel}}
+
+ {{{i18n 'admin.wizard.pro.subscription.last_updated' updated_at=(format-date subscription.updated_at leaveAgo="true")}}}
{{/if}} \ No newline at end of file From f49f5164033ec20124bb375c6e19fc280183b995 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 10 Aug 2021 17:00:42 +0800 Subject: [PATCH 012/556] Add pro restriction --- .../routes/admin-wizards-wizard-show.js.es6 | 1 + .../templates/admin-wizards-wizard-show.hbs | 3 +- .../components/wizard-custom-field.hbs | 21 +++++----- .../components/wizard-custom-step.hbs | 40 ++++++++++--------- assets/stylesheets/common/wizard-admin.scss | 16 ++++++++ config/locales/client.en.yml | 3 +- config/locales/server.en.yml | 1 + controllers/custom_wizard/admin/wizard.rb | 3 +- lib/custom_wizard/builder.rb | 3 ++ lib/custom_wizard/pro.rb | 4 ++ lib/custom_wizard/validators/template.rb | 19 +++++++++ 11 files changed, 84 insertions(+), 30 deletions(-) diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index cb2d54c3..f298fa1c 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -39,6 +39,7 @@ export default DiscourseRoute.extend({ currentStep: wizard.steps[0], currentAction: wizard.actions[0], creating: model.create, + proSubscribed: parentModel.pro_subscribed }; controller.setProperties(props); diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index 7e5b0ee0..9d91cb0b 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -166,7 +166,8 @@ wizard=wizard currentField=currentField wizardFields=wizardFields - fieldTypes=fieldTypes}} + fieldTypes=fieldTypes + proSubscribed=proSubscribed}} {{/if}} {{wizard-links diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 3b63cef7..cf15abd3 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -213,17 +213,20 @@ {{#if field.showAdvanced}}
-
-
- -
+ {{#if proSubscribed}} +
+
+ + {{i18n "admin.wizard.pro.label"}} +
-
- {{wizard-mapper - inputs=field.condition - options=fieldConditionOptions}} +
+ {{wizard-mapper + inputs=field.condition + options=fieldConditionOptions}} +
-
+ {{/if}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 85adfe8a..3d8fad9d 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -38,26 +38,29 @@ {{#if step.showAdvanced}}
-
-
- + {{#if proSubscribed}} +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+ +
+ {{wizard-mapper + inputs=step.condition + options=stepConditionOptions}} +
-
- {{wizard-mapper - inputs=step.condition - options=stepConditionOptions}} +
+
+
+

{{i18n "admin.wizard.step.force_final.label"}}

+ {{input type="checkbox" checked=step.force_final}} + {{i18n "admin.wizard.step.force_final.description"}} +
-
- -
-
-
-

{{i18n "admin.wizard.step.force_final.label"}}

- {{input type="checkbox" checked=step.force_final}} - {{i18n "admin.wizard.step.force_final.description"}} -
-
+ {{/if}}
@@ -129,5 +132,6 @@ currentFieldId=currentField.id fieldTypes=fieldTypes removeField="removeField" - wizardFields=wizardFields}} + wizardFields=wizardFields + proSubscribed=proSubscribed}} {{/each}} diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index b887dace..cca03c74 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -372,6 +372,22 @@ .setting-gutter { margin-top: 5px; } + + &.pro { + .setting-label { + display: flex; + flex-direction: column; + + label { + margin: 0; + } + } + + .pro-label { + color: $tertiary; + font-size: .75em; + } + } } .advanced-settings { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e6a0e849..4a2bf28f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -58,7 +58,7 @@ en: select_type: "Select a type" condition: "Condition" index: "Index" - + message: wizard: select: "Select a wizard, or create a new one" @@ -436,6 +436,7 @@ en: pro: nav_label: PRO + label: PRO title: Custom Wizard PRO authorize: Authorize authorized: Authorized diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 7e507450..c425baa5 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -49,6 +49,7 @@ en: required: "%{property} is required" conflict: "Wizard with id '%{wizard_id}' already exists" after_time: "After time setting is invalid" + pro: "%{property} is PRO only" site_settings: custom_wizard_enabled: "Enable custom wizards." diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index 8da64fac..e824398c 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -10,7 +10,8 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController ), field_types: CustomWizard::Field.types, realtime_validations: CustomWizard::RealtimeValidation.types, - custom_fields: custom_field_list + custom_fields: custom_field_list, + pro_subscribed: CustomWizard::Pro.subscribed? ) end diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 51ca4183..a949653c 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -6,6 +6,7 @@ class CustomWizard::Builder @template = CustomWizard::Template.create(wizard_id) return nil if @template.nil? @wizard = CustomWizard::Wizard.new(template.data, user) + @pro = CustomWizard::Pro.new end def self.sorted_handlers @@ -222,6 +223,8 @@ class CustomWizard::Builder end def check_condition(template) + return false unless @pro.subscribed? + if template['condition'].present? result = CustomWizard::Mapper.new( inputs: template['condition'], diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb index c280c09d..e6bf2b0b 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/pro.rb @@ -18,4 +18,8 @@ class CustomWizard::Pro def subscribed? @subscription.active? end + + def self.subscribed? + self.new.subscribed? + end end \ No newline at end of file diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index c90944e9..deaa5119 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -6,6 +6,7 @@ class CustomWizard::TemplateValidator def initialize(data, opts = {}) @data = data @opts = opts + @pro = CustomWizard::Pro.new end def perform @@ -14,12 +15,15 @@ class CustomWizard::TemplateValidator check_id(data, :wizard) check_required(data, :wizard) validate_after_time + validate_pro(data, :wizard) data[:steps].each do |step| check_required(step, :step) + validate_pro(step, :step) if data[:fields].present? data[:fields].each do |field| + validate_pro(field, :field) check_required(field, :field) end end @@ -47,6 +51,13 @@ class CustomWizard::TemplateValidator } end + def self.pro + { + step: ['condition'], + field: ['conition'] + } + end + private def check_required(object, type) @@ -57,6 +68,14 @@ class CustomWizard::TemplateValidator end end + def validate_pro(object, type) + CustomWizard::TemplateValidator.required[type].each do |property| + if object[property].present? && !@pro.subscribed? + errors.add :base, I18n.t("wizard.validation.pro", property: property) + end + end + end + def check_id(object, type) if type === :wizard && @opts[:create] && CustomWizard::Template.exists?(object[:id]) errors.add :base, I18n.t("wizard.validation.conflict", wizard_id: object[:id]) From e81b773512de22e067cdfb0f40ab2a2d2574185e Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 10 Aug 2021 17:18:49 +0800 Subject: [PATCH 013/556] Move pro conditions out of advanced section --- .../components/wizard-custom-field.hbs | 30 ++++++------ .../components/wizard-custom-step.hbs | 48 +++++++++---------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index cf15abd3..9237cf62 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -207,27 +207,27 @@
{{/if}} +{{#if proSubscribed}} +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+ +
+ {{wizard-mapper + inputs=field.condition + options=fieldConditionOptions}} +
+
+{{/if}} + {{#if showAdvanced}} {{wizard-advanced-toggle showAdvanced=field.showAdvanced}} {{#if field.showAdvanced}}
- {{#if proSubscribed}} -
-
- - {{i18n "admin.wizard.pro.label"}} -
- -
- {{wizard-mapper - inputs=field.condition - options=fieldConditionOptions}} -
-
- {{/if}} -
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 3d8fad9d..3081be66 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -33,35 +33,35 @@
+{{#if proSubscribed}} +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+ +
+ {{wizard-mapper + inputs=step.condition + options=stepConditionOptions}} +
+
+ +
+
+
+

{{i18n "admin.wizard.step.force_final.label"}}

+ {{input type="checkbox" checked=step.force_final}} + {{i18n "admin.wizard.step.force_final.description"}} +
+
+{{/if}} + {{wizard-advanced-toggle showAdvanced=step.showAdvanced}} {{#if step.showAdvanced}}
- {{#if proSubscribed}} -
-
- - {{i18n "admin.wizard.pro.label"}} -
- -
- {{wizard-mapper - inputs=step.condition - options=stepConditionOptions}} -
-
- -
-
-
-

{{i18n "admin.wizard.step.force_final.label"}}

- {{input type="checkbox" checked=step.force_final}} - {{i18n "admin.wizard.step.force_final.description"}} -
-
- {{/if}} -
From eadc40bdae1f580c3b718e2e23685c47e769aef6 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 18 Aug 2021 14:11:00 +0800 Subject: [PATCH 014/556] Add CustomWizard class protections and pro feature restrictions --- config/locales/server.en.yml | 1 + jobs/scheduled/update_pro_status.rb | 10 ++++------ lib/custom_wizard/action.rb | 10 ++++++++++ lib/custom_wizard/builder.rb | 21 +++++++++++++-------- lib/custom_wizard/pro/subscription.rb | 5 +++-- plugin.rb | 12 ++++++++++++ 6 files changed, 43 insertions(+), 16 deletions(-) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index c425baa5..86968fda 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -17,6 +17,7 @@ en: name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})" name_already_taken: "'%{name}' is already taken as a custom field name" save_default: "Failed to save custom field '%{name}'" + pro_required: "PRO Actions require a PRO Subscription" field: too_short: "%{label} must be at least %{min} characters" diff --git a/jobs/scheduled/update_pro_status.rb b/jobs/scheduled/update_pro_status.rb index 962f3ba9..5f76f52b 100644 --- a/jobs/scheduled/update_pro_status.rb +++ b/jobs/scheduled/update_pro_status.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -module Jobs - class UpdateProSubscription < ::Jobs::Scheduled - every 1.days +class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled + every 10.minutes - def execute(args) - CustomWizard::ProSubscription.update - end + def execute(args) + CustomWizard::ProSubscription.update end end \ No newline at end of file diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 6a36af41..1cadbb5b 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -14,9 +14,15 @@ class CustomWizard::Action @submission = opts[:submission] @log = [] @result = CustomWizard::ActionResult.new + @pro = CustomWizard::Pro.new end def perform + if pro_actions.include?(action['type']) && !@pro.subscribed? + log_error(I18n.t("wizard.custom_field.error.pro_required")) + return + end + ActiveRecord::Base.transaction do self.send(action['type'].to_sym) end @@ -752,4 +758,8 @@ class CustomWizard::Action CustomWizard::Log.create(log) end + + def pro_actions + %w[send_message watch_categories send_to_api create_group create_category] + end end diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 656a3820..78a960b9 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -22,13 +22,6 @@ class CustomWizard::Builder @sorted_handlers.sort_by! { |h| -h[:priority] } end - def mapper - CustomWizard::Mapper.new( - user: @wizard.user, - data: @wizard.current_submission&.fields_and_meta - ) - end - def build(build_opts = {}, params = {}) return nil if !SiteSetting.custom_wizard_enabled || !@wizard return @wizard if !@wizard.can_access? && !build_opts[:force] @@ -80,6 +73,15 @@ class CustomWizard::Builder @wizard end + private + + def mapper + CustomWizard::Mapper.new( + user: @wizard.user, + data: @wizard.current_submission&.fields_and_meta + ) + end + def append_field(step, step_template, field_template, build_opts) params = { id: field_template['id'], @@ -224,7 +226,10 @@ class CustomWizard::Builder end def check_condition(template) - return false unless @pro.subscribed? + unless @pro.subscribed? + CustomWizard::Log.create(I18n.t("wizard.custom_field.error.pro_required")) + return false + end if template['condition'].present? result = CustomWizard::Mapper.new( diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/pro/subscription.rb index 2e0c8542..ef0ac2b9 100644 --- a/lib/custom_wizard/pro/subscription.rb +++ b/lib/custom_wizard/pro/subscription.rb @@ -38,14 +38,15 @@ class CustomWizard::ProSubscription end def self.update - @subscribed = nil auth = CustomWizard::ProAuthentication.new subscription = self.new if auth.active? response = Excon.get( "https://#{SUBSCRIPTION_SERVER}/subscription-server/user-subscriptions/#{SUBSCRIPTION_TYPE}/#{CLIENT_NAME}", - headers: { "User-Api-Key" => auth.api_key } + headers: { + "User-Api-Key" => auth.api_key + } ) if response.status == 200 diff --git a/plugin.rb b/plugin.rb index c0379374..7c056b5d 100644 --- a/plugin.rb +++ b/plugin.rb @@ -126,6 +126,18 @@ after_initialize do Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty) + class CustomWizard::UnpermittedOverride < StandardError; end + + CustomWizard.constants.each do |class_name| + klass = CustomWizard.const_get(class_name) + next if !klass.is_a?(Class) || klass.superclass.name.to_s.split("::").first == 'CustomWizard' + + klass.define_singleton_method(:prepend) { |klass| raise CustomWizard::UnpermittedOverride.new } + klass.define_singleton_method(:include) { |klass| raise CustomWizard::UnpermittedOverride.new } + klass.define_singleton_method(:define_method) { |name, &block| raise CustomWizard::UnpermittedOverride.new } + klass.define_singleton_method(:define_singleton_method) { |name, &block| raise CustomWizard::UnpermittedOverride.new } + end + add_class_method(:wizard, :user_requires_completion?) do |user| wizard_result = self.new(user).requires_completion? return wizard_result if wizard_result From a810155f9177e91c2679cc922b352bad88be30c9 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 18 Aug 2021 14:59:43 +0800 Subject: [PATCH 015/556] Clean up pro constants --- controllers/custom_wizard/admin/pro.rb | 8 +-- jobs/scheduled/update_pro_status.rb | 2 +- lib/custom_wizard/pro.rb | 85 ++++++++++++++++++++++++- lib/custom_wizard/pro/authentication.rb | 63 ++++++------------ lib/custom_wizard/pro/subscription.rb | 56 ++++++---------- 5 files changed, 127 insertions(+), 87 deletions(-) diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb index b0686af2..9f32045e 100644 --- a/controllers/custom_wizard/admin/pro.rb +++ b/controllers/custom_wizard/admin/pro.rb @@ -10,15 +10,15 @@ class CustomWizard::AdminProController < CustomWizard::AdminController def authorize request_id = SecureRandom.hex(32) cookies[:user_api_request_id] = request_id - redirect_to CustomWizard::ProAuthentication.generate_request(current_user.id, request_id).to_s + redirect_to CustomWizard::Pro.auth_request(current_user.id, request_id).to_s end def authorize_callback payload = params[:payload] request_id = cookies[:user_api_request_id] - CustomWizard::ProAuthentication.handle_response(request_id, payload) - CustomWizard::ProSubscription.update + CustomWizard::Pro.auth_response(request_id, payload) + CustomWizard::Pro.update_subscription redirect_to '/admin/wizards/pro' end @@ -32,7 +32,7 @@ class CustomWizard::AdminProController < CustomWizard::AdminController end def update_subscription - if CustomWizard::ProSubscription.update + if CustomWizard::Pro.update render json: success_json.merge( subscription: CustomWizard::ProSubscriptionSerializer.new( CustomWizard::ProSubscription.new, diff --git a/jobs/scheduled/update_pro_status.rb b/jobs/scheduled/update_pro_status.rb index 5f76f52b..6f947304 100644 --- a/jobs/scheduled/update_pro_status.rb +++ b/jobs/scheduled/update_pro_status.rb @@ -4,6 +4,6 @@ class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled every 10.minutes def execute(args) - CustomWizard::ProSubscription.update + CustomWizard::Pro.update_subscription end end \ No newline at end of file diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb index e6bf2b0b..f54dd92d 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/pro.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true class CustomWizard::Pro - NAMESPACE ||= "#{CustomWizard::PLUGIN_NAME}_pro" - attr_reader :authentication, :subscription @@ -11,6 +9,18 @@ class CustomWizard::Pro @subscription = CustomWizard::ProSubscription.new end + def server + "test.thepavilion.io" + end + + def subscription_type + "stripe" + end + + def client_name + "custom-wizard" + end + def authorized? @authentication.active? end @@ -19,7 +29,78 @@ class CustomWizard::Pro @subscription.active? end + def update_subscription + if @authentication.active? + response = Excon.get( + "https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}", + headers: { + "User-Api-Key" => @authentication.api_key + } + ) + + if response.status == 200 + begin + data = JSON.parse(response.body).deep_symbolize_keys + rescue JSON::ParserError + return false + end + + return @subscription.update(data) + end + end + + @subscription.destroy + false + end + + def destroy + @authentication.destroy + end + + def auth_request(user_id, request_id) + keys = @authentication.generate_keys(user_id, request_id) + + params = { + public_key: keys.public_key, + nonce: keys.nonce, + client_id: @authentication.client_id, + auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback", + application_name: SiteSetting.title, + scopes: "discourse-subscription-server:user_subscription" + } + + uri = URI.parse("https://#{server}/user-api-key/new") + uri.query = URI.encode_www_form(params) + uri.to_s + end + + def auth_response(request_id, payload) + data = @authentication.decrypt_payload(request_id, payload) + return unless data.is_a?(Hash) && data[:key] && data[:user_id] + @authentication.update(data) + end + + def self.update + self.new.update + end + + def self.destroy + self.new.destroy + end + + def self.generate_request + self.new.generate_request + end + + def self.handle_response + self.new.handle_response + end + def self.subscribed? self.new.subscribed? end + + def self.namespace + "custom_wizard_pro" + end end \ No newline at end of file diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/pro/authentication.rb index c92710bc..0d89ab06 100644 --- a/lib/custom_wizard/pro/authentication.rb +++ b/lib/custom_wizard/pro/authentication.rb @@ -1,10 +1,6 @@ class CustomWizard::ProAuthentication include ActiveModel::Serialization - API_KEY ||= "api_key" - API_CLIENT_ID ||= 'api_client_id' - KEYS ||= "keys" - attr_reader :client_id, :auth_by, :auth_at, @@ -39,10 +35,6 @@ class CustomWizard::ProAuthentication remove end - def self.destroy - self.new.destroy - end - def generate_keys(user_id, request_id) rsa = OpenSSL::PKey::RSA.generate(2048) nonce = SecureRandom.hex(32) @@ -72,37 +64,22 @@ class CustomWizard::ProAuthentication data end - def self.generate_request(user_id, request_id) - authentication = self.new - keys = authentication.generate_keys(user_id, request_id) - - params = { - public_key: keys.public_key, - nonce: keys.nonce, - client_id: authentication.client_id, - auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback", - application_name: SiteSetting.title, - scopes: CustomWizard::ProSubscription::SCOPE - } - - uri = URI.parse("https://#{CustomWizard::ProSubscription::SUBSCRIPTION_SERVER}/user-api-key/new") - uri.query = URI.encode_www_form(params) - uri.to_s - end - - def self.handle_response(request_id, payload) - authentication = self.new - - data = authentication.decrypt_payload(request_id, payload) - return unless data.is_a?(Hash) && data[:key] && data[:user_id] - - authentication.update(data) - end - private + def api_key_db_key + "api_key" + end + + def api_client_id_db_key + "api_client_id" + end + + def keys_db_key + "keys" + end + def get_api_key - raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, API_KEY) + raw = PluginStore.get(CustomWizard::Pro.namespace, api_key_db_key) OpenStruct.new( key: raw && raw['key'], auth_by: raw && raw['auth_by'], @@ -111,7 +88,7 @@ class CustomWizard::ProAuthentication end def set_api_key(key, user_id) - PluginStore.set(CustomWizard::Pro::NAMESPACE, API_KEY, + PluginStore.set(CustomWizard::Pro.namespace, api_key_db_key, key: key, auth_by: user_id, auth_at: Time.now @@ -119,21 +96,21 @@ class CustomWizard::ProAuthentication end def remove - PluginStore.remove(CustomWizard::Pro::NAMESPACE, API_KEY) + PluginStore.remove(CustomWizard::Pro.namespace, api_key_db_key) end def get_client_id - PluginStore.get(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID) + PluginStore.get(CustomWizard::Pro.namespace, api_client_id_db_key) end def set_client_id client_id = SecureRandom.hex(32) - PluginStore.set(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID, client_id) + PluginStore.set(CustomWizard::Pro.namespace, api_client_id_db_key, client_id) client_id end def set_keys(request_id, user_id, rsa, nonce) - PluginStore.set(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}", + PluginStore.set(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}", user_id: user_id, pem: rsa.export, nonce: nonce @@ -141,7 +118,7 @@ class CustomWizard::ProAuthentication end def get_keys(request_id) - raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}") + raw = PluginStore.get(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") OpenStruct.new( user_id: raw && raw['user_id'], pem: raw && raw['pem'], @@ -150,6 +127,6 @@ class CustomWizard::ProAuthentication end def delete_keys(request_id) - PluginStore.remove(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}") + PluginStore.remove(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") end end \ No newline at end of file diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/pro/subscription.rb index ef0ac2b9..f82b58df 100644 --- a/lib/custom_wizard/pro/subscription.rb +++ b/lib/custom_wizard/pro/subscription.rb @@ -1,14 +1,6 @@ class CustomWizard::ProSubscription include ActiveModel::Serialization - SUBSCRIPTION_SERVER ||= "test.thepavilion.io" - SUBSCRIPTION_TYPE ||= "stripe" - SCOPE ||= "discourse-subscription-server:user_subscription" - CLIENT_NAME ||= "custom-wizard" - SUBSCRIPTION_KEY ||= "custom_wizard_pro_subscription" - UPDATE_DAY_BUFFER ||= 2 - TYPES ||= %w(community business) - attr_reader :type, :updated_at @@ -21,8 +13,12 @@ class CustomWizard::ProSubscription end end + def types + %w(community business) + end + def active? - TYPES.include?(type) && updated_at.to_datetime > (Date.today - UPDATE_DAY_BUFFER.days).to_datetime + types.include?(type) && updated_at.to_datetime > (Date.today - 15.minutes).to_datetime end def update(data) @@ -37,39 +33,25 @@ class CustomWizard::ProSubscription end end - def self.update - auth = CustomWizard::ProAuthentication.new - subscription = self.new - - if auth.active? - response = Excon.get( - "https://#{SUBSCRIPTION_SERVER}/subscription-server/user-subscriptions/#{SUBSCRIPTION_TYPE}/#{CLIENT_NAME}", - headers: { - "User-Api-Key" => auth.api_key - } - ) - - if response.status == 200 - begin - data = JSON.parse(response.body).deep_symbolize_keys - rescue JSON::ParserError - return false - end - - return subscription.update(data) - end - end - - false + def destroy + remove end - + private - + + def key + "custom_wizard_pro_subscription" + end + def set(type) - PluginStore.set(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY, type: type, updated_at: Time.now) + PluginStore.set(CustomWizard::Pro.namespace, key, type: type, updated_at: Time.now) end def get - PluginStore.get(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY) + PluginStore.get(CustomWizard::Pro.namespace, key) + end + + def remove + PluginStore.remove(CustomWizard::Pro.namespace, key) end end \ No newline at end of file From 03fb7b7adab3780fc0860ed8da27cc144861095c Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 25 Aug 2021 09:59:24 +0800 Subject: [PATCH 016/556] WIP: update field data handling to support column toggling --- .../admin-wizards-submissions-show.js.es6 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index 6352b3b2..28a1c825 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -24,6 +24,18 @@ export default Controller.extend({ this.set("loadingMore", false); }); }, + + + @discourseComputed('submissions', 'fields.@each.enabled') + displaySubmissions(submissions, fields) { + return submissions.map(submission => { + let field = fields.find(f => Object.keys(submission).includes(f.id)); + if (!field.enabled) { + submission.delete(field.id); + }; + return submission; + }); + }, actions: { loadMore() { From d74d3d25beecae7011b9c102726f8d074cb8f66c Mon Sep 17 00:00:00 2001 From: Keegan George Date: Mon, 30 Aug 2021 16:23:33 -0700 Subject: [PATCH 017/556] UX: Display submission fields unique to each type --- .../components/submission-field.js.es6 | 114 ++++++++++++++++++ .../admin-wizards-submissions-show.hbs | 20 ++- .../templates/components/submission-field.hbs | 114 ++++++++++++++++++ assets/stylesheets/common/wizard-admin.scss | 51 ++++++++ config/locales/client.en.yml | 6 + plugin.rb | 7 ++ 6 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 assets/javascripts/discourse/components/submission-field.js.es6 create mode 100644 assets/javascripts/discourse/templates/components/submission-field.hbs diff --git a/assets/javascripts/discourse/components/submission-field.js.es6 b/assets/javascripts/discourse/components/submission-field.js.es6 new file mode 100644 index 00000000..f76376a5 --- /dev/null +++ b/assets/javascripts/discourse/components/submission-field.js.es6 @@ -0,0 +1,114 @@ +import { action } from "@ember/object"; +import Component from "@ember/component"; +import { equal } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; +import I18n from "I18n"; + + +export default Component.extend({ + isText: equal("value.type", "text"), + isComposer: equal("value.type", "composer"), + isDate: equal("value.type", "date"), + isTime: equal("value.type", "time"), + isDateTime: equal("value.type", "date_time"), + isNumber: equal("value.type", "number"), + isCheckbox: equal("value.type", "checkbox"), + isUrl: equal("value.type", "url"), + isUpload: equal("value.type", "upload"), + isDropdown: equal("value.type", "dropdown"), + isTag: equal("value.type", "tag"), + isCategory: equal("value.type", "category"), + isGroup: equal("value.type", "group"), + isUser: equal("fieldName", "username"), + isUserSelector: equal("value.type", "user_selector"), + isSubmittedAt: equal("fieldName", "submitted_at"), + isTextArea: equal("value.type", "textarea"), + isComposerPreview: equal("value.type", "composer_preview"), + textState: "text-collapsed", + toggleText: I18n.t('admin.wizard.submissions.expand_text'), + + @discourseComputed("value") + checkboxValue(value) { + const isCheckbox = this.get("isCheckbox"); + if (isCheckbox) { + if (value.value.includes("true")) { + return true; + } else if (value.value.includes("false")) { + return false; + } + } + }, + + @action + expandText() { + const state = this.get("textState"); + + if (state === "text-collapsed") { + this.set("textState", "text-expanded"); + this.set("toggleText", I18n.t('admin.wizard.submissions.collapse_text')); + } else if (state === "text-expanded") { + this.set("textState", "text-collapsed"); + this.set("toggleText", I18n.t('admin.wizard.submissions.expand_text')); + } + }, + + @discourseComputed('value') + file(value) { + const isUpload = this.get("isUpload"); + if (isUpload) { + return value.value; + } + }, + + @discourseComputed('value') + submittedUsers(value) { + const isUserSelector = this.get("isUserSelector"); + const users = []; + + if (isUserSelector) { + const userData = value.value; + const usernames = []; + + if (userData.indexOf(',')) { + usernames.push(...userData.split(',')); + + usernames.forEach(u => { + const user = { + username: u, + url: `/u/${u}` + } + users.push(user); + }) + } + } + return users; + }, + + @discourseComputed('value') + userProfileUrl(value) { + const isUser = this.get("isUser"); + const isUserSelector = this.get("isUserSelector"); + + if (isUser) { + return `/u/${value.username}`; + } + }, + + @discourseComputed('value') + categoryUrl(value) { + const isCategory = this.get('isCategory'); + + if (isCategory) { + return `/c/${value.value}`; + } + }, + + @discourseComputed('value') + groupUrl(value) { + const isGroup = this.get('isGroup'); + + if (isGroup) { + return `/g/${value.value}`; + } + }, +}); diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs index 9e8e10c8..2f0689b1 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs @@ -2,6 +2,14 @@
+
+ {{d-button + icon="sliders-h" + label="admin.wizard.submissions.edit_columns" + action=(action "showEditColumnsModal") + class="btn-default open-edit-columns-btn download-link"}} +
+ {{d-icon "download"}} @@ -18,16 +26,18 @@ - {{#each fields as |f|}} - + {{#each fields as |field|}} + {{#if field.enabled}} + + {{/if}} {{/each}} - {{#each submissions as |s|}} + {{#each displaySubmissions as |submission|}} - {{#each-in s as |k v|}} - + {{#each-in submission as |field value|}} + {{/each-in}} {{/each}} diff --git a/assets/javascripts/discourse/templates/components/submission-field.hbs b/assets/javascripts/discourse/templates/components/submission-field.hbs new file mode 100644 index 00000000..52cde1dd --- /dev/null +++ b/assets/javascripts/discourse/templates/components/submission-field.hbs @@ -0,0 +1,114 @@ +{{#if isText}} + {{value.value}} +{{/if}} + +{{#if isTextArea}} +
+

{{value.value}}

+ {{toggleText}} +
+{{/if}} + +{{#if isComposer}} +
+

{{value.value}}

+ {{toggleText}} +
+{{/if}} + +{{#if isComposerPreview}} + {{d-icon "comment-alt" }} {{i18n "admin.wizard.submissions.composer_preview"}}: {{value.value}} +{{/if}} + +{{#if isTextOnly}} + {{value.value}} +{{/if}} + +{{#if isDate}} + + {{d-icon "calendar"}}{{value.value}} + +{{/if}} + +{{#if isTime}} + + {{d-icon "clock"}}{{value.value}} + +{{/if}} + +{{#if isDateTime}} + + {{d-icon "calendar"}}{{format-date value.value format="medium"}} + +{{/if}} + +{{#if isNumber}} + {{value.value}} +{{/if}} + +{{#if isCheckbox}} + {{#if checkboxValue}} + + {{d-icon "check"}}{{value.value}} + + {{else}} + + {{d-icon "times"}}{{value.value}} + + {{/if}} +{{/if}} + +{{#if isUrl}} + + {{ d-icon "link" }} + + {{value.value}} + + +{{/if}} + +{{#if isUpload}} + + {{file.original_filename}} + +{{/if}} + +{{#if isDropdown}} + + {{ d-icon "check-square" }} + {{ value.value }} + +{{/if}} + +{{#if isTag}} + {{#each value.value as |tag|}} + {{discourse-tag tag}} + {{/each}} +{{/if}} + +{{#if isCategory}} + {{i18n "admin.wizard.submissions.category_id"}}: {{value.value}} +{{/if}} + +{{#if isGroup}} + {{i18n "admin.wizard.submissions.group_id"}}: {{ value.value }} +{{/if}} + + +{{#if isUserSelector}} + {{#each submittedUsers as |user|}} + {{ d-icon "user" }} + {{user.username}} + {{/each}} +{{/if}} + +{{#if isUser}} + {{#link-to "user" value}}{{avatar value imageSize="tiny"}}{{/link-to}} + {{value.username}} +{{/if}} + +{{#if isSubmittedAt}} + + {{d-icon "clock"}}{{format-date value format="tiny"}} + +{{/if}} diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 66cc6b43..66c8d2f7 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -71,6 +71,51 @@ table td { min-width: 150px; } + + table thead th { + text-transform: capitalize; + } + + .submission-icon-item { + display: flex; + align-items: center; + svg { + margin-right: 5px; + } + } + + .submission-checkbox-true { + text-transform: capitalize; + color: var(--success); + } + + .submission-checkbox-false { + text-transform: capitalize; + color: var(--danger); + } + + .submission-long-text { + &-content { + white-space: nowrap; + word-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; + width: 250px; + margin-bottom: 0; + + &.text-expanded { + white-space: normal; + } + } + + a { + font-size: var(--font-down-1); + } + } + + .submission-composer-text { + font-family: monospace; + } } .admin-wizards-logs { @@ -203,6 +248,11 @@ &.underline { text-decoration: underline; } + + .controls { + margin-left: auto; + margin-right: 0.5rem; + } } .admin-wizard-buttons { @@ -715,3 +765,4 @@ width: 80px; vertical-align: middle; } + diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ce253455..8501e2a2 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -367,6 +367,12 @@ en: nav_label: "Submissions" title: "{{name}} Submissions" download: "Download" + edit_columns: "Edit Columns" + expand_text: "Read More" + collapse_text: "Show Less" + group_id: "Group ID" + category_id: "Category ID" + composer_preview: "Composer Preview" api: label: "API" diff --git a/plugin.rb b/plugin.rb index 449d0237..993fe0d5 100644 --- a/plugin.rb +++ b/plugin.rb @@ -33,6 +33,13 @@ if respond_to?(:register_svg_icon) register_svg_icon "chevron-right" register_svg_icon "chevron-left" register_svg_icon "save" + register_svg_icon "sliders-h" + register_svg_icon "calendar" + register_svg_icon "check" + register_svg_icon "times" + register_svg_icon "clock" + register_svg_icon "link" + register_svg_icon "comment-alt" end class ::Sprockets::DirectiveProcessor From 0bc151fc7b4c17b9d19ed2e138dd724fe3c44eab Mon Sep 17 00:00:00 2001 From: Keegan George Date: Mon, 30 Aug 2021 16:33:48 -0700 Subject: [PATCH 018/556] WIP: Edit Columns modal functionality Currently modal and edit columns works but removes only field and not corresponding submission. --- .../admin-wizards-submissions-columns.js.es6 | 16 ++++++++++ .../admin-wizards-submissions-show.js.es6 | 19 +++++++++-- .../discourse/models/custom-wizard.js.es6 | 24 +++++++------- .../admin-wizards-submissions-show.js.es6 | 12 +++++-- .../admin-wizards-submissions-columns.hbs | 32 +++++++++++++++++++ 5 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 create mode 100644 assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 new file mode 100644 index 00000000..5627dacf --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 @@ -0,0 +1,16 @@ +import Controller from "@ember/controller"; +import ModalFunctionality from "discourse/mixins/modal-functionality"; + +export default Controller.extend(ModalFunctionality, { + + actions: { + save() { + this.send("closeModal"); + }, + resetToDefault() { + this.get('model.fields').forEach(field => { + field.set("enabled", true); + }); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index 28a1c825..40a08f4f 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -2,6 +2,9 @@ import Controller from "@ember/controller"; import { fmt } from "discourse/lib/computed"; import { empty } from "@ember/object/computed"; import CustomWizard from "../models/custom-wizard"; +import showModal from "discourse/lib/show-modal"; +import discourseComputed from "discourse-common/utils/decorators"; + export default Controller.extend({ downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"), @@ -31,8 +34,11 @@ export default Controller.extend({ return submissions.map(submission => { let field = fields.find(f => Object.keys(submission).includes(f.id)); if (!field.enabled) { - submission.delete(field.id); - }; + // insert field / submission deletion code here: + console.log(field, "is not enabled for ", submission); + } else if (field.enabled) { + console.log(field, "is enabled for ", submission); + } return submission; }); }, @@ -44,5 +50,14 @@ export default Controller.extend({ this.loadMoreSubmissions(); } }, + + showEditColumnsModal() { + const controller = showModal("admin-wizards-submissions-columns", { + model: { + fields: this.get('fields'), + submissions: this.get('submissions') + } + }); + }, }, }); diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index 5e94e31e..d9a2bafa 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -224,31 +224,33 @@ CustomWizard.reopenClass({ }) .then((result) => { if (result.wizard) { - let fields = ["username"]; + console.log(result); + let fields = [{ id: "username", label: "User"}]; let submissions = []; let wizard = result.wizard; let total = result.total; result.submissions.forEach((s) => { let submission = { - username: s.username, + username: s.user, }; - Object.keys(s.fields).forEach((f) => { - if (fields.indexOf(f) < 0) { - fields.push(f); - } - - if (fields.includes(f)) { - submission[f] = s.fields[f]; + Object.keys(s.fields).forEach((fieldId) => { + if (!fields.some(field => field.id === fieldId)) { + fields.push({ id: fieldId, label: s.fields[fieldId].label }); } + submission[fieldId] = s.fields[fieldId]; }); - submission["submitted_at"] = s.submitted_at; submissions.push(submission); }); - fields.push("submitted_at"); + let submittedAt = { + id: "submitted_at", + label: "Submitted At" + } + + fields.push(submittedAt); return { wizard, diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 index 2ff9fbf9..829d4d13 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 @@ -1,6 +1,7 @@ -import CustomWizard from "../models/custom-wizard"; -import DiscourseRoute from "discourse/routes/discourse"; import { A } from "@ember/array"; +import EmberObject from "@ember/object"; +import DiscourseRoute from "discourse/routes/discourse"; +import CustomWizard from "../models/custom-wizard"; export default DiscourseRoute.extend({ model(params) { @@ -8,9 +9,14 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { + const fields = model.fields.map((f) => { + const fieldsObject = EmberObject.create(f); + fieldsObject.enabled = true; + return fieldsObject; + }); controller.setProperties({ wizard: model.wizard, - fields: model.fields, + fields: A(fields), submissions: A(model.submissions), total: model.total, }); diff --git a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs new file mode 100644 index 00000000..a167a954 --- /dev/null +++ b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs @@ -0,0 +1,32 @@ +{{#d-modal-body title="directory.edit_columns.title"}} + {{#if loading}} + {{loading-spinner size="large"}} + {{else}} +
+ {{#each model.fields as |field|}} +
+
+ +
+
+ {{/each}} +
+ {{/if}} +{{/d-modal-body}} + + From aa928c319e8fd7da010a48c894b7535c1b41a5bb Mon Sep 17 00:00:00 2001 From: KC Maddever Date: Tue, 10 Aug 2021 20:11:41 +0800 Subject: [PATCH 019/556] DEV: add data migration to split out log fields --- ...06135416_split_custom_wizard_log_fields.rb | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 db/migrate/20210806135416_split_custom_wizard_log_fields.rb diff --git a/db/migrate/20210806135416_split_custom_wizard_log_fields.rb b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb new file mode 100644 index 00000000..b261c497 --- /dev/null +++ b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb @@ -0,0 +1,70 @@ +class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] + def change + reversible do |dir| + dir.up do + # separate wizard/action/user into their own keys + + wizard_logs = PluginStoreRow.where(" + plugin_name = 'custom_wizard_log' + ") + + if wizard_logs.exists? + wizard_logs.each do |row| + begin + log_json = JSON.parse(row.value) + rescue TypeError, JSON::ParserError + next + end + + # first three keys are wizard/action/user + + if log_json.key?('message') + attr_strs = log_json['message'].split('; ', 4) + + log_json['message'] = attr_strs.pop + + attr_strs.each do |attr_str| + key, value = attr_str.split(': ') + log_json[key] = value + end + + row.value = log_json.to_json + row.save + + end + end + end + end + dir.down do + wizard_logs = PluginStoreRow.where(" + plugin_name = 'custom_wizard_log' + ") + + if wizard_logs.exists? + wizard_logs.each do |row| + begin + log_json = JSON.parse(row.value) + rescue TypeError, JSON::ParserError + next + end + + # concatenate wizard/action/user to start of message + prefixes = log_json.extract!('wizard', 'action', 'user') + + message_prefix = prefixes.map{|k,v| "#{k}: #{v}"}.join('; ') + + if log_json.key?('message') + log_json['message'] = "#{message_prefix}; #{log_json['message']}" + else + log_json['message'] = message_prefix + end + + row.value = log_json.to_json + row.save + + end + end + end + end + end +end From b79039c2e2d98d89d0dee99413b52328666de0ca Mon Sep 17 00:00:00 2001 From: KC Maddever Date: Tue, 10 Aug 2021 21:18:02 +0800 Subject: [PATCH 020/556] DEV: save logs with split fields --- lib/custom_wizard/action.rb | 15 ++++++--------- lib/custom_wizard/log.rb | 12 +++++++++--- serializers/custom_wizard/log_serializer.rb | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 1b5770d7..79094024 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -742,14 +742,11 @@ class CustomWizard::Action end def save_log - log = "wizard: #{@wizard.id}; action: #{action['type']}; user: #{user.username}" - - if @log.any? - @log.each do |item| - log += "; #{item.to_s}" - end - end - - CustomWizard::Log.create(log) + CustomWizard::Log.create( + @wizard.id, + action['type'], + user.username, + @log.join('; ') + ) end end diff --git a/lib/custom_wizard/log.rb b/lib/custom_wizard/log.rb index fc747440..c50a5712 100644 --- a/lib/custom_wizard/log.rb +++ b/lib/custom_wizard/log.rb @@ -2,22 +2,28 @@ class CustomWizard::Log include ActiveModel::Serialization - attr_accessor :message, :date + attr_accessor :date, :wizard, :action, :user, :message PAGE_LIMIT = 100 def initialize(attrs) - @message = attrs['message'] @date = attrs['date'] + @wizard = attrs['wizard'] + @action = attrs['action'] + @user = attrs['user'] + @message = attrs['message'] end - def self.create(message) + def self.create(wizard, action, user, message) log_id = SecureRandom.hex(12) PluginStore.set('custom_wizard_log', log_id.to_s, { date: Time.now, + wizard: wizard, + action: action, + user: user, message: message } ) diff --git a/serializers/custom_wizard/log_serializer.rb b/serializers/custom_wizard/log_serializer.rb index e521c573..c4683ba8 100644 --- a/serializers/custom_wizard/log_serializer.rb +++ b/serializers/custom_wizard/log_serializer.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true class CustomWizard::LogSerializer < ApplicationSerializer - attributes :message, :date + attributes :date, :wizard, :action, :user, :message end From 35ff172967eaa4964e9e919688dc090dd7a29cce Mon Sep 17 00:00:00 2001 From: KC Maddever Date: Tue, 24 Aug 2021 20:41:51 +0800 Subject: [PATCH 021/556] DEV: specify which fields to split in log data migration --- ...06135416_split_custom_wizard_log_fields.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/db/migrate/20210806135416_split_custom_wizard_log_fields.rb b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb index b261c497..2e7b7d21 100644 --- a/db/migrate/20210806135416_split_custom_wizard_log_fields.rb +++ b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb @@ -16,16 +16,23 @@ class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] next end - # first three keys are wizard/action/user - if log_json.key?('message') - attr_strs = log_json['message'].split('; ', 4) + if log_json.key?('message') and log_json['message'].is_a? String - log_json['message'] = attr_strs.pop + attr_strs = [] + + # assumes no whitespace in the values + attr_strs << log_json['message'].slice!(/(wizard: \S*; )/, 1) + attr_strs << log_json['message'].slice!(/(action: \S*; )/, 1) + attr_strs << log_json['message'].slice!(/(user: \S*; )/, 1) attr_strs.each do |attr_str| - key, value = attr_str.split(': ') - log_json[key] = value + if attr_str.is_a? String + attr_str.gsub!(/[;]/ ,"") + key, value = attr_str.split(': ') + value.strip! if value + log_json[key] = value ? value : '' + end end row.value = log_json.to_json From c394656ed877469a90ae86106cbcbbd82fd72751 Mon Sep 17 00:00:00 2001 From: KC Maddever Date: Mon, 30 Aug 2021 21:45:57 +0800 Subject: [PATCH 022/556] DEV: update tests for split log fields --- spec/components/custom_wizard/log_spec.rb | 6 +++--- spec/requests/custom_wizard/admin/logs_controller_spec.rb | 6 +++--- spec/serializers/custom_wizard/log_serializer_spec.rb | 7 +++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/components/custom_wizard/log_spec.rb b/spec/components/custom_wizard/log_spec.rb index 30fd0173..62f2e6df 100644 --- a/spec/components/custom_wizard/log_spec.rb +++ b/spec/components/custom_wizard/log_spec.rb @@ -3,9 +3,9 @@ require_relative '../../plugin_helper' describe CustomWizard::Log do before do - CustomWizard::Log.create("First log message") - CustomWizard::Log.create("Second log message") - CustomWizard::Log.create("Third log message") + CustomWizard::Log.create('first-test-wizard', 'perform_first_action', 'first_test_user', 'First log message') + CustomWizard::Log.create('second-test-wizard', 'perform_second_action', 'second_test_user', 'Second log message') + CustomWizard::Log.create('third-test-wizard', 'perform_third_action', 'third_test_user', 'Third log message') end it "creates logs" do diff --git a/spec/requests/custom_wizard/admin/logs_controller_spec.rb b/spec/requests/custom_wizard/admin/logs_controller_spec.rb index 28b7d785..5aaf9578 100644 --- a/spec/requests/custom_wizard/admin/logs_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/logs_controller_spec.rb @@ -5,9 +5,9 @@ describe CustomWizard::AdminLogsController do fab!(:admin_user) { Fabricate(:user, admin: true) } before do - CustomWizard::Log.create("First log message") - CustomWizard::Log.create("Second log message") - CustomWizard::Log.create("Third log message") + CustomWizard::Log.create('first-test-wizard', 'perform_first_action', 'first_test_user', 'First log message') + CustomWizard::Log.create('second-test-wizard', 'perform_second_action', 'second_test_user', 'Second log message') + CustomWizard::Log.create('third-test-wizard', 'perform_third_action', 'third_test_user', 'Third log message') sign_in(admin_user) end diff --git a/spec/serializers/custom_wizard/log_serializer_spec.rb b/spec/serializers/custom_wizard/log_serializer_spec.rb index bde16199..b452b9c5 100644 --- a/spec/serializers/custom_wizard/log_serializer_spec.rb +++ b/spec/serializers/custom_wizard/log_serializer_spec.rb @@ -6,14 +6,17 @@ describe CustomWizard::LogSerializer do fab!(:user) { Fabricate(:user) } it 'should return log attributes' do - CustomWizard::Log.create("First log message") - CustomWizard::Log.create("Second log message") + CustomWizard::Log.create('first-test-wizard', 'perform_first_action', 'first_test_user', 'First log message') + CustomWizard::Log.create('second-test-wizard', 'perform_second_action', 'second_test_user', 'Second log message') json_array = ActiveModel::ArraySerializer.new( CustomWizard::Log.list(0), each_serializer: CustomWizard::LogSerializer ).as_json expect(json_array.length).to eq(2) + expect(json_array[0][:wizard]).to eq("second-test-wizard") + expect(json_array[0][:action]).to eq("perform_second_action") + expect(json_array[0][:user]).to eq("second_test_user") expect(json_array[0][:message]).to eq("Second log message") end end From 399cc9c2205fed2ebdd7b82a5ffe5d9fbe97f4cd Mon Sep 17 00:00:00 2001 From: KC Maddever Date: Tue, 31 Aug 2021 14:43:21 +0800 Subject: [PATCH 023/556] DEV: rubocop --- ...10806135416_split_custom_wizard_log_fields.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/db/migrate/20210806135416_split_custom_wizard_log_fields.rb b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb index 2e7b7d21..984a7a23 100644 --- a/db/migrate/20210806135416_split_custom_wizard_log_fields.rb +++ b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb @@ -1,11 +1,12 @@ +# frozen_string_literal: true class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] def change reversible do |dir| dir.up do - # separate wizard/action/user into their own keys + # separate wizard/action/user into their own keys - wizard_logs = PluginStoreRow.where(" - plugin_name = 'custom_wizard_log' + wizard_logs = PluginStoreRow.where(" + plugin_name = 'custom_wizard_log' ") if wizard_logs.exists? @@ -16,8 +17,7 @@ class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] next end - - if log_json.key?('message') and log_json['message'].is_a? String + if log_json.key?('message') && log_json['message'].is_a?(String) attr_strs = [] @@ -28,7 +28,7 @@ class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] attr_strs.each do |attr_str| if attr_str.is_a? String - attr_str.gsub!(/[;]/ ,"") + attr_str.gsub!(/[;]/ , "") key, value = attr_str.split(': ') value.strip! if value log_json[key] = value ? value : '' @@ -44,7 +44,7 @@ class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] end dir.down do wizard_logs = PluginStoreRow.where(" - plugin_name = 'custom_wizard_log' + plugin_name = 'custom_wizard_log' ") if wizard_logs.exists? @@ -58,7 +58,7 @@ class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] # concatenate wizard/action/user to start of message prefixes = log_json.extract!('wizard', 'action', 'user') - message_prefix = prefixes.map{|k,v| "#{k}: #{v}"}.join('; ') + message_prefix = prefixes.map { |k, v| "#{k}: #{v}" }.join('; ') if log_json.key?('message') log_json['message'] = "#{message_prefix}; #{log_json['message']}" From 280d2ffe54e4a9e3debe95deafa7bc419665707b Mon Sep 17 00:00:00 2001 From: Keegan George Date: Tue, 31 Aug 2021 13:29:30 -0700 Subject: [PATCH 024/556] IMPROVE: Make edit columns adjust submissions as well --- .../admin-wizards-submissions-show.js.es6 | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index 40a08f4f..2667b6b2 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -31,16 +31,20 @@ export default Controller.extend({ @discourseComputed('submissions', 'fields.@each.enabled') displaySubmissions(submissions, fields) { - return submissions.map(submission => { - let field = fields.find(f => Object.keys(submission).includes(f.id)); - if (!field.enabled) { - // insert field / submission deletion code here: - console.log(field, "is not enabled for ", submission); - } else if (field.enabled) { - console.log(field, "is enabled for ", submission); - } - return submission; + let result = []; + + submissions.forEach(submission => { + let sub = {}; + + Object.keys(submission).forEach(fieldId => { + if (fields.some(f => f.id === fieldId && f.enabled)) { + sub[fieldId] = submission[fieldId]; + } + }); + result.push(sub); }); + + return result; }, actions: { From 6b1e7568c154e545a4bdaabbef0259ddb90ae5ba Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 1 Sep 2021 10:19:00 +0800 Subject: [PATCH 025/556] Improve PRO feature approach --- .../wizard-realtime-validations.js.es6 | 3 +- .../admin-wizards-wizard-show.js.es6 | 7 +- .../components/wizard-custom-field.hbs | 94 ++++++------- .../components/wizard-pro-subscription.hbs | 9 +- .../wizard-realtime-validations.hbs | 98 ++++++------- assets/stylesheets/common/wizard-admin.scss | 40 +++++- config/locales/client.en.yml | 2 +- config/locales/server.en.yml | 4 +- config/routes.rb | 2 +- controllers/custom_wizard/admin/pro.rb | 28 ++-- controllers/custom_wizard/wizard.rb | 5 + ...o_status.rb => update_pro_subscription.rb} | 2 +- lib/custom_wizard/action.rb | 6 - lib/custom_wizard/builder.rb | 6 - lib/custom_wizard/custom_field.rb | 6 + lib/custom_wizard/mapper.rb | 3 +- lib/custom_wizard/pro.rb | 131 +++++++++++++----- lib/custom_wizard/pro/authentication.rb | 78 +++-------- lib/custom_wizard/pro/subscription.rb | 46 +----- lib/custom_wizard/validators/template.rb | 33 ++++- plugin.rb | 16 +-- serializers/custom_wizard/pro_serializer.rb | 26 +--- 22 files changed, 331 insertions(+), 314 deletions(-) rename jobs/scheduled/{update_pro_status.rb => update_pro_subscription.rb} (89%) diff --git a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 index 8332b86e..04123e3d 100644 --- a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 +++ b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 @@ -6,7 +6,8 @@ import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; export default Component.extend({ - classNames: ["realtime-validations"], + classNames: ["realtime-validations", "setting", "full", "pro"], + @discourseComputed timeUnits() { return ["days", "weeks", "months", "years"].map((unit) => { diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 index 332efedd..edb06ad9 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 @@ -77,7 +77,12 @@ export default Controller.extend({ wizard .save(opts) .then((result) => { - this.send("afterSave", result.wizard_id); + console.log(result) + if (result.wizard_id) { + this.send("afterSave", result.wizard_id); + } else if (result.errors) { + this.set('error', result.errors.join(', ')); + } }) .catch((result) => { let errorType = "failed"; diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 9237cf62..afdbd767 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -220,60 +220,54 @@ options=fieldConditionOptions}} -{{/if}} + +
+
+ + {{i18n "admin.wizard.pro.label"}} +
-{{#if showAdvanced}} - {{wizard-advanced-toggle showAdvanced=field.showAdvanced}} +
+ {{wizard-mapper + inputs=field.index + options=fieldIndexOptions}} +
+
- {{#if field.showAdvanced}} -
- -
-
- -
- -
- {{wizard-mapper - inputs=field.index - options=fieldIndexOptions}} -
+ {{#if isCategory}} +
+
+ + {{i18n "admin.wizard.pro.label"}}
- - {{#if isCategory}} -
-
- -
- -
- {{combo-box - value=field.property - content=categoryPropertyTypes - onChange=(action (mut field.property)) - options=(hash - none="admin.wizard.selector.placeholder.property" - )}} -
-
- {{/if}} - -
-
- -
-
- {{input - name="key" - value=field.key - class="medium" - placeholderKey="admin.wizard.translation_placeholder"}} -
+ +
+ {{combo-box + value=field.property + content=categoryPropertyTypes + onChange=(action (mut field.property)) + options=(hash + none="admin.wizard.selector.placeholder.property" + )}}
- - {{#if validations}} - {{wizard-realtime-validations field=field validations=validations}} - {{/if}}
{{/if}} + +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{input + name="key" + value=field.key + class="medium" + placeholderKey="admin.wizard.translation_placeholder"}} +
+
+ + {{#if validations}} + {{wizard-realtime-validations field=field validations=validations}} + {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs index 72631bfd..2bebc9ed 100644 --- a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs @@ -21,8 +21,11 @@ {{#if subscribed}}
{{stateLabel}}
-
- {{{i18n 'admin.wizard.pro.subscription.last_updated' updated_at=(format-date subscription.updated_at leaveAgo="true")}}} -
+ + {{#if subscription.updated_at}} +
+ {{{i18n 'admin.wizard.pro.subscription.last_updated' updated_at=(format-date subscription.updated_at leaveAgo="true")}}} +
+ {{/if}}
{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs index cd1298a9..0cc34f35 100644 --- a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs @@ -1,50 +1,54 @@ -

{{i18n "admin.wizard.field.validations.header"}}

- -
    - {{#each-in field.validations as |type props|}} -
  • - -

    {{i18n (concat "admin.wizard.field.validations." type)}}

    - {{input type="checkbox" checked=props.status}} - {{i18n "admin.wizard.field.validations.enabled"}} -
    -
    -
    -
    - +
    + + {{i18n "admin.wizard.pro.label"}} +
    +
    +
      + {{#each-in field.validations as |type props|}} +
    • + +

      {{i18n (concat "admin.wizard.field.validations." type)}}

      + {{input type="checkbox" checked=props.status}} + {{i18n "admin.wizard.field.validations.enabled"}} +
      +
      +
      +
      + +
      +
      + {{category-selector + categories=(get this (concat "validationBuffer." type ".categories")) + onChange=(action "updateValidationCategories" type props) + class="wizard"}} +
      -
      - {{category-selector - categories=(get this (concat "validationBuffer." type ".categories")) - onChange=(action "updateValidationCategories" type props) - class="wizard"}} +
      +
      + +
      +
      + {{input type="number" class="time-n-value" value=props.time_n_value}} + {{combo-box + value=(readonly props.time_unit) + content=timeUnits + class="time-unit-selector" + onChange=(action (mut props.time_unit))}} +
      +
      +
      +
      + +
      +
      + {{radio-button name=(concat type field.id) value="above" selection=props.position}} + {{i18n "admin.wizard.field.validations.above"}} + {{radio-button name=(concat type field.id) value="below" selection=props.position}} + {{i18n "admin.wizard.field.validations.below"}} +
      -
      -
      - -
      -
      - {{input type="number" class="time-n-value" value=props.time_n_value}} - {{combo-box - value=(readonly props.time_unit) - content=timeUnits - class="time-unit-selector" - onChange=(action (mut props.time_unit))}} -
      -
      -
      -
      - -
      -
      - {{radio-button name=(concat type field.id) value="above" selection=props.position}} - {{i18n "admin.wizard.field.validations.above"}} - {{radio-button name=(concat type field.id) value="below" selection=props.position}} - {{i18n "admin.wizard.field.validations.below"}} -
      -
      -
      -
    • - {{/each-in}} -
    +
  • + {{/each-in}} +
+
\ No newline at end of file diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index cca03c74..178dbc81 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -694,27 +694,59 @@ } } -.realtime-validations > ul { +.admin-wizard-container.settings .realtime-validations .setting-value > ul { list-style: none; margin: 0; + width: 100%; + display: flex; + flex-wrap: wrap; > li { background-color: var(--primary-low); padding: 1em; margin: 0 0 1em 0; + + .setting-title { + display: flex; + align-items: center; - input { - margin-bottom: 0; + h4 { + margin: 0 15px 0 0; + } + + input[type="checkbox"] { + margin: 0 5px 0 0; + } + } + + .setting-label { + width: 100px; + } + + .setting-value { + display: flex; + align-items: center; + + .input .select-kit, + > .select-kit { + max-width: unset !important; + } + + > span { + margin-right: 1em; + } } } } .validation-container { display: flex; + flex-direction: column; padding: 1em 0; .validation-section { - width: 250px; + min-width: 250px; + margin: .5em 0; } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4a2bf28f..bfea46ce 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -192,7 +192,7 @@ en: label: "Format" instructions: "Moment.js format" validations: - header: "Realtime Validations" + header: "Validations" enabled: "Enabled" similar_topics: "Similar Topics" position: "Position" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 86968fda..fffa01cc 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -17,7 +17,7 @@ en: name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})" name_already_taken: "'%{name}' is already taken as a custom field name" save_default: "Failed to save custom field '%{name}'" - pro_required: "PRO Actions require a PRO Subscription" + pro_type: "%{type} custom fields require PRO Subscription" field: too_short: "%{label} must be at least %{min} characters" @@ -50,7 +50,7 @@ en: required: "%{property} is required" conflict: "Wizard with id '%{wizard_id}' already exists" after_time: "After time setting is invalid" - pro: "%{property} is PRO only" + pro: "%{type} %{property} is PRO only" site_settings: custom_wizard_enabled: "Enable custom wizards." diff --git a/config/routes.rb b/config/routes.rb index 94cc5858..abe36479 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -47,7 +47,7 @@ Discourse::Application.routes.append do get 'admin/wizards/pro' => 'admin_pro#index' get 'admin/wizards/pro/authorize' => 'admin_pro#authorize' get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback' - delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy' + delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy_authentication' post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription' end end diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb index 9f32045e..15e9e50f 100644 --- a/controllers/custom_wizard/admin/pro.rb +++ b/controllers/custom_wizard/admin/pro.rb @@ -4,27 +4,27 @@ class CustomWizard::AdminProController < CustomWizard::AdminController skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback] def index - render_serialized(CustomWizard::Pro.new, CustomWizard::ProSerializer, root: false) + render_serialized(pro, CustomWizard::ProSerializer, root: false) end def authorize request_id = SecureRandom.hex(32) cookies[:user_api_request_id] = request_id - redirect_to CustomWizard::Pro.auth_request(current_user.id, request_id).to_s + redirect_to pro.authentication_request(current_user.id, request_id).to_s end def authorize_callback payload = params[:payload] request_id = cookies[:user_api_request_id] - CustomWizard::Pro.auth_response(request_id, payload) - CustomWizard::Pro.update_subscription + pro.authentication_response(request_id, payload) + pro.update_subscription redirect_to '/admin/wizards/pro' end - def destroy - if CustomWizard::ProAuthentication.destroy + def destroy_authentication + if pro.destroy_authentication render json: success_json else render json: failed_json @@ -32,15 +32,17 @@ class CustomWizard::AdminProController < CustomWizard::AdminController end def update_subscription - if CustomWizard::Pro.update - render json: success_json.merge( - subscription: CustomWizard::ProSubscriptionSerializer.new( - CustomWizard::ProSubscription.new, - root: false - ) - ) + if pro.update_subscription + subscription = CustomWizard::ProSubscriptionSerializer.new(pro.subscription, root: false) + render json: success_json.merge(subscription: subscription) else render json: failed_json end end + + protected + + def pro + @pro ||= CustomWizard::Pro.new + end end \ No newline at end of file diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index e0cf669d..804fc422 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -5,6 +5,7 @@ class CustomWizard::WizardController < ::ApplicationController layout 'wizard' before_action :ensure_plugin_enabled + before_action :update_pro_subscription helper_method :wizard_page_title helper_method :wizard_theme_id helper_method :wizard_theme_lookup @@ -81,4 +82,8 @@ class CustomWizard::WizardController < ::ApplicationController redirect_to path("/") end end + + def update_pro_subscription + CustomWizard::Pro.update_subscription + end end diff --git a/jobs/scheduled/update_pro_status.rb b/jobs/scheduled/update_pro_subscription.rb similarity index 89% rename from jobs/scheduled/update_pro_status.rb rename to jobs/scheduled/update_pro_subscription.rb index 6f947304..d00b2560 100644 --- a/jobs/scheduled/update_pro_status.rb +++ b/jobs/scheduled/update_pro_subscription.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled - every 10.minutes + every 1.hour def execute(args) CustomWizard::Pro.update_subscription diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 1cadbb5b..74dc4680 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -14,15 +14,9 @@ class CustomWizard::Action @submission = opts[:submission] @log = [] @result = CustomWizard::ActionResult.new - @pro = CustomWizard::Pro.new end def perform - if pro_actions.include?(action['type']) && !@pro.subscribed? - log_error(I18n.t("wizard.custom_field.error.pro_required")) - return - end - ActiveRecord::Base.transaction do self.send(action['type'].to_sym) end diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 78a960b9..2ecdf12d 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -6,7 +6,6 @@ class CustomWizard::Builder @template = CustomWizard::Template.create(wizard_id) return nil if @template.nil? @wizard = CustomWizard::Wizard.new(template.data, user) - @pro = CustomWizard::Pro.new end def self.sorted_handlers @@ -226,11 +225,6 @@ class CustomWizard::Builder end def check_condition(template) - unless @pro.subscribed? - CustomWizard::Log.create(I18n.t("wizard.custom_field.error.pro_required")) - return false - end - if template['condition'].present? result = CustomWizard::Mapper.new( inputs: template['condition'], diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index 9cc185ba..cc20ee95 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -37,6 +37,8 @@ class ::CustomWizard::CustomField send("#{attr}=", value) end end + + @pro = CustomWizard::Pro.new end def save @@ -92,6 +94,10 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end + if attr == 'type' && value == 'json' && !@pro.subscribed? + add_error(I18n.t("wizard.custom_field.error.pro_type", type: value)) + end + if attr == 'name' unless value.is_a?(String) add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index 0c3543cf..b418b4b9 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -47,6 +47,7 @@ class CustomWizard::Mapper @data = params[:data] || {} @user = params[:user] @opts = params[:opts] || {} + @pro = CustomWizard::Pro.new end def perform @@ -251,7 +252,7 @@ class CustomWizard::Mapper end end - if opts[:template] + if @pro.subscribed? && opts[:template] template = Liquid::Template.parse(string) string = template.render(data) end diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb index f54dd92d..3726e9b4 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/pro.rb @@ -1,12 +1,22 @@ # frozen_string_literal: true class CustomWizard::Pro - attr_reader :authentication, - :subscription + include ActiveModel::Serialization + + attr_accessor :authentication, + :subscription def initialize - @authentication = CustomWizard::ProAuthentication.new - @subscription = CustomWizard::ProSubscription.new + @authentication = CustomWizard::ProAuthentication.new(get_authentication) + @subscription = CustomWizard::ProSubscription.new(get_subscription) + end + + def authorized? + @authentication.active? + end + + def subscribed? + @subscription.active? end def server @@ -21,12 +31,8 @@ class CustomWizard::Pro "custom-wizard" end - def authorized? - @authentication.active? - end - - def subscribed? - @subscription.active? + def scope + "discourse-subscription-server:user_subscription" end def update_subscription @@ -45,28 +51,35 @@ class CustomWizard::Pro return false end - return @subscription.update(data) + return false unless data && data.is_a?(Hash) + subscriptions = data[:subscriptions] + + if subscriptions.present? + subscription = subscriptions.first + type = subscription[:price_nickname] + + @subscription = set_subscription(type) + return true + end end end - @subscription.destroy - false + remove_subscription end - def destroy - @authentication.destroy + def destroy_subscription + remove_subscription end - def auth_request(user_id, request_id) + def authentication_request(user_id, request_id) keys = @authentication.generate_keys(user_id, request_id) - params = { public_key: keys.public_key, nonce: keys.nonce, client_id: @authentication.client_id, auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback", application_name: SiteSetting.title, - scopes: "discourse-subscription-server:user_subscription" + scopes: scope } uri = URI.parse("https://#{server}/user-api-key/new") @@ -74,26 +87,24 @@ class CustomWizard::Pro uri.to_s end - def auth_response(request_id, payload) + def authentication_response(request_id, payload) data = @authentication.decrypt_payload(request_id, payload) return unless data.is_a?(Hash) && data[:key] && data[:user_id] - @authentication.update(data) + + api_key = data[:key] + user_id = data[:user_id] + user = User.find(user_id) + + if user&.admin + @authentication = set_authentication(api_key, user.id) + true + else + false + end end - def self.update - self.new.update - end - - def self.destroy - self.new.destroy - end - - def self.generate_request - self.new.generate_request - end - - def self.handle_response - self.new.handle_response + def destroy_authentication + remove_authentication end def self.subscribed? @@ -103,4 +114,56 @@ class CustomWizard::Pro def self.namespace "custom_wizard_pro" end + + private + + def subscription_db_key + "subscription" + end + + def authentication_db_key + "authentication" + end + + def get_subscription + raw = PluginStore.get(self.class.namespace, subscription_db_key) + + if raw.present? + OpenStruct.new( + type: raw['type'], + updated_at: raw['updated_at'] + ) + end + end + + def remove_subscription + PluginStore.remove(self.class.namespace, subscription_db_key) + end + + def set_subscription(type) + PluginStore.set(CustomWizard::Pro.namespace, subscription_db_key, type: type, updated_at: Time.now) + CustomWizard::ProSubscription.new(get_subscription) + end + + def get_authentication + raw = PluginStore.get(self.class.namespace, authentication_db_key) + OpenStruct.new( + key: raw && raw['key'], + auth_by: raw && raw['auth_by'], + auth_at: raw && raw['auth_at'] + ) + end + + def set_authentication(key, user_id) + PluginStore.set(self.class.namespace, authentication_db_key, + key: key, + auth_by: user_id, + auth_at: Time.now + ) + CustomWizard::ProAuthentication.new(get_authentication) + end + + def remove_authentication + PluginStore.remove(self.class.namespace, authentication_db_key) + end end \ No newline at end of file diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/pro/authentication.rb index 0d89ab06..0f3546eb 100644 --- a/lib/custom_wizard/pro/authentication.rb +++ b/lib/custom_wizard/pro/authentication.rb @@ -6,12 +6,13 @@ class CustomWizard::ProAuthentication :auth_at, :api_key - def initialize - api = get_api_key + def initialize(auth) + if auth + @api_key = auth.key + @auth_at = auth.auth_at + @auth_by = auth.auth_by + end - @api_key = api.key - @auth_at = api.auth_at - @auth_by = api.auth_by @client_id = get_client_id || set_client_id end @@ -19,22 +20,6 @@ class CustomWizard::ProAuthentication @api_key.present? end - def update(data) - api_key = data[:key] - user_id = data[:user_id] - user = User.find(user_id) - - if user&.admin - set_api_key(api_key, user.id) - else - false - end - end - - def destroy - remove - end - def generate_keys(user_id, request_id) rsa = OpenSSL::PKey::RSA.generate(2048) nonce = SecureRandom.hex(32) @@ -63,50 +48,15 @@ class CustomWizard::ProAuthentication data end - + private - def api_key_db_key - "api_key" - end - - def api_client_id_db_key - "api_client_id" - end - def keys_db_key "keys" end - def get_api_key - raw = PluginStore.get(CustomWizard::Pro.namespace, api_key_db_key) - OpenStruct.new( - key: raw && raw['key'], - auth_by: raw && raw['auth_by'], - auth_at: raw && raw['auth_at'] - ) - end - - def set_api_key(key, user_id) - PluginStore.set(CustomWizard::Pro.namespace, api_key_db_key, - key: key, - auth_by: user_id, - auth_at: Time.now - ) - end - - def remove - PluginStore.remove(CustomWizard::Pro.namespace, api_key_db_key) - end - - def get_client_id - PluginStore.get(CustomWizard::Pro.namespace, api_client_id_db_key) - end - - def set_client_id - client_id = SecureRandom.hex(32) - PluginStore.set(CustomWizard::Pro.namespace, api_client_id_db_key, client_id) - client_id + def client_id_db_key + "client_id" end def set_keys(request_id, user_id, rsa, nonce) @@ -129,4 +79,14 @@ class CustomWizard::ProAuthentication def delete_keys(request_id) PluginStore.remove(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") end + + def get_client_id + PluginStore.get(CustomWizard::Pro.namespace, client_id_db_key) + end + + def set_client_id + client_id = SecureRandom.hex(32) + PluginStore.set(CustomWizard::Pro.namespace, client_id_db_key, client_id) + client_id + end end \ No newline at end of file diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/pro/subscription.rb index f82b58df..a5782357 100644 --- a/lib/custom_wizard/pro/subscription.rb +++ b/lib/custom_wizard/pro/subscription.rb @@ -4,12 +4,10 @@ class CustomWizard::ProSubscription attr_reader :type, :updated_at - def initialize - raw = get - - if raw - @type = raw['type'] - @updated_at = raw['updated_at'] + def initialize(subscription) + if subscription + @type = subscription.type + @updated_at = subscription.updated_at end end @@ -18,40 +16,6 @@ class CustomWizard::ProSubscription end def active? - types.include?(type) && updated_at.to_datetime > (Date.today - 15.minutes).to_datetime - end - - def update(data) - return false unless data && data.is_a?(Hash) - subscriptions = data[:subscriptions] - - if subscriptions.present? - subscription = subscriptions.first - type = subscription[:price_nickname] - - set(type) - end - end - - def destroy - remove - end - - private - - def key - "custom_wizard_pro_subscription" - end - - def set(type) - PluginStore.set(CustomWizard::Pro.namespace, key, type: type, updated_at: Time.now) - end - - def get - PluginStore.get(CustomWizard::Pro.namespace, key) - end - - def remove - PluginStore.remove(CustomWizard::Pro.namespace, key) + types.include?(type) && updated_at.to_datetime > (Time.zone.now - 2.hours).to_datetime end end \ No newline at end of file diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index deaa5119..180958ba 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -31,6 +31,7 @@ class CustomWizard::TemplateValidator if data[:actions].present? data[:actions].each do |action| + validate_pro_action(action) check_required(action, :action) end end @@ -53,15 +54,31 @@ class CustomWizard::TemplateValidator def self.pro { - step: ['condition'], - field: ['conition'] + wizard: {}, + step: { + condition: 'present', + index: 'conditional' + }, + field: { + condition: 'present', + index: 'conditional' + }, + action: { + type: %w[ + send_message + create_category + create_group + watch_categories + send_to_api + ] + } } end private def check_required(object, type) - CustomWizard::TemplateValidator.required[type].each do |property| + self.class.required[type].each do |property| if object[property].blank? errors.add :base, I18n.t("wizard.validation.required", property: property) end @@ -69,9 +86,13 @@ class CustomWizard::TemplateValidator end def validate_pro(object, type) - CustomWizard::TemplateValidator.required[type].each do |property| - if object[property].present? && !@pro.subscribed? - errors.add :base, I18n.t("wizard.validation.pro", property: property) + self.class.pro[type].each do |property, pro_type| + is_pro = (pro_type === 'present' && object[property].present?) || + (pro_type === 'conditional' && object[property].is_a?(Hash)) || + (pro_type.is_a?(Array) && pro_type.includes?(object[property])) + + if is_pro && @pro.subscribed? + errors.add :base, I18n.t("wizard.validation.pro", type: type.to_s, property: property) end end end diff --git a/plugin.rb b/plugin.rb index 7c056b5d..b2c6d54c 100644 --- a/plugin.rb +++ b/plugin.rb @@ -69,7 +69,7 @@ after_initialize do ../controllers/custom_wizard/realtime_validations.rb ../jobs/regular/refresh_api_access_token.rb ../jobs/regular/set_after_time_wizard.rb - ../jobs/scheduled/update_pro_status.rb + ../jobs/scheduled/update_pro_subscription.rb ../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/action_result.rb @@ -111,9 +111,9 @@ after_initialize do ../serializers/custom_wizard/log_serializer.rb ../serializers/custom_wizard/submission_serializer.rb ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb - ../serializers/custom_wizard/pro_serializer.rb ../serializers/custom_wizard/pro/authentication_serializer.rb ../serializers/custom_wizard/pro/subscription_serializer.rb + ../serializers/custom_wizard/pro_serializer.rb ../extensions/extra_locales_controller.rb ../extensions/invites_controller.rb ../extensions/users_controller.rb @@ -126,18 +126,6 @@ after_initialize do Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty) - class CustomWizard::UnpermittedOverride < StandardError; end - - CustomWizard.constants.each do |class_name| - klass = CustomWizard.const_get(class_name) - next if !klass.is_a?(Class) || klass.superclass.name.to_s.split("::").first == 'CustomWizard' - - klass.define_singleton_method(:prepend) { |klass| raise CustomWizard::UnpermittedOverride.new } - klass.define_singleton_method(:include) { |klass| raise CustomWizard::UnpermittedOverride.new } - klass.define_singleton_method(:define_method) { |name, &block| raise CustomWizard::UnpermittedOverride.new } - klass.define_singleton_method(:define_singleton_method) { |name, &block| raise CustomWizard::UnpermittedOverride.new } - end - add_class_method(:wizard, :user_requires_completion?) do |user| wizard_result = self.new(user).requires_completion? return wizard_result if wizard_result diff --git a/serializers/custom_wizard/pro_serializer.rb b/serializers/custom_wizard/pro_serializer.rb index 5b351f29..a9623974 100644 --- a/serializers/custom_wizard/pro_serializer.rb +++ b/serializers/custom_wizard/pro_serializer.rb @@ -1,26 +1,6 @@ # frozen_string_literal: true class CustomWizard::ProSerializer < ApplicationSerializer - attributes :server, - :authentication, - :subscription - - def server - CustomWizard::ProSubscription::SUBSCRIPTION_SERVER - end - - def authentication - if object.authentication - CustomWizard::ProAuthenticationSerializer.new(object.authentication, root: false) - else - nil - end - end - - def subscription - if object.subscription - CustomWizard::ProSubscriptionSerializer.new(object.subscription, root: false) - else - nil - end - end + attributes :server + has_one :authentication, serializer: CustomWizard::ProAuthenticationSerializer, embed: :objects + has_one :subscription, serializer: CustomWizard::ProSubscriptionSerializer, embed: :objects end \ No newline at end of file From 0e5fc756df74b61db0c2f5b967827c0b7a73fdfc Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 1 Sep 2021 10:46:52 +0800 Subject: [PATCH 026/556] Fix merge issues --- lib/custom_wizard/submission.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/custom_wizard/submission.rb b/lib/custom_wizard/submission.rb index 050ec000..89de50bf 100644 --- a/lib/custom_wizard/submission.rb +++ b/lib/custom_wizard/submission.rb @@ -45,7 +45,7 @@ class CustomWizard::Submission validate submission_list = self.class.list(wizard, user_id: user.id) - submissions = submission_list.select { |submission| submission.id != self.id } + submissions = submission_list.submissions.select { |submission| submission.id != self.id } self.updated_at = Time.now.iso8601 submissions.push(self) @@ -112,7 +112,7 @@ class CustomWizard::Submission def self.cleanup_incomplete_submissions(wizard) user_id = wizard.user.id all_submissions = list(wizard, user_id: user_id) - sorted_submissions = all_submissions.sort_by do |submission| + sorted_submissions = all_submissions.submissions.sort_by do |submission| zero_epoch_time = DateTime.strptime("0", '%s') [ submission.submitted_at ? Time.iso8601(submission.submitted_at) : zero_epoch_time, @@ -132,7 +132,7 @@ class CustomWizard::Submission PluginStore.set("#{wizard.id}_#{KEY}", user_id, valid_data) end - def self.list(wizard, user_id: nil, order_by: nil) + def self.list(wizard, user_id: nil, order_by: nil, page: nil) params = { plugin_name: "#{wizard.id}_#{KEY}" } params[:key] = user_id if user_id.present? From 403f063b0f1c2799df47784f8f52f68fb19f11dc Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 1 Sep 2021 11:10:49 +0800 Subject: [PATCH 027/556] Fix failing specs --- .../custom_wizard/submission_spec.rb | 69 ++++++++++--------- .../custom_wizard/wizard_controller_spec.rb | 4 +- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index a9caaa0a..4e8d86c0 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -13,39 +13,44 @@ describe CustomWizard::Submission do before do CustomWizard::Template.save(template_json, skip_jobs: true) - - template_json_2 = template_json.dup - template_json_2["id"] = "super_mega_fun_wizard_2" - CustomWizard::Template.save(template_json_2, skip_jobs: true) - @wizard = CustomWizard::Wizard.create(template_json["id"], user) - @wizard2 = CustomWizard::Wizard.create(template_json["id"], user2) - @wizard3 = CustomWizard::Wizard.create(template_json_2["id"], user) - @count = CustomWizard::Submission::PAGE_LIMIT + 20 - - @count.times do |index| - described_class.new(@wizard, step_1_field_1: "I am user submission #{index + 1}").save - end - described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save - described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save + described_class.new(@wizard, step_1_field_1: "I am user submission").save end it "saves a user's submission" do expect( described_class.get(@wizard, user.id).fields["step_1_field_1"] - ).to eq("I am user submission #{@count}") + ).to eq("I am user submission") end - it "list submissions by wizard" do - expect(described_class.list(@wizard).total).to eq(@count + 1) - end + context "#list" do + before do + template_json_2 = template_json.dup + template_json_2["id"] = "super_mega_fun_wizard_2" + CustomWizard::Template.save(template_json_2, skip_jobs: true) - it "list submissions by wizard and user" do - expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count) - end + @wizard2 = CustomWizard::Wizard.create(template_json["id"], user2) + @wizard3 = CustomWizard::Wizard.create(template_json_2["id"], user) + @count = CustomWizard::Submission::PAGE_LIMIT + 20 - it "paginates submission lists" do - expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 1) - CustomWizard::Submission::PAGE_LIMIT) + @count.times do |index| + described_class.new(@wizard, step_1_field_1: "I am user submission #{index + 1}").save + end + described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save + described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save + end + + it "list submissions by wizard" do + expect(described_class.list(@wizard).total).to eq(@count + 2) + end + + it "list submissions by wizard and user" do + expect(described_class.list(@wizard, user_id: user.id).total).to eq(@count + 1) + end + + it "paginates submission lists" do + expect(described_class.list(@wizard, page: 1).submissions.size).to eq((@count + 2) - CustomWizard::Submission::PAGE_LIMIT) + end end context "#cleanup_incomplete_submissions" do @@ -54,10 +59,10 @@ describe CustomWizard::Submission do described_class.new(@wizard, step_1_field_1: "I am the second submission").save builder = CustomWizard::Builder.new(@wizard.id, @wizard.user) builder.build - sub_list = described_class.list(@wizard, user_id: @wizard.user.id) + submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions - expect(sub_list.length).to eq(1) - expect(sub_list.first.fields["step_1_field_1"]).to eq("I am the second submission") + expect(submissions.length).to eq(1) + expect(submissions.first.fields["step_1_field_1"]).to eq("I am the second submission") end it "handles submissions without 'updated_at' field correctly" do @@ -70,10 +75,10 @@ describe CustomWizard::Submission do PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, sub_data) builder = CustomWizard::Builder.new(@wizard.id, @wizard.user) builder.build - sub_list = described_class.list(@wizard, user_id: @wizard.user.id) + submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions - expect(sub_list.length).to eq(1) - expect(sub_list.first.fields["step_1_field_1"]).to eq("I am the third submission") + expect(submissions.length).to eq(1) + expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission") end it "handles submissions with and without 'updated_at' field correctly" do @@ -87,10 +92,10 @@ describe CustomWizard::Submission do builder = CustomWizard::Builder.new(@wizard.id, @wizard.user) builder.build - sub_list = described_class.list(@wizard, user_id: @wizard.user.id) + submissions = described_class.list(@wizard, user_id: @wizard.user.id).submissions - expect(sub_list.length).to eq(1) - expect(sub_list.first.fields["step_1_field_1"]).to eq("I am the third submission") + expect(submissions.length).to eq(1) + expect(submissions.first.fields["step_1_field_1"]).to eq("I am the third submission") end end end diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index 87ff7efe..26f90040 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -84,9 +84,9 @@ describe CustomWizard::WizardController do CustomWizard::Submission.new(@wizard, step_1_field_1: "Hello World").save current_submission = @wizard.current_submission put '/w/super-mega-fun-wizard/skip.json' - list = CustomWizard::Submission.list(@wizard) + submissions = CustomWizard::Submission.list(@wizard).submissions - expect(list.any? { |submission| submission.id == current_submission.id }).to eq(false) + expect(submissions.any? { |submission| submission.id == current_submission.id }).to eq(false) end it "starts from the first step if user visits after skipping the wizard" do From 7b13605c7bb51f3bd3ca351b65f7f1726b6b64d4 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 1 Sep 2021 12:46:39 -0700 Subject: [PATCH 028/556] FIX: Resolve linting issues --- .../discourse/components/submission-field.js.es6 | 9 ++++----- .../controllers/admin-wizards-submissions-columns.js.es6 | 2 +- .../controllers/admin-wizards-submissions-show.js.es6 | 6 +++--- assets/javascripts/discourse/models/custom-wizard.js.es6 | 5 ++--- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/assets/javascripts/discourse/components/submission-field.js.es6 b/assets/javascripts/discourse/components/submission-field.js.es6 index f76376a5..ea9e34cf 100644 --- a/assets/javascripts/discourse/components/submission-field.js.es6 +++ b/assets/javascripts/discourse/components/submission-field.js.es6 @@ -64,21 +64,21 @@ export default Component.extend({ submittedUsers(value) { const isUserSelector = this.get("isUserSelector"); const users = []; - + if (isUserSelector) { const userData = value.value; const usernames = []; - if (userData.indexOf(',')) { + if (userData.indexOf(',')) { usernames.push(...userData.split(',')); usernames.forEach(u => { const user = { username: u, url: `/u/${u}` - } + }; users.push(user); - }) + }); } } return users; @@ -87,7 +87,6 @@ export default Component.extend({ @discourseComputed('value') userProfileUrl(value) { const isUser = this.get("isUser"); - const isUserSelector = this.get("isUserSelector"); if (isUser) { return `/u/${value.username}`; diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 index 5627dacf..981d1139 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 @@ -2,7 +2,7 @@ import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; export default Controller.extend(ModalFunctionality, { - + actions: { save() { this.send("closeModal"); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index 2667b6b2..450f7d53 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -27,8 +27,8 @@ export default Controller.extend({ this.set("loadingMore", false); }); }, - - + + @discourseComputed('submissions', 'fields.@each.enabled') displaySubmissions(submissions, fields) { let result = []; @@ -56,7 +56,7 @@ export default Controller.extend({ }, showEditColumnsModal() { - const controller = showModal("admin-wizards-submissions-columns", { + return showModal("admin-wizards-submissions-columns", { model: { fields: this.get('fields'), submissions: this.get('submissions') diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index d9a2bafa..660ba4ee 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -224,7 +224,6 @@ CustomWizard.reopenClass({ }) .then((result) => { if (result.wizard) { - console.log(result); let fields = [{ id: "username", label: "User"}]; let submissions = []; let wizard = result.wizard; @@ -248,8 +247,8 @@ CustomWizard.reopenClass({ let submittedAt = { id: "submitted_at", label: "Submitted At" - } - + }; + fields.push(submittedAt); return { From 9fc2092951bb22a2daf61883552c3dc13f680488 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 2 Sep 2021 15:38:30 -0700 Subject: [PATCH 029/556] =?UTF-8?q?DEV:=20Run=20prettier=20=F0=9F=92=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/submission-field.js.es6 | 31 +++++++++---------- .../admin-wizards-submissions-show.js.es6 | 22 ++++++------- .../discourse/models/custom-wizard.js.es6 | 16 +++++----- .../admin-wizards-submissions-columns.hbs | 2 +- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/assets/javascripts/discourse/components/submission-field.js.es6 b/assets/javascripts/discourse/components/submission-field.js.es6 index ea9e34cf..5ebc0ccd 100644 --- a/assets/javascripts/discourse/components/submission-field.js.es6 +++ b/assets/javascripts/discourse/components/submission-field.js.es6 @@ -1,10 +1,9 @@ -import { action } from "@ember/object"; import Component from "@ember/component"; +import { action } from "@ember/object"; import { equal } from "@ember/object/computed"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; - export default Component.extend({ isText: equal("value.type", "text"), isComposer: equal("value.type", "composer"), @@ -25,7 +24,7 @@ export default Component.extend({ isTextArea: equal("value.type", "textarea"), isComposerPreview: equal("value.type", "composer_preview"), textState: "text-collapsed", - toggleText: I18n.t('admin.wizard.submissions.expand_text'), + toggleText: I18n.t("admin.wizard.submissions.expand_text"), @discourseComputed("value") checkboxValue(value) { @@ -45,14 +44,14 @@ export default Component.extend({ if (state === "text-collapsed") { this.set("textState", "text-expanded"); - this.set("toggleText", I18n.t('admin.wizard.submissions.collapse_text')); + this.set("toggleText", I18n.t("admin.wizard.submissions.collapse_text")); } else if (state === "text-expanded") { this.set("textState", "text-collapsed"); - this.set("toggleText", I18n.t('admin.wizard.submissions.expand_text')); + this.set("toggleText", I18n.t("admin.wizard.submissions.expand_text")); } }, - @discourseComputed('value') + @discourseComputed("value") file(value) { const isUpload = this.get("isUpload"); if (isUpload) { @@ -60,7 +59,7 @@ export default Component.extend({ } }, - @discourseComputed('value') + @discourseComputed("value") submittedUsers(value) { const isUserSelector = this.get("isUserSelector"); const users = []; @@ -69,13 +68,13 @@ export default Component.extend({ const userData = value.value; const usernames = []; - if (userData.indexOf(',')) { - usernames.push(...userData.split(',')); + if (userData.indexOf(",")) { + usernames.push(...userData.split(",")); - usernames.forEach(u => { + usernames.forEach((u) => { const user = { username: u, - url: `/u/${u}` + url: `/u/${u}`, }; users.push(user); }); @@ -84,7 +83,7 @@ export default Component.extend({ return users; }, - @discourseComputed('value') + @discourseComputed("value") userProfileUrl(value) { const isUser = this.get("isUser"); @@ -93,18 +92,18 @@ export default Component.extend({ } }, - @discourseComputed('value') + @discourseComputed("value") categoryUrl(value) { - const isCategory = this.get('isCategory'); + const isCategory = this.get("isCategory"); if (isCategory) { return `/c/${value.value}`; } }, - @discourseComputed('value') + @discourseComputed("value") groupUrl(value) { - const isGroup = this.get('isGroup'); + const isGroup = this.get("isGroup"); if (isGroup) { return `/g/${value.value}`; diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index 450f7d53..7ba0050f 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -1,10 +1,9 @@ import Controller from "@ember/controller"; -import { fmt } from "discourse/lib/computed"; import { empty } from "@ember/object/computed"; -import CustomWizard from "../models/custom-wizard"; -import showModal from "discourse/lib/show-modal"; import discourseComputed from "discourse-common/utils/decorators"; - +import { fmt } from "discourse/lib/computed"; +import showModal from "discourse/lib/show-modal"; +import CustomWizard from "../models/custom-wizard"; export default Controller.extend({ downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"), @@ -28,16 +27,15 @@ export default Controller.extend({ }); }, - - @discourseComputed('submissions', 'fields.@each.enabled') + @discourseComputed("submissions", "fields.@each.enabled") displaySubmissions(submissions, fields) { let result = []; - submissions.forEach(submission => { + submissions.forEach((submission) => { let sub = {}; - Object.keys(submission).forEach(fieldId => { - if (fields.some(f => f.id === fieldId && f.enabled)) { + Object.keys(submission).forEach((fieldId) => { + if (fields.some((f) => f.id === fieldId && f.enabled)) { sub[fieldId] = submission[fieldId]; } }); @@ -58,9 +56,9 @@ export default Controller.extend({ showEditColumnsModal() { return showModal("admin-wizards-submissions-columns", { model: { - fields: this.get('fields'), - submissions: this.get('submissions') - } + fields: this.get("fields"), + submissions: this.get("submissions"), + }, }); }, }, diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index 660ba4ee..d0b49079 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -1,10 +1,10 @@ -import { ajax } from "discourse/lib/ajax"; import EmberObject from "@ember/object"; -import { buildProperties, mapped, present } from "../lib/wizard-json"; -import { listProperties, snakeCase } from "../lib/wizard"; -import wizardSchema from "../lib/wizard-schema"; -import { Promise } from "rsvp"; +import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { Promise } from "rsvp"; +import { listProperties, snakeCase } from "../lib/wizard"; +import { buildProperties, mapped, present } from "../lib/wizard-json"; +import wizardSchema from "../lib/wizard-schema"; const CustomWizard = EmberObject.extend({ save(opts) { @@ -224,7 +224,7 @@ CustomWizard.reopenClass({ }) .then((result) => { if (result.wizard) { - let fields = [{ id: "username", label: "User"}]; + let fields = [{ id: "username", label: "User" }]; let submissions = []; let wizard = result.wizard; let total = result.total; @@ -235,7 +235,7 @@ CustomWizard.reopenClass({ }; Object.keys(s.fields).forEach((fieldId) => { - if (!fields.some(field => field.id === fieldId)) { + if (!fields.some((field) => field.id === fieldId)) { fields.push({ id: fieldId, label: s.fields[fieldId].label }); } submission[fieldId] = s.fields[fieldId]; @@ -246,7 +246,7 @@ CustomWizard.reopenClass({ let submittedAt = { id: "submitted_at", - label: "Submitted At" + label: "Submitted At", }; fields.push(submittedAt); diff --git a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs index a167a954..24743a92 100644 --- a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs +++ b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs @@ -29,4 +29,4 @@ label="directory.edit_columns.reset_to_default" action=(action "resetToDefault") }} -
+
\ No newline at end of file From 1e38001942fc13e5e379d6415dd1033fb49d9d89 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 2 Sep 2021 15:41:55 -0700 Subject: [PATCH 030/556] =?UTF-8?q?DEV:=20Run=20Prettier=20=F0=9F=92=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/admin-wizards-submissions-columns.js.es6 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 index 981d1139..4af487ee 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 @@ -2,15 +2,14 @@ import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; export default Controller.extend(ModalFunctionality, { - actions: { save() { this.send("closeModal"); }, resetToDefault() { - this.get('model.fields').forEach(field => { + this.get("model.fields").forEach((field) => { field.set("enabled", true); }); - } - } -}); \ No newline at end of file + }, + }, +}); From 4abe30464c1b29ff2234aab1bc0387022e7c07a4 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 2 Sep 2021 15:43:34 -0700 Subject: [PATCH 031/556] =?UTF-8?q?DEV:=20Run=20Prettier=F0=9F=92=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin-wizards-submissions-show.hbs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs index 2f0689b1..6f4996f6 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs @@ -1,16 +1,24 @@ {{#if submissions}}
- +
{{d-button - icon="sliders-h" - label="admin.wizard.submissions.edit_columns" + icon="sliders-h" + label="admin.wizard.submissions.edit_columns" action=(action "showEditColumnsModal") - class="btn-default open-edit-columns-btn download-link"}} + class="btn-default open-edit-columns-btn download-link" + }}
- + {{d-icon "download"}} {{i18n "admin.wizard.submissions.download"}} @@ -21,14 +29,18 @@
{{#load-more selector=".wizard-submissions tr" action=(action "loadMore")}} {{#if noResults}} -

{{i18n "search.no_results"}}

+

+ {{i18n "search.no_results"}} +

{{else}}
{{f}}{{field.label}}
{{v}}{{submission-field fieldName=field value=value}}
{{#each fields as |field|}} {{#if field.enabled}} - + {{/if}} {{/each}} @@ -37,7 +49,9 @@ {{#each displaySubmissions as |submission|}} {{#each-in submission as |field value|}} - + {{/each-in}} {{/each}} @@ -48,4 +62,4 @@ {{conditional-loading-spinner condition=loadingMore}} {{/load-more}} -{{/if}} +{{/if}} \ No newline at end of file From c60d1e33386fa48cc31dde63ffca0f3a4f51bad9 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 2 Sep 2021 15:46:49 -0700 Subject: [PATCH 032/556] =?UTF-8?q?DEV:=20Run=20Prettier=20=F0=9F=92=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/components/submission-field.hbs | 97 ++++++++++++++----- 1 file changed, 73 insertions(+), 24 deletions(-) diff --git a/assets/javascripts/discourse/templates/components/submission-field.hbs b/assets/javascripts/discourse/templates/components/submission-field.hbs index 52cde1dd..831dcc3a 100644 --- a/assets/javascripts/discourse/templates/components/submission-field.hbs +++ b/assets/javascripts/discourse/templates/components/submission-field.hbs @@ -4,20 +4,35 @@ {{#if isTextArea}} {{/if}} {{#if isComposer}}
-

{{value.value}}

- {{toggleText}} +

+ {{value.value}} +

+ + {{toggleText}} +
{{/if}} {{#if isComposerPreview}} - {{d-icon "comment-alt" }} {{i18n "admin.wizard.submissions.composer_preview"}}: {{value.value}} + {{d-icon "comment-alt"}} + + {{i18n "admin.wizard.submissions.composer_preview"}}: {{value.value}} + {{/if}} {{#if isTextOnly}} @@ -48,35 +63,41 @@ {{#if isCheckbox}} {{#if checkboxValue}} - - {{d-icon "check"}}{{value.value}} - + + {{d-icon "check"}}{{value.value}} + {{else}} - - {{d-icon "times"}}{{value.value}} - + + {{d-icon "times"}}{{value.value}} + {{/if}} {{/if}} {{#if isUrl}} - {{ d-icon "link" }} - + {{d-icon "link"}} + {{value.value}} {{/if}} {{#if isUpload}} - + {{file.original_filename}} {{/if}} {{#if isDropdown}} - {{ d-icon "check-square" }} - {{ value.value }} + {{d-icon "check-square"}} + {{value.value}} {{/if}} @@ -87,28 +108,56 @@ {{/if}} {{#if isCategory}} - {{i18n "admin.wizard.submissions.category_id"}}: {{value.value}} + + {{i18n "admin.wizard.submissions.category_id"}}: + + + {{value.value}} + {{/if}} {{#if isGroup}} - {{i18n "admin.wizard.submissions.group_id"}}: {{ value.value }} + + {{i18n "admin.wizard.submissions.group_id"}}: + + {{value.value}} {{/if}} - {{#if isUserSelector}} {{#each submittedUsers as |user|}} - {{ d-icon "user" }} - {{user.username}} + {{d-icon "user"}} + + {{user.username}} + {{/each}} {{/if}} {{#if isUser}} - {{#link-to "user" value}}{{avatar value imageSize="tiny"}}{{/link-to}} - {{value.username}} + {{#link-to "user" value}} + {{avatar value imageSize="tiny"}} + {{/link-to}} + + {{value.username}} + {{/if}} {{#if isSubmittedAt}} {{d-icon "clock"}}{{format-date value format="tiny"}} -{{/if}} +{{/if}} \ No newline at end of file From 6ef333a6573df53b67c50951f383f32aeb19b4c8 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Fri, 3 Sep 2021 16:46:32 +0800 Subject: [PATCH 033/556] Complete pro-feature functionality --- .../components/custom-field-input.js.es6 | 42 ++-- .../components/wizard-advanced-toggle.js.es6 | 21 -- .../components/wizard-custom-action.js.es6 | 25 ++- .../components/wizard-custom-field.js.es6 | 1 - .../components/wizard-pro-selector.js.es6 | 19 ++ .../wizard-pro-selector-header.js.es6 | 17 ++ .../wizard-pro-selector-row.js.es6 | 3 + .../admin-wizards-wizard-show.js.es6 | 5 - .../discourse/lib/wizard-json.js.es6 | 15 -- .../discourse/lib/wizard-schema.js.es6 | 18 +- .../routes/admin-wizards-custom-fields.js.es6 | 9 +- .../templates/admin-wizards-custom-fields.hbs | 3 +- .../templates/admin-wizards-wizard-show.hbs | 47 ++--- .../components/custom-field-input.hbs | 4 +- .../components/wizard-advanced-toggle.hbs | 4 - .../components/wizard-custom-action.hbs | 184 +++++++++--------- .../components/wizard-custom-field.hbs | 3 +- .../components/wizard-custom-step.hbs | 104 +++++----- .../wizard-pro-selector-header.hbs | 15 ++ .../wizard-pro-selector-row.hbs | 15 ++ assets/stylesheets/common/wizard-admin.scss | 34 ++-- config/locales/client.en.yml | 4 +- .../custom_wizard/admin/custom_fields.rb | 5 +- lib/custom_wizard/custom_field.rb | 8 +- lib/custom_wizard/pro.rb | 2 +- lib/custom_wizard/validators/template.rb | 2 +- 26 files changed, 329 insertions(+), 280 deletions(-) delete mode 100644 assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 create mode 100644 assets/javascripts/discourse/components/wizard-pro-selector.js.es6 create mode 100644 assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 create mode 100644 assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 delete mode 100644 assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs create mode 100644 assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs create mode 100644 assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index e49c6f1d..c389fb62 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -1,13 +1,29 @@ import Component from "@ember/component"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { alias, equal, or } from "@ember/object/computed"; +import { computed } from "@ember/object"; import I18n from "I18n"; -const generateContent = function (array, type) { - return array.map((key) => ({ - id: key, - name: I18n.t(`admin.wizard.custom_field.${type}.${key}`), - })); +const klasses = ["topic", "post", "group", "category"]; +const types = ["string", "boolean", "integer", "json"]; +const proTypes = { + klass: ["group", "category"], + type: ["json"] +} + +const generateContent = function (array, type, proSubscribed = false) { + return array.reduce((result, key) => { + let proArr = proTypes[type]; + let pro = proArr && proArr.includes(key); + if (!pro || proSubscribed) { + result.push({ + id: key, + name: I18n.t(`admin.wizard.custom_field.${type}.${key}`), + pro + }); + } + return result; + }, []); }; export default Component.extend({ @@ -16,14 +32,12 @@ export default Component.extend({ postSerializers: ["post"], groupSerializers: ["basic_group"], categorySerializers: ["basic_category"], - klassContent: generateContent( - ["topic", "post", "group", "category"], - "klass" - ), - typeContent: generateContent( - ["string", "boolean", "integer", "json"], - "type" - ), + klassContent: computed("proSubscribed", function() { + return generateContent(klasses, "klass", this.proSubscribed); + }), + typeContent: computed("proSubscribed", function() { + return generateContent(types, "type", this.proSubscribed); + }), showInputs: or("field.new", "field.edit"), classNames: ["custom-field-input"], loading: or("saving", "destroying"), @@ -40,7 +54,7 @@ export default Component.extend({ const serializers = this.get(`${klass}Serializers`); if (serializers) { - return generateContent(serializers, "serializers"); + return generateContent(serializers, "serializers", this.proSubscribed); } else { return []; } diff --git a/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 b/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 deleted file mode 100644 index c6e1fd9c..00000000 --- a/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 +++ /dev/null @@ -1,21 +0,0 @@ -import { default as discourseComputed } from "discourse-common/utils/decorators"; -import Component from "@ember/component"; - -export default Component.extend({ - classNames: "wizard-advanced-toggle", - - @discourseComputed("showAdvanced") - toggleClass(showAdvanced) { - let classes = "btn"; - if (showAdvanced) { - classes += " btn-primary"; - } - return classes; - }, - - actions: { - toggleAdvanced() { - this.toggleProperty("showAdvanced"); - }, - }, -}); diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index feb83754..a04ff563 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,8 +1,8 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; +import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { and, empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; import { computed } from "@ember/object"; -import wizardSchema from "../lib/wizard-schema"; import UndoChanges from "../mixins/undo-changes"; import Component from "@ember/component"; import I18n from "I18n"; @@ -25,8 +25,6 @@ export default Component.extend(UndoChanges, { createGroup: equal("action.type", "create_group"), apiEmpty: empty("action.api"), groupPropertyTypes: selectKitContent(["id", "name"]), - hasAdvanced: or("hasCustomFields", "routeTo"), - showAdvanced: and("hasAdvanced", "action.type"), hasCustomFields: or( "basicTopicFields", "updateProfile", @@ -36,12 +34,6 @@ export default Component.extend(UndoChanges, { basicTopicFields: or("createTopic", "sendMessage", "openComposer"), publicTopicFields: or("createTopic", "openComposer"), showPostAdvanced: or("createTopic", "sendMessage"), - actionTypes: Object.keys(wizardSchema.action.types).map((type) => { - return { - id: type, - name: I18n.t(`admin.wizard.action.${type}.label`), - }; - }), availableNotificationLevels: notificationLevels.map((type) => { return { id: type, @@ -101,4 +93,19 @@ export default Component.extend(UndoChanges, { } return apis.find((a) => a.name === api).endpoints; }, + + @discourseComputed("proSubscribed") + actionTypes(proSubscribed) { + return Object.keys(wizardSchema.action.types).reduce((result, type) => { + let pro = wizardSchema.action.proTypes.includes(type); + if (proSubscribed || !pro) { + result.push({ + id: type, + name: I18n.t(`admin.wizard.action.${type}.label`), + pro + }); + } + return result; + }, []); + } }); diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index b5c10c65..807f90cd 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -27,7 +27,6 @@ export default Component.extend(UndoChanges, { isTextType: or("isText", "isTextarea", "isComposer"), isComposerPreview: equal("field.type", "composer_preview"), categoryPropertyTypes: selectKitContent(["id", "slug"]), - showAdvanced: alias("field.type"), messageUrl: "https://thepavilion.io/t/2809", @discourseComputed("field.type") diff --git a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 new file mode 100644 index 00000000..b06b5943 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 @@ -0,0 +1,19 @@ +import SingleSelectComponent from "select-kit/components/single-select"; +import { computed } from "@ember/object"; + +export default SingleSelectComponent.extend({ + classNames: ["combo-box", 'wizard-pro-selector'], + + selectKitOptions: { + autoFilterable: false, + filterable: false, + showFullTitle: true, + headerComponent: "wizard-pro-selector/wizard-pro-selector-header", + caretUpIcon: "caret-up", + caretDownIcon: "caret-down" + }, + + modifyComponentForRow() { + return "wizard-pro-selector/wizard-pro-selector-row"; + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 new file mode 100644 index 00000000..c1f7251c --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 @@ -0,0 +1,17 @@ +import SingleSelectHeaderComponent from "select-kit/components/select-kit/single-select-header"; +import { computed } from "@ember/object"; +import { reads } from "@ember/object/computed"; + +export default SingleSelectHeaderComponent.extend({ + classNames: ["combo-box-header", "wizard-pro-selector-header"], + caretUpIcon: reads("selectKit.options.caretUpIcon"), + caretDownIcon: reads("selectKit.options.caretDownIcon"), + caretIcon: computed( + "selectKit.isExpanded", + "caretUpIcon", + "caretDownIcon", + function () { + return this.selectKit.isExpanded ? this.caretUpIcon : this.caretDownIcon; + } + ), +}); diff --git a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 new file mode 100644 index 00000000..9640cd0c --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 @@ -0,0 +1,3 @@ +import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; + +export default SelectKitRowComponent.extend(); \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 index edb06ad9..a4be0667 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 @@ -77,7 +77,6 @@ export default Controller.extend({ wizard .save(opts) .then((result) => { - console.log(result) if (result.wizard_id) { this.send("afterSave", result.wizard_id); } else if (result.errors) { @@ -119,10 +118,6 @@ export default Controller.extend({ controller.setup(); }, - toggleAdvanced() { - this.toggleProperty("wizard.showAdvanced"); - }, - copyUrl() { const $copyRange = $('

'); $copyRange.html(this.wizardUrl); diff --git a/assets/javascripts/discourse/lib/wizard-json.js.es6 b/assets/javascripts/discourse/lib/wizard-json.js.es6 index 79da60cb..95eaba49 100644 --- a/assets/javascripts/discourse/lib/wizard-json.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-json.js.es6 @@ -97,11 +97,6 @@ function buildObjectArray(json, type) { if (present(json)) { json.forEach((objJson, objectIndex) => { let object = buildObject(objJson, type, objectIndex); - - if (hasAdvancedProperties(object, type)) { - object.set("showAdvanced", true); - } - array.pushObject(object); }); } @@ -112,21 +107,11 @@ function buildObjectArray(json, type) { function buildBasicProperties(json, type, props, objectIndex = null) { listProperties(type).forEach((p) => { props[p] = buildProperty(json, p, type, objectIndex); - - if (hasAdvancedProperties(json, type)) { - props.showAdvanced = true; - } }); return props; } -function hasAdvancedProperties(object, type) { - return Object.keys(object).some((p) => { - return wizardSchema[type].advanced.indexOf(p) > -1 && present(object[p]); - }); -} - /// to be removed: necessary due to action array being moved from step to wizard function actionPatch(json) { let actions = json.actions || []; diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 8b8200b0..613e6569 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -18,7 +18,6 @@ const wizard = { permitted: null, }, mapped: ["permitted"], - advanced: ["restart_on_revisit"], required: ["id"], dependent: { after_time: "after_time_scheduled", @@ -50,7 +49,6 @@ const step = { force_final: false, }, mapped: ["required_data", "permitted_params", "condition", "index"], - advanced: ["required_data", "permitted_params", "condition", "index"], required: ["id"], dependent: {}, objectArrays: { @@ -68,6 +66,7 @@ const field = { label: null, image: null, description: null, + property: null, required: null, key: null, type: null, @@ -75,7 +74,6 @@ const field = { }, types: {}, mapped: ["prefill", "content", "condition", "index"], - advanced: ["property", "key", "condition", "index"], required: ["id", "type"], dependent: {}, objectArrays: {}, @@ -196,14 +194,14 @@ const action = { "visibility_level", "members_visibility_level", ], - advanced: [ - "code", - "custom_fields", - "skip_redirect", - "suppress_notifications", - "required", - ], required: ["id", "type"], + proTypes: [ + 'send_message', + 'create_category', + 'create_group', + 'watch_categories', + 'send_to_api' + ], dependent: {}, objectArrays: {}, }; diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index a1c625ad..198f9a14 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -8,7 +8,12 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - const customFields = A(model || []); - controller.set("customFields", customFields); + const customFields = A(model.custom_fields || []); + const proSubscribed = model.pro_subscribed; + + controller.setProperties({ + customFields, + proSubscribed + }); }, }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index 10501498..09a0d569 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -32,7 +32,8 @@ {{custom-field-input field=field removeField=(action "removeField") - saveField=(action "saveField")}} + saveField=(action "saveField") + proSubscribed=proSubscribed}} {{/each}}
{{field.label}} + {{field.label}} +
{{submission-field fieldName=field value=value}} + {{submission-field fieldName=field value=value}} +
diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index 9d91cb0b..7645c20e 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -126,33 +126,25 @@
- {{wizard-advanced-toggle showAdvanced=wizard.showAdvanced}} - - {{#if wizard.showAdvanced}} -
- -
-
- -
-
- {{input type="checkbox" checked=wizard.save_submissions}} - {{i18n "admin.wizard.save_submissions_label"}} -
-
- -
-
- -
-
- {{input type="checkbox" checked=wizard.restart_on_revisit}} - {{i18n "admin.wizard.restart_on_revisit_label"}} -
-
- +
+
+
- {{/if}} +
+ {{input type="checkbox" checked=wizard.save_submissions}} + {{i18n "admin.wizard.save_submissions_label"}} +
+
+ +
+
+ +
+
+ {{input type="checkbox" checked=wizard.restart_on_revisit}} + {{i18n "admin.wizard.restart_on_revisit_label"}} +
+
{{wizard-links @@ -183,7 +175,8 @@ wizard=wizard apis=apis removeAction="removeAction" - wizardFields=wizardFields}} + wizardFields=wizardFields + proSubscribed=proSubscribed}} {{/each}}
diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index 43a97be8..2bb9acce 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -1,13 +1,13 @@ {{#if showInputs}} - {{combo-box + {{wizard-pro-selector value=field.klass content=klassContent none="admin.wizard.custom_field.klass.select" onChange=(action (mut field.klass))}} - {{combo-box + {{wizard-pro-selector value=field.type content=typeContent none="admin.wizard.custom_field.type.select" diff --git a/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs b/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs deleted file mode 100644 index ec2bcb76..00000000 --- a/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs +++ /dev/null @@ -1,4 +0,0 @@ -{{d-button - action="toggleAdvanced" - label="admin.wizard.advanced" - class=toggleClass}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 4c645cf7..f5d53200 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -12,13 +12,14 @@
- {{combo-box + {{wizard-pro-selector value=action.type content=actionTypes onChange=(action "changeType") options=(hash none="admin.wizard.select_type" - )}} + ) + }}
@@ -714,99 +715,90 @@
{{/if}} -{{#if showAdvanced}} - {{wizard-advanced-toggle showAdvanced=action.showAdvanced}} - - {{#if action.showAdvanced}} -
- - {{#if hasCustomFields}} -
-
- -
- -
- {{wizard-mapper - inputs=action.custom_fields - property="custom_fields" - onUpdate=(action "mappedFieldUpdated") - options=(hash - inputTypes="association" - customFieldSelection="key" - wizardFieldSelection="value" - wizardActionSelection="value" - userFieldSelection="value" - keyPlaceholder="admin.wizard.action.custom_fields.key" - context=customFieldsContext - )}} -
-
- {{/if}} - - {{#if sendMessage}} -
-
- -
- -
- {{wizard-mapper - inputs=action.required - property="required" - onUpdate=(action "mappedFieldUpdated") - options=(hash - textSelection="value" - wizardFieldSelection=true - userFieldSelection=true - groupSelection=true - context="action" - )}} -
-
- {{/if}} - - {{#if showPostAdvanced}} -
-
- -
- -
- {{input type="checkbox" checked=action.skip_redirect}} - - - {{i18n "admin.wizard.action.skip_redirect.description" type="topic"}} - -
-
- -
-
- -
- -
- {{input type="checkbox" checked=action.suppress_notifications}} - - - {{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}} - -
-
- {{/if}} - - {{#if routeTo}} -
-
- -
- -
- {{input value=action.code}} -
-
- {{/if}} +{{#if hasCustomFields}} +
+
+
- {{/if}} + +
+ {{wizard-mapper + inputs=action.custom_fields + property="custom_fields" + onUpdate=(action "mappedFieldUpdated") + options=(hash + inputTypes="association" + customFieldSelection="key" + wizardFieldSelection="value" + wizardActionSelection="value" + userFieldSelection="value" + keyPlaceholder="admin.wizard.action.custom_fields.key" + context=customFieldsContext + )}} +
+
+{{/if}} + +{{#if sendMessage}} +
+
+ +
+ +
+ {{wizard-mapper + inputs=action.required + property="required" + onUpdate=(action "mappedFieldUpdated") + options=(hash + textSelection="value" + wizardFieldSelection=true + userFieldSelection=true + groupSelection=true + context="action" + )}} +
+
+{{/if}} + +{{#if showPostAdvanced}} +
+
+ +
+ +
+ {{input type="checkbox" checked=action.skip_redirect}} + + + {{i18n "admin.wizard.action.skip_redirect.description" type="topic"}} + +
+
+ +
+
+ +
+ +
+ {{input type="checkbox" checked=action.suppress_notifications}} + + + {{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}} + +
+
+{{/if}} + +{{#if routeTo}} +
+
+ +
+ +
+ {{input value=action.code}} +
+
{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index afdbd767..8c8bb6d4 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -258,11 +258,10 @@ {{i18n "admin.wizard.pro.label"}}
-
+
{{input name="key" value=field.key - class="medium" placeholderKey="admin.wizard.translation_placeholder"}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 3081be66..91476ae3 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -55,66 +55,62 @@ {{i18n "admin.wizard.step.force_final.description"}}
-{{/if}} -{{wizard-advanced-toggle showAdvanced=step.showAdvanced}} - -{{#if step.showAdvanced}} -
- -
-
- -
-
- {{wizard-mapper - inputs=step.required_data - options=(hash - inputTypes="validation" - inputConnector="and" - wizardFieldSelection="value" - userFieldSelection="value" - keyPlaceholder="admin.wizard.submission_key" - context="step" - )}} - {{#if step.required_data}} -
-
- {{i18n "admin.wizard.step.required_data.not_permitted_message"}} -
- {{input value=step.required_data_message}} +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{wizard-mapper + inputs=step.required_data + options=(hash + inputTypes="validation" + inputConnector="and" + wizardFieldSelection="value" + userFieldSelection="value" + keyPlaceholder="admin.wizard.submission_key" + context="step" + )}} + {{#if step.required_data}} +
+
+ {{i18n "admin.wizard.step.required_data.not_permitted_message"}}
- {{/if}} -
+ {{input value=step.required_data_message}} +
+ {{/if}}
+
-
-
- -
-
- {{wizard-mapper - inputs=step.permitted_params - options=(hash - pairConnector="set" - inputTypes="association" - keyPlaceholder="admin.wizard.param_key" - valuePlaceholder="admin.wizard.submission_key" - context="step" - )}} -
+
+
+ + {{i18n "admin.wizard.pro.label"}}
+
+ {{wizard-mapper + inputs=step.permitted_params + options=(hash + pairConnector="set" + inputTypes="association" + keyPlaceholder="admin.wizard.param_key" + valuePlaceholder="admin.wizard.submission_key" + context="step" + )}} +
+
-
-
- -
-
- {{input - name="key" - value=step.key - placeholderKey="admin.wizard.translation_placeholder"}} -
+
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{input + name="key" + value=step.key + placeholderKey="admin.wizard.translation_placeholder"}}
{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs new file mode 100644 index 00000000..a02b0d9c --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs @@ -0,0 +1,15 @@ +
+ + {{component selectKit.options.selectedNameComponent + tabindex=tabindex + item=selectedContent + selectKit=selectKit + shouldDisplayClearableButton=shouldDisplayClearableButton + }} + + {{#if selectedContent.pro}} + {{i18n "admin.wizard.pro.label"}} + {{/if}} + + {{d-icon caretIcon class="caret-icon"}} +
\ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs new file mode 100644 index 00000000..077265ef --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs @@ -0,0 +1,15 @@ +{{#if icons}} +
+ + {{#each icons as |icon|}} + {{d-icon icon translatedtitle=(dasherize title)}} + {{/each}} +
+{{/if}} + +
+ {{html-safe label}} + {{#if item.pro}} + {{i18n "admin.wizard.pro.label"}} + {{/if}} +
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 178dbc81..3146da13 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -243,7 +243,7 @@ font-size: 0.85em; } - span { + > span { font-size: 0.929em; } @@ -382,11 +382,6 @@ margin: 0; } } - - .pro-label { - color: $tertiary; - font-size: .75em; - } } } @@ -764,6 +759,11 @@ vertical-align: middle; } +.pro-label { + color: var(--tertiary); + font-size: .75em; +} + .admin-wizards-pro { .admin-wizard-controls { h3, label { @@ -773,8 +773,8 @@ label { padding: .4em .5em; margin-left: .75em; - background-color: $success; - color: $secondary; + background-color: var(--success); + color: var(--secondary); } .buttons { @@ -807,17 +807,29 @@ display: flex; align-items: center; padding: 1em; - background-color: $primary-very-low; + background-color: var(--primary-very-low); .subscription-state { padding: .25em .5em; margin-right: .75em; &.active { - background-color: $success; - color: $secondary; + background-color: var(--success); + color: var(--secondary); } } } } } + +.wizard-pro-selector.select-kit.single-select { + .select-kit-row .texts { + display: flex; + align-items: center; + } + + .pro-label { + margin-left: .75em; + padding-top: .25em; + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index bfea46ce..451c7786 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -179,9 +179,9 @@ en: min_length_placeholder: "Minimum length in characters" max_length: "Max Length" max_length_placeholder: "Maximum length in characters" - char_counter: "Character Counter" + char_counter: "Counter" char_counter_placeholder: "Display Character Counter" - field_placeholder: "Field Placeholder" + field_placeholder: "Placeholder" file_types: "File Types" preview_template: "Preview Template" limit: "Limit" diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/controllers/custom_wizard/admin/custom_fields.rb index c52759c9..40ff64be 100644 --- a/controllers/custom_wizard/admin/custom_fields.rb +++ b/controllers/custom_wizard/admin/custom_fields.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController def index - render_json_dump(custom_field_list) + render_json_dump( + custom_fields: custom_field_list, + pro_subscribed: CustomWizard::Pro.subscribed? + ) end def update diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index cc20ee95..bc1a146d 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -17,7 +17,9 @@ class ::CustomWizard::CustomField category: ["basic_category"], post: ["post"] } + PRO_CLASSES ||= ['category', 'group'] TYPES ||= ["string", "boolean", "integer", "json"] + PRO_TYPES ||= ["json"] LIST_CACHE_KEY ||= 'custom_field_list' def self.serializers @@ -83,6 +85,10 @@ class ::CustomWizard::CustomField next end + if attr == 'klass' && PRO_CLASSES.include?(value) && !@pro.subscribed? + add_error(I18n.t("wizard.custom_field.error.pro_type", type: value)) + end + if attr == 'serializers' && (unsupported = value - CLASSES[klass.to_sym]).length > 0 add_error(I18n.t("#{i18n_key}.unsupported_serializers", class: klass, @@ -94,7 +100,7 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end - if attr == 'type' && value == 'json' && !@pro.subscribed? + if attr == 'type' && PRO_TYPES.include?(value) && !@pro.subscribed? add_error(I18n.t("wizard.custom_field.error.pro_type", type: value)) end diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb index 3726e9b4..2e2f859d 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/pro.rb @@ -16,7 +16,7 @@ class CustomWizard::Pro end def subscribed? - @subscription.active? + false #@subscription.active? end def server diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index 180958ba..0f5f686c 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -31,7 +31,7 @@ class CustomWizard::TemplateValidator if data[:actions].present? data[:actions].each do |action| - validate_pro_action(action) + validate_pro(action, :action) check_required(action, :action) end end From 8dbb3990e449d9db7c961d939840a37021bc27de Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Fri, 3 Sep 2021 16:46:43 +0800 Subject: [PATCH 034/556] Remove test change --- lib/custom_wizard/pro.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb index 2e2f859d..3726e9b4 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/pro.rb @@ -16,7 +16,7 @@ class CustomWizard::Pro end def subscribed? - false #@subscription.active? + @subscription.active? end def server From f6c3c98d7183aed6efc84e22e550af60526414af Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 6 Sep 2021 17:25:08 +0800 Subject: [PATCH 035/556] Add submission serializer spec --- .../custom_wizard/submission_serializer.rb | 1 - .../submission_serializer_spec.rb | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 spec/serializers/custom_wizard/submission_serializer_spec.rb diff --git a/serializers/custom_wizard/submission_serializer.rb b/serializers/custom_wizard/submission_serializer.rb index f9cc7230..e5e88867 100644 --- a/serializers/custom_wizard/submission_serializer.rb +++ b/serializers/custom_wizard/submission_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class CustomWizard::SubmissionSerializer < ApplicationSerializer attributes :id, - :user, :fields, :submitted_at diff --git a/spec/serializers/custom_wizard/submission_serializer_spec.rb b/spec/serializers/custom_wizard/submission_serializer_spec.rb new file mode 100644 index 00000000..d3f3e7fc --- /dev/null +++ b/spec/serializers/custom_wizard/submission_serializer_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require_relative '../../plugin_helper' + +describe CustomWizard::SubmissionSerializer do + fab!(:user) { Fabricate(:user) } + + let(:template_json) { + JSON.parse(File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" + ).read) + } + + before do + CustomWizard::Template.save(template_json, skip_jobs: true) + wizard = CustomWizard::Wizard.create(template_json["id"], user) + CustomWizard::Submission.new(wizard, + step_1_field_1: "I am user submission", + submitted_at: Time.now.iso8601 + ).save + @list = CustomWizard::Submission.list(wizard, page: 0) + end + + it 'should return submission attributes' do + json_array = ActiveModel::ArraySerializer.new( + @list.submissions, + each_serializer: described_class + ).as_json + + expect(json_array.length).to eq(1) + expect(json_array[0][:id].present?).to eq(true) + expect(json_array[0][:user].present?).to eq(true) + expect(json_array[0][:submitted_at].present?).to eq(true) + end + + it "should return field values, types and labels" do + json_array = ActiveModel::ArraySerializer.new( + @list.submissions, + each_serializer: described_class + ).as_json + + expect(json_array.length).to eq(1) + expect(json_array[0][:fields].as_json).to eq({ + "step_1_field_1": { + "value": "I am user submission", + "type": "text", + "label": "Text" + } + }.as_json) + end +end From 31b9e48c8ff11340d1cfa437c0fea1f4afe39833 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 6 Sep 2021 17:26:22 +0800 Subject: [PATCH 036/556] Apply rubocop --- spec/serializers/custom_wizard/submission_serializer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/serializers/custom_wizard/submission_serializer_spec.rb b/spec/serializers/custom_wizard/submission_serializer_spec.rb index d3f3e7fc..02d8be8a 100644 --- a/spec/serializers/custom_wizard/submission_serializer_spec.rb +++ b/spec/serializers/custom_wizard/submission_serializer_spec.rb @@ -18,7 +18,7 @@ describe CustomWizard::SubmissionSerializer do step_1_field_1: "I am user submission", submitted_at: Time.now.iso8601 ).save - @list = CustomWizard::Submission.list(wizard, page: 0) + @list = CustomWizard::Submission.list(wizard, page: 0) end it 'should return submission attributes' do From 23c4b45195c4efebfaea454a15081ae405f3ed64 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 7 Sep 2021 20:06:13 +0800 Subject: [PATCH 037/556] Add pro feature specs --- .../components/wizard-custom-action.js.es6 | 2 +- .../discourse/lib/wizard-schema.js.es6 | 2 +- controllers/custom_wizard/admin/pro.rb | 2 +- controllers/custom_wizard/wizard.rb | 2 +- coverage/.last_run.json | 2 +- jobs/scheduled/update_pro_subscription.rb | 2 +- lib/custom_wizard/builder.rb | 34 +-- lib/custom_wizard/mapper.rb | 2 +- lib/custom_wizard/pro.rb | 35 ++- lib/custom_wizard/pro/authentication.rb | 22 +- lib/custom_wizard/validators/template.rb | 16 +- lib/custom_wizard/wizard.rb | 10 +- spec/components/custom_wizard/action_spec.rb | 199 ++++++------ spec/components/custom_wizard/builder_spec.rb | 46 +-- .../custom_wizard/custom_field_spec.rb | 76 +++-- spec/components/custom_wizard/field_spec.rb | 6 +- spec/components/custom_wizard/mapper_spec.rb | 177 ++++++----- spec/components/custom_wizard/pro_spec.rb | 125 ++++++++ spec/components/custom_wizard/step_spec.rb | 17 +- .../custom_wizard/submission_spec.rb | 7 +- .../components/custom_wizard/template_spec.rb | 13 +- .../custom_wizard/template_validator_spec.rb | 57 +++- .../custom_wizard/update_validator_spec.rb | 7 +- spec/components/custom_wizard/wizard_spec.rb | 18 +- .../custom_field_extensions_spec.rb | 74 +++-- .../extra_locales_controller_spec.rb | 14 +- spec/extensions/invites_controller_spec.rb | 7 +- spec/extensions/users_controller_spec.rb | 6 +- spec/fixtures/actions/add_to_group.json | 13 + spec/fixtures/actions/create_category.json | 51 ++++ spec/fixtures/actions/create_group.json | 104 +++++++ spec/fixtures/actions/send_message.json | 25 ++ spec/fixtures/actions/send_message_multi.json | 28 ++ spec/fixtures/custom_field/custom_fields.json | 16 - .../custom_field/pro_custom_fields.json | 28 ++ spec/fixtures/wizard.json | 285 ++---------------- spec/jobs/set_after_time_wizard_spec.rb | 6 +- spec/jobs/update_pro_subscription_spec.rb | 11 + spec/plugin_helper.rb | 41 +++ .../admin/custom_fields_controller_spec.rb | 9 +- .../admin/manager_controller_spec.rb | 7 +- .../admin/pro_controller_spec.rb | 71 +++++ .../admin/submissions_controller_spec.rb | 7 +- .../admin/wizard_controller_spec.rb | 7 +- .../application_controller_spec.rb | 16 +- .../custom_field_extensions_spec.rb | 91 +++--- .../custom_wizard/steps_controller_spec.rb | 273 +++++++---------- .../custom_wizard/wizard_controller_spec.rb | 25 +- .../basic_wizard_serializer_spec.rb | 7 +- .../custom_field_serializer_spec.rb | 7 +- .../pro/authentication_serializer_spec.rb | 17 ++ .../pro/subscription_serializer_spec.rb | 14 + .../custom_wizard/pro_serializer_spec.rb | 14 + .../wizard_field_serializer_spec.rb | 7 +- .../custom_wizard/wizard_serializer_spec.rb | 14 +- .../wizard_step_serializer_spec.rb | 18 +- 56 files changed, 1199 insertions(+), 993 deletions(-) create mode 100644 spec/components/custom_wizard/pro_spec.rb create mode 100644 spec/fixtures/actions/add_to_group.json create mode 100644 spec/fixtures/actions/create_category.json create mode 100644 spec/fixtures/actions/create_group.json create mode 100644 spec/fixtures/actions/send_message.json create mode 100644 spec/fixtures/actions/send_message_multi.json create mode 100644 spec/fixtures/custom_field/pro_custom_fields.json create mode 100644 spec/jobs/update_pro_subscription_spec.rb create mode 100644 spec/requests/custom_wizard/admin/pro_controller_spec.rb create mode 100644 spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb create mode 100644 spec/serializers/custom_wizard/pro_serializer_spec.rb diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index a04ff563..b0831d5d 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,6 +1,6 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; -import { and, empty, equal, or } from "@ember/object/computed"; +import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; import { computed } from "@ember/object"; import UndoChanges from "../mixins/undo-changes"; diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 613e6569..2e810634 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -197,9 +197,9 @@ const action = { required: ["id", "type"], proTypes: [ 'send_message', + 'add_to_group', 'create_category', 'create_group', - 'watch_categories', 'send_to_api' ], dependent: {}, diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb index 15e9e50f..70e5de04 100644 --- a/controllers/custom_wizard/admin/pro.rb +++ b/controllers/custom_wizard/admin/pro.rb @@ -10,7 +10,7 @@ class CustomWizard::AdminProController < CustomWizard::AdminController def authorize request_id = SecureRandom.hex(32) cookies[:user_api_request_id] = request_id - redirect_to pro.authentication_request(current_user.id, request_id).to_s + redirect_to pro.authentication_url(current_user.id, request_id).to_s end def authorize_callback diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 804fc422..78f2fd4a 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -5,7 +5,7 @@ class CustomWizard::WizardController < ::ApplicationController layout 'wizard' before_action :ensure_plugin_enabled - before_action :update_pro_subscription + before_action :update_pro_subscription, only: [:index] helper_method :wizard_page_title helper_method :wizard_theme_id helper_method :wizard_theme_lookup diff --git a/coverage/.last_run.json b/coverage/.last_run.json index 2d4d0378..cff5740b 100644 --- a/coverage/.last_run.json +++ b/coverage/.last_run.json @@ -1,5 +1,5 @@ { "result": { - "line": 91.83 + "line": 91.96 } } diff --git a/jobs/scheduled/update_pro_subscription.rb b/jobs/scheduled/update_pro_subscription.rb index d00b2560..773093b0 100644 --- a/jobs/scheduled/update_pro_subscription.rb +++ b/jobs/scheduled/update_pro_subscription.rb @@ -3,7 +3,7 @@ class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled every 1.hour - def execute(args) + def execute(args={}) CustomWizard::Pro.update_subscription end end \ No newline at end of file diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 2ecdf12d..99370f19 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -72,6 +72,23 @@ class CustomWizard::Builder @wizard end + def check_condition(template) + if template['condition'].present? + result = CustomWizard::Mapper.new( + inputs: template['condition'], + user: @wizard.user, + data: @wizard.current_submission&.fields_and_meta, + opts: { + multiple: true + } + ).perform + + result.any? + else + true + end + end + private def mapper @@ -224,23 +241,6 @@ class CustomWizard::Builder end end - def check_condition(template) - if template['condition'].present? - result = CustomWizard::Mapper.new( - inputs: template['condition'], - user: @wizard.user, - data: @wizard.current_submission&.fields_and_meta, - opts: { - multiple: true - } - ).perform - - result.any? - else - true - end - end - def check_if_permitted(step, step_template) step.permitted = true diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index b418b4b9..6700e37d 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -252,7 +252,7 @@ class CustomWizard::Mapper end end - if @pro.subscribed? && opts[:template] + if opts[:template] && @pro.subscribed? template = Liquid::Template.parse(string) string = template.render(data) end diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb index 3726e9b4..5a813e21 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/pro.rb @@ -54,24 +54,27 @@ class CustomWizard::Pro return false unless data && data.is_a?(Hash) subscriptions = data[:subscriptions] - if subscriptions.present? - subscription = subscriptions.first - type = subscription[:price_nickname] - + if subscriptions.present? && type = subscriptions.first[:price_nickname] @subscription = set_subscription(type) return true end end end - remove_subscription + destroy_subscription + false end def destroy_subscription - remove_subscription + if remove_subscription + @subscription = CustomWizard::ProSubscription.new(get_subscription) + !@subscription.active? + else + false + end end - def authentication_request(user_id, request_id) + def authentication_url(user_id, request_id) keys = @authentication.generate_keys(user_id, request_id) params = { public_key: keys.public_key, @@ -89,7 +92,7 @@ class CustomWizard::Pro def authentication_response(request_id, payload) data = @authentication.decrypt_payload(request_id, payload) - return unless data.is_a?(Hash) && data[:key] && data[:user_id] + return false unless data.is_a?(Hash) && data[:key] && data[:user_id] api_key = data[:key] user_id = data[:user_id] @@ -104,13 +107,26 @@ class CustomWizard::Pro end def destroy_authentication - remove_authentication + if remove_authentication + @authentication = CustomWizard::ProAuthentication.new(get_authentication) + !@authentication.active? + else + false + end end def self.subscribed? self.new.subscribed? end + def self.authorized? + self.new.authorized? + end + + def self.update_subscription + self.new.update_subscription + end + def self.namespace "custom_wizard_pro" end @@ -165,5 +181,6 @@ class CustomWizard::Pro def remove_authentication PluginStore.remove(self.class.namespace, authentication_db_key) + get_authentication end end \ No newline at end of file diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/pro/authentication.rb index 0f3546eb..8d96a017 100644 --- a/lib/custom_wizard/pro/authentication.rb +++ b/lib/custom_wizard/pro/authentication.rb @@ -30,11 +30,13 @@ class CustomWizard::ProAuthentication def decrypt_payload(request_id, payload) keys = get_keys(request_id) + return false unless keys.present? && keys.pem delete_keys(request_id) rsa = OpenSSL::PKey::RSA.new(keys.pem) decrypted_payload = rsa.private_decrypt(Base64.decode64(payload)) + return false unless decrypted_payload.present? begin @@ -48,7 +50,16 @@ class CustomWizard::ProAuthentication data end - + + def get_keys(request_id) + raw = PluginStore.get(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") + OpenStruct.new( + user_id: raw && raw['user_id'], + pem: raw && raw['pem'], + nonce: raw && raw['nonce'] + ) + end + private def keys_db_key @@ -67,15 +78,6 @@ class CustomWizard::ProAuthentication ) end - def get_keys(request_id) - raw = PluginStore.get(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") - OpenStruct.new( - user_id: raw && raw['user_id'], - pem: raw && raw['pem'], - nonce: raw && raw['nonce'] - ) - end - def delete_keys(request_id) PluginStore.remove(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") end diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index 0f5f686c..839a070f 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -21,8 +21,8 @@ class CustomWizard::TemplateValidator check_required(step, :step) validate_pro(step, :step) - if data[:fields].present? - data[:fields].each do |field| + if step[:fields].present? + step[:fields].each do |field| validate_pro(field, :field) check_required(field, :field) end @@ -66,9 +66,9 @@ class CustomWizard::TemplateValidator action: { type: %w[ send_message + add_to_group create_category create_group - watch_categories send_to_api ] } @@ -87,11 +87,13 @@ class CustomWizard::TemplateValidator def validate_pro(object, type) self.class.pro[type].each do |property, pro_type| - is_pro = (pro_type === 'present' && object[property].present?) || - (pro_type === 'conditional' && object[property].is_a?(Hash)) || - (pro_type.is_a?(Array) && pro_type.includes?(object[property])) + is_pro = object[property.to_s].present? && ( + pro_type === 'present' || + (pro_type === 'conditional' && object[property.to_s].is_a?(Hash)) || + (pro_type.is_a?(Array) && pro_type.include?(object[property.to_s])) + ) - if is_pro && @pro.subscribed? + if is_pro && !@pro.subscribed? errors.add :base, I18n.t("wizard.validation.pro", type: type.to_s, property: property) end end diff --git a/lib/custom_wizard/wizard.rb b/lib/custom_wizard/wizard.rb index 8f5a897f..cd59f361 100644 --- a/lib/custom_wizard/wizard.rb +++ b/lib/custom_wizard/wizard.rb @@ -308,10 +308,10 @@ class CustomWizard::Wizard end end - def self.list(user, template_opts: {}, not_completed: false) + def self.list(user, template_opts = {}, not_completed = false) return [] unless user - CustomWizard::Template.list(template_opts).reduce([]) do |result, template| + CustomWizard::Template.list(**template_opts).reduce([]) do |result, template| wizard = new(template, user) result.push(wizard) if wizard.can_access? && ( !not_completed || !wizard.completed? @@ -323,7 +323,7 @@ class CustomWizard::Wizard def self.after_signup(user) wizards = list( user, - template_opts: { + { setting: 'after_signup', order: "(value::json ->> 'permitted') IS NOT NULL DESC" } @@ -334,11 +334,11 @@ class CustomWizard::Wizard def self.prompt_completion(user) wizards = list( user, - template_opts: { + { setting: 'prompt_completion', order: "(value::json ->> 'permitted') IS NOT NULL DESC" }, - not_completed: true + true ) if wizards.any? wizards.map do |w| diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 8b617c39..438f29dd 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -6,26 +6,22 @@ describe CustomWizard::Action do fab!(:category) { Fabricate(:category, name: 'cat1', slug: 'cat-slug') } fab!(:group) { Fabricate(:group) } - let(:wizard_template) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read - ) - } + let(:wizard_template) { get_wizard_fixture("wizard") } + let(:open_composer) { get_wizard_fixture("actions/open_composer") } + let(:create_category) { get_wizard_fixture("actions/create_category") } + let(:create_group) { get_wizard_fixture("actions/create_group") } + let(:add_to_group) { get_wizard_fixture("actions/add_to_group") } + let(:send_message) { get_wizard_fixture("actions/send_message") } + let(:send_message_multi) { get_wizard_fixture("actions/send_message_multi") } - let(:open_composer) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/actions/open_composer.json" - ).read - ) - } + def update_template(template) + CustomWizard::Template.save(template, skip_jobs: true) + @template = CustomWizard::Template.find('super_mega_fun_wizard') + end before do Group.refresh_automatic_group!(:trust_level_2) - CustomWizard::Template.save(wizard_template, skip_jobs: true) - @template = CustomWizard::Template.find('super_mega_fun_wizard') + update_template(wizard_template) end context 'creating a topic' do @@ -110,54 +106,6 @@ describe CustomWizard::Action do end end - context 'sending a message' do - it 'works' do - User.create(username: 'angus1', email: "angus1@email.com") - - wizard = CustomWizard::Builder.new(@template[:id], user).build - wizard.create_updater(wizard.steps[0].id, {}).update - wizard.create_updater(wizard.steps[1].id, {}).update - - topic = Topic.where( - archetype: Archetype.private_message, - title: "Message title" - ) - - post = Post.where( - topic_id: topic.pluck(:id), - raw: "I will interpolate some wizard fields" - ) - - expect(topic.exists?).to eq(true) - expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1') - expect(post.exists?).to eq(true) - end - - it 'allows using multiple PM targets' do - User.create(username: 'angus1', email: "angus1@email.com") - User.create(username: 'faiz', email: "faiz@email.com") - Group.create(name: "cool_group") - Group.create(name: 'cool_group_1') - wizard = CustomWizard::Builder.new(@template[:id], user).build - wizard.create_updater(wizard.steps[0].id, {}).update - wizard.create_updater(wizard.steps[1].id, {}).update - - topic = Topic.where( - archetype: Archetype.private_message, - title: "Multiple Recipients title" - ) - - post = Post.where( - topic_id: topic.pluck(:id), - raw: "I will interpolate some wizard fields" - ) - expect(topic.exists?).to eq(true) - expect(topic.first.all_allowed_users.map(&:username)).to include('angus1', 'faiz') - expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1') - expect(post.exists?).to eq(true) - end - end - it 'updates a profile' do wizard = CustomWizard::Builder.new(@template[:id], user).build upload = Upload.create!( @@ -182,10 +130,8 @@ describe CustomWizard::Action do updater = wizard.create_updater(wizard.steps[1].id, {}) updater.update - category = Category.find_by(id: wizard.current_submission.fields['action_8']) - expect(updater.result[:redirect_on_next]).to eq( - "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus%40email.com&category_id=#{category.id}&tags=tag1" + "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus%40email.com&tags=tag1" ) end @@ -210,35 +156,11 @@ describe CustomWizard::Action do end end - it 'creates a category' do - wizard = CustomWizard::Builder.new(@template[:id], user).build - wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update - wizard.create_updater(wizard.steps[1].id, {}).update - expect(Category.where(id: wizard.current_submission.fields['action_8']).exists?).to eq(true) - end - - it 'creates a group' do - wizard = CustomWizard::Builder.new(@template[:id], user).build - wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update - expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true) - end - - it 'adds a user to a group' do - wizard = CustomWizard::Builder.new(@template[:id], user).build - step_id = wizard.steps[0].id - updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update - group = Group.find_by(name: wizard.current_submission.fields['action_9']) - expect(group.users.first.username).to eq('angus') - end - it 'watches categories' do wizard = CustomWizard::Builder.new(@template[:id], user).build wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update wizard.create_updater(wizard.steps[1].id, {}).update - expect(CategoryUser.where( - category_id: wizard.current_submission.fields['action_8'], - user_id: user.id - ).first.notification_level).to eq(2) + expect(CategoryUser.where( category_id: category.id, user_id: user.id @@ -251,4 +173,97 @@ describe CustomWizard::Action do updater.update expect(updater.result[:redirect_on_next]).to eq("https://google.com") end + + context "pro actions" do + before do + enable_pro + end + + it '#send_message' do + wizard_template['actions'] << send_message + update_template(wizard_template) + + User.create(username: 'angus1', email: "angus1@email.com") + + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, {}).update + wizard.create_updater(wizard.steps[1].id, {}).update + + topic = Topic.where( + archetype: Archetype.private_message, + title: "Message title" + ) + + post = Post.where( + topic_id: topic.pluck(:id), + raw: "I will interpolate some wizard fields" + ) + + expect(topic.exists?).to eq(true) + expect(topic.first.topic_allowed_users.first.user.username).to eq('angus1') + expect(post.exists?).to eq(true) + end + + it '#send_message allows using multiple targets' do + wizard_template['actions'] << send_message_multi + update_template(wizard_template) + + User.create(username: 'angus1', email: "angus1@email.com") + User.create(username: 'faiz', email: "faiz@email.com") + Group.create(name: "cool_group") + Group.create(name: 'cool_group_1') + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, {}).update + wizard.create_updater(wizard.steps[1].id, {}).update + + topic = Topic.where( + archetype: Archetype.private_message, + title: "Multiple Recipients title" + ) + + post = Post.where( + topic_id: topic.pluck(:id), + raw: "I will interpolate some wizard fields" + ) + + expect(topic.exists?).to eq(true) + expect(topic.first.all_allowed_users.map(&:username)).to include('angus1', 'faiz') + expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1') + expect(post.exists?).to eq(true) + end + + it '#create_category' do + wizard_template['actions'] << create_category + update_template(wizard_template) + + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update + wizard.create_updater(wizard.steps[1].id, {}).update + + expect(Category.where(id: wizard.current_submission.fields['action_8']).exists?).to eq(true) + end + + it '#create_group' do + wizard_template['actions'] << create_group + update_template(wizard_template) + + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update + + expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true) + end + + it '#add_to_group' do + wizard_template['actions'] << create_group + wizard_template['actions'] << add_to_group + update_template(wizard_template) + + wizard = CustomWizard::Builder.new(@template[:id], user).build + step_id = wizard.steps[0].id + updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update + group = Group.find_by(name: wizard.current_submission.fields['action_9']) + + expect(group.users.first.username).to eq('angus') + end + end end diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 8e80d806..19be5830 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -15,45 +15,15 @@ describe CustomWizard::Builder do fab!(:category2) { Fabricate(:category, name: 'cat2') } fab!(:group) { Fabricate(:group) } - let(:required_data_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" - ).read - ) - } - - let(:permitted_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" - ).read - ) - } - - let(:permitted_param_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/permitted_params.json" - ).read - ) - } - - let(:user_condition_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/condition/user_condition.json" - ).read - ) - } + let(:wizard_template) { get_wizard_fixture("wizard") } + let(:required_data_json) { get_wizard_fixture("step/required_data") } + let(:permitted_json) { get_wizard_fixture("wizard/permitted") } + let(:permitted_param_json) { get_wizard_fixture("step/permitted_params") } + let(:user_condition_json) { get_wizard_fixture("condition/user_condition") } before do Group.refresh_automatic_group!(:trust_level_3) - CustomWizard::Template.save( - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read), - skip_jobs: true) + CustomWizard::Template.save(wizard_template, skip_jobs: true) @template = CustomWizard::Template.find('super_mega_fun_wizard') end @@ -281,8 +251,9 @@ describe CustomWizard::Builder do end end - context "with condition" do + context "with condition" do before do + enable_pro @template[:steps][0][:condition] = user_condition_json['condition'] CustomWizard::Template.save(@template.as_json) end @@ -321,6 +292,7 @@ describe CustomWizard::Builder do context "with condition" do before do + enable_pro @template[:steps][0][:fields][0][:condition] = user_condition_json['condition'] CustomWizard::Template.save(@template.as_json) end diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index b17e26c6..fb1799dd 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -3,12 +3,8 @@ require_relative '../../plugin_helper' describe CustomWizard::CustomField do - - let(:custom_field_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" - ).read) - } + let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } + let(:custom_field_pro_json) { get_wizard_fixture("custom_field/pro_custom_fields") } before do CustomWizard::CustomField.invalidate_cache @@ -104,8 +100,8 @@ describe CustomWizard::CustomField do it "does not save with an unsupported serializer" do invalid_field_json = custom_field_json['custom_fields'].first - invalid_field_json['klass'] = 'category' - invalid_field_json['serializers'] = ['category', 'site_category'] + invalid_field_json['klass'] = 'post' + invalid_field_json['serializers'] = ['post', 'post_revision'] custom_field = CustomWizard::CustomField.new(nil, invalid_field_json) @@ -113,8 +109,8 @@ describe CustomWizard::CustomField do expect(custom_field.valid?).to eq(false) expect(custom_field.errors.full_messages.first).to eq( I18n.t("wizard.custom_field.error.unsupported_serializers", - class: "category", - serializers: "category, site_category" + class: "post", + serializers: "post_revision" ) ) expect( @@ -196,6 +192,50 @@ describe CustomWizard::CustomField do ).exists? ).to eq(false) end + + it "does not save pro field types without a pro subscription" do + pro_field_json = custom_field_pro_json['custom_fields'].first + custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.pro_type", type: "json") + ) + end + + it "does not save pro field classes without a pro subscription" do + pro_field_json = custom_field_pro_json['custom_fields'].second + custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + + expect(custom_field.save).to eq(false) + expect(custom_field.valid?).to eq(false) + expect(custom_field.errors.full_messages.first).to eq( + I18n.t("wizard.custom_field.error.pro_type", type: "category") + ) + end + + context "with a pro subscription" do + before do + enable_pro + end + + it "saves pro field types" do + pro_field_json = custom_field_pro_json['custom_fields'].first + custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + + expect(custom_field.save).to eq(true) + expect(custom_field.valid?).to eq(true) + end + + it "saves pro field classes" do + pro_field_json = custom_field_pro_json['custom_fields'].second + custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + + expect(custom_field.save).to eq(true) + expect(custom_field.valid?).to eq(true) + end + end end context "lists" do @@ -205,15 +245,15 @@ describe CustomWizard::CustomField do end end - it "lists saved custom field records" do - expect(CustomWizard::CustomField.list.length).to eq(4) + it "saved custom field records" do + expect(CustomWizard::CustomField.list.length).to eq(2) end - it "lists saved custom field records by attribute value" do + it "saved custom field records by attribute value" do expect(CustomWizard::CustomField.list_by(:klass, 'topic').length).to eq(1) end - it "lists saved custom field records by optional values" do + it "saved custom field records by optional values" do field_json = custom_field_json['custom_fields'].first field_json['serializers'] = nil @@ -221,12 +261,12 @@ describe CustomWizard::CustomField do expect(CustomWizard::CustomField.list_by(:serializers, ['post']).length).to eq(0) end - it "lists custom field records added by other plugins " do - expect(CustomWizard::CustomField.external_list.length).to eq(11) + it "custom field records added by other plugins " do + expect(CustomWizard::CustomField.external_list.length).to be > 10 end - it "lists all custom field records" do - expect(CustomWizard::CustomField.full_list.length).to eq(15) + it "all custom field records" do + expect(CustomWizard::CustomField.full_list.length).to be > 12 end end diff --git a/spec/components/custom_wizard/field_spec.rb b/spec/components/custom_wizard/field_spec.rb index 871c42cd..0fcf9fc2 100644 --- a/spec/components/custom_wizard/field_spec.rb +++ b/spec/components/custom_wizard/field_spec.rb @@ -2,11 +2,7 @@ require_relative '../../plugin_helper' describe CustomWizard::Field do - let(:field_hash) do - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json" - ).read).with_indifferent_access - end + let(:field_hash) { get_wizard_fixture("field/field") } before do CustomWizard::Field.register( diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index ed66d7c1..b210c588 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -31,16 +31,8 @@ describe CustomWizard::Mapper do ] ) } - let(:inputs) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/inputs.json" - ).read) - } - let(:data) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/data.json" - ).read) - } + let(:inputs) { get_wizard_fixture("mapper/inputs") } + let(:data) { get_wizard_fixture("mapper/data") } let(:template_params) { { "step_1_field_1" => "Hello" @@ -352,7 +344,7 @@ describe CustomWizard::Mapper do expect(result).to eq(template_params["step_1_field_1"]) end - it "treats replaced values as string literals" do + it "requires a pro subscription" do template = '{{ "w{step_1_field_1}" | size }}' mapper = create_template_mapper(template_params, user1) result = mapper.interpolate( @@ -362,60 +354,17 @@ describe CustomWizard::Mapper do wizard: true, value: true ) - expect(result).to eq(template_params["step_1_field_1"].size.to_s) + expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}") end - it "allows the wizard values to be used inside conditionals" do - template = <<-LIQUID - {%- if "w{step_1_field_1}" contains "ello" -%} - Correct - {%- else -%} - Incorrect - {%-endif-%} - LIQUID - mapper = create_template_mapper(template_params, user1) - result = mapper.interpolate( - template.dup, - template: true, - user: true, - wizard: true, - value: true - ) - expect(result).to eq("Correct") - end + context "with a pro subscription" do + before do + enable_pro + end - it "can access data passed to render method as variable" do - template = "{{step_1_field_1.size}}" - mapper = create_template_mapper(template_params, user1) - result = mapper.interpolate( - template.dup, - template: true, - user: true, - wizard: true, - value: true - ) - expect(result).to eq(template_params["step_1_field_1"].size.to_s) - end - - it "doesn't parse the template when template param is false" do - template = <<-LIQUID.strip - {{ "w{step_1_field_1}" | size}} - LIQUID - mapper = create_template_mapper(template_params, user1) - result = mapper.interpolate( - template.dup, - template: false, - ) - expect(result).to eq(template) - end - - context "custom filter: 'first_non_empty'" do - it "gives first non empty element from list" do - template = <<-LIQUID.strip - {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} - {{ entry }} - LIQUID - mapper = create_template_mapper(template_params_non_empty, user1) + it "treats replaced values as string literals" do + template = '{{ "w{step_1_field_1}" | size }}' + mapper = create_template_mapper(template_params, user1) result = mapper.interpolate( template.dup, template: true, @@ -423,15 +372,18 @@ describe CustomWizard::Mapper do wizard: true, value: true ) - expect(result).to eq(template_params_non_empty["step_1_field_3"]) + expect(result).to eq(template_params["step_1_field_1"].size.to_s) end - it "gives first non empty element from list when multiple non empty values present" do - template = <<-LIQUID.strip - {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} - {{ entry }} + it "allows the wizard values to be used inside conditionals" do + template = <<-LIQUID + {%- if "w{step_1_field_1}" contains "ello" -%} + Correct + {%- else -%} + Incorrect + {%-endif-%} LIQUID - mapper = create_template_mapper(template_params_multiple_non_empty, user1) + mapper = create_template_mapper(template_params, user1) result = mapper.interpolate( template.dup, template: true, @@ -439,25 +391,84 @@ describe CustomWizard::Mapper do wizard: true, value: true ) - expect(result).to eq(template_params_multiple_non_empty["step_1_field_2"]) + expect(result).to eq("Correct") end - it "gives empty if all elements are empty" do + it "can access data passed to render method as variable" do + template = "{{step_1_field_1.size}}" + mapper = create_template_mapper(template_params, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params["step_1_field_1"].size.to_s) + end + + it "doesn't parse the template when template param is false" do template = <<-LIQUID.strip - {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} - {%- if entry -%} + {{ "w{step_1_field_1}" | size}} + LIQUID + mapper = create_template_mapper(template_params, user1) + result = mapper.interpolate( + template.dup, + template: false, + ) + expect(result).to eq(template) + end + + context "custom filter: 'first_non_empty'" do + it "gives first non empty element from list" do + template = <<-LIQUID.strip + {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} {{ entry }} - {%- endif -%} - LIQUID - mapper = create_template_mapper(template_params_empty, user1) - result = mapper.interpolate( - template.dup, - template: true, - user: true, - wizard: true, - value: true - ) - expect(result).to eq("") + LIQUID + mapper = create_template_mapper(template_params_non_empty, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params_non_empty["step_1_field_3"]) + end + + it "gives first non empty element from list when multiple non empty values present" do + template = <<-LIQUID.strip + {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} + {{ entry }} + LIQUID + mapper = create_template_mapper(template_params_multiple_non_empty, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params_multiple_non_empty["step_1_field_2"]) + end + + it "gives empty if all elements are empty" do + template = <<-LIQUID.strip + {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} + {%- if entry -%} + {{ entry }} + {%- endif -%} + LIQUID + mapper = create_template_mapper(template_params_empty, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq("") + end end end end diff --git a/spec/components/custom_wizard/pro_spec.rb b/spec/components/custom_wizard/pro_spec.rb new file mode 100644 index 00000000..6c83ae3b --- /dev/null +++ b/spec/components/custom_wizard/pro_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require_relative '../../plugin_helper' + +describe CustomWizard::Pro do + fab!(:user) { Fabricate(:user) } + + it "initializes pro authentication and subscription" do + pro = described_class.new + expect(pro.authentication.class).to eq(CustomWizard::ProAuthentication) + expect(pro.subscription.class).to eq(CustomWizard::ProSubscription) + end + + it "returns authorized and subscribed states" do + pro = described_class.new + expect(pro.authorized?).to eq(false) + expect(pro.subscribed?).to eq(false) + end + + context "subscription" do + before do + @pro = described_class.new + end + + it "updates valid subscriptions" do + stub_subscription_request(200, valid_subscription) + expect(@pro.update_subscription).to eq(true) + expect(@pro.subscribed?).to eq(true) + end + + it "handles invalid subscriptions" do + stub_subscription_request(200, invalid_subscription) + expect(@pro.update_subscription).to eq(false) + expect(@pro.subscribed?).to eq(false) + end + + it "handles subscription http errors" do + stub_subscription_request(404, {}) + expect(@pro.update_subscription).to eq(false) + expect(@pro.subscribed?).to eq(false) + end + + it "destroys subscriptions" do + stub_subscription_request(200, valid_subscription) + expect(@pro.update_subscription).to eq(true) + expect(@pro.destroy_subscription).to eq(true) + expect(@pro.subscribed?).to eq(false) + end + + it "has class aliases" do + authenticate_pro + stub_subscription_request(200, valid_subscription) + expect(described_class.update_subscription).to eq(true) + expect(described_class.subscribed?).to eq(true) + end + end + + context "authentication" do + before do + @pro = described_class.new + user.update!(admin: true) + end + + it "generates a valid authentication request url" do + request_id = SecureRandom.hex(32) + uri = URI(@pro.authentication_url(user.id, request_id)) + expect(uri.host).to eq(@pro.server) + + parsed_query = Rack::Utils.parse_query uri.query + expect(parsed_query['public_key'].present?).to eq(true) + expect(parsed_query['nonce'].present?).to eq(true) + expect(parsed_query['client_id'].present?).to eq(true) + expect(parsed_query['auth_redirect'].present?).to eq(true) + expect(parsed_query['application_name']).to eq(SiteSetting.title) + expect(parsed_query['scopes']).to eq(@pro.scope) + end + + def generate_payload(request_id, user_id) + uri = URI(@pro.authentication_url(user_id, request_id)) + keys = @pro.authentication.get_keys(request_id) + raw_payload = { + key: "12345", + nonce: keys.nonce, + push: false, + api: UserApiKeysController::AUTH_API_VERSION + }.to_json + public_key = OpenSSL::PKey::RSA.new(keys.pem) + Base64.encode64(public_key.public_encrypt(raw_payload)) + end + + it "handles authentication response if request and response is valid" do + request_id = SecureRandom.hex(32) + payload = generate_payload(request_id, user.id) + + expect(@pro.authentication_response(request_id, payload)).to eq(true) + expect(@pro.authorized?).to eq(true) + end + + it "discards authentication response if user who made request as not an admin" do + user.update!(admin: false) + + request_id = SecureRandom.hex(32) + payload = generate_payload(request_id, user.id) + + expect(@pro.authentication_response(request_id, payload)).to eq(false) + expect(@pro.authorized?).to eq(false) + end + + it "discards authentication response if request_id is invalid" do + payload = generate_payload(SecureRandom.hex(32), user.id) + + expect(@pro.authentication_response(SecureRandom.hex(32), payload)).to eq(false) + expect(@pro.authorized?).to eq(false) + end + + it "destroys authentication" do + request_id = SecureRandom.hex(32) + payload = generate_payload(request_id, user.id) + @pro.authentication_response(request_id, payload) + + expect(@pro.destroy_authentication).to eq(true) + expect(@pro.authorized?).to eq(false) + end + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/step_spec.rb b/spec/components/custom_wizard/step_spec.rb index bf4613a4..9ad02176 100644 --- a/spec/components/custom_wizard/step_spec.rb +++ b/spec/components/custom_wizard/step_spec.rb @@ -2,21 +2,8 @@ require_relative '../../plugin_helper' describe CustomWizard::Step do - let(:step_hash) do - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/step.json" - ).read - ).with_indifferent_access - end - - let(:field_hash) do - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/field.json" - ).read - ).with_indifferent_access - end + let(:step_hash) { get_wizard_fixture("step/step") } + let(:field_hash) { get_wizard_fixture("field/field") } before do @step = CustomWizard::Step.new(step_hash[:id]) diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index b85af243..a838820d 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -4,12 +4,7 @@ require_relative '../../plugin_helper' describe CustomWizard::Submission do fab!(:user) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) } - - let(:template_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - } + let(:template_json) { get_wizard_fixture("wizard") } before do CustomWizard::Template.save(template_json, skip_jobs: true) diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb index 0e3dbdbe..fca7f91e 100644 --- a/spec/components/custom_wizard/template_spec.rb +++ b/spec/components/custom_wizard/template_spec.rb @@ -3,17 +3,8 @@ require_relative '../../plugin_helper' describe CustomWizard::Template do fab!(:user) { Fabricate(:user) } - - let(:template_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - } - let(:permitted_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" - ).read) - } + let(:template_json) { get_wizard_fixture("wizard") } + let(:permitted_json) { get_wizard_fixture("wizard/permitted") } before do CustomWizard::Template.save(template_json, skip_jobs: true) diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb index c8ce915a..7a84660c 100644 --- a/spec/components/custom_wizard/template_validator_spec.rb +++ b/spec/components/custom_wizard/template_validator_spec.rb @@ -3,12 +3,9 @@ require_relative '../../plugin_helper' describe CustomWizard::TemplateValidator do fab!(:user) { Fabricate(:user) } - - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read).with_indifferent_access - } + let(:template) { get_wizard_fixture("wizard") } + let(:create_category) { get_wizard_fixture("actions/create_category") } + let(:user_condition) { get_wizard_fixture("condition/user_condition") } it "validates valid templates" do expect( @@ -45,4 +42,52 @@ describe CustomWizard::TemplateValidator do CustomWizard::TemplateValidator.new(template).perform ).to eq(false) end + + it "invalidates pro step attributes without a pro subscription" do + template[:steps][0][:condition] = user_condition['condition'] + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(false) + end + + it "invalidates pro field attributes without a pro subscription" do + template[:steps][0][:fields][0][:condition] = user_condition['condition'] + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(false) + end + + it "invalidates pro actions without a pro subscription" do + template[:actions] << create_category + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(false) + end + + context "with pro subscription" do + before do + enable_pro + end + + it "validates pro step attributes" do + template[:steps][0][:condition] = user_condition['condition'] + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(true) + end + + it "validates pro field attributes" do + template[:steps][0][:fields][0][:condition] = user_condition['condition'] + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(true) + end + + it "validates pro actions" do + template[:actions] << create_category + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(true) + end + end end diff --git a/spec/components/custom_wizard/update_validator_spec.rb b/spec/components/custom_wizard/update_validator_spec.rb index e976e1ff..c79a5b0b 100644 --- a/spec/components/custom_wizard/update_validator_spec.rb +++ b/spec/components/custom_wizard/update_validator_spec.rb @@ -3,12 +3,7 @@ require_relative '../../plugin_helper' describe CustomWizard::UpdateValidator do fab!(:user) { Fabricate(:user) } - - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read).with_indifferent_access - } + let(:template) { get_wizard_fixture("wizard") } before do CustomWizard::Template.save(template, skip_jobs: true) diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 67905f5a..9cccff97 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -6,22 +6,8 @@ describe CustomWizard::Wizard do fab!(:user) { Fabricate(:user) } fab!(:trusted_user) { Fabricate(:user, trust_level: TrustLevel[3]) } fab!(:admin_user) { Fabricate(:user, admin: true) } - - let(:template_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read - ) - } - - let(:permitted_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" - ).read - ) - } + let(:template_json) { get_wizard_fixture("wizard") } + let(:permitted_json) { get_wizard_fixture("wizard/permitted") } before do Group.refresh_automatic_group!(:trust_level_3) diff --git a/spec/extensions/custom_field_extensions_spec.rb b/spec/extensions/custom_field_extensions_spec.rb index f0ce32f5..bf1b3ff2 100644 --- a/spec/extensions/custom_field_extensions_spec.rb +++ b/spec/extensions/custom_field_extensions_spec.rb @@ -9,11 +9,8 @@ describe "custom field extensions" do fab!(:group) { Fabricate(:group) } fab!(:user) { Fabricate(:user) } - let(:custom_field_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" - ).read) - } + let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } + let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") } before do custom_field_json['custom_fields'].each do |field_json| @@ -75,43 +72,54 @@ describe "custom field extensions" do end end - context "category" do - it "registers category custom fields" do - category - expect(Category.get_custom_field_type("category_field_1")).to eq(:json) + context "pro custom fields" do + before do + enable_pro + + pro_custom_field_json['custom_fields'].each do |field_json| + custom_field = CustomWizard::CustomField.new(nil, field_json) + custom_field.save + end end - it "adds category custom fields to the basic category serializer" do - category.custom_fields["category_field_1"] = { a: 1, b: 2 }.to_json - category.save_custom_fields(true) + context "category" do + it "registers" do + category + expect(Category.get_custom_field_type("category_field_1")).to eq(:json) + end - serializer = BasicCategorySerializer.new( - category, - scope: Guardian.new(user), - root: false - ).as_json + it "adds custom fields to the basic category serializer" do + category.custom_fields["category_field_1"] = { a: 1, b: 2 }.to_json + category.save_custom_fields(true) - expect(serializer[:category_field_1]).to eq({ a: 1, b: 2 }.to_json) - end - end + serializer = BasicCategorySerializer.new( + category, + scope: Guardian.new(user), + root: false + ).as_json - context "group" do - it "registers group custom fields" do - group - expect(Group.get_custom_field_type("group_field_1")).to eq(:string) + expect(serializer[:category_field_1]).to eq({ a: 1, b: 2 }.to_json) + end end - it "adds group custom fields to the basic group serializer" do - group.custom_fields["group_field_1"] = "Hello" - group.save_custom_fields(true) + context "group" do + it "registers" do + group + expect(Group.get_custom_field_type("group_field_1")).to eq(:string) + end - serializer = BasicGroupSerializer.new( - group, - scope: Guardian.new(user), - root: false - ).as_json + it "adds custom fields to the basic group serializer" do + group.custom_fields["group_field_1"] = "Hello" + group.save_custom_fields(true) - expect(serializer[:group_field_1]).to eq("Hello") + serializer = BasicGroupSerializer.new( + group, + scope: Guardian.new(user), + root: false + ).as_json + + expect(serializer[:group_field_1]).to eq("Hello") + end end end end diff --git a/spec/extensions/extra_locales_controller_spec.rb b/spec/extensions/extra_locales_controller_spec.rb index a71e39c4..bb451b7c 100644 --- a/spec/extensions/extra_locales_controller_spec.rb +++ b/spec/extensions/extra_locales_controller_spec.rb @@ -4,18 +4,8 @@ require_relative '../plugin_helper' describe ExtraLocalesControllerCustomWizard, type: :request do let(:new_user) { Fabricate(:user, trust_level: TrustLevel[0]) } let(:staff_user) { Fabricate(:moderator) } - - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - } - - let(:permitted) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" - ).read) - } + let(:template) { get_wizard_fixture("wizard") } + let(:permitted) { get_wizard_fixture("wizard/permitted") } before do CustomWizard::Template.save(template, skip_jobs: true) diff --git a/spec/extensions/invites_controller_spec.rb b/spec/extensions/invites_controller_spec.rb index 47c4ca84..93fdd9d7 100644 --- a/spec/extensions/invites_controller_spec.rb +++ b/spec/extensions/invites_controller_spec.rb @@ -4,12 +4,7 @@ require_relative '../plugin_helper' describe InvitesControllerCustomWizard, type: :request do fab!(:topic) { Fabricate(:topic) } let(:invite) { Invite.generate(topic.user, email: "angus@mcleod.org", topic: topic) } - - let(:template) do - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - end + let(:template) { get_wizard_fixture("wizard") } before do @controller = InvitesController.new diff --git a/spec/extensions/users_controller_spec.rb b/spec/extensions/users_controller_spec.rb index f4ba8e51..e4cd972f 100644 --- a/spec/extensions/users_controller_spec.rb +++ b/spec/extensions/users_controller_spec.rb @@ -2,11 +2,7 @@ require_relative '../plugin_helper' describe CustomWizardUsersController, type: :request do - let(:template) do - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - end + let(:template) { get_wizard_fixture("wizard") } before do @controller = UsersController.new diff --git a/spec/fixtures/actions/add_to_group.json b/spec/fixtures/actions/add_to_group.json new file mode 100644 index 00000000..2a5af1c3 --- /dev/null +++ b/spec/fixtures/actions/add_to_group.json @@ -0,0 +1,13 @@ +{ + "id": "action_6", + "run_after": "step_1", + "type": "add_to_group", + "group": [ + { + "type": "assignment", + "output": "action_9", + "output_type": "wizard_action", + "output_connector": "set" + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/create_category.json b/spec/fixtures/actions/create_category.json new file mode 100644 index 00000000..c5f3d5af --- /dev/null +++ b/spec/fixtures/actions/create_category.json @@ -0,0 +1,51 @@ +{ + "id": "action_8", + "run_after": "step_1", + "type": "create_category", + "custom_fields": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "category_custom_field", + "key_type": "text", + "value": "CC Val", + "value_type": "text", + "connector": "association" + } + ] + } + ], + "name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "slug": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "permissions": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "action_9", + "key_type": "wizard_action", + "value": "2", + "value_type": "text", + "connector": "association" + } + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/create_group.json b/spec/fixtures/actions/create_group.json new file mode 100644 index 00000000..e2e52ef2 --- /dev/null +++ b/spec/fixtures/actions/create_group.json @@ -0,0 +1,104 @@ +{ + "id": "action_9", + "run_after": "step_1", + "type": "create_group", + "title": [ + { + "type": "assignment", + "output": "New Group Member", + "output_type": "text", + "output_connector": "set" + } + ], + "custom_fields": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "group_custom_field", + "key_type": "text", + "value": "step_3_field_1", + "value_type": "wizard_field", + "connector": "association" + } + ] + } + ], + "name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "full_name": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "usernames": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus1" + ] + } + ], + "owner_usernames": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus" + ] + } + ], + "grant_trust_level": [ + { + "type": "assignment", + "output": "3", + "output_type": "text", + "output_connector": "set" + } + ], + "mentionable_level": [ + { + "type": "assignment", + "output": "1", + "output_type": "text", + "output_connector": "set" + } + ], + "messageable_level": [ + { + "type": "assignment", + "output": "2", + "output_type": "text", + "output_connector": "set" + } + ], + "visibility_level": [ + { + "type": "assignment", + "output": "3", + "output_type": "text", + "output_connector": "set" + } + ], + "members_visibility_level": [ + { + "type": "assignment", + "output": "99", + "output_type": "text", + "output_connector": "set" + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/send_message.json b/spec/fixtures/actions/send_message.json new file mode 100644 index 00000000..ddc9bf10 --- /dev/null +++ b/spec/fixtures/actions/send_message.json @@ -0,0 +1,25 @@ +{ + "id": "action_2", + "run_after": "step_2", + "type": "send_message", + "post_builder": true, + "post_template": "I will interpolate some wizard fields w{step_1_field_1} w{step_1_field_2}", + "title": [ + { + "type": "assignment", + "output": "Message title", + "output_type": "text", + "output_connector": "set" + } + ], + "recipient": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus1" + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/actions/send_message_multi.json b/spec/fixtures/actions/send_message_multi.json new file mode 100644 index 00000000..8ab0fdb7 --- /dev/null +++ b/spec/fixtures/actions/send_message_multi.json @@ -0,0 +1,28 @@ +{ + "id": "action_11", + "run_after": "step_2", + "type": "send_message", + "post_builder": true, + "post_template": "I will interpolate some wizard fields w{step_1_field_1} w{step_1_field_2}", + "title": [ + { + "type": "assignment", + "output": "Multiple Recipients title", + "output_type": "text", + "output_connector": "set" + } + ], + "recipient": [ + { + "type": "assignment", + "output_type": "user", + "output_connector": "set", + "output": [ + "angus1", + "faiz", + "cool_group", + "cool_group_1" + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/custom_field/custom_fields.json b/spec/fixtures/custom_field/custom_fields.json index 0e7741ce..c9b98476 100644 --- a/spec/fixtures/custom_field/custom_fields.json +++ b/spec/fixtures/custom_field/custom_fields.json @@ -16,22 +16,6 @@ "serializers": [ "post" ] - }, - { - "klass": "category", - "name": "category_field_1", - "type": "json", - "serializers": [ - "basic_category" - ] - }, - { - "klass": "group", - "name": "group_field_1", - "type": "string", - "serializers": [ - "basic_group" - ] } ] } \ No newline at end of file diff --git a/spec/fixtures/custom_field/pro_custom_fields.json b/spec/fixtures/custom_field/pro_custom_fields.json new file mode 100644 index 00000000..b7060bdf --- /dev/null +++ b/spec/fixtures/custom_field/pro_custom_fields.json @@ -0,0 +1,28 @@ +{ + "custom_fields": [ + { + "klass": "topic", + "name": "topic_field_2", + "type": "json", + "serializers": [ + "topic_view" + ] + }, + { + "klass": "category", + "name": "category_field_1", + "type": "json", + "serializers": [ + "basic_category" + ] + }, + { + "klass": "group", + "name": "group_field_1", + "type": "string", + "serializers": [ + "basic_group" + ] + } + ] +} \ No newline at end of file diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index a505c0d3..1560acef 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -163,197 +163,6 @@ } ], "actions": [ - { - "id": "action_9", - "run_after": "step_1", - "type": "create_group", - "title": [ - { - "type": "assignment", - "output": "New Group Member", - "output_type": "text", - "output_connector": "set" - } - ], - "custom_fields": [ - { - "type": "association", - "pairs": [ - { - "index": 0, - "key": "group_custom_field", - "key_type": "text", - "value": "step_3_field_1", - "value_type": "wizard_field", - "connector": "association" - } - ] - } - ], - "name": [ - { - "type": "assignment", - "output": "step_1_field_1", - "output_type": "wizard_field", - "output_connector": "set" - } - ], - "full_name": [ - { - "type": "assignment", - "output": "step_1_field_1", - "output_type": "wizard_field", - "output_connector": "set" - } - ], - "usernames": [ - { - "type": "assignment", - "output_type": "user", - "output_connector": "set", - "output": [ - "angus1" - ] - } - ], - "owner_usernames": [ - { - "type": "assignment", - "output_type": "user", - "output_connector": "set", - "output": [ - "angus" - ] - } - ], - "grant_trust_level": [ - { - "type": "assignment", - "output": "3", - "output_type": "text", - "output_connector": "set" - } - ], - "mentionable_level": [ - { - "type": "assignment", - "output": "1", - "output_type": "text", - "output_connector": "set" - } - ], - "messageable_level": [ - { - "type": "assignment", - "output": "2", - "output_type": "text", - "output_connector": "set" - } - ], - "visibility_level": [ - { - "type": "assignment", - "output": "3", - "output_type": "text", - "output_connector": "set" - } - ], - "members_visibility_level": [ - { - "type": "assignment", - "output": "99", - "output_type": "text", - "output_connector": "set" - } - ] - }, - { - "id": "action_6", - "run_after": "step_1", - "type": "add_to_group", - "group": [ - { - "type": "assignment", - "output": "action_9", - "output_type": "wizard_action", - "output_connector": "set" - } - ] - }, - { - "id": "action_8", - "run_after": "step_1", - "type": "create_category", - "custom_fields": [ - { - "type": "association", - "pairs": [ - { - "index": 0, - "key": "category_custom_field", - "key_type": "text", - "value": "CC Val", - "value_type": "text", - "connector": "association" - } - ] - } - ], - "name": [ - { - "type": "assignment", - "output": "step_1_field_1", - "output_type": "wizard_field", - "output_connector": "set" - } - ], - "slug": [ - { - "type": "assignment", - "output": "step_1_field_1", - "output_type": "wizard_field", - "output_connector": "set" - } - ], - "permissions": [ - { - "type": "association", - "pairs": [ - { - "index": 0, - "key": "action_9", - "key_type": "wizard_action", - "value": "2", - "value_type": "text", - "connector": "association" - } - ] - } - ] - }, - { - "id": "action_5", - "run_after": "step_1", - "type": "watch_categories", - "notification_level": "tracking", - "wizard_user": true, - "categories": [ - { - "type": "assignment", - "output": "action_8", - "output_type": "wizard_action", - "output_connector": "set" - } - ], - "mute_remainder": [ - { - "type": "assignment", - "output": "true", - "output_type": "text", - "output_connector": "set" - } - ] - }, { "id": "action_1", "run_after": "step_3", @@ -442,6 +251,29 @@ } ] }, + { + "id": "action_5", + "run_after": "step_1", + "type": "watch_categories", + "notification_level": "tracking", + "wizard_user": true, + "categories": [ + { + "type": "assignment", + "output": "action_8", + "output_type": "wizard_action", + "output_connector": "set" + } + ], + "mute_remainder": [ + { + "type": "assignment", + "output": "true", + "output_type": "text", + "output_connector": "set" + } + ] + }, { "id": "action_4", "run_after": "step_2", @@ -462,59 +294,6 @@ } ] }, - { - "id": "action_2", - "run_after": "step_2", - "type": "send_message", - "post_builder": true, - "post_template": "I will interpolate some wizard fields w{step_1_field_1} w{step_1_field_2}", - "title": [ - { - "type": "assignment", - "output": "Message title", - "output_type": "text", - "output_connector": "set" - } - ], - "recipient": [ - { - "type": "assignment", - "output_type": "user", - "output_connector": "set", - "output": [ - "angus1" - ] - } - ] - }, - { - "id": "action_11", - "run_after": "step_2", - "type": "send_message", - "post_builder": true, - "post_template": "I will interpolate some wizard fields w{step_1_field_1} w{step_1_field_2}", - "title": [ - { - "type": "assignment", - "output": "Multiple Recipients title", - "output_type": "text", - "output_connector": "set" - } - ], - "recipient": [ - { - "type": "assignment", - "output_type": "user", - "output_connector": "set", - "output": [ - "angus1", - "faiz", - "cool_group", - "cool_group_1" - ] - } - ] - }, { "id": "action_3", "run_after": "step_2", @@ -529,24 +308,6 @@ "output_connector": "set" } ], - "category": [ - { - "type": "assignment", - "output": "action_8", - "output_type": "wizard_action", - "output_connector": "set", - "pairs": [ - { - "index": 0, - "key": "step_2_field_5", - "key_type": "wizard_field", - "value": "true", - "value_type": "text", - "connector": "is" - } - ] - } - ], "tags": [ { "type": "assignment", diff --git a/spec/jobs/set_after_time_wizard_spec.rb b/spec/jobs/set_after_time_wizard_spec.rb index 35576f01..93e46c8b 100644 --- a/spec/jobs/set_after_time_wizard_spec.rb +++ b/spec/jobs/set_after_time_wizard_spec.rb @@ -7,11 +7,7 @@ describe Jobs::SetAfterTimeWizard do fab!(:user2) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) } - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read).with_indifferent_access - } + let(:template) { get_wizard_fixture("wizard") } it "sets wizard redirect for all users " do after_time_template = template.dup diff --git a/spec/jobs/update_pro_subscription_spec.rb b/spec/jobs/update_pro_subscription_spec.rb new file mode 100644 index 00000000..0aae9668 --- /dev/null +++ b/spec/jobs/update_pro_subscription_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative '../plugin_helper' + +describe CustomWizard::UpdateProSubscription do + it "updates the pro subscription" do + stub_subscription_request(200, valid_subscription) + described_class.new.execute + expect(CustomWizard::Pro.subscribed?).to eq(true) + end +end diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 9e4bbbbe..4aa34029 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -15,3 +15,44 @@ require 'oj' Oj.default_options = Oj.default_options.merge(cache_str: -1) require 'rails_helper' + +def get_wizard_fixture(path) + JSON.parse( + File.open( + "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/#{path}.json" + ).read + ).with_indifferent_access +end + +def authenticate_pro + CustomWizard::ProAuthentication.any_instance.stubs(:active?).returns(true) +end + +def enable_pro + CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(true) +end + +def disable_pro + CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(false) +end + +def valid_subscription + { + product_id: "prod_CBTNpi3fqWWkq0", + price_id: "price_id", + price_nickname: "business" + } +end + +def invalid_subscription + { + product_id: "prod_CBTNpi3fqWWkq0", + price_id: "price_id" + } +end + +def stub_subscription_request(status, subscription) + authenticate_pro + pro = CustomWizard::Pro.new + stub_request(:get, "https://#{pro.server}/subscription-server/user-subscriptions/#{pro.subscription_type}/#{pro.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json) +end diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb index 8c1a8550..d2e086d2 100644 --- a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb @@ -3,12 +3,7 @@ require_relative '../../../plugin_helper' describe CustomWizard::AdminCustomFieldsController do fab!(:admin_user) { Fabricate(:user, admin: true) } - - let(:custom_field_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" - ).read) - } + let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } before do custom_field_json['custom_fields'].each do |field_json| @@ -19,7 +14,7 @@ describe CustomWizard::AdminCustomFieldsController do it "returns the full list of custom fields" do get "/admin/wizards/custom-fields.json" - expect(response.parsed_body.length).to eq(15) + expect(response.parsed_body["custom_fields"].length).to be > 12 end it "saves custom fields" do diff --git a/spec/requests/custom_wizard/admin/manager_controller_spec.rb b/spec/requests/custom_wizard/admin/manager_controller_spec.rb index 87c980f2..6218ea7e 100644 --- a/spec/requests/custom_wizard/admin/manager_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/manager_controller_spec.rb @@ -3,12 +3,7 @@ require_relative '../../../plugin_helper' describe CustomWizard::AdminManagerController do fab!(:admin_user) { Fabricate(:user, admin: true) } - - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - } + let(:template) { get_wizard_fixture("wizard") } before do sign_in(admin_user) diff --git a/spec/requests/custom_wizard/admin/pro_controller_spec.rb b/spec/requests/custom_wizard/admin/pro_controller_spec.rb new file mode 100644 index 00000000..049bb88e --- /dev/null +++ b/spec/requests/custom_wizard/admin/pro_controller_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true +require_relative '../../../plugin_helper' + +describe CustomWizard::AdminProController do + fab!(:admin_user) { Fabricate(:user, admin: true) } + + def generate_payload(request_id, user_id) + uri = URI(@pro.authentication_url(user_id, request_id)) + keys = @pro.authentication.get_keys(request_id) + raw_payload = { + key: "12345", + nonce: keys.nonce, + push: false, + api: UserApiKeysController::AUTH_API_VERSION + }.to_json + public_key = OpenSSL::PKey::RSA.new(keys.pem) + Base64.encode64(public_key.public_encrypt(raw_payload)) + end + + before do + @pro = CustomWizard::Pro.new + sign_in(admin_user) + end + + it "#index" do + get "/admin/wizards/pro.json" + expect(response.parsed_body['server']).to eq(@pro.server) + expect(response.parsed_body['authentication'].deep_symbolize_keys).to eq(CustomWizard::ProAuthenticationSerializer.new(@pro.authentication, root: false).as_json) + expect(response.parsed_body['subscription'].deep_symbolize_keys).to eq(CustomWizard::ProSubscriptionSerializer.new(@pro.subscription, root: false).as_json) + end + + it "#authorize" do + get "/admin/wizards/pro/authorize" + expect(response.status).to eq(302) + expect(cookies[:user_api_request_id].present?).to eq(true) + end + + it "#destroy_authentication" do + request_id = SecureRandom.hex(32) + payload = generate_payload(request_id, admin_user.id) + @pro.authentication_response(request_id, payload) + + delete "/admin/wizards/pro/authorize.json" + + expect(response.status).to eq(200) + expect(CustomWizard::Pro.authorized?).to eq(false) + end + + context "subscription" do + before do + stub_subscription_request(200, valid_subscription) + end + + it "handles authentication response and the updates subscription" do + request_id = cookies[:user_api_request_id] = SecureRandom.hex(32) + payload = generate_payload(request_id, admin_user.id) + get "/admin/wizards/pro/authorize/callback", params: { payload: payload } + + expect(response).to redirect_to("/admin/wizards/pro") + expect(CustomWizard::Pro.subscribed?).to eq(true) + end + + it "updates the subscription" do + authenticate_pro + post "/admin/wizards/pro/subscription.json" + + expect(response.status).to eq(200) + expect(CustomWizard::Pro.subscribed?).to eq(true) + end + end +end \ No newline at end of file diff --git a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb index 36296e95..5a79679f 100644 --- a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb @@ -7,12 +7,7 @@ describe CustomWizard::AdminSubmissionsController do fab!(:user2) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) } - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - } - + let(:template) { get_wizard_fixture("wizard") } let(:template_2) { temp = template.dup temp["id"] = "super_mega_fun_wizard_2" diff --git a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb index 82aa4fc5..9d7ed18d 100644 --- a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb @@ -5,12 +5,7 @@ describe CustomWizard::AdminWizardController do fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:user1) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) } - - let(:template) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read) - } + let(:template) { get_wizard_fixture("wizard") } before do CustomWizard::Template.save(template, skip_jobs: true) diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb index 0835f246..f92ac7c0 100644 --- a/spec/requests/custom_wizard/application_controller_spec.rb +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -2,21 +2,11 @@ require_relative '../../plugin_helper' describe ApplicationController do - fab!(:user) { - Fabricate( - :user, - username: 'angus', - email: "angus@email.com", - trust_level: TrustLevel[3] - ) - } + fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) } + let(:wizard_template) { get_wizard_fixture("wizard") } before do - CustomWizard::Template.save( - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read), - skip_jobs: true) + CustomWizard::Template.save(wizard_template, skip_jobs: true) @template = CustomWizard::Template.find('super_mega_fun_wizard') end diff --git a/spec/requests/custom_wizard/custom_field_extensions_spec.rb b/spec/requests/custom_wizard/custom_field_extensions_spec.rb index b991769a..64d7c755 100644 --- a/spec/requests/custom_wizard/custom_field_extensions_spec.rb +++ b/spec/requests/custom_wizard/custom_field_extensions_spec.rb @@ -8,12 +8,8 @@ describe "custom field extensions" do let!(:category) { Fabricate(:category) } let!(:user) { Fabricate(:user) } let!(:group) { Fabricate(:group, users: [user]) } - - let(:custom_field_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" - ).read) - } + let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } + let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") } before do custom_field_json['custom_fields'].each do |field_json| @@ -32,27 +28,6 @@ describe "custom field extensions" do expect(response.parsed_body["topic_field_1"]).to eq(true) end - it "adds category custom fields to the show categories response" do - category.custom_fields["category_field_1"] = { a: 1, b: 2 } - category.save_custom_fields(true) - - get "/c/#{category.id}/show.json" - - expect(response.status).to eq(200) - expect(response.parsed_body["category"]["category_field_1"]).to eq({ a: 1, b: 2 }.as_json) - end - - it "adds group custom fields to the show group response" do - group.custom_fields["group_field_1"] = "Group cf entry" - group.save_custom_fields(true) - - sign_in(user) - get "/groups/#{group.name}.json" - - expect(response.status).to eq(200) - expect(response.parsed_body['group']['group_field_1']).to eq("Group cf entry") - end - it "adds post custom fields to the show post response" do post.custom_fields["post_field_1"] = 7 post.save_custom_fields(true) @@ -63,32 +38,64 @@ describe "custom field extensions" do expect(response.parsed_body['post_field_1']).to eq(7) end - context "preloaded" do - it "preloads category custom fields on site categories" do - Site.preloaded_category_custom_fields << "other_field" + context "with a pro subscription" do + before do + enable_pro + pro_custom_field_json['custom_fields'].each do |field_json| + custom_field = CustomWizard::CustomField.new(nil, field_json) + custom_field.save + end + end + + it "adds category custom fields to the show categories response" do category.custom_fields["category_field_1"] = { a: 1, b: 2 } category.save_custom_fields(true) - get "/site.json" - expect(response.status).to eq(200) + get "/c/#{category.id}/show.json" - site_category = response.parsed_body['categories'].select { |c| c['id'] == category.id }.first - expect(site_category["category_field_1"]).to eq({ a: 1, b: 2 }.as_json) + expect(response.status).to eq(200) + expect(response.parsed_body["category"]["category_field_1"]).to eq({ a: 1, b: 2 }.as_json) end - it "preloads group custom fields on group index" do - Group.preloaded_custom_field_names << "other_field" - - group = Fabricate(:group) + it "adds group custom fields to the show group response" do group.custom_fields["group_field_1"] = "Group cf entry" group.save_custom_fields(true) - get "/groups.json" - expect(response.status).to eq(200) + sign_in(user) + get "/groups/#{group.name}.json" - group = response.parsed_body['groups'].select { |g| g['id'] == group.id }.first - expect(group['group_field_1']).to eq("Group cf entry") + expect(response.status).to eq(200) + expect(response.parsed_body['group']['group_field_1']).to eq("Group cf entry") + end + + context "preloaded" do + it "preloads category custom fields on site categories" do + Site.preloaded_category_custom_fields << "other_field" + + category.custom_fields["category_field_1"] = { a: 1, b: 2 } + category.save_custom_fields(true) + + get "/site.json" + expect(response.status).to eq(200) + + site_category = response.parsed_body['categories'].select { |c| c['id'] == category.id }.first + expect(site_category["category_field_1"]).to eq({ a: 1, b: 2 }.as_json) + end + + it "preloads group custom fields on group index" do + Group.preloaded_custom_field_names << "other_field" + + group = Fabricate(:group) + group.custom_fields["group_field_1"] = "Group cf entry" + group.save_custom_fields(true) + + get "/groups.json" + expect(response.status).to eq(200) + + group = response.parsed_body['groups'].select { |g| g['id'] == group.id }.first + expect(group['group_field_1']).to eq("Group cf entry") + end end end end diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 5da75d8d..3564780f 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -2,55 +2,12 @@ require_relative '../../plugin_helper' describe CustomWizard::StepsController do - fab!(:user) { - Fabricate( - :user, - username: 'angus', - email: "angus@email.com", - trust_level: TrustLevel[3] - ) - } - - fab!(:user2) { - Fabricate( - :user, - username: 'bob', - email: "bob@email.com", - trust_level: TrustLevel[2] - ) - } - - let(:wizard_template) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read - ) - } - - let(:wizard_field_condition_template) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/condition/wizard_field_condition.json" - ).read - ) - } - - let(:user_condition_template) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/condition/user_condition.json" - ).read - ) - } - - let(:permitted_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" - ).read - ) - } + fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) } + fab!(:user2) { Fabricate(:user, username: 'bob', email: "bob@email.com", trust_level: TrustLevel[2]) } + let(:wizard_template) { get_wizard_fixture("wizard") } + let(:wizard_field_condition_template) { get_wizard_fixture("condition/wizard_field_condition") } + let(:user_condition_template) { get_wizard_fixture("condition/user_condition") } + let(:permitted_json) { get_wizard_fixture("wizard/permitted") } before do CustomWizard::Template.save(wizard_template, skip_jobs: true) @@ -90,17 +47,6 @@ describe CustomWizard::StepsController do put '/w/super-mega-fun-wizard/steps/step_10.json' expect(response.status).to eq(400) end - - it "when user cant see the step due to conditions" do - sign_in(user2) - - new_wizard_template = wizard_template.dup - new_wizard_template['steps'][0]['condition'] = user_condition_template['condition'] - CustomWizard::Template.save(new_wizard_template, skip_jobs: true) - - put '/w/super-mega-fun-wizard/steps/step_1.json' - expect(response.status).to eq(403) - end end it "works if the step has no fields" do @@ -123,64 +69,40 @@ describe CustomWizard::StepsController do expect(response.parsed_body['wizard']['start']).to eq("step_2") end - it "returns an updated wizard when condition doesnt pass" do - new_template = wizard_template.dup - new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] - CustomWizard::Template.save(new_template, skip_jobs: true) - - put '/w/super-mega-fun-wizard/steps/step_1.json', params: { - fields: { - step_1_field_1: "Condition wont pass" - } - } - expect(response.status).to eq(200) - expect(response.parsed_body['wizard']['start']).to eq("step_3") - end - it "runs completion actions if user has completed wizard" do new_template = wizard_template.dup ## route_to action new_template['actions'].last['run_after'] = 'wizard_completion' - new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] - new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] CustomWizard::Template.save(new_template, skip_jobs: true) - put '/w/super-mega-fun-wizard/steps/step_1.json', params: { - fields: { - step_1_field_1: "Condition wont pass" - } - } + put '/w/super-mega-fun-wizard/steps/step_1.json' + put '/w/super-mega-fun-wizard/steps/step_2.json' + put '/w/super-mega-fun-wizard/steps/step_3.json' expect(response.status).to eq(200) expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com") end it "saves results of completion actions if user has completed wizard" do new_template = wizard_template.dup - - ## Create group action new_template['actions'].first['run_after'] = 'wizard_completion' - new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] CustomWizard::Template.save(new_template, skip_jobs: true) put '/w/super-mega-fun-wizard/steps/step_1.json', params: { fields: { - step_1_field_1: "My cool group" + step_1_field_1: "Topic title", + step_1_field_2: "Topic post" } } - expect(response.status).to eq(200) - - put '/w/super-mega-fun-wizard/steps/step_3.json' - expect(response.status).to eq(200) + put '/w/super-mega-fun-wizard/steps/step_2.json' + put '/w/super-mega-fun-wizard/steps/step_3.json' wizard_id = response.parsed_body['wizard']['id'] wizard = CustomWizard::Wizard.create(wizard_id, user) - group_name = wizard.submissions.first.fields['action_9'] - group = Group.find_by(name: group_name) - - expect(group.present?).to eq(true) - expect(group.full_name).to eq("My cool group") + topic_id = wizard.submissions.first.fields[new_template['actions'].first['id']] + topic = Topic.find(topic_id) + expect(topic.present?).to eq(true) end it "returns a final step without conditions" do @@ -197,88 +119,119 @@ describe CustomWizard::StepsController do expect(response.parsed_body['final']).to eq(true) end - it "returns the correct final step when the conditional final step and last step are the same" do - new_template = wizard_template.dup - new_template['steps'][0]['condition'] = user_condition_template['condition'] - new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] - CustomWizard::Template.save(new_template, skip_jobs: true) + context "pro" do + before do + enable_pro + end - put '/w/super-mega-fun-wizard/steps/step_1.json', params: { - fields: { - step_1_field_1: "Condition will pass" + it "raises an error when user cant see the step due to conditions" do + sign_in(user2) + + new_wizard_template = wizard_template.dup + new_wizard_template['steps'][0]['condition'] = user_condition_template['condition'] + CustomWizard::Template.save(new_wizard_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json' + expect(response.status).to eq(403) + end + + it "returns an updated wizard when condition doesnt pass" do + new_template = wizard_template.dup + new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition wont pass" + } } - } - expect(response.status).to eq(200) - expect(response.parsed_body['final']).to eq(false) + expect(response.status).to eq(200) + expect(response.parsed_body['wizard']['start']).to eq("step_3") + end - put '/w/super-mega-fun-wizard/steps/step_2.json' - expect(response.status).to eq(200) - expect(response.parsed_body['final']).to eq(false) + it "returns the correct final step when the conditional final step and last step are the same" do + new_template = wizard_template.dup + new_template['steps'][0]['condition'] = user_condition_template['condition'] + new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) - put '/w/super-mega-fun-wizard/steps/step_3.json' - expect(response.status).to eq(200) - expect(response.parsed_body['final']).to eq(true) - end - - it "returns the correct final step when the conditional final step and last step are different" do - new_template = wizard_template.dup - new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] - CustomWizard::Template.save(new_template, skip_jobs: true) - - put '/w/super-mega-fun-wizard/steps/step_1.json', params: { - fields: { - step_1_field_1: "Condition will not pass" + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition will pass" + } } - } - expect(response.status).to eq(200) - expect(response.parsed_body['final']).to eq(false) + expect(response.status).to eq(200) + expect(response.parsed_body['final']).to eq(false) - put '/w/super-mega-fun-wizard/steps/step_2.json' - expect(response.status).to eq(200) - expect(response.parsed_body['final']).to eq(true) - end + put '/w/super-mega-fun-wizard/steps/step_2.json' + expect(response.status).to eq(200) + expect(response.parsed_body['final']).to eq(false) - it "returns the correct final step when the conditional final step is determined in the same action" do - new_template = wizard_template.dup - new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] - new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] - CustomWizard::Template.save(new_template, skip_jobs: true) + put '/w/super-mega-fun-wizard/steps/step_3.json' + expect(response.status).to eq(200) + expect(response.parsed_body['final']).to eq(true) + end - put '/w/super-mega-fun-wizard/steps/step_1.json', params: { - fields: { - step_1_field_1: "Condition will not pass" + it "returns the correct final step when the conditional final step and last step are different" do + new_template = wizard_template.dup + new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition will not pass" + } } - } - expect(response.status).to eq(200) - expect(response.parsed_body['final']).to eq(true) - end + expect(response.status).to eq(200) + expect(response.parsed_body['final']).to eq(false) - it "excludes the non-included conditional fields from the submissions" do - new_template = wizard_template.dup - new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition'] - CustomWizard::Template.save(new_template, skip_jobs: true) + put '/w/super-mega-fun-wizard/steps/step_2.json' + expect(response.status).to eq(200) + expect(response.parsed_body['final']).to eq(true) + end - put '/w/super-mega-fun-wizard/steps/step_1.json', params: { - fields: { - step_1_field_1: "Condition will pass" + it "returns the correct final step when the conditional final step is determined in the same action" do + new_template = wizard_template.dup + new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] + new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition will not pass" + } } - } + expect(response.status).to eq(200) + expect(response.parsed_body['final']).to eq(true) + end - put '/w/super-mega-fun-wizard/steps/step_2.json', params: { - fields: { - step_2_field_1: "1995-04-23" + it "excludes the non-included conditional fields from the submissions" do + new_template = wizard_template.dup + new_template['steps'][1]['fields'][0]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition will pass" + } } - } - put '/w/super-mega-fun-wizard/steps/step_1.json', params: { - fields: { - step_1_field_1: "Condition will not pass" + put '/w/super-mega-fun-wizard/steps/step_2.json', params: { + fields: { + step_2_field_1: "1995-04-23" + } } - } - wizard_id = response.parsed_body['wizard']['id'] - wizard = CustomWizard::Wizard.create(wizard_id, user) - submission = wizard.current_submission - expect(submission.fields.keys).not_to include("step_2_field_1") + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition will not pass" + } + } + + wizard_id = response.parsed_body['wizard']['id'] + wizard = CustomWizard::Wizard.create(wizard_id, user) + submission = wizard.current_submission + expect(submission.fields.keys).not_to include("step_2_field_1") + end end end diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index f2000bda..c28f8783 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -2,29 +2,12 @@ require_relative '../../plugin_helper' describe CustomWizard::WizardController do - fab!(:user) { - Fabricate( - :user, - username: 'angus', - email: "angus@email.com", - trust_level: TrustLevel[3] - ) - } - - let(:permitted_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard/permitted.json" - ).read - ) - } + fab!(:user) { Fabricate(:user, username: 'angus', email: "angus@email.com", trust_level: TrustLevel[3]) } + let(:wizard_template) { get_wizard_fixture("wizard") } + let(:permitted_json) { get_wizard_fixture("wizard/permitted") } before do - CustomWizard::Template.save( - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read), - skip_jobs: true) + CustomWizard::Template.save(wizard_template, skip_jobs: true) @template = CustomWizard::Template.find("super_mega_fun_wizard") sign_in(user) end diff --git a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb index bf575827..6694e979 100644 --- a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb +++ b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb @@ -4,13 +4,10 @@ require_relative '../../plugin_helper' describe CustomWizard::BasicWizardSerializer do fab!(:user) { Fabricate(:user) } + let(:template) { get_wizard_fixture("wizard") } it 'should return basic wizard attributes' do - CustomWizard::Template.save( - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read), - skip_jobs: true) + CustomWizard::Template.save(template, skip_jobs: true) json = CustomWizard::BasicWizardSerializer.new( CustomWizard::Builder.new("super_mega_fun_wizard", user).build, scope: Guardian.new(user) diff --git a/spec/serializers/custom_wizard/custom_field_serializer_spec.rb b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb index 4f5ffd72..2cf92f52 100644 --- a/spec/serializers/custom_wizard/custom_field_serializer_spec.rb +++ b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb @@ -4,12 +4,7 @@ require_relative '../../plugin_helper' describe CustomWizard::CustomFieldSerializer do fab!(:user) { Fabricate(:user) } - - let(:custom_field_json) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/custom_field/custom_fields.json" - ).read) - } + let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } it 'should return custom field attributes' do custom_field_json['custom_fields'].each do |field_json| diff --git a/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb b/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb new file mode 100644 index 00000000..53dd74c2 --- /dev/null +++ b/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative '../../../plugin_helper' + +describe CustomWizard::ProAuthenticationSerializer do + fab!(:user) { Fabricate(:user) } + + it 'should return pro authentication attributes' do + auth = CustomWizard::ProAuthentication.new(OpenStruct.new(key: '1234', auth_at: Time.now, auth_by: user.id)) + serialized = described_class.new(auth, root: false).as_json + + expect(serialized[:active]).to eq(true) + expect(serialized[:client_id]).to eq(auth.client_id) + expect(serialized[:auth_by]).to eq(auth.auth_by) + expect(serialized[:auth_at]).to eq(auth.auth_at) + end +end diff --git a/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb b/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb new file mode 100644 index 00000000..a775863e --- /dev/null +++ b/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative '../../../plugin_helper' + +describe CustomWizard::ProSubscriptionSerializer do + it 'should return pro subscription attributes' do + sub = CustomWizard::ProSubscription.new(OpenStruct.new(type: 'community', updated_at: Time.now)) + serialized = described_class.new(sub, root: false).as_json + + expect(serialized[:active]).to eq(true) + expect(serialized[:type]).to eq('community') + expect(serialized[:updated_at]).to eq(sub.updated_at) + end +end diff --git a/spec/serializers/custom_wizard/pro_serializer_spec.rb b/spec/serializers/custom_wizard/pro_serializer_spec.rb new file mode 100644 index 00000000..45c1956e --- /dev/null +++ b/spec/serializers/custom_wizard/pro_serializer_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative '../../plugin_helper' + +describe CustomWizard::ProSerializer do + it 'should return pro attributes' do + pro = CustomWizard::Pro.new + serialized = described_class.new(pro, root: false) + + expect(serialized.server).to eq(pro.server) + expect(serialized.authentication.class).to eq(CustomWizard::ProAuthentication) + expect(serialized.subscription.class).to eq(CustomWizard::ProSubscription) + end +end diff --git a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb index 1fa9671c..349b21f8 100644 --- a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb @@ -4,13 +4,10 @@ require_relative '../../plugin_helper' describe CustomWizard::FieldSerializer do fab!(:user) { Fabricate(:user) } + let(:template) { get_wizard_fixture("wizard") } before do - CustomWizard::Template.save( - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read), - skip_jobs: true) + CustomWizard::Template.save(template, skip_jobs: true) @wizard = CustomWizard::Builder.new("super_mega_fun_wizard", user).build end diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb index 2052639a..fe36d5a2 100644 --- a/spec/serializers/custom_wizard/wizard_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_serializer_spec.rb @@ -5,19 +5,11 @@ require_relative '../../plugin_helper' describe CustomWizard::WizardSerializer do fab!(:user) { Fabricate(:user) } fab!(:category) { Fabricate(:category) } - - let(:similar_topics_validation) { - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/field/validation/similar_topics.json" - ).read) - } + let(:template) { get_wizard_fixture("wizard") } + let(:similar_topics_validation) { get_wizard_fixture("field/validation/similar_topics") } before do - CustomWizard::Template.save( - JSON.parse(File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read), - skip_jobs: true) + CustomWizard::Template.save(template, skip_jobs: true) @template = CustomWizard::Template.find('super_mega_fun_wizard') end diff --git a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb index 35ce0fd2..53afa8e5 100644 --- a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb @@ -4,22 +4,8 @@ require_relative '../../plugin_helper' describe CustomWizard::StepSerializer do fab!(:user) { Fabricate(:user) } - - let(:wizard_template) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json" - ).read - ) - } - - let(:required_data_json) { - JSON.parse( - File.open( - "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/step/required_data.json" - ).read - ) - } + let(:wizard_template) { get_wizard_fixture("wizard") } + let(:required_data_json) { get_wizard_fixture("step/required_data") } before do CustomWizard::Template.save(wizard_template, skip_jobs: true) From 7c9a0ef862d5c2a301ae262443c76f12f29b03e5 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 7 Sep 2021 20:11:50 +0800 Subject: [PATCH 038/556] Apply rubocop --- controllers/custom_wizard/admin/pro.rb | 6 +++--- jobs/scheduled/update_pro_subscription.rb | 4 ++-- lib/custom_wizard/action.rb | 2 +- lib/custom_wizard/pro.rb | 2 +- lib/custom_wizard/pro/authentication.rb | 5 +++-- lib/custom_wizard/pro/subscription.rb | 3 ++- serializers/custom_wizard/pro/authentication_serializer.rb | 2 +- serializers/custom_wizard/pro/subscription_serializer.rb | 2 +- serializers/custom_wizard/pro_serializer.rb | 2 +- spec/components/custom_wizard/action_spec.rb | 2 +- spec/components/custom_wizard/builder_spec.rb | 2 +- spec/components/custom_wizard/custom_field_spec.rb | 2 +- spec/components/custom_wizard/pro_spec.rb | 2 +- spec/requests/custom_wizard/admin/pro_controller_spec.rb | 4 ++-- spec/requests/custom_wizard/steps_controller_spec.rb | 4 ++-- 15 files changed, 23 insertions(+), 21 deletions(-) diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb index 70e5de04..650743e6 100644 --- a/controllers/custom_wizard/admin/pro.rb +++ b/controllers/custom_wizard/admin/pro.rb @@ -39,10 +39,10 @@ class CustomWizard::AdminProController < CustomWizard::AdminController render json: failed_json end end - + protected - + def pro @pro ||= CustomWizard::Pro.new end -end \ No newline at end of file +end diff --git a/jobs/scheduled/update_pro_subscription.rb b/jobs/scheduled/update_pro_subscription.rb index 773093b0..c790d529 100644 --- a/jobs/scheduled/update_pro_subscription.rb +++ b/jobs/scheduled/update_pro_subscription.rb @@ -3,7 +3,7 @@ class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled every 1.hour - def execute(args={}) + def execute(args = {}) CustomWizard::Pro.update_subscription end -end \ No newline at end of file +end diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 74dc4680..912f3d3a 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -752,7 +752,7 @@ class CustomWizard::Action CustomWizard::Log.create(log) end - + def pro_actions %w[send_message watch_categories send_to_api create_group create_category] end diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb index 5a813e21..61097069 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/pro.rb @@ -183,4 +183,4 @@ class CustomWizard::Pro PluginStore.remove(self.class.namespace, authentication_db_key) get_authentication end -end \ No newline at end of file +end diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/pro/authentication.rb index 8d96a017..23603898 100644 --- a/lib/custom_wizard/pro/authentication.rb +++ b/lib/custom_wizard/pro/authentication.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true class CustomWizard::ProAuthentication include ActiveModel::Serialization @@ -21,7 +22,7 @@ class CustomWizard::ProAuthentication end def generate_keys(user_id, request_id) - rsa = OpenSSL::PKey::RSA.generate(2048) + rsa = OpenSSL::PKey::RSA.generate(2048) nonce = SecureRandom.hex(32) set_keys(request_id, user_id, rsa, nonce) @@ -91,4 +92,4 @@ class CustomWizard::ProAuthentication PluginStore.set(CustomWizard::Pro.namespace, client_id_db_key, client_id) client_id end -end \ No newline at end of file +end diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/pro/subscription.rb index a5782357..7f5cf911 100644 --- a/lib/custom_wizard/pro/subscription.rb +++ b/lib/custom_wizard/pro/subscription.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true class CustomWizard::ProSubscription include ActiveModel::Serialization @@ -18,4 +19,4 @@ class CustomWizard::ProSubscription def active? types.include?(type) && updated_at.to_datetime > (Time.zone.now - 2.hours).to_datetime end -end \ No newline at end of file +end diff --git a/serializers/custom_wizard/pro/authentication_serializer.rb b/serializers/custom_wizard/pro/authentication_serializer.rb index b54f428f..0a2915e4 100644 --- a/serializers/custom_wizard/pro/authentication_serializer.rb +++ b/serializers/custom_wizard/pro/authentication_serializer.rb @@ -8,4 +8,4 @@ class CustomWizard::ProAuthenticationSerializer < ApplicationSerializer def active object.active? end -end \ No newline at end of file +end diff --git a/serializers/custom_wizard/pro/subscription_serializer.rb b/serializers/custom_wizard/pro/subscription_serializer.rb index 6be5ec6f..d3e04e4e 100644 --- a/serializers/custom_wizard/pro/subscription_serializer.rb +++ b/serializers/custom_wizard/pro/subscription_serializer.rb @@ -7,4 +7,4 @@ class CustomWizard::ProSubscriptionSerializer < ApplicationSerializer def active object.active? end -end \ No newline at end of file +end diff --git a/serializers/custom_wizard/pro_serializer.rb b/serializers/custom_wizard/pro_serializer.rb index a9623974..2f141c6d 100644 --- a/serializers/custom_wizard/pro_serializer.rb +++ b/serializers/custom_wizard/pro_serializer.rb @@ -3,4 +3,4 @@ class CustomWizard::ProSerializer < ApplicationSerializer attributes :server has_one :authentication, serializer: CustomWizard::ProAuthenticationSerializer, embed: :objects has_one :subscription, serializer: CustomWizard::ProSubscriptionSerializer, embed: :objects -end \ No newline at end of file +end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 438f29dd..34a08461 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -178,7 +178,7 @@ describe CustomWizard::Action do before do enable_pro end - + it '#send_message' do wizard_template['actions'] << send_message update_template(wizard_template) diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 19be5830..9b9c6000 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -251,7 +251,7 @@ describe CustomWizard::Builder do end end - context "with condition" do + context "with condition" do before do enable_pro @template[:steps][0][:condition] = user_condition_json['condition'] diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index fb1799dd..2204264f 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -192,7 +192,7 @@ describe CustomWizard::CustomField do ).exists? ).to eq(false) end - + it "does not save pro field types without a pro subscription" do pro_field_json = custom_field_pro_json['custom_fields'].first custom_field = CustomWizard::CustomField.new(nil, pro_field_json) diff --git a/spec/components/custom_wizard/pro_spec.rb b/spec/components/custom_wizard/pro_spec.rb index 6c83ae3b..6499b668 100644 --- a/spec/components/custom_wizard/pro_spec.rb +++ b/spec/components/custom_wizard/pro_spec.rb @@ -122,4 +122,4 @@ describe CustomWizard::Pro do expect(@pro.authorized?).to eq(false) end end -end \ No newline at end of file +end diff --git a/spec/requests/custom_wizard/admin/pro_controller_spec.rb b/spec/requests/custom_wizard/admin/pro_controller_spec.rb index 049bb88e..563572bd 100644 --- a/spec/requests/custom_wizard/admin/pro_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/pro_controller_spec.rb @@ -38,7 +38,7 @@ describe CustomWizard::AdminProController do it "#destroy_authentication" do request_id = SecureRandom.hex(32) payload = generate_payload(request_id, admin_user.id) - @pro.authentication_response(request_id, payload) + @pro.authentication_response(request_id, payload) delete "/admin/wizards/pro/authorize.json" @@ -68,4 +68,4 @@ describe CustomWizard::AdminProController do expect(CustomWizard::Pro.subscribed?).to eq(true) end end -end \ No newline at end of file +end diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 3564780f..85353e4c 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -78,7 +78,7 @@ describe CustomWizard::StepsController do put '/w/super-mega-fun-wizard/steps/step_1.json' put '/w/super-mega-fun-wizard/steps/step_2.json' - put '/w/super-mega-fun-wizard/steps/step_3.json' + put '/w/super-mega-fun-wizard/steps/step_3.json' expect(response.status).to eq(200) expect(response.parsed_body['redirect_on_complete']).to eq("https://google.com") end @@ -95,7 +95,7 @@ describe CustomWizard::StepsController do } } put '/w/super-mega-fun-wizard/steps/step_2.json' - put '/w/super-mega-fun-wizard/steps/step_3.json' + put '/w/super-mega-fun-wizard/steps/step_3.json' wizard_id = response.parsed_body['wizard']['id'] wizard = CustomWizard::Wizard.create(wizard_id, user) From 0313c773e83ac55499058a69b43980d56b3be31d Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 7 Sep 2021 20:13:01 +0800 Subject: [PATCH 039/556] Apply prettier --- .../components/custom-field-input.js.es6 | 10 +-- .../components/wizard-custom-action.js.es6 | 4 +- .../components/wizard-pro-selector.js.es6 | 8 +-- .../wizard-pro-selector-row.js.es6 | 2 +- .../components/wizard-pro-subscription.js.es6 | 61 ++++++++++-------- .../controllers/admin-wizards-pro.js.es6 | 64 ++++++++++--------- .../admin-wizards-wizard-show.js.es6 | 2 +- .../custom-wizard-admin-route-map.js.es6 | 2 +- .../discourse/lib/wizard-schema.js.es6 | 10 +-- .../discourse/models/custom-wizard-pro.js.es6 | 6 +- .../discourse/models/custom-wizard.js.es6 | 2 +- .../routes/admin-wizards-custom-fields.js.es6 | 2 +- .../discourse/routes/admin-wizards-pro.js.es6 | 6 +- .../routes/admin-wizards-wizard-show.js.es6 | 2 +- assets/stylesheets/common/wizard-admin.scss | 29 +++++---- 15 files changed, 111 insertions(+), 99 deletions(-) diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index c389fb62..877b83fb 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -8,8 +8,8 @@ const klasses = ["topic", "post", "group", "category"]; const types = ["string", "boolean", "integer", "json"]; const proTypes = { klass: ["group", "category"], - type: ["json"] -} + type: ["json"], +}; const generateContent = function (array, type, proSubscribed = false) { return array.reduce((result, key) => { @@ -19,7 +19,7 @@ const generateContent = function (array, type, proSubscribed = false) { result.push({ id: key, name: I18n.t(`admin.wizard.custom_field.${type}.${key}`), - pro + pro, }); } return result; @@ -32,10 +32,10 @@ export default Component.extend({ postSerializers: ["post"], groupSerializers: ["basic_group"], categorySerializers: ["basic_category"], - klassContent: computed("proSubscribed", function() { + klassContent: computed("proSubscribed", function () { return generateContent(klasses, "klass", this.proSubscribed); }), - typeContent: computed("proSubscribed", function() { + typeContent: computed("proSubscribed", function () { return generateContent(types, "type", this.proSubscribed); }), showInputs: or("field.new", "field.edit"), diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index b0831d5d..c50be2ba 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -102,10 +102,10 @@ export default Component.extend(UndoChanges, { result.push({ id: type, name: I18n.t(`admin.wizard.action.${type}.label`), - pro + pro, }); } return result; }, []); - } + }, }); diff --git a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 index b06b5943..7c9567fe 100644 --- a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 @@ -2,7 +2,7 @@ import SingleSelectComponent from "select-kit/components/single-select"; import { computed } from "@ember/object"; export default SingleSelectComponent.extend({ - classNames: ["combo-box", 'wizard-pro-selector'], + classNames: ["combo-box", "wizard-pro-selector"], selectKitOptions: { autoFilterable: false, @@ -10,10 +10,10 @@ export default SingleSelectComponent.extend({ showFullTitle: true, headerComponent: "wizard-pro-selector/wizard-pro-selector-header", caretUpIcon: "caret-up", - caretDownIcon: "caret-down" + caretDownIcon: "caret-down", }, modifyComponentForRow() { return "wizard-pro-selector/wizard-pro-selector-row"; - } -}); \ No newline at end of file + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 index 9640cd0c..23034ac1 100644 --- a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 +++ b/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 @@ -1,3 +1,3 @@ import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; -export default SelectKitRowComponent.extend(); \ No newline at end of file +export default SelectKitRowComponent.extend(); diff --git a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 index 8ea56699..1c015da5 100644 --- a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 +++ b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 @@ -4,44 +4,49 @@ import { notEmpty } from "@ember/object/computed"; import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ - classNameBindings: [':custom-wizard-pro-subscription', 'subscription.active:active:inactive'], - subscribed: notEmpty('subscription'), + classNameBindings: [ + ":custom-wizard-pro-subscription", + "subscription.active:active:inactive", + ], + subscribed: notEmpty("subscription"), - @discourseComputed('subscription.type') + @discourseComputed("subscription.type") title(type) { - return type ? - I18n.t(`admin.wizard.pro.subscription.title.${type}`) : - I18n.t("admin.wizard.pro.not_subscribed"); + return type + ? I18n.t(`admin.wizard.pro.subscription.title.${type}`) + : I18n.t("admin.wizard.pro.not_subscribed"); }, - @discourseComputed('subscription.active') + @discourseComputed("subscription.active") stateClass(active) { - return active ? 'active' : 'inactive'; + return active ? "active" : "inactive"; }, - @discourseComputed('stateClass') + @discourseComputed("stateClass") stateLabel(stateClass) { return I18n.t(`admin.wizard.pro.subscription.status.${stateClass}`); }, actions: { update() { - this.set('updating', true); - CustomWizardPro.update_subscription().then(result => { - if (result.success) { - this.setProperties({ - updateIcon: 'check', - subscription: result.subscription - }); - } else { - this.set('updateIcon', 'times'); - } - }).finally(() => { - this.set('updating', false); - setTimeout(() => { - this.set('updateIcon', null); - }, 7000); - }) - } - } -}); \ No newline at end of file + this.set("updating", true); + CustomWizardPro.update_subscription() + .then((result) => { + if (result.success) { + this.setProperties({ + updateIcon: "check", + subscription: result.subscription, + }); + } else { + this.set("updateIcon", "times"); + } + }) + .finally(() => { + this.set("updating", false); + setTimeout(() => { + this.set("updateIcon", null); + }, 7000); + }); + }, + }, +}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 index 7c873c66..61012d8f 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 @@ -5,52 +5,58 @@ import { alias } from "@ember/object/computed"; export default Controller.extend({ messageUrl: "https://thepavilion.io/t/3652", - messageType: 'info', + messageType: "info", messageKey: null, - showSubscription: alias('model.authentication.active'), + showSubscription: alias("model.authentication.active"), setup() { - const authentication = this.get('model.authentication'); - const subscription = this.get('model.subscription'); + const authentication = this.get("model.authentication"); + const subscription = this.get("model.subscription"); const subscribed = subscription && subscription.active; const authenticated = authentication && authentication.active; if (!subscribed) { - this.set('messageKey', authenticated ? 'not_subscribed' : 'authorize'); + this.set("messageKey", authenticated ? "not_subscribed" : "authorize"); } else { - this.set('messageKey', !authenticated ? - 'subscription_expiring' : - subscribed ? 'subscription_active' : 'subscription_inactive' + this.set( + "messageKey", + !authenticated + ? "subscription_expiring" + : subscribed + ? "subscription_active" + : "subscription_inactive" ); } }, - @discourseComputed('model.server') + @discourseComputed("model.server") messageOpts(server) { return { server }; }, actions: { unauthorize() { - this.set('unauthorizing', true); + this.set("unauthorizing", true); - CustomWizardPro.unauthorize().then(result => { - if (result.success) { - this.setProperties({ - messageKey: 'unauthorized', - messageType: 'warn', - "model.authentication": null, - "model.subscription": null - }); - } else { - this.setProperties({ - messageKey: 'unauthorize_failed', - messageType: 'error' - }); - } - }).finally(() => { - this.set('unauthorizing', false); - }) - } - } + CustomWizardPro.unauthorize() + .then((result) => { + if (result.success) { + this.setProperties({ + messageKey: "unauthorized", + messageType: "warn", + "model.authentication": null, + "model.subscription": null, + }); + } else { + this.setProperties({ + messageKey: "unauthorize_failed", + messageType: "error", + }); + } + }) + .finally(() => { + this.set("unauthorizing", false); + }); + }, + }, }); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 index a4be0667..6aa5dea4 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 @@ -80,7 +80,7 @@ export default Controller.extend({ if (result.wizard_id) { this.send("afterSave", result.wizard_id); } else if (result.errors) { - this.set('error', result.errors.join(', ')); + this.set("error", result.errors.join(", ")); } }) .catch((result) => { diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index ec2f1b98..5468e863 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -45,7 +45,7 @@ export default { this.route("adminWizardsLogs", { path: "/logs", - resetNamespace: true + resetNamespace: true, }); this.route("adminWizardsManager", { diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 2e810634..a25ab6d0 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -196,11 +196,11 @@ const action = { ], required: ["id", "type"], proTypes: [ - 'send_message', - 'add_to_group', - 'create_category', - 'create_group', - 'send_to_api' + "send_message", + "add_to_group", + "create_category", + "create_group", + "send_to_api", ], dependent: {}, objectArrays: {}, diff --git a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 index 66d80572..83afa161 100644 --- a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 @@ -23,12 +23,12 @@ CustomWizardPro.reopenClass({ type: "DELETE", }).catch(popupAjaxError); }, - + update_subscription() { return ajax(`${basePath}/subscription`, { type: "POST", }).catch(popupAjaxError); - } + }, }); -export default CustomWizardPro; \ No newline at end of file +export default CustomWizardPro; diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index 80c4d86a..e6a8408d 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -221,7 +221,7 @@ CustomWizard.reopenClass({ const wizard = this._super.apply(this); wizard.setProperties(buildProperties(wizardJson)); return wizard; - } + }, }); export default CustomWizard; diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index 198f9a14..45ca9ae7 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -13,7 +13,7 @@ export default DiscourseRoute.extend({ controller.setProperties({ customFields, - proSubscribed + proSubscribed, }); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 index 2fa091c7..d5c7fbd7 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 @@ -7,13 +7,13 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - controller.set('model', model); + controller.set("model", model); controller.setup(); }, actions: { authorize() { CustomWizardPro.authorize(); - } - } + }, + }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index f298fa1c..7032b974 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -39,7 +39,7 @@ export default DiscourseRoute.extend({ currentStep: wizard.steps[0], currentAction: wizard.actions[0], creating: model.create, - proSubscribed: parentModel.pro_subscribed + proSubscribed: parentModel.pro_subscribed, }; controller.setProperties(props); diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 3146da13..86cb818a 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -700,7 +700,7 @@ background-color: var(--primary-low); padding: 1em; margin: 0 0 1em 0; - + .setting-title { display: flex; align-items: center; @@ -711,7 +711,7 @@ input[type="checkbox"] { margin: 0 5px 0 0; - } + } } .setting-label { @@ -729,7 +729,7 @@ > span { margin-right: 1em; - } + } } } } @@ -741,7 +741,7 @@ .validation-section { min-width: 250px; - margin: .5em 0; + margin: 0.5em 0; } } @@ -761,18 +761,19 @@ .pro-label { color: var(--tertiary); - font-size: .75em; + font-size: 0.75em; } .admin-wizards-pro { .admin-wizard-controls { - h3, label { + h3, + label { margin: 0; } label { - padding: .4em .5em; - margin-left: .75em; + padding: 0.4em 0.5em; + margin-left: 0.75em; background-color: var(--success); color: var(--secondary); } @@ -792,14 +793,14 @@ display: flex; justify-content: space-between; align-items: center; - margin-bottom: .5em; + margin-bottom: 0.5em; h3 { margin: 0; } .buttons > span { - margin-right: .5em; + margin-right: 0.5em; } } @@ -810,8 +811,8 @@ background-color: var(--primary-very-low); .subscription-state { - padding: .25em .5em; - margin-right: .75em; + padding: 0.25em 0.5em; + margin-right: 0.75em; &.active { background-color: var(--success); @@ -829,7 +830,7 @@ } .pro-label { - margin-left: .75em; - padding-top: .25em; + margin-left: 0.75em; + padding-top: 0.25em; } } From ba897abf8ee98aacca53bcaf0b192ab36419dbe8 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 7 Sep 2021 20:15:04 +0800 Subject: [PATCH 040/556] Apply eslint --- .../javascripts/discourse/components/wizard-custom-field.js.es6 | 2 +- .../javascripts/discourse/components/wizard-pro-selector.js.es6 | 1 - .../discourse/components/wizard-pro-subscription.js.es6 | 1 + assets/javascripts/discourse/models/custom-wizard-pro.js.es6 | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index 807f90cd..37266a6b 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -1,5 +1,5 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; -import { alias, equal, or } from "@ember/object/computed"; +import { equal, or } from "@ember/object/computed"; import { computed } from "@ember/object"; import { selectKitContent } from "../lib/wizard"; import UndoChanges from "../mixins/undo-changes"; diff --git a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 index 7c9567fe..8e472782 100644 --- a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 @@ -1,5 +1,4 @@ import SingleSelectComponent from "select-kit/components/single-select"; -import { computed } from "@ember/object"; export default SingleSelectComponent.extend({ classNames: ["combo-box", "wizard-pro-selector"], diff --git a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 index 1c015da5..7824cb83 100644 --- a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 +++ b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 @@ -2,6 +2,7 @@ import Component from "@ember/component"; import CustomWizardPro from "../models/custom-wizard-pro"; import { notEmpty } from "@ember/object/computed"; import discourseComputed from "discourse-common/utils/decorators"; +import I18n from "I18n"; export default Component.extend({ classNameBindings: [ diff --git a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 index 83afa161..76429726 100644 --- a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 @@ -1,7 +1,6 @@ import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import EmberObject from "@ember/object"; -import DiscourseURL from "discourse/lib/url"; const CustomWizardPro = EmberObject.extend(); From 18c43f499e599a3cf8e0d43b881072f71d313b4e Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 7 Sep 2021 20:53:45 +0800 Subject: [PATCH 041/556] Apply template lint --- assets/javascripts/discourse/templates/admin-wizards-pro.hbs | 2 +- .../discourse/templates/admin-wizards-submissions-show.hbs | 2 +- .../discourse/templates/components/submission-field.hbs | 2 +- .../wizard-pro-selector/wizard-pro-selector-header.hbs | 2 +- .../templates/components/wizard-pro-subscription.hbs | 4 ++-- .../templates/components/wizard-realtime-validations.hbs | 2 +- .../templates/modal/admin-wizards-submissions-columns.hbs | 2 +- config/locales/client.en.yml | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs index a0658ccb..b4f0691b 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs @@ -4,7 +4,7 @@
{{#if model.authentication.active}} {{conditional-loading-spinner size="small" condition=unauthorizing}} - + {{i18n "admin.wizard.pro.unauthorize"}}
-{{/if}} \ No newline at end of file +{{/if}} diff --git a/assets/javascripts/discourse/templates/components/submission-field.hbs b/assets/javascripts/discourse/templates/components/submission-field.hbs index 831dcc3a..c635ff47 100644 --- a/assets/javascripts/discourse/templates/components/submission-field.hbs +++ b/assets/javascripts/discourse/templates/components/submission-field.hbs @@ -160,4 +160,4 @@ {{d-icon "clock"}}{{format-date value format="tiny"}} -{{/if}} \ No newline at end of file +{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs index a02b0d9c..db467b02 100644 --- a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs @@ -12,4 +12,4 @@ {{/if}} {{d-icon caretIcon class="caret-icon"}} -
\ No newline at end of file +
diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs index 2bebc9ed..8eca5996 100644 --- a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs @@ -24,8 +24,8 @@ {{#if subscription.updated_at}}
- {{{i18n 'admin.wizard.pro.subscription.last_updated' updated_at=(format-date subscription.updated_at leaveAgo="true")}}} + {{i18n "admin.wizard.pro.subscription.last_updated"}} {{format-date subscription.updated_at leaveAgo="true"}}
{{/if}}
-{{/if}} \ No newline at end of file +{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs index 0cc34f35..1aa0893b 100644 --- a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs @@ -51,4 +51,4 @@ {{/each-in}} -
\ No newline at end of file +
diff --git a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs index 24743a92..a167a954 100644 --- a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs +++ b/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs @@ -29,4 +29,4 @@ label="directory.edit_columns.reset_to_default" action=(action "resetToDefault") }} -
\ No newline at end of file +
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f24b5866..ea6f7571 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -467,7 +467,7 @@ en: active: Active inactive: Inactive update: Update - last_updated: Last updated {{updated_at}} + last_updated: Last updated wizard_js: group: From 4a0d176f1a195b4eeba1dddd9c14cb341ed724a5 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 7 Sep 2021 20:55:46 +0800 Subject: [PATCH 042/556] Fix locale file --- 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 ea6f7571..e7103957 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -109,7 +109,7 @@ en: documentation: "Check out the submissions documentation" logs: viewing: "View recent logs for wizards on the forum" - documentation: "Check out the logs documentation + documentation: "Check out the logs documentation" editor: show: "Show" From a8e81150f10ef426b3049b1bb88ae66136c1f09e Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 8 Sep 2021 18:25:30 +0800 Subject: [PATCH 043/556] Remove additional sentence from submissions.viewing --- 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 e7103957..ae2594d2 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -105,7 +105,7 @@ en: unauthorize_failed: Failed to unauthorize. submissions: select: "Select a wizard to see its submissions" - viewing: "You're viewing the submissions of the %{wizardName}. Click 'Download' on the right to download them." + viewing: "You're viewing the submissions of the %{wizardName}" documentation: "Check out the submissions documentation" logs: viewing: "View recent logs for wizards on the forum" From 7b57e7fcab64fc037ec7b293dfaabc579034f105 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Thu, 9 Sep 2021 14:07:12 +0800 Subject: [PATCH 044/556] Apply new table style to wizard logs view --- ...field.js.es6 => wizard-table-field.js.es6} | 51 ++++-- ...ns.js.es6 => admin-wizards-columns.js.es6} | 5 +- .../admin-wizards-logs-show.js.es6 | 52 ++++++ .../controllers/admin-wizards-logs.js.es6 | 68 +++----- .../admin-wizards-submissions-show.js.es6 | 10 +- .../custom-wizard-admin-route-map.js.es6 | 14 +- .../models/custom-wizard-logs.js.es6 | 46 ++++- .../routes/admin-wizards-logs-show.js.es6 | 17 ++ .../routes/admin-wizards-logs.js.es6 | 18 +- .../templates/admin-wizards-logs-show.hbs | 45 +++++ .../templates/admin-wizards-logs.hbs | 43 ++--- .../admin-wizards-submissions-show.hbs | 14 +- .../templates/components/submission-field.hbs | 163 ------------------ .../components/wizard-table-field.hbs | 161 +++++++++++++++++ ...-columns.hbs => admin-wizards-columns.hbs} | 8 +- assets/stylesheets/common/wizard-admin.scss | 37 ++-- config/locales/client.en.yml | 14 +- config/routes.rb | 1 + controllers/custom_wizard/admin/logs.rb | 41 ++++- coverage/.last_run.json | 2 +- ...06135416_split_custom_wizard_log_fields.rb | 80 +++++---- lib/custom_wizard/log.rb | 31 ++-- serializers/custom_wizard/log_serializer.rb | 8 +- spec/components/custom_wizard/log_spec.rb | 12 +- .../admin/logs_controller_spec.rb | 31 +++- .../custom_wizard/log_serializer_spec.rb | 5 +- 26 files changed, 599 insertions(+), 378 deletions(-) rename assets/javascripts/discourse/components/{submission-field.js.es6 => wizard-table-field.js.es6} (69%) rename assets/javascripts/discourse/controllers/{admin-wizards-submissions-columns.js.es6 => admin-wizards-columns.js.es6} (73%) create mode 100644 assets/javascripts/discourse/controllers/admin-wizards-logs-show.js.es6 create mode 100644 assets/javascripts/discourse/routes/admin-wizards-logs-show.js.es6 create mode 100644 assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs delete mode 100644 assets/javascripts/discourse/templates/components/submission-field.hbs create mode 100644 assets/javascripts/discourse/templates/components/wizard-table-field.hbs rename assets/javascripts/discourse/templates/modal/{admin-wizards-submissions-columns.hbs => admin-wizards-columns.hbs} (72%) diff --git a/assets/javascripts/discourse/components/submission-field.js.es6 b/assets/javascripts/discourse/components/wizard-table-field.js.es6 similarity index 69% rename from assets/javascripts/discourse/components/submission-field.js.es6 rename to assets/javascripts/discourse/components/wizard-table-field.js.es6 index 5ebc0ccd..ce6b1584 100644 --- a/assets/javascripts/discourse/components/submission-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-table-field.js.es6 @@ -1,10 +1,11 @@ import Component from "@ember/component"; import { action } from "@ember/object"; -import { equal } from "@ember/object/computed"; +import { equal, notEmpty } from "@ember/object/computed"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; export default Component.extend({ + classNameBindings: ["value.type"], isText: equal("value.type", "text"), isComposer: equal("value.type", "composer"), isDate: equal("value.type", "date"), @@ -18,13 +19,29 @@ export default Component.extend({ isTag: equal("value.type", "tag"), isCategory: equal("value.type", "category"), isGroup: equal("value.type", "group"), - isUser: equal("fieldName", "username"), isUserSelector: equal("value.type", "user_selector"), - isSubmittedAt: equal("fieldName", "submitted_at"), - isTextArea: equal("value.type", "textarea"), + isSubmittedAt: equal("field", "submitted_at"), isComposerPreview: equal("value.type", "composer_preview"), textState: "text-collapsed", - toggleText: I18n.t("admin.wizard.submissions.expand_text"), + toggleText: I18n.t("admin.wizard.expand_text"), + + @discourseComputed("value", "isUser") + hasValue(value, isUser) { + if (isUser) { + return value; + } + return value && value.value; + }, + + @discourseComputed("field", "value.type") + isUser(field, type) { + return field === "username" || field === "user" || type === "user"; + }, + + @discourseComputed("value.type") + isLongtext(type) { + return type === "textarea" || type === "long_text"; + }, @discourseComputed("value") checkboxValue(value) { @@ -44,10 +61,10 @@ export default Component.extend({ if (state === "text-collapsed") { this.set("textState", "text-expanded"); - this.set("toggleText", I18n.t("admin.wizard.submissions.collapse_text")); + this.set("toggleText", I18n.t("admin.wizard.collapse_text")); } else if (state === "text-expanded") { this.set("textState", "text-collapsed"); - this.set("toggleText", I18n.t("admin.wizard.submissions.expand_text")); + this.set("toggleText", I18n.t("admin.wizard.expand_text")); } }, @@ -83,19 +100,24 @@ export default Component.extend({ return users; }, - @discourseComputed("value") - userProfileUrl(value) { - const isUser = this.get("isUser"); + @discourseComputed("isUser", "field", "value") + username(isUser, field, value) { + if (isUser) {return value.username;} + if (field === "username") {return value.value;} + return null; + }, - if (isUser) { - return `/u/${value.username}`; - } + showUsername: notEmpty("username"), + + @discourseComputed("username") + userProfileUrl(username) { + if (username) {return `/u/${username}`;} + return "/"; }, @discourseComputed("value") categoryUrl(value) { const isCategory = this.get("isCategory"); - if (isCategory) { return `/c/${value.value}`; } @@ -104,7 +126,6 @@ export default Component.extend({ @discourseComputed("value") groupUrl(value) { const isGroup = this.get("isGroup"); - if (isGroup) { return `/g/${value.value}`; } diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-columns.js.es6 similarity index 73% rename from assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 rename to assets/javascripts/discourse/controllers/admin-wizards-columns.js.es6 index 4af487ee..4754c577 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-columns.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-columns.js.es6 @@ -6,10 +6,9 @@ export default Controller.extend(ModalFunctionality, { save() { this.send("closeModal"); }, + resetToDefault() { - this.get("model.fields").forEach((field) => { - field.set("enabled", true); - }); + this.get("model.reset")(); }, }, }); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-logs-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-logs-show.js.es6 new file mode 100644 index 00000000..7e3fdff1 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-logs-show.js.es6 @@ -0,0 +1,52 @@ +import discourseComputed from "discourse-common/utils/decorators"; +import { notEmpty } from "@ember/object/computed"; +import CustomWizardLogs from "../models/custom-wizard-logs"; +import Controller from "@ember/controller"; + +export default Controller.extend({ + refreshing: false, + hasLogs: notEmpty("logs"), + page: 0, + canLoadMore: true, + logs: [], + messageKey: "viewing", + + loadLogs() { + if (!this.canLoadMore) { + return; + } + const page = this.get("page"); + const wizardId = this.get("wizard.id"); + + this.set("refreshing", true); + + CustomWizardLogs.list(wizardId, page) + .then((result) => { + this.set("logs", this.logs.concat(result.logs)); + }) + .finally(() => this.set("refreshing", false)); + }, + + @discourseComputed("hasLogs", "refreshing") + noResults(hasLogs, refreshing) { + return !hasLogs && !refreshing; + }, + + actions: { + loadMore() { + if (!this.loadingMore && this.logs.length < this.total) { + this.set("page", (this.page += 1)); + this.loadLogs(); + } + }, + + refresh() { + this.setProperties({ + canLoadMore: true, + page: 0, + logs: [], + }); + this.loadLogs(); + }, + }, +}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 index f45013d7..7388a8d6 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 @@ -1,52 +1,34 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { notEmpty } from "@ember/object/computed"; -import CustomWizardLogs from "../models/custom-wizard-logs"; import Controller from "@ember/controller"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; export default Controller.extend({ - refreshing: false, - hasLogs: notEmpty("logs"), - page: 0, - canLoadMore: true, - logs: [], documentationUrl: "https://thepavilion.io/t/2818", - messageKey: "viewing", - loadLogs() { - if (!this.canLoadMore) { - return; + @discourseComputed("wizardId") + wizardName(wizardId) { + let currentWizard = this.wizardList.find( + (wizard) => wizard.id === wizardId + ); + if (currentWizard) { + return currentWizard.name; + } + }, + + @discourseComputed("wizardName") + messageOpts(wizardName) { + return { + wizardName, + }; + }, + + @discourseComputed("wizardId") + messageKey(wizardId) { + let key = "select"; + + if (wizardId) { + key = "viewing"; } - this.set("refreshing", true); - - CustomWizardLogs.list() - .then((result) => { - if (!result || result.length === 0) { - this.set("canLoadMore", false); - } - this.set("logs", this.logs.concat(result)); - }) - .finally(() => this.set("refreshing", false)); - }, - - @discourseComputed("hasLogs", "refreshing") - noResults(hasLogs, refreshing) { - return !hasLogs && !refreshing; - }, - - actions: { - loadMore() { - this.set("page", (this.page += 1)); - this.loadLogs(); - }, - - refresh() { - this.setProperties({ - canLoadMore: true, - page: 0, - logs: [], - }); - this.loadLogs(); - }, + return key; }, }); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 index 7ba0050f..10621cd3 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -54,10 +54,14 @@ export default Controller.extend({ }, showEditColumnsModal() { - return showModal("admin-wizards-submissions-columns", { + return showModal("admin-wizards-columns", { model: { - fields: this.get("fields"), - submissions: this.get("submissions"), + columns: this.get("fields"), + reset: () => { + this.get("fields").forEach((field) => { + field.set("enabled", true); + }); + }, }, }); }, diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 5468e863..1ca6d41b 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -43,10 +43,16 @@ export default { } ); - this.route("adminWizardsLogs", { - path: "/logs", - resetNamespace: true, - }); + this.route( + "adminWizardsLogs", + { path: "/logs", resetNamespace: true }, + function () { + this.route("adminWizardsLogsShow", { + path: "/:wizardId/", + resetNamespace: true, + }); + } + ); this.route("adminWizardsManager", { path: "/manager", diff --git a/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 b/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 index e2de8a07..1bd19dfe 100644 --- a/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 @@ -3,14 +3,48 @@ import { popupAjaxError } from "discourse/lib/ajax-error"; import EmberObject from "@ember/object"; const CustomWizardLogs = EmberObject.extend(); +const logItemTypes = { + date: "date_time", + action: "text", + message: "long_text", + user: "user", + username: "text", +}; + +function logItem(item, attr) { + return { + value: item[attr], + type: logItemTypes[attr], + }; +} CustomWizardLogs.reopenClass({ - list(page = 0) { - return ajax("/admin/wizards/logs", { - data: { - page, - }, - }).catch(popupAjaxError); + list(wizardId, page = 0) { + let data = { + page, + }; + + return ajax(`/admin/wizards/logs/${wizardId}`, { data }) + .catch(popupAjaxError) + .then((result) => { + if (result.logs) { + result.logs = result.logs.map((item) => { + let map = {}; + + if (item.date) {map.date = logItem(item, "date");} + if (item.action) {map.action = logItem(item, "action");} + if (item.user) { + map.user = item.user; + } else { + map.user = logItem(item, "username"); + } + if (item.message) {map.message = logItem(item, "message");} + + return map; + }); + } + return result; + }); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-logs-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-logs-show.js.es6 new file mode 100644 index 00000000..474360ec --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-logs-show.js.es6 @@ -0,0 +1,17 @@ +import CustomWizardLogs from "../models/custom-wizard-logs"; +import DiscourseRoute from "discourse/routes/discourse"; +import { A } from "@ember/array"; + +export default DiscourseRoute.extend({ + model(params) { + return CustomWizardLogs.list(params.wizardId); + }, + + setupController(controller, model) { + controller.setProperties({ + wizard: model.wizard, + logs: A(model.logs), + total: model.total, + }); + }, +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-logs.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-logs.js.es6 index 56b91350..a1575050 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-logs.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-logs.js.es6 @@ -1,12 +1,24 @@ -import CustomWizardLogs from "../models/custom-wizard-logs"; import DiscourseRoute from "discourse/routes/discourse"; +import { ajax } from "discourse/lib/ajax"; export default DiscourseRoute.extend({ model() { - return CustomWizardLogs.list(); + return ajax(`/admin/wizards/wizard`); }, setupController(controller, model) { - controller.set("logs", model); + const showParams = this.paramsFor("adminWizardsLogsShow"); + + controller.setProperties({ + wizardId: showParams.wizardId, + wizardList: model.wizard_list, + }); + }, + + actions: { + changeWizard(wizardId) { + this.controllerFor("adminWizardsLogs").set("wizardId", wizardId); + this.transitionTo("adminWizardsLogsShow", wizardId); + }, }, }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs new file mode 100644 index 00000000..898198d1 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs @@ -0,0 +1,45 @@ +{{#if logs}} +
+ + +
+ {{d-button + label="refresh" + icon="sync" + action="refresh" + class="refresh"}} +
+
+ +
+ {{#load-more selector=".wizard-table tr" action=(action "loadMore")}} + {{#if noResults}} +

{{i18n "search.no_results"}}

+ {{else}} + + + + + + + + + + + {{#each logs as |log|}} + + {{#each-in log as |field value|}} + + {{/each-in}} + + {{/each}} + +
{{i18n "admin.wizard.log.date"}}{{i18n "admin.wizard.log.action"}}{{i18n "admin.wizard.log.user"}}{{i18n "admin.wizard.log.message"}}
{{wizard-table-field field=field value=value}}
+ {{/if}} + + {{conditional-loading-spinner condition=refreshing}} + {{/load-more}} +
+{{/if}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs index ea0afb7c..45738a9f 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs @@ -1,11 +1,11 @@ -
-

{{i18n "admin.wizard.log.nav_label"}}

- - {{d-button - label="refresh" - icon="sync" - action="refresh" - class="refresh"}} +
+ {{combo-box + value=wizardId + content=wizardList + onChange=(route-action "changeWizard") + options=(hash + none="admin.wizard.select" + )}}
{{wizard-message @@ -14,27 +14,6 @@ url=documentationUrl component="logs"}} -{{#load-more selector=".log-list tr" action=(action "loadMore") class="wizard-logs"}} - {{#if noResults}} -

{{i18n "search.no_results"}}

- {{else}} - - - - - - - - - {{#each logs as |log|}} - - - - - {{/each}} - -
MessageDate
{{log.message}}{{bound-date log.date}}
- {{/if}} - - {{conditional-loading-spinner condition=refreshing}} -{{/load-more}} +
+ {{outlet}} +
diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs index df97513b..72ec7c38 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs @@ -7,7 +7,7 @@
{{d-button icon="sliders-h" - label="admin.wizard.submissions.edit_columns" + label="admin.wizard.edit_columns" action=(action "showEditColumnsModal") class="btn-default open-edit-columns-btn download-link" }} @@ -26,12 +26,10 @@
-
- {{#load-more selector=".wizard-submissions tr" action=(action "loadMore")}} +
+ {{#load-more selector=".wizard-table tr" action=(action "loadMore")}} {{#if noResults}} -

- {{i18n "search.no_results"}} -

+

{{i18n "search.no_results"}}

{{else}} @@ -49,9 +47,7 @@ {{#each displaySubmissions as |submission|}} {{#each-in submission as |field value|}} - + {{/each-in}} {{/each}} diff --git a/assets/javascripts/discourse/templates/components/submission-field.hbs b/assets/javascripts/discourse/templates/components/submission-field.hbs deleted file mode 100644 index c635ff47..00000000 --- a/assets/javascripts/discourse/templates/components/submission-field.hbs +++ /dev/null @@ -1,163 +0,0 @@ -{{#if isText}} - {{value.value}} -{{/if}} - -{{#if isTextArea}} -
-

- {{value.value}} -

- - {{toggleText}} - -
-{{/if}} - -{{#if isComposer}} -
-

- {{value.value}} -

- - {{toggleText}} - -
-{{/if}} - -{{#if isComposerPreview}} - {{d-icon "comment-alt"}} - - {{i18n "admin.wizard.submissions.composer_preview"}}: {{value.value}} - -{{/if}} - -{{#if isTextOnly}} - {{value.value}} -{{/if}} - -{{#if isDate}} - - {{d-icon "calendar"}}{{value.value}} - -{{/if}} - -{{#if isTime}} - - {{d-icon "clock"}}{{value.value}} - -{{/if}} - -{{#if isDateTime}} - - {{d-icon "calendar"}}{{format-date value.value format="medium"}} - -{{/if}} - -{{#if isNumber}} - {{value.value}} -{{/if}} - -{{#if isCheckbox}} - {{#if checkboxValue}} - - {{d-icon "check"}}{{value.value}} - - {{else}} - - {{d-icon "times"}}{{value.value}} - - {{/if}} -{{/if}} - -{{#if isUrl}} - - {{d-icon "link"}} - - {{value.value}} - - -{{/if}} - -{{#if isUpload}} - - {{file.original_filename}} - -{{/if}} - -{{#if isDropdown}} - - {{d-icon "check-square"}} - {{value.value}} - -{{/if}} - -{{#if isTag}} - {{#each value.value as |tag|}} - {{discourse-tag tag}} - {{/each}} -{{/if}} - -{{#if isCategory}} - - {{i18n "admin.wizard.submissions.category_id"}}: - - - {{value.value}} - -{{/if}} - -{{#if isGroup}} - - {{i18n "admin.wizard.submissions.group_id"}}: - - {{value.value}} -{{/if}} - -{{#if isUserSelector}} - {{#each submittedUsers as |user|}} - {{d-icon "user"}} - - {{user.username}} - - {{/each}} -{{/if}} - -{{#if isUser}} - {{#link-to "user" value}} - {{avatar value imageSize="tiny"}} - {{/link-to}} - - {{value.username}} - -{{/if}} - -{{#if isSubmittedAt}} - - {{d-icon "clock"}}{{format-date value format="tiny"}} - -{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-table-field.hbs b/assets/javascripts/discourse/templates/components/wizard-table-field.hbs new file mode 100644 index 00000000..bd7da5c4 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-table-field.hbs @@ -0,0 +1,161 @@ +{{#if hasValue}} + {{#if isText}} + {{value.value}} + {{/if}} + + {{#if isLongtext}} +
+

+ {{value.value}} +

+ + {{toggleText}} + +
+ {{/if}} + + {{#if isComposer}} +
+

+ {{value.value}} +

+ + {{toggleText}} + +
+ {{/if}} + + {{#if isComposerPreview}} + {{d-icon "comment-alt"}} + + {{i18n "admin.wizard.submissions.composer_preview"}}: {{value.value}} + + {{/if}} + + {{#if isTextOnly}} + {{value.value}} + {{/if}} + + {{#if isDate}} + + {{d-icon "calendar"}}{{value.value}} + + {{/if}} + + {{#if isTime}} + + {{d-icon "clock"}}{{value.value}} + + {{/if}} + + {{#if isDateTime}} + + {{d-icon "calendar"}}{{format-date value.value format="medium"}} + + {{/if}} + + {{#if isNumber}} + {{value.value}} + {{/if}} + + {{#if isCheckbox}} + {{#if checkboxValue}} + + {{d-icon "check"}}{{value.value}} + + {{else}} + + {{d-icon "times"}}{{value.value}} + + {{/if}} + {{/if}} + + {{#if isUrl}} + + {{d-icon "link"}} + + {{value.value}} + + + {{/if}} + + {{#if isUpload}} + + {{file.original_filename}} + + {{/if}} + + {{#if isDropdown}} + + {{d-icon "check-square"}} + {{value.value}} + + {{/if}} + + {{#if isTag}} + {{#each value.value as |tag|}} + {{discourse-tag tag}} + {{/each}} + {{/if}} + + {{#if isCategory}} + + {{i18n "admin.wizard.submissions.category_id"}}: + + + {{value.value}} + + {{/if}} + + {{#if isGroup}} + + {{i18n "admin.wizard.submissions.group_id"}}: + + {{value.value}} + {{/if}} + + {{#if isUserSelector}} + {{#each submittedUsers as |user|}} + {{d-icon "user"}} + + {{user.username}} + + {{/each}} + {{/if}} + + {{#if isUser}} + {{#link-to "user" value}} + {{avatar value imageSize="tiny"}} + {{/link-to}} + {{/if}} + + {{#if showUsername}} + + {{username}} + + {{/if}} + + {{#if isSubmittedAt}} + + {{d-icon "clock"}}{{format-date value format="tiny"}} + + {{/if}} +{{else}} + — +{{/if}} diff --git a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs b/assets/javascripts/discourse/templates/modal/admin-wizards-columns.hbs similarity index 72% rename from assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs rename to assets/javascripts/discourse/templates/modal/admin-wizards-columns.hbs index a167a954..eb5218b1 100644 --- a/assets/javascripts/discourse/templates/modal/admin-wizards-submissions-columns.hbs +++ b/assets/javascripts/discourse/templates/modal/admin-wizards-columns.hbs @@ -1,14 +1,14 @@ -{{#d-modal-body title="directory.edit_columns.title"}} +{{#d-modal-body title="admin.wizard.edit_columns"}} {{#if loading}} {{loading-spinner size="large"}} {{else}}
- {{#each model.fields as |field|}} + {{#each model.columns as |column|}}
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 7027950a..7c6cd95d 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -66,10 +66,10 @@ } } -.wizard-submissions { +.wizard-table { overflow: scroll; - table td { + table td:not(.small) { min-width: 150px; } @@ -77,25 +77,26 @@ text-transform: capitalize; } - .submission-icon-item { + .wizard-table-icon-item { display: flex; align-items: center; + svg { margin-right: 5px; } } - .submission-checkbox-true { + .wizard-table-checkbox-true { text-transform: capitalize; color: var(--success); } - .submission-checkbox-false { + .wizard-table-checkbox-false { text-transform: capitalize; color: var(--danger); } - .submission-long-text { + .wizard-table-long-text { &-content { white-space: nowrap; word-wrap: break-word; @@ -114,25 +115,11 @@ } } - .submission-composer-text { + .wizard-table-composer-text { font-family: monospace; } } -.admin-wizards-logs { - .admin-wizard-controls { - h3 { - margin: 0 7px; - } - } - - .wizard-logs { - .date { - width: 100px; - } - } -} - .wizard-settings-parent { padding: 20px; border: 1px solid var(--primary-low); @@ -215,6 +202,10 @@ margin-bottom: 0; } + button { + font-size: 1rem; + } + .download-link { font-size: 1rem; line-height: 20px; @@ -230,10 +221,6 @@ font-size: 1rem; background-color: var(--primary-low); } - - button { - font-size: 1rem; - } } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ae2594d2..cb30cf76 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -58,6 +58,9 @@ en: select_type: "Select a type" condition: "Condition" index: "Index" + edit_columns: "Edit Columns" + expand_text: "Read More" + collapse_text: "Show Less" pro_support_button: title: "Request Pro Support" @@ -108,6 +111,7 @@ en: viewing: "You're viewing the submissions of the %{wizardName}" documentation: "Check out the submissions documentation" logs: + select: "Select a wizard to see its logs" viewing: "View recent logs for wizards on the forum" documentation: "Check out the logs documentation" @@ -378,9 +382,6 @@ en: nav_label: "Submissions" title: "{{name}} Submissions" download: "Download" - edit_columns: "Edit Columns" - expand_text: "Read More" - collapse_text: "Show Less" group_id: "Group ID" category_id: "Category ID" composer_preview: "Composer Preview" @@ -437,9 +438,14 @@ en: log: label: "Logs" - + log: nav_label: "Logs" + title: "{{name}} Logs" + date: Date + action: Action + user: User + message: Message manager: nav_label: Manager diff --git a/config/routes.rb b/config/routes.rb index abe36479..3a37a137 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -38,6 +38,7 @@ Discourse::Application.routes.append do get 'admin/wizards/api/:name/authorize' => 'admin_api#authorize' get 'admin/wizards/logs' => 'admin_logs#index' + get 'admin/wizards/logs/:wizard_id' => 'admin_logs#show' get 'admin/wizards/manager' => 'admin_manager#index' get 'admin/wizards/manager/export' => 'admin_manager#export' diff --git a/controllers/custom_wizard/admin/logs.rb b/controllers/custom_wizard/admin/logs.rb index 976814f8..7ca37bb2 100644 --- a/controllers/custom_wizard/admin/logs.rb +++ b/controllers/custom_wizard/admin/logs.rb @@ -1,9 +1,44 @@ # frozen_string_literal: true class CustomWizard::AdminLogsController < CustomWizard::AdminController + before_action :find_wizard, except: [:index] + def index - render_serialized( - CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i), - CustomWizard::LogSerializer + render json: ActiveModel::ArraySerializer.new( + CustomWizard::Wizard.list(current_user), + each_serializer: CustomWizard::BasicWizardSerializer ) end + + def show + render_json_dump( + wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), + logs: ActiveModel::ArraySerializer.new( + log_list.logs, + each_serializer: CustomWizard::LogSerializer + ), + total: log_list.total + ) + end + + protected + + def log_list + @log_list ||= begin + list = CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i, params[:wizard_id]) + + if list.logs.any? && (usernames = list.logs.map(&:username)).present? + user_map = User.where(username: usernames) + .reduce({}) do |result, user| + result[user.username] = user + result + end + + list.logs.each do |log_item| + log_item.user = user_map[log_item.username] + end + end + + list + end + end end diff --git a/coverage/.last_run.json b/coverage/.last_run.json index cff5740b..23e2ecb9 100644 --- a/coverage/.last_run.json +++ b/coverage/.last_run.json @@ -1,5 +1,5 @@ { "result": { - "line": 91.96 + "line": 92.14 } } diff --git a/db/migrate/20210806135416_split_custom_wizard_log_fields.rb b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb index 984a7a23..8107bc07 100644 --- a/db/migrate/20210806135416_split_custom_wizard_log_fields.rb +++ b/db/migrate/20210806135416_split_custom_wizard_log_fields.rb @@ -1,51 +1,56 @@ # frozen_string_literal: true class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] + KEY_MAP = { + wizard: "wizard_id", + action: "action", + user: "username", + date: "date", + message: "message" + } + def change reversible do |dir| dir.up do # separate wizard/action/user into their own keys - wizard_logs = PluginStoreRow.where(" - plugin_name = 'custom_wizard_log' - ") + wizard_logs = PluginStoreRow.where("plugin_name = 'custom_wizard_log'") - if wizard_logs.exists? - wizard_logs.each do |row| - begin - log_json = JSON.parse(row.value) - rescue TypeError, JSON::ParserError - next - end + if wizard_logs.exists? + wizard_logs.each do |row| + begin + log_json = JSON.parse(row.value) + rescue TypeError, JSON::ParserError + next + end - if log_json.key?('message') && log_json['message'].is_a?(String) + if log_json.key?('message') && log_json['message'].is_a?(String) - attr_strs = [] + attr_strs = [] - # assumes no whitespace in the values - attr_strs << log_json['message'].slice!(/(wizard: \S*; )/, 1) - attr_strs << log_json['message'].slice!(/(action: \S*; )/, 1) - attr_strs << log_json['message'].slice!(/(user: \S*; )/, 1) + # assumes no whitespace in the values + attr_strs << log_json['message'].slice!(/(wizard: \S*; )/, 1) + attr_strs << log_json['message'].slice!(/(action: \S*; )/, 1) + attr_strs << log_json['message'].slice!(/(user: \S*; )/, 1) - attr_strs.each do |attr_str| - if attr_str.is_a? String - attr_str.gsub!(/[;]/ , "") - key, value = attr_str.split(': ') - value.strip! if value - log_json[key] = value ? value : '' - end + attr_strs.each do |attr_str| + if attr_str.is_a? String + attr_str.gsub!(/[;]/ , "") + key, value = attr_str.split(': ') + value.strip! if value + key = KEY_MAP[key.to_sym] ? KEY_MAP[key.to_sym] : key + log_json[key] = value ? value : '' end - - row.value = log_json.to_json - row.save - end + + row.value = log_json.to_json + row.save end end + end end + dir.down do - wizard_logs = PluginStoreRow.where(" - plugin_name = 'custom_wizard_log' - ") + wizard_logs = PluginStoreRow.where("plugin_name = 'custom_wizard_log'") if wizard_logs.exists? wizard_logs.each do |row| @@ -56,19 +61,26 @@ class SplitCustomWizardLogFields < ActiveRecord::Migration[6.1] end # concatenate wizard/action/user to start of message - prefixes = log_json.extract!('wizard', 'action', 'user') + prefixes = log_json.extract!('wizard_id', 'action', 'username') + message_prefix = "" - message_prefix = prefixes.map { |k, v| "#{k}: #{v}" }.join('; ') + if prefixes.present? + message_prefix = prefixes.map do |k, v| + key = KEY_MAP.key(k) ? KEY_MAP.key(k) : k + "#{key.to_s}: #{v};" + end.join(' ') + end if log_json.key?('message') - log_json['message'] = "#{message_prefix}; #{log_json['message']}" + message = log_json['message'] + message = "#{message_prefix} #{message}" if message_prefix.present? + log_json['message'] = message else log_json['message'] = message_prefix end row.value = log_json.to_json row.save - end end end diff --git a/lib/custom_wizard/log.rb b/lib/custom_wizard/log.rb index c50a5712..cb5b78c7 100644 --- a/lib/custom_wizard/log.rb +++ b/lib/custom_wizard/log.rb @@ -2,46 +2,51 @@ class CustomWizard::Log include ActiveModel::Serialization - attr_accessor :date, :wizard, :action, :user, :message + attr_reader :date, :wizard_id, :action, :username, :message + attr_accessor :user PAGE_LIMIT = 100 def initialize(attrs) @date = attrs['date'] - @wizard = attrs['wizard'] @action = attrs['action'] - @user = attrs['user'] @message = attrs['message'] + @wizard_id = attrs['wizard_id'] + @username = attrs['username'] end - def self.create(wizard, action, user, message) + def self.create(wizard_id, action, username, message) log_id = SecureRandom.hex(12) PluginStore.set('custom_wizard_log', log_id.to_s, { date: Time.now, - wizard: wizard, + wizard_id: wizard_id, action: action, - user: user, + username: username, message: message } ) end - def self.list_query - PluginStoreRow.where(" - plugin_name = 'custom_wizard_log' AND - (value::json->'date') IS NOT NULL - ").order("value::json->>'date' DESC") + def self.list_query(wizard_id = nil) + query = PluginStoreRow.where("plugin_name = 'custom_wizard_log' AND (value::json->'date') IS NOT NULL") + query = query.where("(value::json->>'wizard_id') = ?", wizard_id) if wizard_id + query.order("value::json->>'date' DESC") end - def self.list(page = 0, limit = nil) + def self.list(page = 0, limit = nil, wizard_id = nil) limit = limit.to_i > 0 ? limit.to_i : PAGE_LIMIT page = page.to_i + logs = self.list_query(wizard_id) - self.list_query.limit(limit) + result = OpenStruct.new(logs: [], total: nil) + result.total = logs.size + result.logs = logs.limit(limit) .offset(page * limit) .map { |r| self.new(JSON.parse(r.value)) } + + result end end diff --git a/serializers/custom_wizard/log_serializer.rb b/serializers/custom_wizard/log_serializer.rb index c4683ba8..56c5fd8f 100644 --- a/serializers/custom_wizard/log_serializer.rb +++ b/serializers/custom_wizard/log_serializer.rb @@ -1,4 +1,10 @@ # frozen_string_literal: true + class CustomWizard::LogSerializer < ApplicationSerializer - attributes :date, :wizard, :action, :user, :message + attributes :date, + :action, + :username, + :message + + has_one :user, serializer: ::BasicUserSerializer, embed: :objects end diff --git a/spec/components/custom_wizard/log_spec.rb b/spec/components/custom_wizard/log_spec.rb index 62f2e6df..0b4832c0 100644 --- a/spec/components/custom_wizard/log_spec.rb +++ b/spec/components/custom_wizard/log_spec.rb @@ -10,19 +10,25 @@ describe CustomWizard::Log do it "creates logs" do expect( - CustomWizard::Log.list.length + CustomWizard::Log.list.logs.length ).to eq(3) end it "lists logs by time created" do expect( - CustomWizard::Log.list.first.message + CustomWizard::Log.list.logs.first.message ).to eq("Third log message") end it "paginates logs" do expect( - CustomWizard::Log.list(0, 2).length + CustomWizard::Log.list(0, 2).logs.length ).to eq(2) end + + it "lists logs by wizard" do + expect( + CustomWizard::Log.list(0, 2, 'third-test-wizard').logs.length + ).to eq(1) + end end diff --git a/spec/requests/custom_wizard/admin/logs_controller_spec.rb b/spec/requests/custom_wizard/admin/logs_controller_spec.rb index 5aaf9578..1559fa6f 100644 --- a/spec/requests/custom_wizard/admin/logs_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/logs_controller_spec.rb @@ -3,21 +3,40 @@ require_relative '../../../plugin_helper' describe CustomWizard::AdminLogsController do fab!(:admin_user) { Fabricate(:user, admin: true) } + let(:template) { get_wizard_fixture("wizard") } before do - CustomWizard::Log.create('first-test-wizard', 'perform_first_action', 'first_test_user', 'First log message') - CustomWizard::Log.create('second-test-wizard', 'perform_second_action', 'second_test_user', 'Second log message') - CustomWizard::Log.create('third-test-wizard', 'perform_third_action', 'third_test_user', 'Third log message') + ["first", "second", "third"].each_with_index do |key, index| + temp = template.dup + temp["id"] = "#{key}_test_wizard" + CustomWizard::Template.save(temp, skip_jobs: true) + CustomWizard::Log.create("#{key}_test_wizard", "perform_#{key}_action", "#{key}_test_user", "#{key} log message") + end sign_in(admin_user) end - it "returns a list of logs" do + it "returns a list of wizards" do get "/admin/wizards/logs.json" expect(response.parsed_body.length).to eq(3) end + it "returns a list of logs for a wizard" do + get "/admin/wizards/logs/first_test_wizard.json" + expect(response.parsed_body['logs'].length).to eq(1) + end + it "paginates" do - get "/admin/wizards/logs.json", params: { page: 1, limit: 2 } - expect(response.parsed_body.length).to eq(1) + get "/admin/wizards/logs/first_test_wizard.json", params: { page: 1 } + expect(response.parsed_body['logs'].length).to eq(0) + end + + it "returns total logs for a wizard" do + get "/admin/wizards/logs/first_test_wizard.json" + expect(response.parsed_body['total']).to eq(1) + end + + it "returns basic wizard" do + get "/admin/wizards/logs/first_test_wizard.json" + expect(response.parsed_body['wizard']['id']).to eq("first_test_wizard") end end diff --git a/spec/serializers/custom_wizard/log_serializer_spec.rb b/spec/serializers/custom_wizard/log_serializer_spec.rb index b452b9c5..d520fefa 100644 --- a/spec/serializers/custom_wizard/log_serializer_spec.rb +++ b/spec/serializers/custom_wizard/log_serializer_spec.rb @@ -10,13 +10,12 @@ describe CustomWizard::LogSerializer do CustomWizard::Log.create('second-test-wizard', 'perform_second_action', 'second_test_user', 'Second log message') json_array = ActiveModel::ArraySerializer.new( - CustomWizard::Log.list(0), + CustomWizard::Log.list(0).logs, each_serializer: CustomWizard::LogSerializer ).as_json expect(json_array.length).to eq(2) - expect(json_array[0][:wizard]).to eq("second-test-wizard") expect(json_array[0][:action]).to eq("perform_second_action") - expect(json_array[0][:user]).to eq("second_test_user") + expect(json_array[0][:username]).to eq('second_test_user') expect(json_array[0][:message]).to eq("Second log message") end end From 270d3bccf5468ac5f1cfc0759f577feafe6c86fb Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 14 Sep 2021 11:33:16 +0800 Subject: [PATCH 045/556] IMPROVE: translation feature --- .../components/wizard-table-field.js.es6 | 12 +++- .../discourse/lib/wizard-schema.js.es6 | 2 - .../models/custom-wizard-logs.js.es6 | 12 +++- .../templates/admin-wizards-wizard-show.hbs | 36 +++++----- .../components/wizard-custom-field.hbs | 13 ---- .../components/wizard-custom-step.hbs | 13 ---- .../components/wizard-composer-editor.js.es6 | 2 +- .../initializers/custom-wizard-field.js.es6 | 32 +++++++-- .../initializers/custom-wizard-step.js.es6 | 32 +++++++-- .../javascripts/wizard/lib/wizard-i18n.js.es6 | 14 +++- .../javascripts/wizard/models/custom.js.es6 | 7 +- .../components/wizard-field-text.hbs | 2 +- .../components/wizard-field-textarea.hbs | 2 +- .../templates/components/wizard-field.hbs | 2 +- config/locales/client.en.yml | 6 +- lib/custom_wizard/field.rb | 2 - lib/custom_wizard/step.rb | 1 - lib/custom_wizard/validators/template.rb | 23 ++++-- .../custom_wizard/wizard_field_serializer.rb | 37 +++++----- .../custom_wizard/wizard_serializer.rb | 7 +- .../custom_wizard/wizard_step_serializer.rb | 20 +++--- spec/components/custom_wizard/action_spec.rb | 2 +- spec/components/custom_wizard/builder_spec.rb | 8 ++- .../custom_wizard/custom_field_spec.rb | 2 +- spec/components/custom_wizard/field_spec.rb | 1 - spec/components/custom_wizard/mapper_spec.rb | 2 +- .../custom_wizard/template_validator_spec.rb | 70 ++++++++++++------- .../custom_field_extensions_spec.rb | 2 +- spec/plugin_helper.rb | 2 +- .../custom_field_extensions_spec.rb | 2 +- .../custom_wizard/steps_controller_spec.rb | 2 +- .../wizard_step_serializer_spec.rb | 3 +- 32 files changed, 230 insertions(+), 143 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-table-field.js.es6 b/assets/javascripts/discourse/components/wizard-table-field.js.es6 index ce6b1584..049b4d40 100644 --- a/assets/javascripts/discourse/components/wizard-table-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-table-field.js.es6 @@ -102,8 +102,12 @@ export default Component.extend({ @discourseComputed("isUser", "field", "value") username(isUser, field, value) { - if (isUser) {return value.username;} - if (field === "username") {return value.value;} + if (isUser) { + return value.username; + } + if (field === "username") { + return value.value; + } return null; }, @@ -111,7 +115,9 @@ export default Component.extend({ @discourseComputed("username") userProfileUrl(username) { - if (username) {return `/u/${username}`;} + if (username) { + return `/u/${username}`; + } return "/"; }, diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index a25ab6d0..a20b61a7 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -39,7 +39,6 @@ const step = { id: null, index: null, title: null, - key: null, banner: null, raw_description: null, required_data: null, @@ -68,7 +67,6 @@ const field = { description: null, property: null, required: null, - key: null, type: null, condition: null, }, diff --git a/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 b/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 index 1bd19dfe..23565e2c 100644 --- a/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 @@ -31,14 +31,20 @@ CustomWizardLogs.reopenClass({ result.logs = result.logs.map((item) => { let map = {}; - if (item.date) {map.date = logItem(item, "date");} - if (item.action) {map.action = logItem(item, "action");} + if (item.date) { + map.date = logItem(item, "date"); + } + if (item.action) { + map.action = logItem(item, "action"); + } if (item.user) { map.user = item.user; } else { map.user = logItem(item, "username"); } - if (item.message) {map.message = logItem(item, "message");} + if (item.message) { + map.message = logItem(item, "message"); + } return map; }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index 7645c20e..472cbfcb 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -126,25 +126,29 @@
-
-
- + {{#if proSubscribed}} +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{input type="checkbox" checked=wizard.save_submissions}} + {{i18n "admin.wizard.save_submissions_label"}} +
-
- {{input type="checkbox" checked=wizard.save_submissions}} - {{i18n "admin.wizard.save_submissions_label"}} -
-
-
-
- +
+
+ + {{i18n "admin.wizard.pro.label"}} +
+
+ {{input type="checkbox" checked=wizard.restart_on_revisit}} + {{i18n "admin.wizard.restart_on_revisit_label"}} +
-
- {{input type="checkbox" checked=wizard.restart_on_revisit}} - {{i18n "admin.wizard.restart_on_revisit_label"}} -
-
+ {{/if}}
{{wizard-links diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 8c8bb6d4..fe3089ac 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -253,19 +253,6 @@
{{/if}} -
-
- - {{i18n "admin.wizard.pro.label"}} -
-
- {{input - name="key" - value=field.key - placeholderKey="admin.wizard.translation_placeholder"}} -
-
- {{#if validations}} {{wizard-realtime-validations field=field validations=validations}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 91476ae3..3feb6731 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -100,19 +100,6 @@ )}} - -
-
- - {{i18n "admin.wizard.pro.label"}} -
-
- {{input - name="key" - value=step.key - placeholderKey="admin.wizard.translation_placeholder"}} -
-
{{/if}} {{wizard-links diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 2a92f12a..c4cc950c 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -33,7 +33,7 @@ export default ComposerEditor.extend({ lastValidatedAt: "lastValidatedAt", popupMenuOptions: [], draftStatus: "null", - replyPlaceholder: alias("field.placeholder"), + replyPlaceholder: alias("field.translatedPlaceholder"), @on("didInsertElement") _composerEditorInit() { diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 index f5deb927..6f2a80b7 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 +++ b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 @@ -16,19 +16,23 @@ export default { const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const toMarkdown = requirejs("discourse/lib/to-markdown").default; + const { translatedText } = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" + ); FieldComponent.reopen({ classNameBindings: ["field.id"], + @discourseComputed("field.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + @discourseComputed("field.type") textType(fieldType) { return ["text", "textarea"].includes(fieldType); }, - cookedDescription: function () { - return cook(this.get("field.description")); - }.property("field.description"), - inputComponentName: function () { const type = this.get("field.type"); const id = this.get("field.id"); @@ -57,6 +61,26 @@ export default { ]; FieldModel.reopen({ + @discourseComputed("wizardId", "stepId", "id") + i18nKey(wizardId, stepId, id) { + return `${wizardId}.${stepId}.${id}`; + }, + + @discourseComputed("i18nKey", "label") + translatedLabel(i18nKey, label) { + return translatedText(`${i18nKey}.label`, label); + }, + + @discourseComputed("i18nKey", "placeholder") + translatedPlaceholder(i18nKey, placeholder) { + return translatedText(`${i18nKey}.placeholder`, placeholder); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + check() { if (this.customCheck) { return this.customCheck(); diff --git a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 index fbbe7d8b..6a679796 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 +++ b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 @@ -22,8 +22,26 @@ export default { ).cook; const { schedule } = requirejs("@ember/runloop"); const { alias, not } = requirejs("@ember/object/computed"); + const { translatedText } = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" + ); StepModel.reopen({ + @discourseComputed("wizardId", "id") + i18nKey(wizardId, stepId) { + return `${wizardId}.${stepId}`; + }, + + @discourseComputed("i18nKey", "title") + translatedTitle(i18nKey, title) { + return translatedText(`${i18nKey}.title`, title); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + save() { const wizardId = this.get("wizardId"); const fields = {}; @@ -128,13 +146,15 @@ export default { return index === 0 && !required; }.property("step.index", "wizard.required"), - cookedTitle: function () { - return cook(this.get("step.title")); - }.property("step.title"), + @discourseComputed("step.translatedTitle") + cookedTitle(title) { + return cook(title); + }, - cookedDescription: function () { - return cook(this.get("step.description")); - }.property("step.description"), + @discourseComputed("step.translatedDescription") + cookedDescription(description) { + return cook(description); + }, bannerImage: function () { const src = this.get("step.banner"); diff --git a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 index fdefab77..ae69f54c 100644 --- a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 +++ b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 @@ -20,13 +20,25 @@ const translationExists = (key) => { ); }; +const getThemeKey = (key) => { + const themeId = getThemeId(); + return `theme_translations.${themeId}.${key}`; +}; + +const translatedText = (key, value) => { + const themeKey = getThemeKey(key); + return translationExists(themeKey) ? I18n.t(themeKey) : value; +}; + +export { translatedText }; + const WizardI18n = (key, params = {}) => { const themeId = getThemeId(); if (!themeId) { return I18n.t(key, params); } - const themeKey = `theme_translations.${themeId}.${key}`; + let themeKey = getThemeKey(key); if (translationExists(themeKey)) { return I18n.t(themeKey, params); diff --git a/assets/javascripts/wizard/models/custom.js.es6 b/assets/javascripts/wizard/models/custom.js.es6 index 31a403da..39ce098c 100644 --- a/assets/javascripts/wizard/models/custom.js.es6 +++ b/assets/javascripts/wizard/models/custom.js.es6 @@ -41,6 +41,7 @@ CustomWizard.reopenClass({ wizardJson.steps = wizardJson.steps .map((step) => { const stepObj = Step.create(step); + stepObj.wizardId = wizardJson.id; stepObj.fields.sort((a, b) => { return parseFloat(a.number) - parseFloat(b.number); @@ -57,7 +58,11 @@ CustomWizard.reopenClass({ } }); - stepObj.fields = stepObj.fields.map((f) => WizardField.create(f)); + stepObj.fields = stepObj.fields.map((f) => { + f.wizardId = wizardJson.id; + f.stepId = stepObj.id; + return WizardField.create(f); + }); return stepObj; }) diff --git a/assets/javascripts/wizard/templates/components/wizard-field-text.hbs b/assets/javascripts/wizard/templates/components/wizard-field-text.hbs index be44e8e7..08733d3f 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-text.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-text.hbs @@ -1 +1 @@ -{{input id=field.id value=field.value class=fieldClass placeholder=field.placeholder tabindex=field.tabindex autocomplete=autocomplete}} +{{input id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex autocomplete=autocomplete}} diff --git a/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs b/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs index 6efb7560..dda299bc 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs @@ -1 +1 @@ -{{textarea id=field.id value=field.value class=fieldClass placeholder=field.placeholder tabindex=field.tabindex}} +{{textarea id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex}} diff --git a/assets/javascripts/wizard/templates/components/wizard-field.hbs b/assets/javascripts/wizard/templates/components/wizard-field.hbs index e0dd1551..efda8629 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field.hbs @@ -1,5 +1,5 @@ {{#if field.image}} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index cb30cf76..04b9ceab 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -44,8 +44,6 @@ en: key: "Key" value: "Value" profile: "profile" - translation: "Translation" - translation_placeholder: "key" type: "Type" none: "Make a selection" submission_key: 'submission key' @@ -174,10 +172,10 @@ en: banner: "Banner" description: "Description" required_data: - label: "Required" + label: "Required Data" not_permitted_message: "Message shown when required data not present" permitted_params: - label: "Params" + label: "Permitted Params" force_final: label: "Conditional Final Step" description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step." diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index eb4af65d..888afa6b 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -11,7 +11,6 @@ class CustomWizard::Field :label, :description, :image, - :key, :validations, :min_length, :max_length, @@ -36,7 +35,6 @@ class CustomWizard::Field @value = attrs[:value] || default_value @description = attrs[:description] @image = attrs[:image] - @key = attrs[:key] @validations = attrs[:validations] @min_length = attrs[:min_length] @max_length = attrs[:max_length] diff --git a/lib/custom_wizard/step.rb b/lib/custom_wizard/step.rb index 5ffd8024..17827ea1 100644 --- a/lib/custom_wizard/step.rb +++ b/lib/custom_wizard/step.rb @@ -9,7 +9,6 @@ class CustomWizard::Step attr_accessor :index, :title, :description, - :key, :permitted, :permitted_message, :fields, diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index 839a070f..4311bbbc 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -54,10 +54,15 @@ class CustomWizard::TemplateValidator def self.pro { - wizard: {}, + wizard: { + save_submissions: 'false', + restart_on_revisit: 'true', + }, step: { condition: 'present', - index: 'conditional' + index: 'conditional', + required_data: 'present', + permitted_params: 'present' }, field: { condition: 'present', @@ -87,10 +92,12 @@ class CustomWizard::TemplateValidator def validate_pro(object, type) self.class.pro[type].each do |property, pro_type| - is_pro = object[property.to_s].present? && ( - pro_type === 'present' || - (pro_type === 'conditional' && object[property.to_s].is_a?(Hash)) || - (pro_type.is_a?(Array) && pro_type.include?(object[property.to_s])) + val = object[property.to_s] + is_pro = (val != nil) && ( + pro_type === 'present' && val.present? || + (['true', 'false'].include?(pro_type) && cast_bool(val) == cast_bool(pro_type)) || + (pro_type === 'conditional' && val.is_a?(Hash)) || + (pro_type.is_a?(Array) && pro_type.include?(val)) ) if is_pro && !@pro.subscribed? @@ -122,4 +129,8 @@ class CustomWizard::TemplateValidator errors.add :base, I18n.t("wizard.validation.after_time") end end + + def cast_bool(val) + ActiveRecord::Type::Boolean.new.cast(val) + end end diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/serializers/custom_wizard/wizard_field_serializer.rb index 37f7a80f..fc2575ef 100644 --- a/serializers/custom_wizard/wizard_field_serializer.rb +++ b/serializers/custom_wizard/wizard_field_serializer.rb @@ -41,13 +41,8 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer object.value end - def i18n_key - @i18n_key ||= "wizard.step.#{object.step.id}.fields.#{object.id}".underscore - end - def label - return object.label if object.label.present? - I18n.t("#{object.key || i18n_key}.label", default: '') + I18n.t("#{i18n_key}.label", default: object.label, base_url: Discourse.base_url) end def include_label? @@ -55,14 +50,21 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer end def description - return object.description if object.description.present? - I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url) + I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url) end def include_description? description.present? end + def placeholder + I18n.t("#{i18n_key}.placeholder", default: object.placeholder) + end + + def include_placeholder? + placeholder.present? + end + def image object.image end @@ -71,15 +73,6 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer object.image.present? end - def placeholder - return object.placeholder if object.placeholder.present? - I18n.t("#{object.key || i18n_key}.placeholder", default: '') - end - - def include_placeholder? - placeholder.present? - end - def file_types object.file_types end @@ -122,4 +115,14 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer def preview_template object.preview_template end + + protected + + def i18n_key + @i18n_key ||= "#{object.step.wizard.id}.#{object.step.id}.#{object.id}".underscore + end + + def subscribed? + @subscribed ||= CustomWizard::Pro.subscribed? + end end diff --git a/serializers/custom_wizard/wizard_serializer.rb b/serializers/custom_wizard/wizard_serializer.rb index 7a162ba5..4a29d3ea 100644 --- a/serializers/custom_wizard/wizard_serializer.rb +++ b/serializers/custom_wizard/wizard_serializer.rb @@ -9,7 +9,8 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer :required, :permitted, :uncategorized_category_id, - :categories + :categories, + :pro_subscribed has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects has_one :user, serializer: ::BasicUserSerializer, embed: :objects @@ -60,4 +61,8 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer def categories object.categories.map { |c| c.to_h } end + + def pro_subscribed + CustomWizard::Pro.subscribed? + end end diff --git a/serializers/custom_wizard/wizard_step_serializer.rb b/serializers/custom_wizard/wizard_step_serializer.rb index 85f527bb..463fa3d6 100644 --- a/serializers/custom_wizard/wizard_step_serializer.rb +++ b/serializers/custom_wizard/wizard_step_serializer.rb @@ -39,13 +39,8 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer object.previous.present? end - def i18n_key - @i18n_key ||= "wizard.step.#{object.id}".underscore - end - def title - return PrettyText.cook(object.title) if object.title - PrettyText.cook(I18n.t("#{object.key || i18n_key}.title", default: '')) + I18n.t("#{i18n_key}.title", default: object.title, base_url: Discourse.base_url) end def include_title? @@ -53,8 +48,7 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer end def description - return object.description if object.description - PrettyText.cook(I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url)) + I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url) end def include_description? @@ -80,4 +74,14 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer def final object.final? end + + protected + + def i18n_key + @i18n_key ||= "#{object.wizard.id}.#{object.id}".underscore + end + + def subscribed? + @subscribed ||= CustomWizard::Pro.subscribed? + end end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 34a08461..c92a0d61 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -176,7 +176,7 @@ describe CustomWizard::Action do context "pro actions" do before do - enable_pro + enable_subscription end it '#send_message' do diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 9b9c6000..28880409 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -176,6 +176,7 @@ describe CustomWizard::Builder do context "restart is enabled" do before do + enable_subscription @template[:restart_on_revisit] = true CustomWizard::Template.save(@template.as_json) end @@ -204,6 +205,7 @@ describe CustomWizard::Builder do context 'with required data' do before do + enable_subscription @template[:steps][0][:required_data] = required_data_json['required_data'] @template[:steps][0][:required_data_message] = required_data_json['required_data_message'] CustomWizard::Template.save(@template.as_json) @@ -239,6 +241,7 @@ describe CustomWizard::Builder do context "with permitted params" do before do + enable_subscription @template[:steps][0][:permitted_params] = permitted_param_json['permitted_params'] CustomWizard::Template.save(@template.as_json) end @@ -253,7 +256,7 @@ describe CustomWizard::Builder do context "with condition" do before do - enable_pro + enable_subscription @template[:steps][0][:condition] = user_condition_json['condition'] CustomWizard::Template.save(@template.as_json) end @@ -292,7 +295,7 @@ describe CustomWizard::Builder do context "with condition" do before do - enable_pro + enable_subscription @template[:steps][0][:fields][0][:condition] = user_condition_json['condition'] CustomWizard::Template.save(@template.as_json) end @@ -324,6 +327,7 @@ describe CustomWizard::Builder do context 'save submissions disabled' do before do + enable_subscription @template[:save_submissions] = false CustomWizard::Template.save(@template.as_json) @wizard = CustomWizard::Builder.new(@template[:id], user).build diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index 2204264f..3bcfab46 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -217,7 +217,7 @@ describe CustomWizard::CustomField do context "with a pro subscription" do before do - enable_pro + enable_subscription end it "saves pro field types" do diff --git a/spec/components/custom_wizard/field_spec.rb b/spec/components/custom_wizard/field_spec.rb index 0fcf9fc2..c6daa516 100644 --- a/spec/components/custom_wizard/field_spec.rb +++ b/spec/components/custom_wizard/field_spec.rb @@ -23,7 +23,6 @@ describe CustomWizard::Field do expect(field.image).to eq("field_image_url.png") expect(field.description).to eq("Field description") expect(field.required).to eq(true) - expect(field.key).to eq("field.locale.key") expect(field.type).to eq("field_type") expect(field.content).to eq([]) end diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index b210c588..7ef2a5a8 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -359,7 +359,7 @@ describe CustomWizard::Mapper do context "with a pro subscription" do before do - enable_pro + enable_subscription end it "treats replaced values as string literals" do diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb index 7a84660c..e8b8af28 100644 --- a/spec/components/custom_wizard/template_validator_spec.rb +++ b/spec/components/custom_wizard/template_validator_spec.rb @@ -43,47 +43,63 @@ describe CustomWizard::TemplateValidator do ).to eq(false) end - it "invalidates pro step attributes without a pro subscription" do - template[:steps][0][:condition] = user_condition['condition'] - expect( - CustomWizard::TemplateValidator.new(template).perform - ).to eq(false) - end - - it "invalidates pro field attributes without a pro subscription" do - template[:steps][0][:fields][0][:condition] = user_condition['condition'] - expect( - CustomWizard::TemplateValidator.new(template).perform - ).to eq(false) - end - - it "invalidates pro actions without a pro subscription" do - template[:actions] << create_category - expect( - CustomWizard::TemplateValidator.new(template).perform - ).to eq(false) - end - - context "with pro subscription" do - before do - enable_pro + context "without subscription" do + it "invalidates subscription wizard attributes" do + template[:save_submissions] = false + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(false) end - it "validates pro step attributes" do + it "invalidates subscription step attributes" do + template[:steps][0][:condition] = user_condition['condition'] + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(false) + end + + it "invalidates subscription field attributes" do + template[:steps][0][:fields][0][:condition] = user_condition['condition'] + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(false) + end + + it "invalidates subscription actions" do + template[:actions] << create_category + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(false) + end + end + + context "with subscription" do + before do + enable_subscription + end + + it "validates wizard attributes" do + template[:save_submissions] = false + expect( + CustomWizard::TemplateValidator.new(template).perform + ).to eq(true) + end + + it "validates step attributes" do template[:steps][0][:condition] = user_condition['condition'] expect( CustomWizard::TemplateValidator.new(template).perform ).to eq(true) end - it "validates pro field attributes" do + it "validates field attributes" do template[:steps][0][:fields][0][:condition] = user_condition['condition'] expect( CustomWizard::TemplateValidator.new(template).perform ).to eq(true) end - it "validates pro actions" do + it "validates actions" do template[:actions] << create_category expect( CustomWizard::TemplateValidator.new(template).perform diff --git a/spec/extensions/custom_field_extensions_spec.rb b/spec/extensions/custom_field_extensions_spec.rb index bf1b3ff2..7ada17a4 100644 --- a/spec/extensions/custom_field_extensions_spec.rb +++ b/spec/extensions/custom_field_extensions_spec.rb @@ -74,7 +74,7 @@ describe "custom field extensions" do context "pro custom fields" do before do - enable_pro + enable_subscription pro_custom_field_json['custom_fields'].each do |field_json| custom_field = CustomWizard::CustomField.new(nil, field_json) diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 4aa34029..348e7791 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -28,7 +28,7 @@ def authenticate_pro CustomWizard::ProAuthentication.any_instance.stubs(:active?).returns(true) end -def enable_pro +def enable_subscription CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(true) end diff --git a/spec/requests/custom_wizard/custom_field_extensions_spec.rb b/spec/requests/custom_wizard/custom_field_extensions_spec.rb index 64d7c755..9ec3f5b1 100644 --- a/spec/requests/custom_wizard/custom_field_extensions_spec.rb +++ b/spec/requests/custom_wizard/custom_field_extensions_spec.rb @@ -40,7 +40,7 @@ describe "custom field extensions" do context "with a pro subscription" do before do - enable_pro + enable_subscription pro_custom_field_json['custom_fields'].each do |field_json| custom_field = CustomWizard::CustomField.new(nil, field_json) diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 85353e4c..d4ad4042 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -121,7 +121,7 @@ describe CustomWizard::StepsController do context "pro" do before do - enable_pro + enable_subscription end it "raises an error when user cant see the step due to conditions" do diff --git a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb index 53afa8e5..0a9f7e89 100644 --- a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb @@ -18,7 +18,7 @@ describe CustomWizard::StepSerializer do each_serializer: described_class, scope: Guardian.new(user) ).as_json - expect(json_array[0][:title]).to eq("

Text

") + expect(json_array[0][:title]).to eq("Text") expect(json_array[0][:description]).to eq("

Text inputs!

") expect(json_array[1][:index]).to eq(1) end @@ -34,6 +34,7 @@ describe CustomWizard::StepSerializer do context 'with required data' do before do + enable_subscription wizard_template['steps'][0]['required_data'] = required_data_json['required_data'] wizard_template['steps'][0]['required_data_message'] = required_data_json['required_data_message'] CustomWizard::Template.save(wizard_template) From 084c6f4a7afc773e75faa2770e6e155e49771658 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Fri, 24 Sep 2021 17:58:42 +0800 Subject: [PATCH 046/556] wip --- .../components/custom-field-input.js.es6 | 22 +- .../components/subscription-container.js.es6 | 21 ++ .../components/wizard-custom-action.js.es6 | 10 +- .../discourse/components/wizard-notice.js.es6 | 34 +++ .../wizard-realtime-validations.js.es6 | 2 +- ...s6 => wizard-subscription-selector.js.es6} | 6 +- ...izard-subscription-selector-header.js.es6} | 2 +- .../wizard-subscription-selector-row.js.es6} | 0 ...tion.js.es6 => wizard-subscription.js.es6} | 12 +- .../custom-wizard-issue-notice.hbs | 3 + .../custom-wizard-issue-notice.js.es6 | 12 + .../admin-menu/wizards-nav-button.hbs | 4 + ....es6 => admin-wizards-subscription.js.es6} | 2 +- .../controllers/admin-wizards.js.es6 | 20 ++ .../custom-wizard-admin-route-map.js.es6 | 4 +- .../initializers/custom-wizard-edits.js.es6 | 28 +++ .../discourse/lib/wizard-schema.js.es6 | 8 + .../models/custom-wizard-notice.js.es6 | 23 ++ ....es6 => custom-wizard-subscription.js.es6} | 12 +- .../routes/admin-wizards-custom-fields.js.es6 | 4 +- ....es6 => admin-wizards-subscription.js.es6} | 6 +- .../routes/admin-wizards-wizard-show.js.es6 | 2 +- .../discourse/routes/admin-wizards.js.es6 | 14 +- .../templates/admin-wizards-custom-fields.hbs | 2 +- .../discourse/templates/admin-wizards-pro.hbs | 34 --- .../templates/admin-wizards-subscription.hbs | 35 +++ .../templates/admin-wizards-wizard-show.hbs | 14 +- .../discourse/templates/admin-wizards.hbs | 9 +- .../components/custom-field-input.hbs | 4 +- .../components/subscription-container.hbs | 12 + .../components/wizard-custom-action.hbs | 2 +- .../components/wizard-custom-field.hbs | 12 +- .../components/wizard-custom-step.hbs | 12 +- .../templates/components/wizard-notice.hbs | 37 +++ .../wizard-realtime-validations.hbs | 1 - .../wizard-subscription-selector-header.hbs} | 4 +- .../wizard-subscription-selector-row.hbs} | 4 +- ...bscription.hbs => wizard-subscription.hbs} | 6 +- .../wizard-admin.scss => admin/admin.scss} | 138 +++++++++-- .../wizard-api.scss => admin/wizard/api.scss} | 0 .../wizard/manager.scss} | 0 .../wizard/mapper.scss} | 0 .../stylesheets/admin/wizard/variables.scss | 9 + .../stylesheets/common/wizard-variables.scss | 7 - config/locales/client.en.yml | 40 ++- config/locales/server.en.yml | 13 +- config/routes.rb | 13 +- controllers/custom_wizard/admin/admin.rb | 6 + .../custom_wizard/admin/custom_fields.rb | 2 +- controllers/custom_wizard/admin/notice.rb | 22 ++ controllers/custom_wizard/admin/pro.rb | 48 ---- .../custom_wizard/admin/subscription.rb | 48 ++++ controllers/custom_wizard/admin/wizard.rb | 2 +- controllers/custom_wizard/wizard.rb | 6 +- coverage/.last_run.json | 2 +- .../scheduled/custom_wizard/update_notices.rb | 9 + .../custom_wizard/update_subscription.rb | 9 + jobs/scheduled/update_pro_subscription.rb | 9 - lib/custom_wizard/action.rb | 4 - lib/custom_wizard/custom_field.rb | 14 +- lib/custom_wizard/mapper.rb | 4 +- lib/custom_wizard/notice.rb | 234 ++++++++++++++++++ lib/custom_wizard/{pro.rb => subscription.rb} | 26 +- .../{pro => subscription}/authentication.rb | 12 +- .../{pro => subscription}/subscription.rb | 2 +- lib/custom_wizard/validators/template.rb | 30 +-- plugin.rb | 31 ++- .../custom_wizard/notice_serializer.rb | 20 ++ serializers/custom_wizard/pro_serializer.rb | 6 - .../authentication_serializer.rb | 2 +- .../subscription_serializer.rb | 2 +- .../custom_wizard/subscription_serializer.rb | 6 + .../custom_wizard/wizard_field_serializer.rb | 2 +- .../custom_wizard/wizard_serializer.rb | 6 +- .../custom_wizard/wizard_step_serializer.rb | 4 - spec/components/custom_wizard/action_spec.rb | 2 +- .../custom_wizard/custom_field_spec.rb | 32 +-- spec/components/custom_wizard/mapper_spec.rb | 4 +- spec/components/custom_wizard/notice_spec.rb | 80 ++++++ .../{pro_spec.rb => subscription_spec.rb} | 70 +++--- .../custom_field_extensions_spec.rb | 6 +- ...s.json => subscription_custom_fields.json} | 0 spec/jobs/update_notices_spec.rb | 29 +++ ...on_spec.rb => update_subscription_spec.rb} | 6 +- spec/plugin_helper.rb | 16 +- .../admin/notice_controller_spec.rb | 31 +++ ...pec.rb => subscription_controller_spec.rb} | 36 +-- .../custom_field_extensions_spec.rb | 6 +- .../custom_wizard/steps_controller_spec.rb | 34 ++- .../custom_wizard/notice_serializer_spec.rb | 22 ++ .../custom_wizard/pro_serializer_spec.rb | 14 -- .../authentication_serializer_spec.rb | 6 +- .../subscription_serializer_spec.rb | 6 +- .../subscription_serializer_spec.rb | 14 ++ 94 files changed, 1228 insertions(+), 413 deletions(-) create mode 100644 assets/javascripts/discourse/components/subscription-container.js.es6 create mode 100644 assets/javascripts/discourse/components/wizard-notice.js.es6 rename assets/javascripts/discourse/components/{wizard-pro-selector.js.es6 => wizard-subscription-selector.js.es6} (58%) rename assets/javascripts/discourse/components/{wizard-pro-selector/wizard-pro-selector-header.js.es6 => wizard-subscription-selector/wizard-subscription-selector-header.js.es6} (88%) rename assets/javascripts/discourse/components/{wizard-pro-selector/wizard-pro-selector-row.js.es6 => wizard-subscription-selector/wizard-subscription-selector-row.js.es6} (100%) rename assets/javascripts/discourse/components/{wizard-pro-subscription.js.es6 => wizard-subscription.js.es6} (75%) create mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs create mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 rename assets/javascripts/discourse/controllers/{admin-wizards-pro.js.es6 => admin-wizards-subscription.js.es6} (95%) create mode 100644 assets/javascripts/discourse/controllers/admin-wizards.js.es6 create mode 100644 assets/javascripts/discourse/models/custom-wizard-notice.js.es6 rename assets/javascripts/discourse/models/{custom-wizard-pro.js.es6 => custom-wizard-subscription.js.es6} (68%) rename assets/javascripts/discourse/routes/{admin-wizards-pro.js.es6 => admin-wizards-subscription.js.es6} (61%) delete mode 100644 assets/javascripts/discourse/templates/admin-wizards-pro.hbs create mode 100644 assets/javascripts/discourse/templates/admin-wizards-subscription.hbs create mode 100644 assets/javascripts/discourse/templates/components/subscription-container.hbs create mode 100644 assets/javascripts/discourse/templates/components/wizard-notice.hbs rename assets/javascripts/discourse/templates/components/{wizard-pro-selector/wizard-pro-selector-header.hbs => wizard-subscription-selector/wizard-subscription-selector-header.hbs} (70%) rename assets/javascripts/discourse/templates/components/{wizard-pro-selector/wizard-pro-selector-row.hbs => wizard-subscription-selector/wizard-subscription-selector-row.hbs} (71%) rename assets/javascripts/discourse/templates/components/{wizard-pro-subscription.hbs => wizard-subscription.hbs} (72%) rename assets/stylesheets/{common/wizard-admin.scss => admin/admin.scss} (86%) rename assets/stylesheets/{common/wizard-api.scss => admin/wizard/api.scss} (100%) rename assets/stylesheets/{common/wizard-manager.scss => admin/wizard/manager.scss} (100%) rename assets/stylesheets/{common/wizard-mapper.scss => admin/wizard/mapper.scss} (100%) create mode 100644 assets/stylesheets/admin/wizard/variables.scss delete mode 100644 assets/stylesheets/common/wizard-variables.scss create mode 100644 controllers/custom_wizard/admin/notice.rb delete mode 100644 controllers/custom_wizard/admin/pro.rb create mode 100644 controllers/custom_wizard/admin/subscription.rb create mode 100644 jobs/scheduled/custom_wizard/update_notices.rb create mode 100644 jobs/scheduled/custom_wizard/update_subscription.rb delete mode 100644 jobs/scheduled/update_pro_subscription.rb create mode 100644 lib/custom_wizard/notice.rb rename lib/custom_wizard/{pro.rb => subscription.rb} (81%) rename lib/custom_wizard/{pro => subscription}/authentication.rb (77%) rename lib/custom_wizard/{pro => subscription}/subscription.rb (89%) create mode 100644 serializers/custom_wizard/notice_serializer.rb delete mode 100644 serializers/custom_wizard/pro_serializer.rb rename serializers/custom_wizard/{pro => subscription}/authentication_serializer.rb (66%) rename serializers/custom_wizard/{pro => subscription}/subscription_serializer.rb (63%) create mode 100644 serializers/custom_wizard/subscription_serializer.rb create mode 100644 spec/components/custom_wizard/notice_spec.rb rename spec/components/custom_wizard/{pro_spec.rb => subscription_spec.rb} (54%) rename spec/fixtures/custom_field/{pro_custom_fields.json => subscription_custom_fields.json} (100%) create mode 100644 spec/jobs/update_notices_spec.rb rename spec/jobs/{update_pro_subscription_spec.rb => update_subscription_spec.rb} (52%) create mode 100644 spec/requests/custom_wizard/admin/notice_controller_spec.rb rename spec/requests/custom_wizard/admin/{pro_controller_spec.rb => subscription_controller_spec.rb} (53%) create mode 100644 spec/serializers/custom_wizard/notice_serializer_spec.rb delete mode 100644 spec/serializers/custom_wizard/pro_serializer_spec.rb rename spec/serializers/custom_wizard/{pro => subscription}/authentication_serializer_spec.rb (60%) rename spec/serializers/custom_wizard/{pro => subscription}/subscription_serializer_spec.rb (57%) create mode 100644 spec/serializers/custom_wizard/subscription_serializer_spec.rb diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index 877b83fb..a673112b 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -6,20 +6,20 @@ import I18n from "I18n"; const klasses = ["topic", "post", "group", "category"]; const types = ["string", "boolean", "integer", "json"]; -const proTypes = { +const subscriptionTypes = { klass: ["group", "category"], type: ["json"], }; -const generateContent = function (array, type, proSubscribed = false) { +const generateContent = function (array, type, subscribed = false) { return array.reduce((result, key) => { - let proArr = proTypes[type]; - let pro = proArr && proArr.includes(key); - if (!pro || proSubscribed) { + let subArr = subscriptionTypes[type]; + let subscription = subArr && subArr.includes(key); + if (!subscription || subscribed) { result.push({ id: key, name: I18n.t(`admin.wizard.custom_field.${type}.${key}`), - pro, + subscription, }); } return result; @@ -32,11 +32,11 @@ export default Component.extend({ postSerializers: ["post"], groupSerializers: ["basic_group"], categorySerializers: ["basic_category"], - klassContent: computed("proSubscribed", function () { - return generateContent(klasses, "klass", this.proSubscribed); + klassContent: computed("subscribed", function () { + return generateContent(klasses, "klass", this.subscribed); }), - typeContent: computed("proSubscribed", function () { - return generateContent(types, "type", this.proSubscribed); + typeContent: computed("subscribed", function () { + return generateContent(types, "type", this.subscribed); }), showInputs: or("field.new", "field.edit"), classNames: ["custom-field-input"], @@ -54,7 +54,7 @@ export default Component.extend({ const serializers = this.get(`${klass}Serializers`); if (serializers) { - return generateContent(serializers, "serializers", this.proSubscribed); + return generateContent(serializers, "serializers", this.subscribed); } else { return []; } diff --git a/assets/javascripts/discourse/components/subscription-container.js.es6 b/assets/javascripts/discourse/components/subscription-container.js.es6 new file mode 100644 index 00000000..08498f6f --- /dev/null +++ b/assets/javascripts/discourse/components/subscription-container.js.es6 @@ -0,0 +1,21 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [":subscription-container", "subscribed"], + + @discourseComputed('subscribed') + subscribedIcon(subscribed) { + return subscribed ? 'check' : 'dash'; + }, + + @discourseComputed('subscribed') + subscribedLabel(subscribed) { + return `admin.wizard.subscription_container.${subscribed ? 'subscribed' : 'not_subscribed'}.label`; + }, + + @discourseComputed('subscribed') + subscribedTitle(subscribed) { + return `admin.wizard.subscription_container.${subscribed ? 'subscribed' : 'not_subscribed'}.title`; + } +}) \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index c50be2ba..ffe5b514 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -94,15 +94,15 @@ export default Component.extend(UndoChanges, { return apis.find((a) => a.name === api).endpoints; }, - @discourseComputed("proSubscribed") - actionTypes(proSubscribed) { + @discourseComputed("subscribed") + actionTypes(subscribed) { return Object.keys(wizardSchema.action.types).reduce((result, type) => { - let pro = wizardSchema.action.proTypes.includes(type); - if (proSubscribed || !pro) { + let subscription = wizardSchema.action.subscriptionTypes.includes(type); + if (subscribed || !subscription) { result.push({ id: type, name: I18n.t(`admin.wizard.action.${type}.label`), - pro, + subscription, }); } return result; diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 new file mode 100644 index 00000000..86a82c94 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-notice.js.es6 @@ -0,0 +1,34 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; +import { notEmpty, not } from "@ember/object/computed"; +import I18n from "I18n"; + +export default Component.extend({ + classNameBindings: [':wizard-notice', 'notice.type', 'dismissed', 'expired'], + showFull: false, + resolved: notEmpty('notice.expired_at'), + dismissed: notEmpty('notice.dismissed_at'), + canDismiss: not('dismissed'), + + @discourseComputed('notice.type') + title(type) { + return I18n.t(`admin.wizard.notice.title.${type}`); + }, + + @discourseComputed('notice.type') + icon(type) { + return { + warning: 'exclamation-circle', + info: 'info-circle' + }[type]; + }, + + actions: { + dismiss() { + this.set('dismissing', true) + this.notice.dismiss().then(() => { + this.set('dismissing', false); + }); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 index 04123e3d..b1d8a0f5 100644 --- a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 +++ b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 @@ -6,7 +6,7 @@ import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; export default Component.extend({ - classNames: ["realtime-validations", "setting", "full", "pro"], + classNames: ["realtime-validations", "setting", "full", "subscription"], @discourseComputed timeUnits() { diff --git a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 similarity index 58% rename from assets/javascripts/discourse/components/wizard-pro-selector.js.es6 rename to assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 index 8e472782..ec94bce5 100644 --- a/assets/javascripts/discourse/components/wizard-pro-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 @@ -1,18 +1,18 @@ import SingleSelectComponent from "select-kit/components/single-select"; export default SingleSelectComponent.extend({ - classNames: ["combo-box", "wizard-pro-selector"], + classNames: ["combo-box", "wizard-subscription-selector"], selectKitOptions: { autoFilterable: false, filterable: false, showFullTitle: true, - headerComponent: "wizard-pro-selector/wizard-pro-selector-header", + headerComponent: "wizard-subscription-selector/wizard-subscription-selector-header", caretUpIcon: "caret-up", caretDownIcon: "caret-down", }, modifyComponentForRow() { - return "wizard-pro-selector/wizard-pro-selector-row"; + return "wizard-subscription-selector/wizard-subscription-selector-row"; }, }); diff --git a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-header.js.es6 similarity index 88% rename from assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 rename to assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-header.js.es6 index c1f7251c..74f29f08 100644 --- a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-header.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-header.js.es6 @@ -3,7 +3,7 @@ import { computed } from "@ember/object"; import { reads } from "@ember/object/computed"; export default SingleSelectHeaderComponent.extend({ - classNames: ["combo-box-header", "wizard-pro-selector-header"], + classNames: ["combo-box-header", "wizard-subscription-selector-header"], caretUpIcon: reads("selectKit.options.caretUpIcon"), caretDownIcon: reads("selectKit.options.caretDownIcon"), caretIcon: computed( diff --git a/assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 similarity index 100% rename from assets/javascripts/discourse/components/wizard-pro-selector/wizard-pro-selector-row.js.es6 rename to assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 diff --git a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-subscription.js.es6 similarity index 75% rename from assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 rename to assets/javascripts/discourse/components/wizard-subscription.js.es6 index 7824cb83..0d839a5f 100644 --- a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription.js.es6 @@ -1,12 +1,12 @@ import Component from "@ember/component"; -import CustomWizardPro from "../models/custom-wizard-pro"; +import CustomWizardSubscription from "../models/custom-wizard-subscription"; import { notEmpty } from "@ember/object/computed"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "I18n"; export default Component.extend({ classNameBindings: [ - ":custom-wizard-pro-subscription", + ":custom-wizard-subscription", "subscription.active:active:inactive", ], subscribed: notEmpty("subscription"), @@ -14,8 +14,8 @@ export default Component.extend({ @discourseComputed("subscription.type") title(type) { return type - ? I18n.t(`admin.wizard.pro.subscription.title.${type}`) - : I18n.t("admin.wizard.pro.not_subscribed"); + ? I18n.t(`admin.wizard.subscription.subscription.title.${type}`) + : I18n.t("admin.wizard.subscription.not_subscribed"); }, @discourseComputed("subscription.active") @@ -25,13 +25,13 @@ export default Component.extend({ @discourseComputed("stateClass") stateLabel(stateClass) { - return I18n.t(`admin.wizard.pro.subscription.status.${stateClass}`); + return I18n.t(`admin.wizard.subscription.subscription.status.${stateClass}`); }, actions: { update() { this.set("updating", true); - CustomWizardPro.update_subscription() + CustomWizardSubscription.update() .then((result) => { if (result.success) { this.setProperties({ diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs new file mode 100644 index 00000000..a8aad815 --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs @@ -0,0 +1,3 @@ +{{#if wizardWarningNotice}} + {{wizard-notice notice=wizardWarningNotice}} +{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 new file mode 100644 index 00000000..b92e7897 --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 @@ -0,0 +1,12 @@ +import { getOwner } from "discourse-common/lib/get-owner"; + +export default { + setupComponent() { + const controller = getOwner(this).lookup('controller:admin-dashboard') + const wizardWarningNotice = controller.get('wizardWarningNotice'); + + if (wizardWarningNotice) { + this.set('wizardWarningNotice', wizardWarningNotice); + } + } +} \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs b/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs index f76722fc..f893d4ac 100644 --- a/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs +++ b/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs @@ -1,3 +1,7 @@ {{#if currentUser.admin}} {{nav-item route="adminWizards" label="admin.wizard.nav_label"}} + + {{#if wizardErrorNotice}} + {{d-icon "exclaimation-circle"}} + {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 similarity index 95% rename from assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 rename to assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 index 61012d8f..844d5a25 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 @@ -1,6 +1,6 @@ import Controller from "@ember/controller"; import discourseComputed from "discourse-common/utils/decorators"; -import CustomWizardPro from "../models/custom-wizard-pro"; +import CustomWizardSubscription from "../models/custom-wizard-subscription"; import { alias } from "@ember/object/computed"; export default Controller.extend({ diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 new file mode 100644 index 00000000..a94222c1 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -0,0 +1,20 @@ +import Controller from "@ember/controller"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import { ajax } from "discourse/lib/ajax"; + +export default Controller.extend({ + actions: { + dismissNotice(noticeId) { + ajax(`/admin/wizards/notice/${this.id}`, { + type: "DELETE", + }) + .then(result => { + if (result.success) { + const notices = this.notices; + notices.removeObject(notices.findBy('id', noticeId)); + } + }) + .catch(popupAjaxError); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 1ca6d41b..67b91f87 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -59,8 +59,8 @@ export default { resetNamespace: true, }); - this.route("adminWizardsPro", { - path: "/pro", + this.route("adminWizardsSubscription", { + path: "/subscription", resetNamespace: true, }); } diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 63ddd5e8..4bf50fa9 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -1,4 +1,8 @@ import DiscourseURL from "discourse/lib/url"; +import { withPluginApi } from "discourse/lib/plugin-api"; +import { ajax } from "discourse/lib/ajax"; +import CustomWizardNotice from "../models/custom-wizard-notice"; +import { A } from "@ember/array"; export default { name: "custom-wizard-edits", @@ -16,5 +20,29 @@ export default { } return existing.apply(this, [path, opts]); }; + + withPluginApi("0.8.36", (api) => { + api.modifyClass('route:admin-dashboard', { + afterModel() { + return CustomWizardNotice.list().then(result => { + if (result && result.length) { + this.set('notices', A(result.map(n => CustomWizardNotice.create(n)))); + } + }); + }, + + setupController(controller, model) { + if (this.notices) { + let warningNotices = this.notices.filter(n => n.type === 'warning'); + + if (warningNotices.length) { + controller.set('wizardWarningNotice', warningNotices[0]); + } + } + + this._super(...arguments); + } + }); + }); }, }; diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index a20b61a7..5d876ed0 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -193,6 +193,14 @@ const action = { "members_visibility_level", ], required: ["id", "type"], + subscriptionTypes: [ + "send_message", + "add_to_group", + "create_category", + "create_group", + "send_to_api", + ], + required: ["id", "type"], proTypes: [ "send_message", "add_to_group", diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 new file mode 100644 index 00000000..bae81822 --- /dev/null +++ b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 @@ -0,0 +1,23 @@ +import EmberObject from "@ember/object"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +const CustomWizardNotice = EmberObject.extend(); + +CustomWizardNotice.reopen({ + dismiss() { + return ajax(`/admin/wizards/notice/${this.id}`, { type: 'PUT' }).then(result => { + if (result.success) { + this.set('dismissed_at', result.dismissed_at); + } + }).catch(popupAjaxError) + } +}); + +CustomWizardNotice.reopenClass({ + list() { + return ajax('/admin/wizards/notice').catch(popupAjaxError) + } +}); + +export default CustomWizardNotice; \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 b/assets/javascripts/discourse/models/custom-wizard-subscription.js.es6 similarity index 68% rename from assets/javascripts/discourse/models/custom-wizard-pro.js.es6 rename to assets/javascripts/discourse/models/custom-wizard-subscription.js.es6 index 76429726..469460d5 100644 --- a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-subscription.js.es6 @@ -2,11 +2,11 @@ import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import EmberObject from "@ember/object"; -const CustomWizardPro = EmberObject.extend(); +const CustomWizardSubscription = EmberObject.extend(); -const basePath = "/admin/wizards/pro"; +const basePath = "/admin/wizards/subscription"; -CustomWizardPro.reopenClass({ +CustomWizardSubscription.reopenClass({ status() { return ajax(basePath, { type: "GET", @@ -23,11 +23,11 @@ CustomWizardPro.reopenClass({ }).catch(popupAjaxError); }, - update_subscription() { - return ajax(`${basePath}/subscription`, { + update() { + return ajax(basePath, { type: "POST", }).catch(popupAjaxError); }, }); -export default CustomWizardPro; +export default CustomWizardSubscription; diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index 45ca9ae7..ca9c4c40 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -9,11 +9,11 @@ export default DiscourseRoute.extend({ setupController(controller, model) { const customFields = A(model.custom_fields || []); - const proSubscribed = model.pro_subscribed; + const subscribed = model.subscribed; controller.setProperties({ customFields, - proSubscribed, + subscribed, }); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-subscription.js.es6 similarity index 61% rename from assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 rename to assets/javascripts/discourse/routes/admin-wizards-subscription.js.es6 index d5c7fbd7..4824425a 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-subscription.js.es6 @@ -1,9 +1,9 @@ -import CustomWizardPro from "../models/custom-wizard-pro"; +import CustomWizardSubscription from "../models/custom-wizard-subscription"; import DiscourseRoute from "discourse/routes/discourse"; export default DiscourseRoute.extend({ model() { - return CustomWizardPro.status(); + return CustomWizardSubscription.status(); }, setupController(controller, model) { @@ -13,7 +13,7 @@ export default DiscourseRoute.extend({ actions: { authorize() { - CustomWizardPro.authorize(); + CustomWizardSubscription.authorize(); }, }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index 7032b974..046fc6d4 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -39,7 +39,7 @@ export default DiscourseRoute.extend({ currentStep: wizard.steps[0], currentAction: wizard.actions[0], creating: model.create, - proSubscribed: parentModel.pro_subscribed, + subscribed: parentModel.subscribed, }; controller.setProperties(props); diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index 5de271a8..da184d93 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -1,9 +1,19 @@ import DiscourseRoute from "discourse/routes/discourse"; +import { ajax } from "discourse/lib/ajax"; +import { A } from "@ember/array"; export default DiscourseRoute.extend({ - beforeModel(transition) { + model() { + return ajax('/admin/wizards'); + }, + + setupController(controller, model) { + controller.set('notices', A(model.notices)); + }, + + afterModel(model, transition) { if (transition.targetName === "adminWizards.index") { this.transitionTo("adminWizardsWizard"); } - }, + } }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index 09a0d569..361d2d44 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -33,7 +33,7 @@ field=field removeField=(action "removeField") saveField=(action "saveField") - proSubscribed=proSubscribed}} + subscribed=subscribed}} {{/each}}
- {{submission-field fieldName=field value=value}} - {{wizard-table-field field=field value=value}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs deleted file mode 100644 index b4f0691b..00000000 --- a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs +++ /dev/null @@ -1,34 +0,0 @@ -
-

{{i18n "admin.wizard.pro.title"}}

- -
- {{#if model.authentication.active}} - {{conditional-loading-spinner size="small" condition=unauthorizing}} - - {{i18n "admin.wizard.pro.unauthorize"}} - - - {{else}} - {{d-button - icon="id-card" - label="admin.wizard.pro.authorize" - title="admin.wizard.pro.authorize" - action=(route-action "authorize")}} - {{/if}} -
-
- -{{wizard-message - key=messageKey - url=messageUrl - type=messageType - opts=messageOpts - component="pro"}} - -
- {{#if showSubscription}} - {{wizard-pro-subscription subscription=model.subscription}} - {{/if}} -
diff --git a/assets/javascripts/discourse/templates/admin-wizards-subscription.hbs b/assets/javascripts/discourse/templates/admin-wizards-subscription.hbs new file mode 100644 index 00000000..c75f3e1b --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-subscription.hbs @@ -0,0 +1,35 @@ +
+

{{i18n "admin.wizard.subscription.title"}}

+ +
+ {{#if model.authentication.active}} + {{conditional-loading-spinner size="small" condition=unauthorizing}} + + {{i18n "admin.wizard.subscription.unauthorize"}} + + + {{else}} + {{d-button + icon="id-card" + class="btn-primary" + label="admin.wizard.subscription.authorize" + title="admin.wizard.subscription.authorize" + action=(route-action "authorize")}} + {{/if}} +
+
+ +{{wizard-message + key=messageKey + url=messageUrl + type=messageType + opts=messageOpts + component="subscription"}} + +
+ {{#if showSubscription}} + {{wizard-subscription subscription=model.subscription}} + {{/if}} +
diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index 472cbfcb..3966a50c 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -126,11 +126,10 @@
- {{#if proSubscribed}} -
+ {{#subscription-container subscribed=subscribed}} +
- {{i18n "admin.wizard.pro.label"}}
{{input type="checkbox" checked=wizard.save_submissions}} @@ -138,17 +137,16 @@
-
+
- {{i18n "admin.wizard.pro.label"}}
{{input type="checkbox" checked=wizard.restart_on_revisit}} {{i18n "admin.wizard.restart_on_revisit_label"}}
- {{/if}} + {{/subscription-container}}
{{wizard-links @@ -163,7 +161,7 @@ currentField=currentField wizardFields=wizardFields fieldTypes=fieldTypes - proSubscribed=proSubscribed}} + subscribed=subscribed}} {{/if}} {{wizard-links @@ -180,7 +178,7 @@ apis=apis removeAction="removeAction" wizardFields=wizardFields - proSubscribed=proSubscribed}} + subscribed=subscribed}} {{/each}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index 0578e01f..5ab666c7 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -7,13 +7,18 @@ {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} - {{nav-item route="adminWizardsPro" label="admin.wizard.pro.nav_label"}} + {{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}} {{/admin-nav}}
+ {{#each notices as |notice|}} + {{wizard-notice notice=notice}} + {{/each}} {{outlet}}
diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index 2bb9acce..ac7e8689 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -1,13 +1,13 @@ {{#if showInputs}} - {{wizard-pro-selector + {{wizard-subscription-selector value=field.klass content=klassContent none="admin.wizard.custom_field.klass.select" onChange=(action (mut field.klass))}} - {{wizard-pro-selector + {{wizard-subscription-selector value=field.type content=typeContent none="admin.wizard.custom_field.type.select" diff --git a/assets/javascripts/discourse/templates/components/subscription-container.hbs b/assets/javascripts/discourse/templates/components/subscription-container.hbs new file mode 100644 index 00000000..7a02f555 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/subscription-container.hbs @@ -0,0 +1,12 @@ +
+

{{i18n 'admin.wizard.subscription_container.title'}}

+ + + {{d-icon subscribedIcon}} + {{i18n subscribedLabel}} + +
+ +
+ {{yield}} +
\ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index f5d53200..cb4cf28d 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -12,7 +12,7 @@
- {{wizard-pro-selector + {{wizard-subscription-selector value=action.type content=actionTypes onChange=(action "changeType") diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index fe3089ac..e271d0af 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -207,11 +207,10 @@
{{/if}} -{{#if proSubscribed}} -
+{{#subscription-container subscribed=subscribed}} +
- {{i18n "admin.wizard.pro.label"}}
@@ -221,10 +220,9 @@
-
+
- - {{i18n "admin.wizard.pro.label"}} + >
@@ -256,4 +254,4 @@ {{#if validations}} {{wizard-realtime-validations field=field validations=validations}} {{/if}} -{{/if}} +{{/subscription-container}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 3feb6731..320b5e47 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -33,11 +33,10 @@
-{{#if proSubscribed}} -
+{{#subscription-container subscribed=subscribed}} +
- {{i18n "admin.wizard.pro.label"}}
@@ -83,10 +82,9 @@
-
+
- {{i18n "admin.wizard.pro.label"}}
{{wizard-mapper @@ -100,7 +98,7 @@ )}}
-{{/if}} +{{/subscription-container}} {{wizard-links itemType="field" @@ -116,5 +114,5 @@ fieldTypes=fieldTypes removeField="removeField" wizardFields=wizardFields - proSubscribed=proSubscribed}} + subscribed=subscribed}} {{/each}} diff --git a/assets/javascripts/discourse/templates/components/wizard-notice.hbs b/assets/javascripts/discourse/templates/components/wizard-notice.hbs new file mode 100644 index 00000000..f6f923a3 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-notice.hbs @@ -0,0 +1,37 @@ +
+ {{#if resolved}} +
+ {{d-icon "check"}} + {{i18n "admin.wizard.notice.resolved"}} + {{format-date notice.expired_at leaveAgo="true"}} +
+ {{/if}} + +
+ {{d-icon icon}} + {{title}} +
+ +
+ {{d-icon "calendar-alt"}} + {{i18n "admin.wizard.notice.issued"}} + {{format-date notice.created_at leaveAgo="true"}} +
+ +
+ {{d-icon "plug"}} + {{i18n "admin.wizard.notice.plugin"}} +
+
+ +
+ {{{notice.message}}} +
+ +{{#if canDismiss}} + {{#if dismissing}} + {{loading-spinner size="small"}} + {{else}} + {{d-icon "times"}} + {{/if}} +{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs index 1aa0893b..8269d6ca 100644 --- a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs @@ -1,6 +1,5 @@
- {{i18n "admin.wizard.pro.label"}}
    diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-header.hbs similarity index 70% rename from assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs rename to assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-header.hbs index db467b02..a7e3d2e6 100644 --- a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-header.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-header.hbs @@ -7,8 +7,8 @@ shouldDisplayClearableButton=shouldDisplayClearableButton }} - {{#if selectedContent.pro}} - {{i18n "admin.wizard.pro.label"}} + {{#if selectedContent.subscription}} + {{i18n "admin.wizard.subscription.label"}} {{/if}} {{d-icon caretIcon class="caret-icon"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs similarity index 71% rename from assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs rename to assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs index 077265ef..e2650408 100644 --- a/assets/javascripts/discourse/templates/components/wizard-pro-selector/wizard-pro-selector-row.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs @@ -9,7 +9,7 @@
    {{html-safe label}} - {{#if item.pro}} - {{i18n "admin.wizard.pro.label"}} + {{#if item.subscription}} + {{i18n "admin.wizard.subscription.label"}} {{/if}}
    diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription.hbs similarity index 72% rename from assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs rename to assets/javascripts/discourse/templates/components/wizard-subscription.hbs index 8eca5996..418225a3 100644 --- a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription.hbs @@ -13,8 +13,8 @@ icon="sync" action=(action "update") disabled=updating - title="admin.wizard.pro.subscription.update" - label="admin.wizard.pro.subscription.update"}} + title="admin.wizard.subscription.subscription.update" + label="admin.wizard.subscription.subscription.update"}}
@@ -24,7 +24,7 @@ {{#if subscription.updated_at}}
- {{i18n "admin.wizard.pro.subscription.last_updated"}} {{format-date subscription.updated_at leaveAgo="true"}} + {{i18n "admin.wizard.subscription.subscription.last_updated"}} {{format-date subscription.updated_at leaveAgo="true"}}
{{/if}}
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/admin/admin.scss similarity index 86% rename from assets/stylesheets/common/wizard-admin.scss rename to assets/stylesheets/admin/admin.scss index 7c6cd95d..2eb32bf3 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -1,8 +1,8 @@ -@import "wizard-mapper"; -@import "wizard-manager"; -@import "wizard-api"; +@import "wizard/mapper"; +@import "wizard/manager"; +@import "wizard/api"; @import "common/components/buttons"; -@import "wizard-variables"; +@import "wizard/variables"; .admin-wizard-controls { display: flex; @@ -121,7 +121,7 @@ } .wizard-settings-parent { - padding: 20px; + padding: 1em; border: 1px solid var(--primary-low); } @@ -142,7 +142,7 @@ .wizard-basic-details, .wizard-custom-field, -.advanced-settings { +.subscription-settings { @extend .wizard-settings-group; } @@ -411,7 +411,7 @@ margin-top: 5px; } - &.pro { + &.subscription { .setting-label { display: flex; flex-direction: column; @@ -423,15 +423,6 @@ } } - .advanced-settings { - width: 100%; - margin-top: 30px; - - [class~="setting"]:first-of-type { - border-top: none; - } - } - .wizard-custom-action > [class~="setting"]:first-of-type { margin-bottom: 0; } @@ -735,7 +726,7 @@ flex-wrap: wrap; > li { - background-color: var(--primary-low); + border: 1px solid var(--primary); padding: 1em; margin: 0 0 1em 0; @@ -797,12 +788,12 @@ vertical-align: middle; } -.pro-label { +.subscription-label { color: var(--tertiary); font-size: 0.75em; } -.admin-wizards-pro { +.admin-wizards-subscription { .admin-wizard-controls { h3, label { @@ -826,7 +817,7 @@ } } - .custom-wizard-pro-subscription { + .custom-wizard-subscription { .title-container { display: flex; justify-content: space-between; @@ -861,19 +852,19 @@ } } -.wizard-pro-selector.select-kit.single-select { +.wizard-subscription-selector.select-kit.single-select { .select-kit-row .texts { display: flex; align-items: center; } - .pro-label { + .subscription-label { margin-left: 0.75em; padding-top: 0.25em; } } -.btn.btn-pavilion-pro { +.btn.btn-pavilion-support { background: var(--pavilion-primary); color: var(--pavilion-secondary); @@ -883,11 +874,108 @@ &:hover, &:focus { - background: darken($pavilionPrimary, 5%); + background: darken($pavilion_primary, 5%); &[href], svg.d-icon { - color: darken($pavilionSecondary, 10%); + color: darken($pavilion_secondary, 10%); } } } + +.subscription-container { + width: 100%; + padding: 1em; + background-color: rgba($pavilion_primary, 0.1); + + .subscription-header { + display: flex; + justify-content: space-between; + margin-bottom: 1em; + + h3 { + margin: 0; + } + + a { + color: var(--pavilion-primary); + } + } + + &:not(.subscribed) .subscription-settings { + filter: blur(1px); + } +} + +.wizard-notice { + padding: 1em; + margin-bottom: 1em; + border: 1px solid var(--primary); + border-radius: 4px; + position: relative; + + &.dismissed { + display: none; + } + + .d-icon { + margin-right: .4em; + } + + .notice-header { + display: flex; + } + + .notice-badge { + border: 1px solid var(--primary); + display: inline-flex; + align-items: center; + padding: 0 .5em; + border-radius: 4px; + margin-right: 1em; + font-size: .9em; + line-height: 25px; + min-height: 25px; + box-sizing: border-box; + + &:last-of-type { + margin-right: 0; + } + } + + &.warning { + .notice-expired-at { + border: 1px solid var(--success); + background-color: rgba($success, 0.1); + color: var(--success); + } + + .notice-title { + border: 1px solid var(--pavilion-warning); + background-color: rgba($pavilion_warning, 0.1); + color: var(--pavilion-warning); + } + } + + .notice-issued { + margin-right: .3em; + } + + .notice-message { + p { + margin: .5em 0; + } + + p:last-of-type { + margin-bottom: 0; + } + } + + .dismiss-notice, + .spinner { + position: absolute; + top: 1em; + right: 1em; + color: var(--primary); + } +} diff --git a/assets/stylesheets/common/wizard-api.scss b/assets/stylesheets/admin/wizard/api.scss similarity index 100% rename from assets/stylesheets/common/wizard-api.scss rename to assets/stylesheets/admin/wizard/api.scss diff --git a/assets/stylesheets/common/wizard-manager.scss b/assets/stylesheets/admin/wizard/manager.scss similarity index 100% rename from assets/stylesheets/common/wizard-manager.scss rename to assets/stylesheets/admin/wizard/manager.scss diff --git a/assets/stylesheets/common/wizard-mapper.scss b/assets/stylesheets/admin/wizard/mapper.scss similarity index 100% rename from assets/stylesheets/common/wizard-mapper.scss rename to assets/stylesheets/admin/wizard/mapper.scss diff --git a/assets/stylesheets/admin/wizard/variables.scss b/assets/stylesheets/admin/wizard/variables.scss new file mode 100644 index 00000000..5912f961 --- /dev/null +++ b/assets/stylesheets/admin/wizard/variables.scss @@ -0,0 +1,9 @@ +$pavilion_primary: #3c1c8c; +$pavilion_secondary: #ffffff; +$pavilion_warning: rgb(243, 163, 61); + +:root { + --pavilion-primary: #{$pavilion_primary}; + --pavilion-secondary: #{$pavilion_secondary}; + --pavilion-warning: #{$pavilion_warning}; +} diff --git a/assets/stylesheets/common/wizard-variables.scss b/assets/stylesheets/common/wizard-variables.scss deleted file mode 100644 index 68f02b6b..00000000 --- a/assets/stylesheets/common/wizard-variables.scss +++ /dev/null @@ -1,7 +0,0 @@ -$pavilionPrimary: #3c1c8c; -$pavilionSecondary: #ffffff; - -:root { - --pavilion-primary: #3c1c8c; - --pavilion-secondary: #ffffff; -} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 04b9ceab..a63677c6 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -60,9 +60,9 @@ en: expand_text: "Read More" collapse_text: "Show Less" - pro_support_button: - title: "Request Pro Support" - label: "Pro Support" + support_button: + title: "Request Support" + label: "Support" message: wizard: @@ -95,10 +95,10 @@ en: destroying: Destroying wizards... import_complete: Import complete destroy_complete: Destruction complete - pro: - documentation: Check out the PRO documentation - authorize: "Authorize this forum to use your PRO subscription plan on %{server}." - not_subscribed: "You've authorized, but are not currently subscribed to a PRO plan on %{server}." + subscription: + documentation: Check out the subscription documentation + authorize: "Authorize this forum to use your Custom Wizard subscription plan on %{server}." + not_subscribed: "You've authorized, but are not currently subscribed to a Custom Wizard plan on %{server}." subscription_expiring: "Your subscription is active, but will expire in the next 48 hours." subscription_active: "Your subscription is active." subscription_inactive: "Your subscription is inactive on this forum. Read more in the documentation." @@ -455,10 +455,19 @@ en: destroy: Destroy destroyed: destroyed - pro: - nav_label: PRO - label: PRO - title: Custom Wizard PRO + subscription_container: + title: Subscriber Features + subscribed: + label: Subscribed + title: You're subscribed and can use these features + not_subscribed: + label: Not Subscribed + title: Subscribe to use these features + + subscription: + nav_label: Subscription + label: Subscription + title: Custom Wizard Subscription authorize: Authorize authorized: Authorized unauthorize: cancel @@ -471,7 +480,14 @@ en: active: Active inactive: Inactive update: Update - last_updated: Last updated + last_updated: Last updated + + notice: + plugin: Custom Wizard Plugin + issued: Issued + resolved: Resolved + title: + warning: Warning Notice wizard_js: group: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index fffa01cc..63996b02 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -17,7 +17,7 @@ en: name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})" name_already_taken: "'%{name}' is already taken as a custom field name" save_default: "Failed to save custom field '%{name}'" - pro_type: "%{type} custom fields require PRO Subscription" + subscription_type: "%{type} custom fields require a subscription" field: too_short: "%{label} must be at least %{min} characters" @@ -50,7 +50,16 @@ en: required: "%{property} is required" conflict: "Wizard with id '%{wizard_id}' already exists" after_time: "After time setting is invalid" - pro: "%{type} %{property} is PRO only" + subscription: "%{type} %{property} is subscription only" + + notice: + connection_error: "Failed to connect to [%{server}](http://%{server})" + compatibility_issue: > + The Custom Wizard Plugin may have a compatibility issue with the latest version of Discourse. + Please check the Custom Wizard Plugin status on [%{server}](http://%{server}) before updating Discourse. + plugin_status_connection_error_limit: > + We're unable to connect to the plugin status server to determine whether there are any compatibility issues with the latest version of Discourse. + Please contact support@thepavilion.io for further assistance. site_settings: custom_wizard_enabled: "Enable custom wizards." diff --git a/config/routes.rb b/config/routes.rb index 3a37a137..0d59b200 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -45,10 +45,13 @@ Discourse::Application.routes.append do post 'admin/wizards/manager/import' => 'admin_manager#import' delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy' - get 'admin/wizards/pro' => 'admin_pro#index' - get 'admin/wizards/pro/authorize' => 'admin_pro#authorize' - get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback' - delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy_authentication' - post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription' + get 'admin/wizards/subscription' => 'admin_subscription#index' + post 'admin/wizards/subscription' => 'admin_subscription#update_subscription' + get 'admin/wizards/subscription/authorize' => 'admin_subscription#authorize' + get 'admin/wizards/subscription/authorize/callback' => 'admin_subscription#authorize_callback' + delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication' + + get 'admin/wizards/notice' => 'admin_notice#index' + put 'admin/wizards/notice/:notice_id' => 'admin_notice#dismiss' end end diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index c99954d6..ff01ddac 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -3,6 +3,12 @@ class CustomWizard::AdminController < ::Admin::AdminController before_action :ensure_admin def index + render_json_dump( + notices: ActiveModel::ArraySerializer.new( + CustomWizard::Notice.list, + each_serializer: CustomWizard::NoticeSerializer + ) + ) end private diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/controllers/custom_wizard/admin/custom_fields.rb index 40ff64be..d10f82ed 100644 --- a/controllers/custom_wizard/admin/custom_fields.rb +++ b/controllers/custom_wizard/admin/custom_fields.rb @@ -3,7 +3,7 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController def index render_json_dump( custom_fields: custom_field_list, - pro_subscribed: CustomWizard::Pro.subscribed? + subscribed: CustomWizard::Subscription.subscribed? ) end diff --git a/controllers/custom_wizard/admin/notice.rb b/controllers/custom_wizard/admin/notice.rb new file mode 100644 index 00000000..bb332810 --- /dev/null +++ b/controllers/custom_wizard/admin/notice.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class CustomWizard::AdminNoticeController < CustomWizard::AdminController + before_action :find_notice, only: [:dismiss] + + def index + render_serialized(CustomWizard::Notice.list, CustomWizard::NoticeSerializer) + end + + def dismiss + if @notice.dismissable? && @notice.dismiss + render json: success_json.merge(dismissed_at: @notice.dismissed_at) + else + render json: failed_json + end + end + + def find_notice + @notice = CustomWizard::Notice.find(params[:notice_id]) + raise Discourse::InvalidParameters.new(:notice_id) unless @notice + end +end \ No newline at end of file diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb deleted file mode 100644 index 650743e6..00000000 --- a/controllers/custom_wizard/admin/pro.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -class CustomWizard::AdminProController < CustomWizard::AdminController - skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback] - - def index - render_serialized(pro, CustomWizard::ProSerializer, root: false) - end - - def authorize - request_id = SecureRandom.hex(32) - cookies[:user_api_request_id] = request_id - redirect_to pro.authentication_url(current_user.id, request_id).to_s - end - - def authorize_callback - payload = params[:payload] - request_id = cookies[:user_api_request_id] - - pro.authentication_response(request_id, payload) - pro.update_subscription - - redirect_to '/admin/wizards/pro' - end - - def destroy_authentication - if pro.destroy_authentication - render json: success_json - else - render json: failed_json - end - end - - def update_subscription - if pro.update_subscription - subscription = CustomWizard::ProSubscriptionSerializer.new(pro.subscription, root: false) - render json: success_json.merge(subscription: subscription) - else - render json: failed_json - end - end - - protected - - def pro - @pro ||= CustomWizard::Pro.new - end -end diff --git a/controllers/custom_wizard/admin/subscription.rb b/controllers/custom_wizard/admin/subscription.rb new file mode 100644 index 00000000..15ff6396 --- /dev/null +++ b/controllers/custom_wizard/admin/subscription.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class CustomWizard::AdminSubscriptionController < CustomWizard::AdminController + skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback] + + def index + render_serialized(subscription, CustomWizard::SubscriptionSerializer, root: false) + end + + def authorize + request_id = SecureRandom.hex(32) + cookies[:user_api_request_id] = request_id + redirect_to subscription.authentication_url(current_user.id, request_id).to_s + end + + def authorize_callback + payload = params[:payload] + request_id = cookies[:user_api_request_id] + + subscription.authentication_response(request_id, payload) + subscription.update + + redirect_to '/admin/wizards/subscription' + end + + def destroy_authentication + if subscription.destroy_authentication + render json: success_json + else + render json: failed_json + end + end + + def update_subscription + if subscription.update + serialized_subscription = CustomWizard::Subscription::SubscriptionSerializer.new(subscription.subscription, root: false) + render json: success_json.merge(subscription: serialized_subscription) + else + render json: failed_json + end + end + + protected + + def subscription + @subscription ||= CustomWizard::Subscription.new + end +end diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index e824398c..fdf338bf 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -11,7 +11,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController field_types: CustomWizard::Field.types, realtime_validations: CustomWizard::RealtimeValidation.types, custom_fields: custom_field_list, - pro_subscribed: CustomWizard::Pro.subscribed? + subscribed: CustomWizard::Subscription.subscribed? ) end diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 732c5d5e..40db52e6 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -5,7 +5,7 @@ class CustomWizard::WizardController < ::ApplicationController layout 'wizard' before_action :ensure_plugin_enabled - before_action :update_pro_subscription, only: [:index] + before_action :update_subscription, only: [:index] helper_method :wizard_page_title helper_method :wizard_theme_id helper_method :wizard_theme_lookup @@ -84,7 +84,7 @@ class CustomWizard::WizardController < ::ApplicationController end end - def update_pro_subscription - CustomWizard::Pro.update_subscription + def update_subscription + CustomWizard::Subscription.update end end diff --git a/coverage/.last_run.json b/coverage/.last_run.json index 23e2ecb9..1c721888 100644 --- a/coverage/.last_run.json +++ b/coverage/.last_run.json @@ -1,5 +1,5 @@ { "result": { - "line": 92.14 + "line": 92.3 } } diff --git a/jobs/scheduled/custom_wizard/update_notices.rb b/jobs/scheduled/custom_wizard/update_notices.rb new file mode 100644 index 00000000..5194e2b8 --- /dev/null +++ b/jobs/scheduled/custom_wizard/update_notices.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Jobs::CustomWizardUpdateNotices < ::Jobs::Scheduled + every 5.minutes + + def execute(args = {}) + CustomWizard::Notice.update + end +end \ No newline at end of file diff --git a/jobs/scheduled/custom_wizard/update_subscription.rb b/jobs/scheduled/custom_wizard/update_subscription.rb new file mode 100644 index 00000000..72e34435 --- /dev/null +++ b/jobs/scheduled/custom_wizard/update_subscription.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Jobs::CustomWizardUpdateSubscription < ::Jobs::Scheduled + every 1.hour + + def execute(args = {}) + CustomWizard::Subscription.update + end +end diff --git a/jobs/scheduled/update_pro_subscription.rb b/jobs/scheduled/update_pro_subscription.rb deleted file mode 100644 index c790d529..00000000 --- a/jobs/scheduled/update_pro_subscription.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled - every 1.hour - - def execute(args = {}) - CustomWizard::Pro.update_subscription - end -end diff --git a/lib/custom_wizard/action.rb b/lib/custom_wizard/action.rb index 8ee11e54..113a0393 100644 --- a/lib/custom_wizard/action.rb +++ b/lib/custom_wizard/action.rb @@ -749,8 +749,4 @@ class CustomWizard::Action @log.join('; ') ) end - - def pro_actions - %w[send_message watch_categories send_to_api create_group create_category] - end end diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index bc1a146d..eb93e292 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -17,9 +17,9 @@ class ::CustomWizard::CustomField category: ["basic_category"], post: ["post"] } - PRO_CLASSES ||= ['category', 'group'] + SUBSCRIPTION_CLASSES ||= ['category', 'group'] TYPES ||= ["string", "boolean", "integer", "json"] - PRO_TYPES ||= ["json"] + SUBSCRIPTION_TYPES ||= ["json"] LIST_CACHE_KEY ||= 'custom_field_list' def self.serializers @@ -40,7 +40,7 @@ class ::CustomWizard::CustomField end end - @pro = CustomWizard::Pro.new + @subscription = CustomWizard::Subscription.new end def save @@ -85,8 +85,8 @@ class ::CustomWizard::CustomField next end - if attr == 'klass' && PRO_CLASSES.include?(value) && !@pro.subscribed? - add_error(I18n.t("wizard.custom_field.error.pro_type", type: value)) + if attr == 'klass' && SUBSCRIPTION_CLASSES.include?(value) && !@subscription.subscribed? + add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end if attr == 'serializers' && (unsupported = value - CLASSES[klass.to_sym]).length > 0 @@ -100,8 +100,8 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end - if attr == 'type' && PRO_TYPES.include?(value) && !@pro.subscribed? - add_error(I18n.t("wizard.custom_field.error.pro_type", type: value)) + if attr == 'type' && SUBSCRIPTION_TYPES.include?(value) && !@subscription.subscribed? + add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end if attr == 'name' diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index b66c1716..05d19a1d 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -47,7 +47,7 @@ class CustomWizard::Mapper @data = params[:data] || {} @user = params[:user] @opts = params[:opts] || {} - @pro = CustomWizard::Pro.new + @subscription = CustomWizard::Subscription.new end def perform @@ -252,7 +252,7 @@ class CustomWizard::Mapper end end - if opts[:template] && @pro.subscribed? + if opts[:template] && @subscription.subscribed? template = Liquid::Template.parse(string) string = template.render(data) end diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb new file mode 100644 index 00000000..096cc579 --- /dev/null +++ b/lib/custom_wizard/notice.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true + +class CustomWizard::Notice + include ActiveModel::Serialization + + PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing) + + attr_reader :id, + :message, + :type, + :created_at + + attr_accessor :retrieved_at, + :dismissed_at, + :expired_at + + def initialize(attrs) + @id = Digest::SHA1.hexdigest(attrs[:message]) + @message = attrs[:message] + @type = attrs[:type].to_i + @created_at = attrs[:created_at] + @retrieved_at = attrs[:retrieved_at] + @dismissed_at = attrs[:dismissed_at] + @expired_at = attrs[:expired_at] + end + + def dismiss + if dismissable? + self.dismissed_at = Time.now + self.save + end + end + + def expire + self.expired_at = Time.now + self.save + end + + def expired? + expired_at.present? + end + + def dismissed? + dismissed_at.present? + end + + def dismissable? + true + end + + def save + attrs = { + expired_at: expired_at, + created_at: created_at, + expired_at: expired_at, + message: message, + type: type + } + + if current = self.class.find(self.id) + attrs[:dismissed_at] = current.dismissed_at || self.dismissed_at + end + + self.class.store(id, attrs) + end + + def self.types + @types ||= Enum.new( + info: 0, + warning: 1 + ) + end + + def self.connection_types + @connection_types ||= Enum.new( + plugin_status: 0, + subscription: 1 + ) + end + + def self.update(skip_subscription: false, skip_plugin: false) + notices = [] + + if !skip_subscription + subscription_messages = request(subscription_messages_url) + if subscription_messages.present? + subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages]) + notices.push(*subscription_notices) + end + end + + if !skip_plugin && (Discourse.git_branch === 'tests-passed' || (Rails.env.test? || Rails.env.development?)) + plugin_status = request(plugin_status_url) + + if plugin_status.present? && plugin_status[:status].present? && plugin_status[:status].is_a?(Hash) + plugin_notice = convert_plugin_status_to_notice(plugin_status[:status]) + notices.push(plugin_notice) if plugin_notice + + expire_connection_errors(connection_types[:plugin_status]) + else + create_connection_error(connection_types[:plugin_status]) + end + end + + notices.each do |notice_data| + notice = new(notice_data) + notice.retrieved_at = Time.now + notice.save + end + + if reached_connection_error_limit(connection_types[:plugin_status]) + new( + message: I18n.t("wizard.notice.plugin_status_connection_error_limit"), + type: types[:warning], + created_at: Time.now + ) + end + end + + def self.convert_subscription_messages_to_notices(messages) + messages.map do |message| + { + message: message[:message], + type: types[message[:type].to_sym], + created_at: message[:created_at], + expired_at: message[:expired_at] + } + end + end + + def self.convert_plugin_status_to_notice(plugin_status) + notice = nil + + if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status]) + notice = { + message: PrettyText.cook(I18n.t('wizard.notice.compatibility_issue', server: plugin_status_domain)), + type: types[:warning], + created_at: plugin_status[:status_changed_at] + } + else + list(types[:warning]).each(&:expire) + end + + notice + end + + def self.subscription_messages_domain + "localhost:3000" + end + + def self.subscription_messages_url + "http://#{subscription_messages_domain}/subscription-server/messages.json" + end + + def self.plugin_status_domain + "localhost:4200" + end + + def self.plugin_status_url + "http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard" + end + + def self.request(url) + response = Excon.get(url) + + if response.status == 200 + begin + data = JSON.parse(response.body).deep_symbolize_keys + rescue JSON::ParserError + return nil + end + + data + else + nil + end + end + + def self.namespace + "#{CustomWizard::PLUGIN_NAME}_notice" + end + + def self.namespace_connection + "#{CustomWizard::PLUGIN_NAME}_notice_connection" + end + + def self.find(id) + raw = PluginStore.get(namespace, id) + new(raw.symbolize_keys) if raw.present? + end + + def self.store(id, raw_notice) + PluginStore.set(namespace, id, raw_notice) + end + + def self.plugin_status_connection_error_limit + 5 + end + + def self.list_connection_query(type) + query = PluginStoreRow.where(plugin_name: namespace_connection) + query.where("(value::json->>'type')::integer = ?", type) + end + + def self.expire_connection_errors(type) + list_connection_query(type).update_all("value = jsonb_set(value::jsonb, '{ expired_at }', (to_char(current_timestamp, 'HH12:MI:SS'))::jsonb)") + end + + def self.create_connection_error(type) + id = SecureRandom.hex(16) + attrs = { + message: I18n.t("wizard.notice.connection_error", domain: self.send("#{type}_domain")), + type: type, + created_at: Time.now + } + PluginStore.set(namespace_connection, id, attrs) + end + + def self.reached_connection_error_limit(type) + list_connection_query(type).size >= self.send("#{connection_types.key(type)}_connection_error_limit") + end + + def self.list_query(type = nil) + query = PluginStoreRow.where(plugin_name: namespace) + query = query.where("(value::json->>'expired_at') IS NULL OR (value::json->>'expired_at')::date > now()::date - 1") + query = query.where("(value::json->>'type')::integer = ?", type) if type + query.order("value::json->>'created_at' DESC") + end + + def self.list(type = nil) + list_query(type) + .map { |r| self.new(JSON.parse(r.value).symbolize_keys) } + end +end diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/subscription.rb similarity index 81% rename from lib/custom_wizard/pro.rb rename to lib/custom_wizard/subscription.rb index 61097069..11e6d4d3 100644 --- a/lib/custom_wizard/pro.rb +++ b/lib/custom_wizard/subscription.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -class CustomWizard::Pro +class CustomWizard::Subscription include ActiveModel::Serialization attr_accessor :authentication, :subscription def initialize - @authentication = CustomWizard::ProAuthentication.new(get_authentication) - @subscription = CustomWizard::ProSubscription.new(get_subscription) + @authentication = CustomWizard::Subscription::Authentication.new(get_authentication) + @subscription = CustomWizard::Subscription::Subscription.new(get_subscription) end def authorized? @@ -35,7 +35,7 @@ class CustomWizard::Pro "discourse-subscription-server:user_subscription" end - def update_subscription + def update if @authentication.active? response = Excon.get( "https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}", @@ -67,7 +67,7 @@ class CustomWizard::Pro def destroy_subscription if remove_subscription - @subscription = CustomWizard::ProSubscription.new(get_subscription) + @subscription = CustomWizard::Subscription::Subscription.new(get_subscription) !@subscription.active? else false @@ -80,7 +80,7 @@ class CustomWizard::Pro public_key: keys.public_key, nonce: keys.nonce, client_id: @authentication.client_id, - auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback", + auth_redirect: "#{Discourse.base_url}/admin/wizards/subscription/authorize/callback", application_name: SiteSetting.title, scopes: scope } @@ -108,7 +108,7 @@ class CustomWizard::Pro def destroy_authentication if remove_authentication - @authentication = CustomWizard::ProAuthentication.new(get_authentication) + @authentication = CustomWizard::Subscription::Authentication.new(get_authentication) !@authentication.active? else false @@ -123,12 +123,12 @@ class CustomWizard::Pro self.new.authorized? end - def self.update_subscription - self.new.update_subscription + def self.update + self.new.update end def self.namespace - "custom_wizard_pro" + "custom_wizard_subscription" end private @@ -157,8 +157,8 @@ class CustomWizard::Pro end def set_subscription(type) - PluginStore.set(CustomWizard::Pro.namespace, subscription_db_key, type: type, updated_at: Time.now) - CustomWizard::ProSubscription.new(get_subscription) + PluginStore.set(CustomWizard::Subscription.namespace, subscription_db_key, type: type, updated_at: Time.now) + CustomWizard::Subscription::Subscription.new(get_subscription) end def get_authentication @@ -176,7 +176,7 @@ class CustomWizard::Pro auth_by: user_id, auth_at: Time.now ) - CustomWizard::ProAuthentication.new(get_authentication) + CustomWizard::Subscription::Authentication.new(get_authentication) end def remove_authentication diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/subscription/authentication.rb similarity index 77% rename from lib/custom_wizard/pro/authentication.rb rename to lib/custom_wizard/subscription/authentication.rb index 23603898..7ad02d2c 100644 --- a/lib/custom_wizard/pro/authentication.rb +++ b/lib/custom_wizard/subscription/authentication.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class CustomWizard::ProAuthentication +class CustomWizard::Subscription::Authentication include ActiveModel::Serialization attr_reader :client_id, @@ -53,7 +53,7 @@ class CustomWizard::ProAuthentication end def get_keys(request_id) - raw = PluginStore.get(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") + raw = PluginStore.get(CustomWizard::Subscription.namespace, "#{keys_db_key}_#{request_id}") OpenStruct.new( user_id: raw && raw['user_id'], pem: raw && raw['pem'], @@ -72,7 +72,7 @@ class CustomWizard::ProAuthentication end def set_keys(request_id, user_id, rsa, nonce) - PluginStore.set(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}", + PluginStore.set(CustomWizard::Subscription.namespace, "#{keys_db_key}_#{request_id}", user_id: user_id, pem: rsa.export, nonce: nonce @@ -80,16 +80,16 @@ class CustomWizard::ProAuthentication end def delete_keys(request_id) - PluginStore.remove(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") + PluginStore.remove(CustomWizard::Subscription.namespace, "#{keys_db_key}_#{request_id}") end def get_client_id - PluginStore.get(CustomWizard::Pro.namespace, client_id_db_key) + PluginStore.get(CustomWizard::Subscription.namespace, client_id_db_key) end def set_client_id client_id = SecureRandom.hex(32) - PluginStore.set(CustomWizard::Pro.namespace, client_id_db_key, client_id) + PluginStore.set(CustomWizard::Subscription.namespace, client_id_db_key, client_id) client_id end end diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/subscription/subscription.rb similarity index 89% rename from lib/custom_wizard/pro/subscription.rb rename to lib/custom_wizard/subscription/subscription.rb index 7f5cf911..129f993a 100644 --- a/lib/custom_wizard/pro/subscription.rb +++ b/lib/custom_wizard/subscription/subscription.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class CustomWizard::ProSubscription +class CustomWizard::Subscription::Subscription include ActiveModel::Serialization attr_reader :type, diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index 4311bbbc..4928c4e5 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -6,7 +6,7 @@ class CustomWizard::TemplateValidator def initialize(data, opts = {}) @data = data @opts = opts - @pro = CustomWizard::Pro.new + @subscription = CustomWizard::Subscription.new end def perform @@ -15,15 +15,15 @@ class CustomWizard::TemplateValidator check_id(data, :wizard) check_required(data, :wizard) validate_after_time - validate_pro(data, :wizard) + validate_subscription(data, :wizard) data[:steps].each do |step| check_required(step, :step) - validate_pro(step, :step) + validate_subscription(step, :step) if step[:fields].present? step[:fields].each do |field| - validate_pro(field, :field) + validate_subscription(field, :field) check_required(field, :field) end end @@ -31,7 +31,7 @@ class CustomWizard::TemplateValidator if data[:actions].present? data[:actions].each do |action| - validate_pro(action, :action) + validate_subscription(action, :action) check_required(action, :action) end end @@ -52,7 +52,7 @@ class CustomWizard::TemplateValidator } end - def self.pro + def self.subscription { wizard: { save_submissions: 'false', @@ -90,18 +90,18 @@ class CustomWizard::TemplateValidator end end - def validate_pro(object, type) - self.class.pro[type].each do |property, pro_type| + def validate_subscription(object, type) + self.class.subscription[type].each do |property, subscription_type| val = object[property.to_s] - is_pro = (val != nil) && ( - pro_type === 'present' && val.present? || - (['true', 'false'].include?(pro_type) && cast_bool(val) == cast_bool(pro_type)) || - (pro_type === 'conditional' && val.is_a?(Hash)) || - (pro_type.is_a?(Array) && pro_type.include?(val)) + is_subscription = (val != nil) && ( + subscription_type === 'present' && val.present? || + (['true', 'false'].include?(subscription_type) && cast_bool(val) == cast_bool(subscription_type)) || + (subscription_type === 'conditional' && val.is_a?(Hash)) || + (subscription_type.is_a?(Array) && subscription_type.include?(val)) ) - if is_pro && !@pro.subscribed? - errors.add :base, I18n.t("wizard.validation.pro", type: type.to_s, property: property) + if is_subscription && !@subscription.subscribed? + errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property) end end end diff --git a/plugin.rb b/plugin.rb index 808975d6..c3063d29 100644 --- a/plugin.rb +++ b/plugin.rb @@ -7,9 +7,7 @@ # contact emails: angus@thepavilion.io gem 'liquid', '5.0.1', require: true -register_asset 'stylesheets/common/wizard-admin.scss' -register_asset 'stylesheets/common/wizard-mapper.scss' - +register_asset 'stylesheets/admin/admin.scss', :desktop enabled_site_setting :custom_wizard_enabled config = Rails.application.config @@ -42,6 +40,7 @@ if respond_to?(:register_svg_icon) register_svg_icon "comment-alt" register_svg_icon "far-life-ring" register_svg_icon "arrow-right" + register_svg_icon "shield-virus" end class ::Sprockets::DirectiveProcessor @@ -71,13 +70,15 @@ after_initialize do ../controllers/custom_wizard/admin/logs.rb ../controllers/custom_wizard/admin/manager.rb ../controllers/custom_wizard/admin/custom_fields.rb - ../controllers/custom_wizard/admin/pro.rb + ../controllers/custom_wizard/admin/subscription.rb + ../controllers/custom_wizard/admin/notice.rb ../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/steps.rb ../controllers/custom_wizard/realtime_validations.rb ../jobs/regular/refresh_api_access_token.rb ../jobs/regular/set_after_time_wizard.rb - ../jobs/scheduled/update_pro_subscription.rb + ../jobs/scheduled/custom_wizard/update_subscription.rb + ../jobs/scheduled/custom_wizard/update_notices.rb ../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/action_result.rb @@ -96,9 +97,10 @@ after_initialize do ../lib/custom_wizard/submission.rb ../lib/custom_wizard/template.rb ../lib/custom_wizard/wizard.rb - ../lib/custom_wizard/pro.rb - ../lib/custom_wizard/pro/subscription.rb - ../lib/custom_wizard/pro/authentication.rb + ../lib/custom_wizard/notice.rb + ../lib/custom_wizard/subscription.rb + ../lib/custom_wizard/subscription/subscription.rb + ../lib/custom_wizard/subscription/authentication.rb ../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/endpoint.rb @@ -119,9 +121,10 @@ after_initialize do ../serializers/custom_wizard/log_serializer.rb ../serializers/custom_wizard/submission_serializer.rb ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb - ../serializers/custom_wizard/pro/authentication_serializer.rb - ../serializers/custom_wizard/pro/subscription_serializer.rb - ../serializers/custom_wizard/pro_serializer.rb + ../serializers/custom_wizard/subscription/authentication_serializer.rb + ../serializers/custom_wizard/subscription/subscription_serializer.rb + ../serializers/custom_wizard/subscription_serializer.rb + ../serializers/custom_wizard/notice_serializer.rb ../extensions/extra_locales_controller.rb ../extensions/invites_controller.rb ../extensions/users_controller.rb @@ -238,5 +241,11 @@ after_initialize do "#{serializer_klass}_serializer".classify.constantize.prepend CustomWizardCustomFieldSerializer end + AdminDashboardData.add_problem_check do + warning_notices = CustomWizard::Notice.list(CustomWizard::Notice.types[:warning]) + warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil + end + + Jobs.enqueue(:custom_wizard_update_notices) DiscourseEvent.trigger(:custom_wizard_ready) end diff --git a/serializers/custom_wizard/notice_serializer.rb b/serializers/custom_wizard/notice_serializer.rb new file mode 100644 index 00000000..310827f7 --- /dev/null +++ b/serializers/custom_wizard/notice_serializer.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CustomWizard::NoticeSerializer < ApplicationSerializer + attributes :id, + :message, + :type, + :created_at, + :expired_at, + :dismissed_at, + :retrieved_at, + :dismissable + + def dismissable + object.dismissable? + end + + def type + CustomWizard::Notice.types.key(object.type) + end +end diff --git a/serializers/custom_wizard/pro_serializer.rb b/serializers/custom_wizard/pro_serializer.rb deleted file mode 100644 index 2f141c6d..00000000 --- a/serializers/custom_wizard/pro_serializer.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -class CustomWizard::ProSerializer < ApplicationSerializer - attributes :server - has_one :authentication, serializer: CustomWizard::ProAuthenticationSerializer, embed: :objects - has_one :subscription, serializer: CustomWizard::ProSubscriptionSerializer, embed: :objects -end diff --git a/serializers/custom_wizard/pro/authentication_serializer.rb b/serializers/custom_wizard/subscription/authentication_serializer.rb similarity index 66% rename from serializers/custom_wizard/pro/authentication_serializer.rb rename to serializers/custom_wizard/subscription/authentication_serializer.rb index 0a2915e4..3d54d0f2 100644 --- a/serializers/custom_wizard/pro/authentication_serializer.rb +++ b/serializers/custom_wizard/subscription/authentication_serializer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class CustomWizard::ProAuthenticationSerializer < ApplicationSerializer +class CustomWizard::Subscription::AuthenticationSerializer < ApplicationSerializer attributes :active, :client_id, :auth_by, diff --git a/serializers/custom_wizard/pro/subscription_serializer.rb b/serializers/custom_wizard/subscription/subscription_serializer.rb similarity index 63% rename from serializers/custom_wizard/pro/subscription_serializer.rb rename to serializers/custom_wizard/subscription/subscription_serializer.rb index d3e04e4e..95a2b323 100644 --- a/serializers/custom_wizard/pro/subscription_serializer.rb +++ b/serializers/custom_wizard/subscription/subscription_serializer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class CustomWizard::ProSubscriptionSerializer < ApplicationSerializer +class CustomWizard::Subscription::SubscriptionSerializer < ApplicationSerializer attributes :type, :active, :updated_at diff --git a/serializers/custom_wizard/subscription_serializer.rb b/serializers/custom_wizard/subscription_serializer.rb new file mode 100644 index 00000000..067cfbcd --- /dev/null +++ b/serializers/custom_wizard/subscription_serializer.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +class CustomWizard::SubscriptionSerializer < ApplicationSerializer + attributes :server + has_one :authentication, serializer: CustomWizard::Subscription::AuthenticationSerializer, embed: :objects + has_one :subscription, serializer: CustomWizard::Subscription::SubscriptionSerializer, embed: :objects +end diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/serializers/custom_wizard/wizard_field_serializer.rb index fc2575ef..bba2b41a 100644 --- a/serializers/custom_wizard/wizard_field_serializer.rb +++ b/serializers/custom_wizard/wizard_field_serializer.rb @@ -123,6 +123,6 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer end def subscribed? - @subscribed ||= CustomWizard::Pro.subscribed? + @subscribed ||= CustomWizard::Subscription.subscribed? end end diff --git a/serializers/custom_wizard/wizard_serializer.rb b/serializers/custom_wizard/wizard_serializer.rb index 4a29d3ea..fe2ac355 100644 --- a/serializers/custom_wizard/wizard_serializer.rb +++ b/serializers/custom_wizard/wizard_serializer.rb @@ -10,7 +10,7 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer :permitted, :uncategorized_category_id, :categories, - :pro_subscribed + :subscribed has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects has_one :user, serializer: ::BasicUserSerializer, embed: :objects @@ -62,7 +62,7 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer object.categories.map { |c| c.to_h } end - def pro_subscribed - CustomWizard::Pro.subscribed? + def subscribed + CustomWizard::Subscription.subscribed? end end diff --git a/serializers/custom_wizard/wizard_step_serializer.rb b/serializers/custom_wizard/wizard_step_serializer.rb index 463fa3d6..a2a314a4 100644 --- a/serializers/custom_wizard/wizard_step_serializer.rb +++ b/serializers/custom_wizard/wizard_step_serializer.rb @@ -80,8 +80,4 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer def i18n_key @i18n_key ||= "#{object.wizard.id}.#{object.id}".underscore end - - def subscribed? - @subscribed ||= CustomWizard::Pro.subscribed? - end end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index c92a0d61..e34dd861 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -174,7 +174,7 @@ describe CustomWizard::Action do expect(updater.result[:redirect_on_next]).to eq("https://google.com") end - context "pro actions" do + context "subscription actions" do before do enable_subscription end diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index 3bcfab46..a30ec02b 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -4,7 +4,7 @@ require_relative '../../plugin_helper' describe CustomWizard::CustomField do let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } - let(:custom_field_pro_json) { get_wizard_fixture("custom_field/pro_custom_fields") } + let(:custom_field_subscription_json) { get_wizard_fixture("custom_field/subscription_custom_fields") } before do CustomWizard::CustomField.invalidate_cache @@ -193,44 +193,44 @@ describe CustomWizard::CustomField do ).to eq(false) end - it "does not save pro field types without a pro subscription" do - pro_field_json = custom_field_pro_json['custom_fields'].first - custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + it "does not save subscription field types without a subscription" do + subscription_field_json = custom_field_subscription_json['custom_fields'].first + custom_field = CustomWizard::CustomField.new(nil, subscription_field_json) expect(custom_field.save).to eq(false) expect(custom_field.valid?).to eq(false) expect(custom_field.errors.full_messages.first).to eq( - I18n.t("wizard.custom_field.error.pro_type", type: "json") + I18n.t("wizard.custom_field.error.subscription_type", type: "json") ) end - it "does not save pro field classes without a pro subscription" do - pro_field_json = custom_field_pro_json['custom_fields'].second - custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + it "does not save subscription field classes without a subscription" do + subscription_field_json = custom_field_subscription_json['custom_fields'].second + custom_field = CustomWizard::CustomField.new(nil, subscription_field_json) expect(custom_field.save).to eq(false) expect(custom_field.valid?).to eq(false) expect(custom_field.errors.full_messages.first).to eq( - I18n.t("wizard.custom_field.error.pro_type", type: "category") + I18n.t("wizard.custom_field.error.subscription_type", type: "category") ) end - context "with a pro subscription" do + context "with a subscription" do before do enable_subscription end - it "saves pro field types" do - pro_field_json = custom_field_pro_json['custom_fields'].first - custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + it "saves subscription field types" do + subscription_field_json = custom_field_subscription_json['custom_fields'].first + custom_field = CustomWizard::CustomField.new(nil, subscription_field_json) expect(custom_field.save).to eq(true) expect(custom_field.valid?).to eq(true) end - it "saves pro field classes" do - pro_field_json = custom_field_pro_json['custom_fields'].second - custom_field = CustomWizard::CustomField.new(nil, pro_field_json) + it "saves subscription field classes" do + subscription_field_json = custom_field_subscription_json['custom_fields'].second + custom_field = CustomWizard::CustomField.new(nil, subscription_field_json) expect(custom_field.save).to eq(true) expect(custom_field.valid?).to eq(true) diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index 7ef2a5a8..1ac945d0 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -344,7 +344,7 @@ describe CustomWizard::Mapper do expect(result).to eq(template_params["step_1_field_1"]) end - it "requires a pro subscription" do + it "requires a subscription" do template = '{{ "w{step_1_field_1}" | size }}' mapper = create_template_mapper(template_params, user1) result = mapper.interpolate( @@ -357,7 +357,7 @@ describe CustomWizard::Mapper do expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}") end - context "with a pro subscription" do + context "with a subscription" do before do enable_subscription end diff --git a/spec/components/custom_wizard/notice_spec.rb b/spec/components/custom_wizard/notice_spec.rb new file mode 100644 index 00000000..373d2e31 --- /dev/null +++ b/spec/components/custom_wizard/notice_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require_relative '../../plugin_helper' + +describe CustomWizard::Notice do + fab!(:user) { Fabricate(:user) } + let(:subscription_message) { + { + message: "Message about subscription", + type: "info", + created_at: Time.now - 3.day, + expired_at: nil + } + } + let(:plugin_status) { + { + name: 'discourse-custom-wizard', + status: 'incompatible', + status_changed_at: Time.now - 3.day + } + } + + context "subscription message" do + before do + freeze_time + stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + described_class.update(skip_plugin: true) + end + + it "converts subscription messages into notices" do + notice = described_class.list.first + expect(notice.type).to eq(described_class.types[:info]) + expect(notice.message).to eq(subscription_message[:message]) + expect(notice.created_at.to_datetime).to be_within(1.second).of (subscription_message[:created_at].to_datetime) + end + + it "expires notice if subscription message is expired" do + subscription_message[:expired_at] = Time.now + stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + described_class.update(skip_plugin: true) + + notice = described_class.list.first + expect(notice.expired?).to eq(true) + end + end + + context "plugin status" do + before do + freeze_time + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + described_class.update(skip_subscription: true) + end + + it "converts plugin statuses to warn into notices" do + notice = described_class.list.first + expect(notice.type).to eq(described_class.types[:warning]) + expect(notice.message).to eq(PrettyText.cook(I18n.t("wizard.notice.compatibility_issue", server: described_class.plugin_status_domain))) + expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime) + end + + it "expires unexpired warning notices if status is recommended or compatible" do + plugin_status[:status] = 'compatible' + plugin_status[:status_changed_at] = Time.now + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + described_class.update(skip_subscription: true) + + notice = described_class.list(described_class.types[:warning]).first + expect(notice.expired?).to eq(true) + end + end + + it "lists notices not expired more than a day ago" do + subscription_message[:expired_at] = Time.now - 8.hours + stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + + described_class.update + expect(described_class.list.length).to eq(2) + end +end \ No newline at end of file diff --git a/spec/components/custom_wizard/pro_spec.rb b/spec/components/custom_wizard/subscription_spec.rb similarity index 54% rename from spec/components/custom_wizard/pro_spec.rb rename to spec/components/custom_wizard/subscription_spec.rb index 6499b668..47fb2d3f 100644 --- a/spec/components/custom_wizard/pro_spec.rb +++ b/spec/components/custom_wizard/subscription_spec.rb @@ -2,69 +2,69 @@ require_relative '../../plugin_helper' -describe CustomWizard::Pro do +describe CustomWizard::Subscription do fab!(:user) { Fabricate(:user) } - it "initializes pro authentication and subscription" do - pro = described_class.new - expect(pro.authentication.class).to eq(CustomWizard::ProAuthentication) - expect(pro.subscription.class).to eq(CustomWizard::ProSubscription) + it "initializes subscription authentication and subscription" do + subscription = described_class.new + expect(subscription.authentication.class).to eq(CustomWizard::Subscription::Authentication) + expect(subscription.subscription.class).to eq(CustomWizard::Subscription::Subscription) end it "returns authorized and subscribed states" do - pro = described_class.new - expect(pro.authorized?).to eq(false) - expect(pro.subscribed?).to eq(false) + subscription = described_class.new + expect(subscription.authorized?).to eq(false) + expect(subscription.subscribed?).to eq(false) end context "subscription" do before do - @pro = described_class.new + @subscription = described_class.new end it "updates valid subscriptions" do stub_subscription_request(200, valid_subscription) - expect(@pro.update_subscription).to eq(true) - expect(@pro.subscribed?).to eq(true) + expect(@subscription.update).to eq(true) + expect(@subscription.subscribed?).to eq(true) end it "handles invalid subscriptions" do stub_subscription_request(200, invalid_subscription) - expect(@pro.update_subscription).to eq(false) - expect(@pro.subscribed?).to eq(false) + expect(@subscription.update).to eq(false) + expect(@subscription.subscribed?).to eq(false) end it "handles subscription http errors" do stub_subscription_request(404, {}) - expect(@pro.update_subscription).to eq(false) - expect(@pro.subscribed?).to eq(false) + expect(@subscription.update).to eq(false) + expect(@subscription.subscribed?).to eq(false) end it "destroys subscriptions" do stub_subscription_request(200, valid_subscription) - expect(@pro.update_subscription).to eq(true) - expect(@pro.destroy_subscription).to eq(true) - expect(@pro.subscribed?).to eq(false) + expect(@subscription.update).to eq(true) + expect(@subscription.destroy_subscription).to eq(true) + expect(@subscription.subscribed?).to eq(false) end it "has class aliases" do - authenticate_pro + authenticate_subscription stub_subscription_request(200, valid_subscription) - expect(described_class.update_subscription).to eq(true) + expect(described_class.update).to eq(true) expect(described_class.subscribed?).to eq(true) end end context "authentication" do before do - @pro = described_class.new + @subscription = described_class.new user.update!(admin: true) end it "generates a valid authentication request url" do request_id = SecureRandom.hex(32) - uri = URI(@pro.authentication_url(user.id, request_id)) - expect(uri.host).to eq(@pro.server) + uri = URI(@subscription.authentication_url(user.id, request_id)) + expect(uri.host).to eq(@subscription.server) parsed_query = Rack::Utils.parse_query uri.query expect(parsed_query['public_key'].present?).to eq(true) @@ -72,12 +72,12 @@ describe CustomWizard::Pro do expect(parsed_query['client_id'].present?).to eq(true) expect(parsed_query['auth_redirect'].present?).to eq(true) expect(parsed_query['application_name']).to eq(SiteSetting.title) - expect(parsed_query['scopes']).to eq(@pro.scope) + expect(parsed_query['scopes']).to eq(@subscription.scope) end def generate_payload(request_id, user_id) - uri = URI(@pro.authentication_url(user_id, request_id)) - keys = @pro.authentication.get_keys(request_id) + uri = URI(@subscription.authentication_url(user_id, request_id)) + keys = @subscription.authentication.get_keys(request_id) raw_payload = { key: "12345", nonce: keys.nonce, @@ -92,8 +92,8 @@ describe CustomWizard::Pro do request_id = SecureRandom.hex(32) payload = generate_payload(request_id, user.id) - expect(@pro.authentication_response(request_id, payload)).to eq(true) - expect(@pro.authorized?).to eq(true) + expect(@subscription.authentication_response(request_id, payload)).to eq(true) + expect(@subscription.authorized?).to eq(true) end it "discards authentication response if user who made request as not an admin" do @@ -102,24 +102,24 @@ describe CustomWizard::Pro do request_id = SecureRandom.hex(32) payload = generate_payload(request_id, user.id) - expect(@pro.authentication_response(request_id, payload)).to eq(false) - expect(@pro.authorized?).to eq(false) + expect(@subscription.authentication_response(request_id, payload)).to eq(false) + expect(@subscription.authorized?).to eq(false) end it "discards authentication response if request_id is invalid" do payload = generate_payload(SecureRandom.hex(32), user.id) - expect(@pro.authentication_response(SecureRandom.hex(32), payload)).to eq(false) - expect(@pro.authorized?).to eq(false) + expect(@subscription.authentication_response(SecureRandom.hex(32), payload)).to eq(false) + expect(@subscription.authorized?).to eq(false) end it "destroys authentication" do request_id = SecureRandom.hex(32) payload = generate_payload(request_id, user.id) - @pro.authentication_response(request_id, payload) + @subscription.authentication_response(request_id, payload) - expect(@pro.destroy_authentication).to eq(true) - expect(@pro.authorized?).to eq(false) + expect(@subscription.destroy_authentication).to eq(true) + expect(@subscription.authorized?).to eq(false) end end end diff --git a/spec/extensions/custom_field_extensions_spec.rb b/spec/extensions/custom_field_extensions_spec.rb index 7ada17a4..7bba27a0 100644 --- a/spec/extensions/custom_field_extensions_spec.rb +++ b/spec/extensions/custom_field_extensions_spec.rb @@ -10,7 +10,7 @@ describe "custom field extensions" do fab!(:user) { Fabricate(:user) } let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } - let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") } + let(:subscription_custom_field_json) { get_wizard_fixture("custom_field/subscription_custom_fields") } before do custom_field_json['custom_fields'].each do |field_json| @@ -72,11 +72,11 @@ describe "custom field extensions" do end end - context "pro custom fields" do + context "subscription custom fields" do before do enable_subscription - pro_custom_field_json['custom_fields'].each do |field_json| + subscription_custom_field_json['custom_fields'].each do |field_json| custom_field = CustomWizard::CustomField.new(nil, field_json) custom_field.save end diff --git a/spec/fixtures/custom_field/pro_custom_fields.json b/spec/fixtures/custom_field/subscription_custom_fields.json similarity index 100% rename from spec/fixtures/custom_field/pro_custom_fields.json rename to spec/fixtures/custom_field/subscription_custom_fields.json diff --git a/spec/jobs/update_notices_spec.rb b/spec/jobs/update_notices_spec.rb new file mode 100644 index 00000000..d0e5a468 --- /dev/null +++ b/spec/jobs/update_notices_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative '../plugin_helper' + +describe Jobs::CustomWizardUpdateNotices do + let(:subscription_message) { + { + message: "Message about subscription", + type: "info", + created_at: Time.now - 3.day, + expired_at: nil + } + } + let(:plugin_status) { + { + name: 'discourse-custom-wizard', + status: 'incompatible', + status_changed_at: Time.now - 3.day + } + } + + it "updates the notices" do + stub_request(:get, CustomWizard::Notice.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + + described_class.new.execute + expect(CustomWizard::Notice.list.length).to eq(2) + end +end diff --git a/spec/jobs/update_pro_subscription_spec.rb b/spec/jobs/update_subscription_spec.rb similarity index 52% rename from spec/jobs/update_pro_subscription_spec.rb rename to spec/jobs/update_subscription_spec.rb index 0aae9668..c5adaf38 100644 --- a/spec/jobs/update_pro_subscription_spec.rb +++ b/spec/jobs/update_subscription_spec.rb @@ -2,10 +2,10 @@ require_relative '../plugin_helper' -describe CustomWizard::UpdateProSubscription do - it "updates the pro subscription" do +describe Jobs::CustomWizardUpdateSubscription do + it "updates the subscription" do stub_subscription_request(200, valid_subscription) described_class.new.execute - expect(CustomWizard::Pro.subscribed?).to eq(true) + expect(CustomWizard::Subscription.subscribed?).to eq(true) end end diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 348e7791..d47c47c1 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -24,16 +24,16 @@ def get_wizard_fixture(path) ).with_indifferent_access end -def authenticate_pro - CustomWizard::ProAuthentication.any_instance.stubs(:active?).returns(true) +def authenticate_subscription + CustomWizard::Subscription::Authentication.any_instance.stubs(:active?).returns(true) end def enable_subscription - CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(true) + CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(true) end -def disable_pro - CustomWizard::Pro.any_instance.stubs(:subscribed?).returns(false) +def disable_subscription + CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(false) end def valid_subscription @@ -52,7 +52,7 @@ def invalid_subscription end def stub_subscription_request(status, subscription) - authenticate_pro - pro = CustomWizard::Pro.new - stub_request(:get, "https://#{pro.server}/subscription-server/user-subscriptions/#{pro.subscription_type}/#{pro.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json) + authenticate_subscription + sub = CustomWizard::Subscription.new + stub_request(:get, "https://#{sub.server}/subscription-server/user-subscriptions/#{sub.subscription_type}/#{sub.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json) end diff --git a/spec/requests/custom_wizard/admin/notice_controller_spec.rb b/spec/requests/custom_wizard/admin/notice_controller_spec.rb new file mode 100644 index 00000000..bd174e90 --- /dev/null +++ b/spec/requests/custom_wizard/admin/notice_controller_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +require_relative '../../../plugin_helper' + +describe CustomWizard::AdminNoticeController do + fab!(:admin_user) { Fabricate(:user, admin: true) } + + before do + sign_in(admin_user) + @notice = CustomWizard::Notice.new( + message: "Message about subscription", + type: "info", + created_at: Time.now - 3.day, + expired_at: nil + ) + @notice.save + end + + it "lists notices" do + get "/admin/wizards/notice.json" + expect(response.status).to eq(200) + expect(response.parsed_body.length).to eq(1) + end + + it "dismisses notices" do + put "/admin/wizards/notice/#{@notice.id}.json" + expect(response.status).to eq(200) + + updated = CustomWizard::Notice.find(@notice.id) + expect(updated.dismissed?).to eq(true) + end +end diff --git a/spec/requests/custom_wizard/admin/pro_controller_spec.rb b/spec/requests/custom_wizard/admin/subscription_controller_spec.rb similarity index 53% rename from spec/requests/custom_wizard/admin/pro_controller_spec.rb rename to spec/requests/custom_wizard/admin/subscription_controller_spec.rb index 563572bd..2f8aad20 100644 --- a/spec/requests/custom_wizard/admin/pro_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/subscription_controller_spec.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true require_relative '../../../plugin_helper' -describe CustomWizard::AdminProController do +describe CustomWizard::AdminSubscriptionController do fab!(:admin_user) { Fabricate(:user, admin: true) } def generate_payload(request_id, user_id) - uri = URI(@pro.authentication_url(user_id, request_id)) - keys = @pro.authentication.get_keys(request_id) + uri = URI(@subscription.authentication_url(user_id, request_id)) + keys = @subscription.authentication.get_keys(request_id) raw_payload = { key: "12345", nonce: keys.nonce, @@ -18,19 +18,19 @@ describe CustomWizard::AdminProController do end before do - @pro = CustomWizard::Pro.new + @subscription = CustomWizard::Subscription.new sign_in(admin_user) end it "#index" do - get "/admin/wizards/pro.json" - expect(response.parsed_body['server']).to eq(@pro.server) - expect(response.parsed_body['authentication'].deep_symbolize_keys).to eq(CustomWizard::ProAuthenticationSerializer.new(@pro.authentication, root: false).as_json) - expect(response.parsed_body['subscription'].deep_symbolize_keys).to eq(CustomWizard::ProSubscriptionSerializer.new(@pro.subscription, root: false).as_json) + get "/admin/wizards/subscription.json" + expect(response.parsed_body['server']).to eq(@subscription.server) + expect(response.parsed_body['authentication'].deep_symbolize_keys).to eq(CustomWizard::Subscription::AuthenticationSerializer.new(@subscription.authentication, root: false).as_json) + expect(response.parsed_body['subscription'].deep_symbolize_keys).to eq(CustomWizard::Subscription::SubscriptionSerializer.new(@subscription.subscription, root: false).as_json) end it "#authorize" do - get "/admin/wizards/pro/authorize" + get "/admin/wizards/subscription/authorize" expect(response.status).to eq(302) expect(cookies[:user_api_request_id].present?).to eq(true) end @@ -38,12 +38,12 @@ describe CustomWizard::AdminProController do it "#destroy_authentication" do request_id = SecureRandom.hex(32) payload = generate_payload(request_id, admin_user.id) - @pro.authentication_response(request_id, payload) + @subscription.authentication_response(request_id, payload) - delete "/admin/wizards/pro/authorize.json" + delete "/admin/wizards/subscription/authorize.json" expect(response.status).to eq(200) - expect(CustomWizard::Pro.authorized?).to eq(false) + expect(CustomWizard::Subscription.authorized?).to eq(false) end context "subscription" do @@ -54,18 +54,18 @@ describe CustomWizard::AdminProController do it "handles authentication response and the updates subscription" do request_id = cookies[:user_api_request_id] = SecureRandom.hex(32) payload = generate_payload(request_id, admin_user.id) - get "/admin/wizards/pro/authorize/callback", params: { payload: payload } + get "/admin/wizards/subscription/authorize/callback", params: { payload: payload } - expect(response).to redirect_to("/admin/wizards/pro") - expect(CustomWizard::Pro.subscribed?).to eq(true) + expect(response).to redirect_to("/admin/wizards/subscription") + expect(CustomWizard::Subscription.subscribed?).to eq(true) end it "updates the subscription" do - authenticate_pro - post "/admin/wizards/pro/subscription.json" + authenticate_subscription + post "/admin/wizards/subscription.json" expect(response.status).to eq(200) - expect(CustomWizard::Pro.subscribed?).to eq(true) + expect(CustomWizard::Subscription.subscribed?).to eq(true) end end end diff --git a/spec/requests/custom_wizard/custom_field_extensions_spec.rb b/spec/requests/custom_wizard/custom_field_extensions_spec.rb index 9ec3f5b1..775e3ee0 100644 --- a/spec/requests/custom_wizard/custom_field_extensions_spec.rb +++ b/spec/requests/custom_wizard/custom_field_extensions_spec.rb @@ -9,7 +9,7 @@ describe "custom field extensions" do let!(:user) { Fabricate(:user) } let!(:group) { Fabricate(:group, users: [user]) } let(:custom_field_json) { get_wizard_fixture("custom_field/custom_fields") } - let(:pro_custom_field_json) { get_wizard_fixture("custom_field/pro_custom_fields") } + let(:subscription_custom_field_json) { get_wizard_fixture("custom_field/subscription_custom_fields") } before do custom_field_json['custom_fields'].each do |field_json| @@ -38,11 +38,11 @@ describe "custom field extensions" do expect(response.parsed_body['post_field_1']).to eq(7) end - context "with a pro subscription" do + context "with a subscription" do before do enable_subscription - pro_custom_field_json['custom_fields'].each do |field_json| + subscription_custom_field_json['custom_fields'].each do |field_json| custom_field = CustomWizard::CustomField.new(nil, field_json) custom_field.save end diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index d4ad4042..8396135c 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -119,7 +119,7 @@ describe CustomWizard::StepsController do expect(response.parsed_body['final']).to eq(true) end - context "pro" do + context "subscription" do before do enable_subscription end @@ -149,6 +149,38 @@ describe CustomWizard::StepsController do expect(response.parsed_body['wizard']['start']).to eq("step_3") end + it "returns the correct final step when the conditional final step and last step are the same" do + new_template = wizard_template.dup + new_template['steps'][0]['condition'] = user_condition_template['condition'] + new_template['steps'][2]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) + end + + it "raises an error when user cant see the step due to conditions" do + sign_in(user2) + + new_wizard_template = wizard_template.dup + new_wizard_template['steps'][0]['condition'] = user_condition_template['condition'] + CustomWizard::Template.save(new_wizard_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json' + expect(response.status).to eq(403) + end + + it "returns an updated wizard when condition doesnt pass" do + new_template = wizard_template.dup + new_template['steps'][1]['condition'] = wizard_field_condition_template['condition'] + CustomWizard::Template.save(new_template, skip_jobs: true) + + put '/w/super-mega-fun-wizard/steps/step_1.json', params: { + fields: { + step_1_field_1: "Condition wont pass" + } + } + expect(response.status).to eq(200) + expect(response.parsed_body['wizard']['start']).to eq("step_3") + end + it "returns the correct final step when the conditional final step and last step are the same" do new_template = wizard_template.dup new_template['steps'][0]['condition'] = user_condition_template['condition'] diff --git a/spec/serializers/custom_wizard/notice_serializer_spec.rb b/spec/serializers/custom_wizard/notice_serializer_spec.rb new file mode 100644 index 00000000..5184d890 --- /dev/null +++ b/spec/serializers/custom_wizard/notice_serializer_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative '../../plugin_helper' + +describe CustomWizard::NoticeSerializer do + before do + @notice = CustomWizard::Notice.new( + message: "Message about subscription", + type: "info", + created_at: Time.now - 3.day, + expired_at: nil + ) + @notice.save + end + + it 'should return notice attributes' do + serialized_notice = described_class.new(@notice) + expect(serialized_notice.message).to eq(@notice.message) + expect(serialized_notice.type).to eq(CustomWizard::Notice.types.key(@notice.type)) + expect(serialized_notice.dismissable).to eq(true) + end +end diff --git a/spec/serializers/custom_wizard/pro_serializer_spec.rb b/spec/serializers/custom_wizard/pro_serializer_spec.rb deleted file mode 100644 index 45c1956e..00000000 --- a/spec/serializers/custom_wizard/pro_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../plugin_helper' - -describe CustomWizard::ProSerializer do - it 'should return pro attributes' do - pro = CustomWizard::Pro.new - serialized = described_class.new(pro, root: false) - - expect(serialized.server).to eq(pro.server) - expect(serialized.authentication.class).to eq(CustomWizard::ProAuthentication) - expect(serialized.subscription.class).to eq(CustomWizard::ProSubscription) - end -end diff --git a/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb b/spec/serializers/custom_wizard/subscription/authentication_serializer_spec.rb similarity index 60% rename from spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb rename to spec/serializers/custom_wizard/subscription/authentication_serializer_spec.rb index 53dd74c2..ac29f95a 100644 --- a/spec/serializers/custom_wizard/pro/authentication_serializer_spec.rb +++ b/spec/serializers/custom_wizard/subscription/authentication_serializer_spec.rb @@ -2,11 +2,11 @@ require_relative '../../../plugin_helper' -describe CustomWizard::ProAuthenticationSerializer do +describe CustomWizard::Subscription::AuthenticationSerializer do fab!(:user) { Fabricate(:user) } - it 'should return pro authentication attributes' do - auth = CustomWizard::ProAuthentication.new(OpenStruct.new(key: '1234', auth_at: Time.now, auth_by: user.id)) + it 'should return subscription authentication attributes' do + auth = CustomWizard::Subscription::Authentication.new(OpenStruct.new(key: '1234', auth_at: Time.now, auth_by: user.id)) serialized = described_class.new(auth, root: false).as_json expect(serialized[:active]).to eq(true) diff --git a/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb b/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb similarity index 57% rename from spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb rename to spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb index a775863e..63caf363 100644 --- a/spec/serializers/custom_wizard/pro/subscription_serializer_spec.rb +++ b/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb @@ -2,9 +2,9 @@ require_relative '../../../plugin_helper' -describe CustomWizard::ProSubscriptionSerializer do - it 'should return pro subscription attributes' do - sub = CustomWizard::ProSubscription.new(OpenStruct.new(type: 'community', updated_at: Time.now)) +describe CustomWizard::Subscription::SubscriptionSerializer do + it 'should return subscription attributes' do + sub = CustomWizard::Subscription::Subscription.new(OpenStruct.new(type: 'community', updated_at: Time.now)) serialized = described_class.new(sub, root: false).as_json expect(serialized[:active]).to eq(true) diff --git a/spec/serializers/custom_wizard/subscription_serializer_spec.rb b/spec/serializers/custom_wizard/subscription_serializer_spec.rb new file mode 100644 index 00000000..c6ea0ef2 --- /dev/null +++ b/spec/serializers/custom_wizard/subscription_serializer_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative '../../plugin_helper' + +describe CustomWizard::SubscriptionSerializer do + it 'should return subscription attributes' do + subscription = CustomWizard::Subscription.new + serialized = described_class.new(subscription, root: false) + + expect(serialized.server).to eq(subscription.server) + expect(serialized.authentication.class).to eq(CustomWizard::Subscription::Authentication) + expect(serialized.subscription.class).to eq(CustomWizard::Subscription::Subscription) + end +end From c9453a0bdd76027194e31953265555c2feb11c93 Mon Sep 17 00:00:00 2001 From: merefield Date: Mon, 4 Oct 2021 14:40:17 +0100 Subject: [PATCH 047/556] API: remove GET method from API options --- .../discourse/controllers/admin-wizards-api-show.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 index 5dba2d7f..7d3c1084 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 @@ -11,7 +11,7 @@ export default Controller.extend({ queryParams: ["refresh_list"], loadingSubscriptions: false, notAuthorized: not("api.authorized"), - endpointMethods: selectKitContent(["GET", "PUT", "POST", "PATCH", "DELETE"]), + endpointMethods: selectKitContent(["PUT", "POST", "PATCH", "DELETE"]), showRemove: not("isNew"), showRedirectUri: and("threeLeggedOauth", "api.name"), responseIcon: null, From a2ebd5c463f09e7bf9801f76a30d0853e113a11c Mon Sep 17 00:00:00 2001 From: merefield Date: Mon, 4 Oct 2021 18:51:47 +0100 Subject: [PATCH 048/556] API: don't present an API menu entry if not subscribed --- assets/javascripts/discourse/templates/admin-wizards.hbs | 2 +- controllers/custom_wizard/admin/admin.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index 5ab666c7..a9169d66 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -2,7 +2,7 @@ {{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}} {{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.nav_label"}} {{nav-item route="adminWizardsSubmissions" label="admin.wizard.submissions.nav_label"}} - {{#if siteSettings.wizard_apis_enabled}} + {{#if subscribed}} {{nav-item route="adminWizardsApi" label="admin.wizard.api.nav_label"}} {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index ff01ddac..63f148b9 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -4,6 +4,7 @@ class CustomWizard::AdminController < ::Admin::AdminController def index render_json_dump( + subscribed: CustomWizard::Subscription.subscribed?, notices: ActiveModel::ArraySerializer.new( CustomWizard::Notice.list, each_serializer: CustomWizard::NoticeSerializer From 6056351b7c7da031b80a0e580d39b45c66733957 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 5 Oct 2021 10:06:15 +0100 Subject: [PATCH 049/556] API: fix request call --- lib/custom_wizard/api/endpoint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_wizard/api/endpoint.rb b/lib/custom_wizard/api/endpoint.rb index 9dbbc590..b82dd39a 100644 --- a/lib/custom_wizard/api/endpoint.rb +++ b/lib/custom_wizard/api/endpoint.rb @@ -74,7 +74,7 @@ class CustomWizard::Api::Endpoint headers["Authorization"] = auth_string if auth_string headers["Content-Type"] = content_type if content_type - connection = Excon.new(UrlHelper.encode_and_parse(endpoint.url), headers: headers) + connection = Excon.new(URI.encode(endpoint.url), headers: headers) params = { method: endpoint.method } From 5a424e8e3dfdd00202a5dcd07da4dca96e8fc4df Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 5 Oct 2021 10:07:20 +0100 Subject: [PATCH 050/556] API: add mocked request spec --- spec/components/custom_wizard/action_spec.rb | 27 +++++++++++++++++++ spec/fixtures/api/no_authorization.json | 3 +++ spec/fixtures/endpoints/test_endpoint.json | 5 ++++ .../endpoints/test_endpoint_body.json | 3 +++ spec/plugin_helper.rb | 1 + 5 files changed, 39 insertions(+) create mode 100644 spec/fixtures/api/no_authorization.json create mode 100644 spec/fixtures/endpoints/test_endpoint.json create mode 100644 spec/fixtures/endpoints/test_endpoint_body.json diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index e34dd861..f5296a54 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -13,6 +13,9 @@ describe CustomWizard::Action do let(:add_to_group) { get_wizard_fixture("actions/add_to_group") } let(:send_message) { get_wizard_fixture("actions/send_message") } let(:send_message_multi) { get_wizard_fixture("actions/send_message_multi") } + let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") } + let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body")} + let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization")} def update_template(template) CustomWizard::Template.save(template, skip_jobs: true) @@ -265,5 +268,29 @@ describe CustomWizard::Action do expect(group.users.first.username).to eq('angus') end + + it '#send_to_api' do + stub_request(:put, "https://myexternalapi.com/update"). + with( + body: "some_body", + headers: { + 'Host'=>'myexternalapi.com' + }). + to_return(status: 200, body: "success", headers: {}) + + new_api = CustomWizard::Api.new("my_api") + CustomWizard::Api.set("my_api", title: "Mocked external api") + CustomWizard::Api::Authorization.set("my_api", api_test_no_authorization) + CustomWizard::Api::Endpoint.new("my_api") + CustomWizard::Api::Endpoint.set("my_api", api_test_endpoint) + endpoint_id = CustomWizard::Api::Endpoint.list("my_api").first.id + + result = CustomWizard::Api::Endpoint.request("my_api", endpoint_id, "some_body") + log_entry = CustomWizard::Api::LogEntry.list("my_api").first + byebug + expect(result).to eq('success') + expect(log_entry.status).to eq('SUCCESS') + end end end + diff --git a/spec/fixtures/api/no_authorization.json b/spec/fixtures/api/no_authorization.json new file mode 100644 index 00000000..b1c6506e --- /dev/null +++ b/spec/fixtures/api/no_authorization.json @@ -0,0 +1,3 @@ +{ + "auth_type": "none" +} \ No newline at end of file diff --git a/spec/fixtures/endpoints/test_endpoint.json b/spec/fixtures/endpoints/test_endpoint.json new file mode 100644 index 00000000..ba979064 --- /dev/null +++ b/spec/fixtures/endpoints/test_endpoint.json @@ -0,0 +1,5 @@ +{ + "method": "PUT", + "url": "https://myexternalapi.com/update", + "success_codes": [200] +} \ No newline at end of file diff --git a/spec/fixtures/endpoints/test_endpoint_body.json b/spec/fixtures/endpoints/test_endpoint_body.json new file mode 100644 index 00000000..837e7c72 --- /dev/null +++ b/spec/fixtures/endpoints/test_endpoint_body.json @@ -0,0 +1,3 @@ +{ + "data": "some_data" +} \ No newline at end of file diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index d47c47c1..e72fe355 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -15,6 +15,7 @@ require 'oj' Oj.default_options = Oj.default_options.merge(cache_str: -1) require 'rails_helper' +require 'webmock/rspec' def get_wizard_fixture(path) JSON.parse( From 31e275668cd9d9d509876e28930fae20def9956d Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 5 Oct 2021 10:19:47 +0100 Subject: [PATCH 051/556] API: remove byebug --- spec/components/custom_wizard/action_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index f5296a54..4bd626ad 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -287,7 +287,7 @@ describe CustomWizard::Action do result = CustomWizard::Api::Endpoint.request("my_api", endpoint_id, "some_body") log_entry = CustomWizard::Api::LogEntry.list("my_api").first - byebug + expect(result).to eq('success') expect(log_entry.status).to eq('SUCCESS') end From 1f939c1ca039778daa88ba6f179fdd6986af8cda Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 5 Oct 2021 10:20:36 +0100 Subject: [PATCH 052/556] API: only show API menu on business sub --- assets/javascripts/discourse/templates/admin-wizards.hbs | 2 +- config/settings.yml | 3 --- controllers/custom_wizard/admin/admin.rb | 3 ++- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index a9169d66..b72f5a7b 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -2,7 +2,7 @@ {{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}} {{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.nav_label"}} {{nav-item route="adminWizardsSubmissions" label="admin.wizard.submissions.nav_label"}} - {{#if subscribed}} + {{#if api_subscription}} {{nav-item route="adminWizardsApi" label="admin.wizard.api.nav_label"}} {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} diff --git a/config/settings.yml b/config/settings.yml index 0d93524d..d88dbaf5 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -15,6 +15,3 @@ plugins: refresh: true type: list list_type: compact - wizard_apis_enabled: - client: true - default: false \ No newline at end of file diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 63f148b9..df11d25c 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -4,7 +4,8 @@ class CustomWizard::AdminController < ::Admin::AdminController def index render_json_dump( - subscribed: CustomWizard::Subscription.subscribed?, + #TODO replace with appropriate static? + api_subscription: ["business"].includes?(CustomWizard::Subscription.type), notices: ActiveModel::ArraySerializer.new( CustomWizard::Notice.list, each_serializer: CustomWizard::NoticeSerializer From 925c8c009a180230004ca042e22128499624bc17 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 5 Oct 2021 20:54:06 +0800 Subject: [PATCH 053/556] DEV: Add notice specs and UI updates --- .../components/subscription-container.js.es6 | 2 +- .../discourse/components/wizard-notice.js.es6 | 10 +- .../custom-wizard-important-notice.hbs | 3 + .../custom-wizard-important-notice.js.es6 | 16 +++ .../custom-wizard-issue-notice.hbs | 3 - .../custom-wizard-issue-notice.js.es6 | 12 -- .../admin-wizards-subscription.js.es6 | 2 +- .../initializers/custom-wizard-edits.js.es6 | 10 +- .../models/custom-wizard-notice.js.es6 | 4 +- .../components/subscription-container.hbs | 6 +- .../templates/components/wizard-notice.hbs | 20 ++- assets/stylesheets/admin/admin.scss | 20 ++- config/locales/client.en.yml | 6 +- config/locales/server.en.yml | 18 ++- config/settings.yml | 5 +- controllers/custom_wizard/admin/notice.rb | 4 +- .../scheduled/custom_wizard/update_notices.rb | 2 +- lib/custom_wizard/notice.rb | 132 +++++++++--------- lib/custom_wizard/notice/connection_error.rb | 82 +++++++++++ plugin.rb | 6 +- .../custom_wizard/notice_serializer.rb | 1 + spec/components/custom_wizard/notice_spec.rb | 69 +++++++-- .../sprockets/require_tree_discourse_empty.js | 2 +- 23 files changed, 306 insertions(+), 129 deletions(-) create mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs create mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 delete mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs delete mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 create mode 100644 lib/custom_wizard/notice/connection_error.rb diff --git a/assets/javascripts/discourse/components/subscription-container.js.es6 b/assets/javascripts/discourse/components/subscription-container.js.es6 index 08498f6f..620c6d88 100644 --- a/assets/javascripts/discourse/components/subscription-container.js.es6 +++ b/assets/javascripts/discourse/components/subscription-container.js.es6 @@ -18,4 +18,4 @@ export default Component.extend({ subscribedTitle(subscribed) { return `admin.wizard.subscription_container.${subscribed ? 'subscribed' : 'not_subscribed'}.title`; } -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 index 86a82c94..15da3f35 100644 --- a/assets/javascripts/discourse/components/wizard-notice.js.es6 +++ b/assets/javascripts/discourse/components/wizard-notice.js.es6 @@ -1,10 +1,10 @@ import Component from "@ember/component"; import discourseComputed from "discourse-common/utils/decorators"; -import { notEmpty, not } from "@ember/object/computed"; +import { not, notEmpty } from "@ember/object/computed"; import I18n from "I18n"; export default Component.extend({ - classNameBindings: [':wizard-notice', 'notice.type', 'dismissed', 'expired'], + classNameBindings: [':wizard-notice', 'notice.type', 'dismissed', 'expired', 'resolved'], showFull: false, resolved: notEmpty('notice.expired_at'), dismissed: notEmpty('notice.dismissed_at'), @@ -18,14 +18,16 @@ export default Component.extend({ @discourseComputed('notice.type') icon(type) { return { - warning: 'exclamation-circle', + plugin_status_warning: 'exclamation-circle', + plugin_status_connection_error: 'bolt', + subscription_messages_connection_error: 'bolt', info: 'info-circle' }[type]; }, actions: { dismiss() { - this.set('dismissing', true) + this.set('dismissing', true); this.notice.dismiss().then(() => { this.set('dismissing', false); }); diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs new file mode 100644 index 00000000..9b01c468 --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs @@ -0,0 +1,3 @@ +{{#if importantNotice}} + {{wizard-notice notice=importantNotice importantOnDashboard=true}} +{{/if}} diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 new file mode 100644 index 00000000..43a2152b --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 @@ -0,0 +1,16 @@ +import { getOwner } from "discourse-common/lib/get-owner"; + +export default { + shouldRender(attrs, ctx) { + return ctx.siteSettings.wizard_important_notices_on_dashboard; + }, + + setupComponent() { + const controller = getOwner(this).lookup('controller:admin-dashboard'); + const importantNotice = controller.get('customWizardImportantNotice'); + + if (importantNotice) { + this.set('importantNotice', importantNotice); + } + } +}; \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs deleted file mode 100644 index a8aad815..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if wizardWarningNotice}} - {{wizard-notice notice=wizardWarningNotice}} -{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 deleted file mode 100644 index b92e7897..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-issue-notice.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -import { getOwner } from "discourse-common/lib/get-owner"; - -export default { - setupComponent() { - const controller = getOwner(this).lookup('controller:admin-dashboard') - const wizardWarningNotice = controller.get('wizardWarningNotice'); - - if (wizardWarningNotice) { - this.set('wizardWarningNotice', wizardWarningNotice); - } - } -} \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 index 844d5a25..76f16119 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 @@ -38,7 +38,7 @@ export default Controller.extend({ unauthorize() { this.set("unauthorizing", true); - CustomWizardPro.unauthorize() + CustomWizardSubscription.unauthorize() .then((result) => { if (result.success) { this.setProperties({ diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 4bf50fa9..f53dc2cd 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -1,6 +1,5 @@ import DiscourseURL from "discourse/lib/url"; import { withPluginApi } from "discourse/lib/plugin-api"; -import { ajax } from "discourse/lib/ajax"; import CustomWizardNotice from "../models/custom-wizard-notice"; import { A } from "@ember/array"; @@ -31,12 +30,13 @@ export default { }); }, - setupController(controller, model) { + setupController(controller) { if (this.notices) { - let warningNotices = this.notices.filter(n => n.type === 'warning'); + let pluginStatusConnectionError = this.notices.filter(n => n.type === 'plugin_status_connection_error')[0]; + let pluginStatusWarning = this.notices.filter(n => n.type === 'plugin_status_warning')[0]; - if (warningNotices.length) { - controller.set('wizardWarningNotice', warningNotices[0]); + if (pluginStatusConnectionError || pluginStatusWarning) { + controller.set('customWizardImportantNotice', pluginStatusConnectionError || pluginStatusWarning); } } diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 index bae81822..a6b47c40 100644 --- a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 @@ -10,13 +10,13 @@ CustomWizardNotice.reopen({ if (result.success) { this.set('dismissed_at', result.dismissed_at); } - }).catch(popupAjaxError) + }).catch(popupAjaxError); } }); CustomWizardNotice.reopenClass({ list() { - return ajax('/admin/wizards/notice').catch(popupAjaxError) + return ajax('/admin/wizards/notice').catch(popupAjaxError); } }); diff --git a/assets/javascripts/discourse/templates/components/subscription-container.hbs b/assets/javascripts/discourse/templates/components/subscription-container.hbs index 7a02f555..8b012671 100644 --- a/assets/javascripts/discourse/templates/components/subscription-container.hbs +++ b/assets/javascripts/discourse/templates/components/subscription-container.hbs @@ -1,5 +1,5 @@ - {{/if}} - +
{{d-icon icon}} {{title}}
- {{d-icon "calendar-alt"}} + {{d-icon "far-clock"}} {{i18n "admin.wizard.notice.issued"}} {{format-date notice.created_at leaveAgo="true"}}
+ {{#if notice.updated_at}} +
+ {{d-icon "calendar-alt"}} + {{i18n "admin.wizard.notice.updated"}} + {{format-date notice.updated_at leaveAgo="true"}} +
+ {{/if}} +
{{d-icon "plug"}} {{i18n "admin.wizard.notice.plugin"}} @@ -28,10 +36,16 @@ {{{notice.message}}}
+{{#if importantOnDashboard}} +
+ {{i18n "admin.wizard.notice.disable_important_on_dashboard"}} + +{{/if}} + {{#if canDismiss}} {{#if dismissing}} {{loading-spinner size="small"}} {{else}} {{d-icon "times"}} {{/if}} -{{/if}} \ No newline at end of file +{{/if}} diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index 2eb32bf3..862f6761 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -911,13 +911,18 @@ padding: 1em; margin-bottom: 1em; border: 1px solid var(--primary); - border-radius: 4px; position: relative; &.dismissed { display: none; } + &.resolved .notice-badge:not(.notice-expired-at), + &.resolved a, + &.resolved p { + color: var(--primary-medium) !important; + } + .d-icon { margin-right: .4em; } @@ -931,7 +936,6 @@ display: inline-flex; align-items: center; padding: 0 .5em; - border-radius: 4px; margin-right: 1em; font-size: .9em; line-height: 25px; @@ -957,7 +961,8 @@ } } - .notice-issued { + .notice-issued, + .notice-resolved { margin-right: .3em; } @@ -976,6 +981,13 @@ position: absolute; top: 1em; right: 1em; - color: var(--primary); + color: var(--primary-medium); + } + + .disable-important { + position: absolute; + right: 3em; + top: 1em; + color: var(--primary-medium); } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index a63677c6..61e8274c 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -485,9 +485,13 @@ en: notice: plugin: Custom Wizard Plugin issued: Issued + update: Updated resolved: Resolved title: - warning: Warning Notice + plugin_status_warning: Warning Notice + plugin_status_connection_error: Connection Notice + subscription_messages_connection_error: Connection Notice + disable_important_on_dashboard: disable wizard_js: group: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 63996b02..5fd7c076 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -53,16 +53,22 @@ en: subscription: "%{type} %{property} is subscription only" notice: - connection_error: "Failed to connect to [%{server}](http://%{server})" + connection_error: "Failed to connect to http://%{domain}" compatibility_issue: > - The Custom Wizard Plugin may have a compatibility issue with the latest version of Discourse. - Please check the Custom Wizard Plugin status on [%{server}](http://%{server}) before updating Discourse. - plugin_status_connection_error_limit: > - We're unable to connect to the plugin status server to determine whether there are any compatibility issues with the latest version of Discourse. - Please contact support@thepavilion.io for further assistance. + The Custom Wizard Plugin has a compatibility issue with the latest version of Discourse. + Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. + plugin_status: + connection_error_limit: > + We're unable to connect to the Pavilion Plugin Status Server. Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse or the plugin. + If this connection issue persists please contact support@thepavilion.io for further assistance. + subscription_messages: + connection_error_limit: > + We're unable to connect to the Pavilion Subscription Server. This will not affect the operation of the plugin. + If this connection issue persists please contact support@thepavilion.io for further assistance. site_settings: custom_wizard_enabled: "Enable custom wizards." wizard_redirect_exclude_paths: "Routes excluded from wizard redirects." wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview" wizard_apis_enabled: "Enable API features (experimental)." + wizard_important_notices_on_dashboard: "Show important notices about the custom wizard plugin on the admin dashboard." diff --git a/config/settings.yml b/config/settings.yml index 0d93524d..d7b34aa9 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -17,4 +17,7 @@ plugins: list_type: compact wizard_apis_enabled: client: true - default: false \ No newline at end of file + default: false + wizard_important_notices_on_dashboard: + client: true + default: true \ No newline at end of file diff --git a/controllers/custom_wizard/admin/notice.rb b/controllers/custom_wizard/admin/notice.rb index bb332810..f28240e3 100644 --- a/controllers/custom_wizard/admin/notice.rb +++ b/controllers/custom_wizard/admin/notice.rb @@ -4,7 +4,7 @@ class CustomWizard::AdminNoticeController < CustomWizard::AdminController before_action :find_notice, only: [:dismiss] def index - render_serialized(CustomWizard::Notice.list, CustomWizard::NoticeSerializer) + render_serialized(CustomWizard::Notice.list(include_recently_expired: true), CustomWizard::NoticeSerializer) end def dismiss @@ -19,4 +19,4 @@ class CustomWizard::AdminNoticeController < CustomWizard::AdminController @notice = CustomWizard::Notice.find(params[:notice_id]) raise Discourse::InvalidParameters.new(:notice_id) unless @notice end -end \ No newline at end of file +end diff --git a/jobs/scheduled/custom_wizard/update_notices.rb b/jobs/scheduled/custom_wizard/update_notices.rb index 5194e2b8..25ebdf3b 100644 --- a/jobs/scheduled/custom_wizard/update_notices.rb +++ b/jobs/scheduled/custom_wizard/update_notices.rb @@ -6,4 +6,4 @@ class Jobs::CustomWizardUpdateNotices < ::Jobs::Scheduled def execute(args = {}) CustomWizard::Notice.update end -end \ No newline at end of file +end diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index 096cc579..335ccbd4 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -3,7 +3,14 @@ class CustomWizard::Notice include ActiveModel::Serialization + PLUGIN_STATUS_DOMAINS = { + "tests-passed" => "try.thepavilion.io", + "stable" => "stable.try.thepavilion.io" + } + SUBSCRIPTION_MESSAGES_DOMAIN = "thepavilion.io" + LOCALHOST_DOMAIN = "localhost:3000" PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing) + CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable) attr_reader :id, :message, @@ -11,6 +18,7 @@ class CustomWizard::Notice :created_at attr_accessor :retrieved_at, + :updated_at, :dismissed_at, :expired_at @@ -19,6 +27,7 @@ class CustomWizard::Notice @message = attrs[:message] @type = attrs[:type].to_i @created_at = attrs[:created_at] + @updated_at = attrs[:updated_at] @retrieved_at = attrs[:retrieved_at] @dismissed_at = attrs[:dismissed_at] @expired_at = attrs[:expired_at] @@ -52,7 +61,6 @@ class CustomWizard::Notice attrs = { expired_at: expired_at, created_at: created_at, - expired_at: expired_at, message: message, type: type } @@ -67,14 +75,9 @@ class CustomWizard::Notice def self.types @types ||= Enum.new( info: 0, - warning: 1 - ) - end - - def self.connection_types - @connection_types ||= Enum.new( - plugin_status: 0, - subscription: 1 + plugin_status_warning: 1, + plugin_status_connection_error: 2, + subscription_messages_connection_error: 3 ) end @@ -82,23 +85,20 @@ class CustomWizard::Notice notices = [] if !skip_subscription - subscription_messages = request(subscription_messages_url) + subscription_messages = request(:subscription_messages) + if subscription_messages.present? subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages]) notices.push(*subscription_notices) end end - if !skip_plugin && (Discourse.git_branch === 'tests-passed' || (Rails.env.test? || Rails.env.development?)) - plugin_status = request(plugin_status_url) + if !skip_plugin && request_plugin_status? + plugin_status = request(:plugin_status) if plugin_status.present? && plugin_status[:status].present? && plugin_status[:status].is_a?(Hash) plugin_notice = convert_plugin_status_to_notice(plugin_status[:status]) notices.push(plugin_notice) if plugin_notice - - expire_connection_errors(connection_types[:plugin_status]) - else - create_connection_error(connection_types[:plugin_status]) end end @@ -107,14 +107,6 @@ class CustomWizard::Notice notice.retrieved_at = Time.now notice.save end - - if reached_connection_error_limit(connection_types[:plugin_status]) - new( - message: I18n.t("wizard.notice.plugin_status_connection_error_limit"), - type: types[:warning], - created_at: Time.now - ) - end end def self.convert_subscription_messages_to_notices(messages) @@ -133,19 +125,46 @@ class CustomWizard::Notice if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status]) notice = { - message: PrettyText.cook(I18n.t('wizard.notice.compatibility_issue', server: plugin_status_domain)), - type: types[:warning], + message: PrettyText.cook(I18n.t('wizard.notice.compatibility_issue', domain: plugin_status_domain)), + type: types[:plugin_status_warning], created_at: plugin_status[:status_changed_at] } else - list(types[:warning]).each(&:expire) + expire_notices(types[:plugin_status_warning]) end notice end + def self.notify_connection_errors(connection_type_key) + domain = self.send("#{connection_type_key.to_s}_domain") + message = PrettyText.cook(I18n.t("wizard.notice.#{connection_type_key.to_s}.connection_error_limit", domain: domain)) + notices = list(type: types[:connection_error], message: message) + + if notices.any? + notice = notices.first + notice.updated_at = Time.now + notice.save + else + notice = new( + message: message, + type: types["#{connection_type_key}_connection_error".to_sym], + created_at: Time.now + ) + notice.save + end + end + + def self.expire_notices(type) + list(type: type).each(&:expire) + end + + def self.request_plugin_status? + CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development? + end + def self.subscription_messages_domain - "localhost:3000" + (Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGES_DOMAIN end def self.subscription_messages_url @@ -153,25 +172,34 @@ class CustomWizard::Notice end def self.plugin_status_domain - "localhost:4200" + return LOCALHOST_DOMAIN if (Rails.env.test? || Rails.env.development?) + PLUGIN_STATUS_DOMAINS[Discourse.git_branch] end def self.plugin_status_url "http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard" end - - def self.request(url) + + def self.request(type) + url = self.send("#{type.to_s}_url") response = Excon.get(url) + connection_error = CustomWizard::Notice::ConnectionError.new(type) if response.status == 200 + connection_error.expire! + expire_notices(types["#{type}_connection_error".to_sym]) + begin data = JSON.parse(response.body).deep_symbolize_keys rescue JSON::ParserError return nil end - + data else + connection_error.create! + notify_connection_errors(type) if connection_error.reached_limit? + nil end end @@ -180,10 +208,6 @@ class CustomWizard::Notice "#{CustomWizard::PLUGIN_NAME}_notice" end - def self.namespace_connection - "#{CustomWizard::PLUGIN_NAME}_notice_connection" - end - def self.find(id) raw = PluginStore.get(namespace, id) new(raw.symbolize_keys) if raw.present? @@ -193,42 +217,16 @@ class CustomWizard::Notice PluginStore.set(namespace, id, raw_notice) end - def self.plugin_status_connection_error_limit - 5 - end - - def self.list_connection_query(type) - query = PluginStoreRow.where(plugin_name: namespace_connection) - query.where("(value::json->>'type')::integer = ?", type) - end - - def self.expire_connection_errors(type) - list_connection_query(type).update_all("value = jsonb_set(value::jsonb, '{ expired_at }', (to_char(current_timestamp, 'HH12:MI:SS'))::jsonb)") - end - - def self.create_connection_error(type) - id = SecureRandom.hex(16) - attrs = { - message: I18n.t("wizard.notice.connection_error", domain: self.send("#{type}_domain")), - type: type, - created_at: Time.now - } - PluginStore.set(namespace_connection, id, attrs) - end - - def self.reached_connection_error_limit(type) - list_connection_query(type).size >= self.send("#{connection_types.key(type)}_connection_error_limit") - end - - def self.list_query(type = nil) + def self.list_query(type: nil, message: nil, include_recently_expired: false) query = PluginStoreRow.where(plugin_name: namespace) - query = query.where("(value::json->>'expired_at') IS NULL OR (value::json->>'expired_at')::date > now()::date - 1") + query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}") query = query.where("(value::json->>'type')::integer = ?", type) if type + query = query.where("(value::json->>'message') = ?", message) if message query.order("value::json->>'created_at' DESC") end - def self.list(type = nil) - list_query(type) + def self.list(type: nil, message: nil, include_recently_expired: false) + list_query(type: type, message: message, include_recently_expired: include_recently_expired) .map { |r| self.new(JSON.parse(r.value).symbolize_keys) } end end diff --git a/lib/custom_wizard/notice/connection_error.rb b/lib/custom_wizard/notice/connection_error.rb new file mode 100644 index 00000000..84620c0d --- /dev/null +++ b/lib/custom_wizard/notice/connection_error.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +class CustomWizard::Notice::ConnectionError + + attr_reader :type_key + + def initialize(type_key) + @type_key = type_key + end + + def create! + id = "#{type_key.to_s}_error" + + if attrs = PluginStore.get(namespace, id) + attrs['updated_at'] = Time.now + attrs['count'] = attrs['count'].to_i + 1 + else + domain = CustomWizard::Notice.send("#{type_key.to_s}_domain") + attrs = { + message: I18n.t("wizard.notice.connection_error", domain: domain), + type: self.class.types[type_key], + created_at: Time.now, + count: 1 + } + end + + PluginStore.set(namespace, id, attrs) + + @errors = nil + end + + def expire! + if errors.exists? + errors.each do |error_row| + error = JSON.parse(error_row.value) + error['expired_at'] = Time.now + error_row.value = error + error_row.save + end + end + end + + def self.types + @types ||= Enum.new( + plugin_status: 0, + subscription_messages: 1 + ) + end + + def plugin_status_limit + 5 + end + + def subscription_messages_limit + 10 + end + + def limit + self.send("#{type_key.to_s}_limit") + end + + def reached_limit? + return false unless errors.exists? + current_error['count'].to_i >= limit + end + + def current_error + JSON.parse(errors.first.value) + end + + def namespace + "#{CustomWizard::PLUGIN_NAME}_notice_connection" + end + + def errors + @errors ||= begin + query = PluginStoreRow.where(plugin_name: namespace) + query = query.where("(value::json->>'type')::integer = ?", self.class.types[type_key]) + query.where("(value::json->>'expired_at') IS NULL") + end + end +end diff --git a/plugin.rb b/plugin.rb index c3063d29..97d5472b 100644 --- a/plugin.rb +++ b/plugin.rb @@ -40,7 +40,7 @@ if respond_to?(:register_svg_icon) register_svg_icon "comment-alt" register_svg_icon "far-life-ring" register_svg_icon "arrow-right" - register_svg_icon "shield-virus" + register_svg_icon "bolt" end class ::Sprockets::DirectiveProcessor @@ -98,6 +98,7 @@ after_initialize do ../lib/custom_wizard/template.rb ../lib/custom_wizard/wizard.rb ../lib/custom_wizard/notice.rb + ../lib/custom_wizard/notice/connection_error.rb ../lib/custom_wizard/subscription.rb ../lib/custom_wizard/subscription/subscription.rb ../lib/custom_wizard/subscription/authentication.rb @@ -242,10 +243,9 @@ after_initialize do end AdminDashboardData.add_problem_check do - warning_notices = CustomWizard::Notice.list(CustomWizard::Notice.types[:warning]) + warning_notices = CustomWizard::Notice.list(type: CustomWizard::Notice.types[:plugin_status_warning]) warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil end - Jobs.enqueue(:custom_wizard_update_notices) DiscourseEvent.trigger(:custom_wizard_ready) end diff --git a/serializers/custom_wizard/notice_serializer.rb b/serializers/custom_wizard/notice_serializer.rb index 310827f7..48b7b381 100644 --- a/serializers/custom_wizard/notice_serializer.rb +++ b/serializers/custom_wizard/notice_serializer.rb @@ -6,6 +6,7 @@ class CustomWizard::NoticeSerializer < ApplicationSerializer :type, :created_at, :expired_at, + :updated_at, :dismissed_at, :retrieved_at, :dismissable diff --git a/spec/components/custom_wizard/notice_spec.rb b/spec/components/custom_wizard/notice_spec.rb index 373d2e31..ef29ea21 100644 --- a/spec/components/custom_wizard/notice_spec.rb +++ b/spec/components/custom_wizard/notice_spec.rb @@ -33,13 +33,13 @@ describe CustomWizard::Notice do expect(notice.message).to eq(subscription_message[:message]) expect(notice.created_at.to_datetime).to be_within(1.second).of (subscription_message[:created_at].to_datetime) end - + it "expires notice if subscription message is expired" do subscription_message[:expired_at] = Time.now stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) described_class.update(skip_plugin: true) - notice = described_class.list.first + notice = described_class.list(include_recently_expired: true).first expect(notice.expired?).to eq(true) end end @@ -51,20 +51,20 @@ describe CustomWizard::Notice do described_class.update(skip_subscription: true) end - it "converts plugin statuses to warn into notices" do + it "converts warning into notice" do notice = described_class.list.first - expect(notice.type).to eq(described_class.types[:warning]) + expect(notice.type).to eq(described_class.types[:plugin_status_warning]) expect(notice.message).to eq(PrettyText.cook(I18n.t("wizard.notice.compatibility_issue", server: described_class.plugin_status_domain))) expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime) end - - it "expires unexpired warning notices if status is recommended or compatible" do + + it "expires warning notices if status is recommended or compatible" do plugin_status[:status] = 'compatible' plugin_status[:status_changed_at] = Time.now stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) described_class.update(skip_subscription: true) - notice = described_class.list(described_class.types[:warning]).first + notice = described_class.list(type: described_class.types[:plugin_status_warning], include_recently_expired: true).first expect(notice.expired?).to eq(true) end end @@ -75,6 +75,57 @@ describe CustomWizard::Notice do stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) described_class.update - expect(described_class.list.length).to eq(2) + expect(described_class.list(include_recently_expired: true).length).to eq(2) end -end \ No newline at end of file + + context "connection errors" do + before do + freeze_time + end + + it "creates an error if connection to notice server fails" do + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + described_class.update(skip_subscription: true) + + error = CustomWizard::Notice::ConnectionError.new(:plugin_status) + expect(error.errors.exists?).to eq(true) + end + + it "only creates one connection error per type at a time" do + stub_request(:get, described_class.subscription_messages_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + + 5.times { described_class.update } + + plugin_status_errors = CustomWizard::Notice::ConnectionError.new(:plugin_status) + subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_messages) + + expect(plugin_status_errors.errors.length).to eq(1) + expect(subscription_message_errors.errors.length).to eq(1) + end + + it "creates a connection error notice if connection errors reach limit" do + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + + error = CustomWizard::Notice::ConnectionError.new(:plugin_status) + error.limit.times { described_class.update(skip_subscription: true) } + notice = described_class.list(type: described_class.types[:plugin_status_connection_error]).first + + expect(error.current_error['count']).to eq(error.limit) + expect(notice.type).to eq(described_class.types[:plugin_status_connection_error]) + end + + it "expires a connection error notice if connection succeeds" do + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + error = CustomWizard::Notice::ConnectionError.new(:plugin_status) + error.limit.times { described_class.update(skip_subscription: true) } + + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + described_class.update(skip_subscription: true) + notice = described_class.list(type: described_class.types[:plugin_status_connection_error], include_recently_expired: true).first + + expect(notice.type).to eq(described_class.types[:plugin_status_connection_error]) + expect(notice.expired_at.present?).to eq(true) + end + end +end diff --git a/spec/fixtures/sprockets/require_tree_discourse_empty.js b/spec/fixtures/sprockets/require_tree_discourse_empty.js index df264ec5..953c5ec4 100644 --- a/spec/fixtures/sprockets/require_tree_discourse_empty.js +++ b/spec/fixtures/sprockets/require_tree_discourse_empty.js @@ -1 +1 @@ -//= require_tree_discourse \ No newline at end of file +//= require_tree_discourse \ No newline at end of file From b475e39ee9698083ddf933ae6e4b0c31029bbaf5 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Thu, 7 Oct 2021 21:19:19 +0800 Subject: [PATCH 054/556] Fix specs --- coverage/.last_run.json | 2 +- lib/custom_wizard/notice.rb | 6 +++--- serializers/custom_wizard/notice_serializer.rb | 4 ++++ spec/components/custom_wizard/notice_spec.rb | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/coverage/.last_run.json b/coverage/.last_run.json index 1c721888..a5de03be 100644 --- a/coverage/.last_run.json +++ b/coverage/.last_run.json @@ -1,5 +1,5 @@ { "result": { - "line": 92.3 + "line": 92.45 } } diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index 335ccbd4..8d41d634 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -125,7 +125,7 @@ class CustomWizard::Notice if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status]) notice = { - message: PrettyText.cook(I18n.t('wizard.notice.compatibility_issue', domain: plugin_status_domain)), + message: I18n.t('wizard.notice.compatibility_issue', domain: plugin_status_domain), type: types[:plugin_status_warning], created_at: plugin_status[:status_changed_at] } @@ -138,7 +138,7 @@ class CustomWizard::Notice def self.notify_connection_errors(connection_type_key) domain = self.send("#{connection_type_key.to_s}_domain") - message = PrettyText.cook(I18n.t("wizard.notice.#{connection_type_key.to_s}.connection_error_limit", domain: domain)) + message = I18n.t("wizard.notice.#{connection_type_key.to_s}.connection_error_limit", domain: domain) notices = list(type: types[:connection_error], message: message) if notices.any? @@ -221,7 +221,7 @@ class CustomWizard::Notice query = PluginStoreRow.where(plugin_name: namespace) query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}") query = query.where("(value::json->>'type')::integer = ?", type) if type - query = query.where("(value::json->>'message') = ?", message) if message + query = query.where("(value::json->>'message')::text = ?", message) if message query.order("value::json->>'created_at' DESC") end diff --git a/serializers/custom_wizard/notice_serializer.rb b/serializers/custom_wizard/notice_serializer.rb index 48b7b381..5564de1f 100644 --- a/serializers/custom_wizard/notice_serializer.rb +++ b/serializers/custom_wizard/notice_serializer.rb @@ -18,4 +18,8 @@ class CustomWizard::NoticeSerializer < ApplicationSerializer def type CustomWizard::Notice.types.key(object.type) end + + def messsage + PrettyText.cook(object.message) + end end diff --git a/spec/components/custom_wizard/notice_spec.rb b/spec/components/custom_wizard/notice_spec.rb index ef29ea21..b51305e1 100644 --- a/spec/components/custom_wizard/notice_spec.rb +++ b/spec/components/custom_wizard/notice_spec.rb @@ -54,7 +54,7 @@ describe CustomWizard::Notice do it "converts warning into notice" do notice = described_class.list.first expect(notice.type).to eq(described_class.types[:plugin_status_warning]) - expect(notice.message).to eq(PrettyText.cook(I18n.t("wizard.notice.compatibility_issue", server: described_class.plugin_status_domain))) + expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue", domain: described_class.plugin_status_domain)) expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime) end From 702ccc868c7d5b845937d5a97ff2405ac57d122e Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 12 Oct 2021 09:45:50 +0800 Subject: [PATCH 055/556] Update connection_error.rb --- lib/custom_wizard/notice/connection_error.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/custom_wizard/notice/connection_error.rb b/lib/custom_wizard/notice/connection_error.rb index 84620c0d..8b8b944a 100644 --- a/lib/custom_wizard/notice/connection_error.rb +++ b/lib/custom_wizard/notice/connection_error.rb @@ -25,7 +25,6 @@ class CustomWizard::Notice::ConnectionError end PluginStore.set(namespace, id, attrs) - @errors = nil end @@ -34,7 +33,7 @@ class CustomWizard::Notice::ConnectionError errors.each do |error_row| error = JSON.parse(error_row.value) error['expired_at'] = Time.now - error_row.value = error + error_row.value = error.to_json error_row.save end end From 14e7e9c0db9718b259e5446065a81e4b83377370 Mon Sep 17 00:00:00 2001 From: Robert Barrow Date: Tue, 12 Oct 2021 09:54:52 +0100 Subject: [PATCH 056/556] improve api section switch --- assets/javascripts/discourse/routes/admin-wizards.js.es6 | 1 + assets/javascripts/discourse/templates/admin-wizards.hbs | 2 +- controllers/custom_wizard/admin/admin.rb | 2 +- lib/custom_wizard/subscription.rb | 8 ++++++++ lib/custom_wizard/subscription/subscription.rb | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index da184d93..4e4e3c40 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -9,6 +9,7 @@ export default DiscourseRoute.extend({ setupController(controller, model) { controller.set('notices', A(model.notices)); + controller.set('api_section', model.api_section); }, afterModel(model, transition) { diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index b72f5a7b..aa3b2cf5 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -2,7 +2,7 @@ {{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}} {{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.nav_label"}} {{nav-item route="adminWizardsSubmissions" label="admin.wizard.submissions.nav_label"}} - {{#if api_subscription}} + {{#if api_section}} {{nav-item route="adminWizardsApi" label="admin.wizard.api.nav_label"}} {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index df11d25c..8a3b69ac 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -5,7 +5,7 @@ class CustomWizard::AdminController < ::Admin::AdminController def index render_json_dump( #TODO replace with appropriate static? - api_subscription: ["business"].includes?(CustomWizard::Subscription.type), + api_section: ["business"].include?(CustomWizard::Subscription.type), notices: ActiveModel::ArraySerializer.new( CustomWizard::Notice.list, each_serializer: CustomWizard::NoticeSerializer diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index 11e6d4d3..6c535602 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -27,6 +27,10 @@ class CustomWizard::Subscription "stripe" end + def type + @subscription.type + end + def client_name "custom-wizard" end @@ -119,6 +123,10 @@ class CustomWizard::Subscription self.new.subscribed? end + def self.type + self.new.type + end + def self.authorized? self.new.authorized? end diff --git a/lib/custom_wizard/subscription/subscription.rb b/lib/custom_wizard/subscription/subscription.rb index 129f993a..b2ddf744 100644 --- a/lib/custom_wizard/subscription/subscription.rb +++ b/lib/custom_wizard/subscription/subscription.rb @@ -13,7 +13,7 @@ class CustomWizard::Subscription::Subscription end def types - %w(community business) + %w(core advanced business) end def active? From 67cfeb6ed95663809d0768e1adab3bb481bbbfed Mon Sep 17 00:00:00 2001 From: Robert Barrow Date: Tue, 12 Oct 2021 12:51:38 +0100 Subject: [PATCH 057/556] Distinguish actions which require additional subscription --- .../components/wizard-custom-action.js.es6 | 23 ++++++++-------- .../discourse/lib/wizard-schema.js.es6 | 27 +++++++++++++------ .../routes/admin-wizards-wizard-show.js.es6 | 1 + .../templates/admin-wizards-wizard-show.hbs | 3 ++- controllers/custom_wizard/admin/wizard.rb | 3 ++- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index ffe5b514..39c84fa2 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,5 +1,7 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; -import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; +import wizardSchema, { + actionsAvailableWithAdditionalSubscription, +} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; import { computed } from "@ember/object"; @@ -94,17 +96,16 @@ export default Component.extend(UndoChanges, { return apis.find((a) => a.name === api).endpoints; }, - @discourseComputed("subscribed") - actionTypes(subscribed) { + @discourseComputed("subscribed", "subscription") + actionTypes(subscribed, subscription) { + let unsubscribedActions = + actionsAvailableWithAdditionalSubscription(subscription); return Object.keys(wizardSchema.action.types).reduce((result, type) => { - let subscription = wizardSchema.action.subscriptionTypes.includes(type); - if (subscribed || !subscription) { - result.push({ - id: type, - name: I18n.t(`admin.wizard.action.${type}.label`), - subscription, - }); - } + result.push({ + id: type, + name: I18n.t(`admin.wizard.action.${type}.label`), + subscribed: unsubscribedActions.includes(type), + }); return result; }, []); }, diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 5d876ed0..f36110da 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -200,14 +200,10 @@ const action = { "create_group", "send_to_api", ], - required: ["id", "type"], - proTypes: [ - "send_message", - "add_to_group", - "create_category", - "create_group", - "send_to_api", - ], + actionTypesWithSubscription: { + advanced: ["send_message", "add_to_group", "watch_categories"], + business: ["create_category", "create_group", "send_to_api"], + }, dependent: {}, objectArrays: {}, }; @@ -219,6 +215,21 @@ const wizardSchema = { action, }; +export function actionsAvailableWithAdditionalSubscription( + currentSubscription +) { + switch (currentSubscription) { + case "business": + return []; + case "advanced": + return action.actionTypesWithSubscription["business"]; + case "community": + return action.actionTypesWithSubscription["advanced"].concat( + action.actionTypesWithSubscription["business"] + ); + } +} + export function buildFieldTypes(types) { wizardSchema.field.types = types; } diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index 046fc6d4..d6263471 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -40,6 +40,7 @@ export default DiscourseRoute.extend({ currentAction: wizard.actions[0], creating: model.create, subscribed: parentModel.subscribed, + subscription: parentModel.subscription, }; controller.setProperties(props); diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index 3966a50c..abf06ba9 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -178,7 +178,8 @@ apis=apis removeAction="removeAction" wizardFields=wizardFields - subscribed=subscribed}} + subscribed=subscribed + subscription=subscription}} {{/each}}
diff --git a/controllers/custom_wizard/admin/wizard.rb b/controllers/custom_wizard/admin/wizard.rb index fdf338bf..e6ea684a 100644 --- a/controllers/custom_wizard/admin/wizard.rb +++ b/controllers/custom_wizard/admin/wizard.rb @@ -11,7 +11,8 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController field_types: CustomWizard::Field.types, realtime_validations: CustomWizard::RealtimeValidation.types, custom_fields: custom_field_list, - subscribed: CustomWizard::Subscription.subscribed? + subscribed: CustomWizard::Subscription.subscribed?, + subscription: CustomWizard::Subscription.type ) end From d4e489456e61e39359c79d77834e094b371b2b70 Mon Sep 17 00:00:00 2001 From: Robert Barrow Date: Tue, 12 Oct 2021 13:01:39 +0100 Subject: [PATCH 058/556] rename functional levels --- assets/javascripts/discourse/lib/wizard-schema.js.es6 | 4 ++-- lib/custom_wizard/subscription/subscription.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index f36110da..34df21d2 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -219,11 +219,11 @@ export function actionsAvailableWithAdditionalSubscription( currentSubscription ) { switch (currentSubscription) { - case "business": + case "complete": return []; case "advanced": return action.actionTypesWithSubscription["business"]; - case "community": + case "core": return action.actionTypesWithSubscription["advanced"].concat( action.actionTypesWithSubscription["business"] ); diff --git a/lib/custom_wizard/subscription/subscription.rb b/lib/custom_wizard/subscription/subscription.rb index b2ddf744..84d21c9d 100644 --- a/lib/custom_wizard/subscription/subscription.rb +++ b/lib/custom_wizard/subscription/subscription.rb @@ -13,7 +13,7 @@ class CustomWizard::Subscription::Subscription end def types - %w(core advanced business) + %w(core advanced complete) end def active? From 3e2907abc22a30a9181e39644011c3edae1b01cc Mon Sep 17 00:00:00 2001 From: Robert Barrow Date: Tue, 12 Oct 2021 14:37:36 +0100 Subject: [PATCH 059/556] show api for complete sub --- controllers/custom_wizard/admin/admin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 8a3b69ac..9f48e632 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -5,7 +5,7 @@ class CustomWizard::AdminController < ::Admin::AdminController def index render_json_dump( #TODO replace with appropriate static? - api_section: ["business"].include?(CustomWizard::Subscription.type), + api_section: ["complete"].include?(CustomWizard::Subscription.type), notices: ActiveModel::ArraySerializer.new( CustomWizard::Notice.list, each_serializer: CustomWizard::NoticeSerializer From e5fbc408f52be36cad318c1a595cde04f17657c2 Mon Sep 17 00:00:00 2001 From: Robert Barrow Date: Tue, 12 Oct 2021 14:49:09 +0100 Subject: [PATCH 060/556] Add failing API call test --- spec/components/custom_wizard/action_spec.rb | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 4bd626ad..8c8e2d31 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -269,7 +269,7 @@ describe CustomWizard::Action do expect(group.users.first.username).to eq('angus') end - it '#send_to_api' do + it '#send_to_api successful' do stub_request(:put, "https://myexternalapi.com/update"). with( body: "some_body", @@ -291,6 +291,29 @@ describe CustomWizard::Action do expect(result).to eq('success') expect(log_entry.status).to eq('SUCCESS') end + + it '#send_to_api failure' do + stub_request(:put, "https://myexternalapi.com/update"). + with( + body: "some_body", + headers: { + 'Host'=>'myexternalapi.com' + }). + to_return(status: 500, body: "failure", headers: {}) + + new_api = CustomWizard::Api.new("my_api") + CustomWizard::Api.set("my_api", title: "Mocked external api") + CustomWizard::Api::Authorization.set("my_api", api_test_no_authorization) + CustomWizard::Api::Endpoint.new("my_api") + CustomWizard::Api::Endpoint.set("my_api", api_test_endpoint) + endpoint_id = CustomWizard::Api::Endpoint.list("my_api").first.id + + result = CustomWizard::Api::Endpoint.request("my_api", endpoint_id, "some_body") + log_entry = CustomWizard::Api::LogEntry.list("my_api").first + + expect(result).to eq('failure') + expect(log_entry.status).to eq('FAILURE') + end end end From 450f7bfc2520829814a9004ffd033e2ecaa6fc9f Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 13 Oct 2021 19:32:49 +0800 Subject: [PATCH 061/556] Update notice.rb --- lib/custom_wizard/notice.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index 8d41d634..fb10b724 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -4,10 +4,10 @@ class CustomWizard::Notice include ActiveModel::Serialization PLUGIN_STATUS_DOMAINS = { - "tests-passed" => "try.thepavilion.io", - "stable" => "stable.try.thepavilion.io" + "tests-passed" => "plugins.thepavilion.io", + "stable" => "stable.plugins.thepavilion.io" } - SUBSCRIPTION_MESSAGES_DOMAIN = "thepavilion.io" + SUBSCRIPTION_MESSAGES_DOMAIN = "test.thepavilion.io" LOCALHOST_DOMAIN = "localhost:3000" PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing) CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable) From a42e23d352434059acf5c396c0435f03d6c72527 Mon Sep 17 00:00:00 2001 From: merefield Date: Thu, 14 Oct 2021 13:41:24 +0100 Subject: [PATCH 062/556] Show but differentiate unsubbed action options --- .../components/wizard-custom-action.js.es6 | 8 ++++- .../wizard-subscription-selector-row.js.es6 | 19 +++++++++++- .../discourse/lib/wizard-schema.js.es6 | 30 ++++++++++++++----- .../wizard-subscription-selector-row.hbs | 8 ++++- assets/stylesheets/admin/admin.scss | 4 +++ config/locales/client.en.yml | 1 + controllers/custom_wizard/admin/admin.rb | 2 +- .../subscription/subscription.rb | 2 +- 8 files changed, 61 insertions(+), 13 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 39c84fa2..915e83aa 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,6 +1,7 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; import wizardSchema, { actionsAvailableWithAdditionalSubscription, + actionsAvailableWithCurrentSubscription } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; @@ -100,11 +101,16 @@ export default Component.extend(UndoChanges, { actionTypes(subscribed, subscription) { let unsubscribedActions = actionsAvailableWithAdditionalSubscription(subscription); + let subscribedActions = actionsAvailableWithCurrentSubscription(subscription); return Object.keys(wizardSchema.action.types).reduce((result, type) => { + let subscriptionLabel = (subscribedActions.includes(type) || unsubscribedActions.includes(type)); + let disabled = unsubscribedActions.includes(type); + console.log(subscriptionLabel, disabled); result.push({ id: type, name: I18n.t(`admin.wizard.action.${type}.label`), - subscribed: unsubscribedActions.includes(type), + subscription: subscriptionLabel, + disabled: disabled, }); return result; }, []); diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 index 23034ac1..11dd314a 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 @@ -1,3 +1,20 @@ import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row"; +import { default as discourseComputed } from "discourse-common/utils/decorators"; -export default SelectKitRowComponent.extend(); +export default SelectKitRowComponent.extend({ + classNameBindings: ["isDisabled:disabled"], + + @discourseComputed("item") + isDisabled() { + return this.item.disabled; + }, + + click(event) { + event.preventDefault(); + event.stopPropagation(); + if (!this.item.disabled) { + this.selectKit.select(this.rowValue, this.item); + } + return false; + } +}); diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 34df21d2..3559c98d 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -201,8 +201,8 @@ const action = { "send_to_api", ], actionTypesWithSubscription: { - advanced: ["send_message", "add_to_group", "watch_categories"], - business: ["create_category", "create_group", "send_to_api"], + basic: ["send_message", "add_to_group", "watch_categories"], + advanced: ["create_category", "create_group", "send_to_api"], }, dependent: {}, objectArrays: {}, @@ -219,17 +219,31 @@ export function actionsAvailableWithAdditionalSubscription( currentSubscription ) { switch (currentSubscription) { - case "complete": - return []; case "advanced": - return action.actionTypesWithSubscription["business"]; - case "core": - return action.actionTypesWithSubscription["advanced"].concat( - action.actionTypesWithSubscription["business"] + return []; + case "basic": + return action.actionTypesWithSubscription["advanced"]; + case "none", "": + return action.actionTypesWithSubscription["basic"].concat( + action.actionTypesWithSubscription["advanced"] ); } } +export function actionsAvailableWithCurrentSubscription( + currentSubscription +) { + switch (currentSubscription) { + case "advanced": + return action.actionTypesWithSubscription["advanced"].concat( + action.actionTypesWithSubscription["basic"]); + case "basic": + return action.actionTypesWithSubscription["basic"]; + case "none", "": + return []; + } +} + export function buildFieldTypes(types) { wizardSchema.field.types = types; } diff --git a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs index e2650408..92cb2750 100644 --- a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs @@ -10,6 +10,12 @@
{{html-safe label}} {{#if item.subscription}} - {{i18n "admin.wizard.subscription.label"}} + + {{#if item.disabled}} + {{i18n "admin.wizard.subscription.additional_label"}} + {{else}} + {{i18n "admin.wizard.subscription.label"}} + {{/if}} + {{/if}}
diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index 862f6761..8a8c7239 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -427,6 +427,10 @@ margin-bottom: 0; } + .wizard-custom-action .select-kit-row.disabled { + background: var(--primary-low); + } + .select-box-kit-header { height: initial; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 61e8274c..ced433ce 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -467,6 +467,7 @@ en: subscription: nav_label: Subscription label: Subscription + additional_label: "Add subscription" title: Custom Wizard Subscription authorize: Authorize authorized: Authorized diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 9f48e632..2bd5c86e 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -5,7 +5,7 @@ class CustomWizard::AdminController < ::Admin::AdminController def index render_json_dump( #TODO replace with appropriate static? - api_section: ["complete"].include?(CustomWizard::Subscription.type), + api_section: ["advanced"].include?(CustomWizard::Subscription.type), notices: ActiveModel::ArraySerializer.new( CustomWizard::Notice.list, each_serializer: CustomWizard::NoticeSerializer diff --git a/lib/custom_wizard/subscription/subscription.rb b/lib/custom_wizard/subscription/subscription.rb index 84d21c9d..4c804513 100644 --- a/lib/custom_wizard/subscription/subscription.rb +++ b/lib/custom_wizard/subscription/subscription.rb @@ -13,7 +13,7 @@ class CustomWizard::Subscription::Subscription end def types - %w(core advanced complete) + %w(none basic advanced) end def active? From deb4460d2ccfb9fddb225eaed331b8b72d1eca62 Mon Sep 17 00:00:00 2001 From: merefield Date: Thu, 14 Oct 2021 13:53:58 +0100 Subject: [PATCH 063/556] modify dropdown sub text slightly --- 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 ced433ce..0b318922 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -466,7 +466,7 @@ en: subscription: nav_label: Subscription - label: Subscription + label: In subscription additional_label: "Add subscription" title: Custom Wizard Subscription authorize: Authorize From 231051d8ea00263ba364a4c8d9ee7775e60b050f Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 19 Oct 2021 16:08:03 +0800 Subject: [PATCH 064/556] Update status domains --- lib/custom_wizard/notice.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index fb10b724..3ba710c5 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -4,8 +4,8 @@ class CustomWizard::Notice include ActiveModel::Serialization PLUGIN_STATUS_DOMAINS = { - "tests-passed" => "plugins.thepavilion.io", - "stable" => "stable.plugins.thepavilion.io" + "tests-passed" => "plugins.discourse.pavilion.tech", + "stable" => "stable.plugins.discourse.pavilion.tech" } SUBSCRIPTION_MESSAGES_DOMAIN = "test.thepavilion.io" LOCALHOST_DOMAIN = "localhost:3000" From 487ad3c46d5f9e083f3f5d69916195d543617b2b Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 19 Oct 2021 13:09:11 +0100 Subject: [PATCH 065/556] Fix test --- spec/components/custom_wizard/action_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 8c8e2d31..230a1760 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -311,7 +311,7 @@ describe CustomWizard::Action do result = CustomWizard::Api::Endpoint.request("my_api", endpoint_id, "some_body") log_entry = CustomWizard::Api::LogEntry.list("my_api").first - expect(result).to eq('failure') + expect(result).to eq({:error=>"API request failed"}) expect(log_entry.status).to eq('FAILURE') end end From b616adaf7147227f8e7f215d94150c7c93121c4e Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 19 Oct 2021 13:13:32 +0100 Subject: [PATCH 066/556] fix linting --- .../discourse/components/wizard-custom-action.js.es6 | 1 - assets/javascripts/discourse/lib/wizard-schema.js.es6 | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 915e83aa..f9d90a8c 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -105,7 +105,6 @@ export default Component.extend(UndoChanges, { return Object.keys(wizardSchema.action.types).reduce((result, type) => { let subscriptionLabel = (subscribedActions.includes(type) || unsubscribedActions.includes(type)); let disabled = unsubscribedActions.includes(type); - console.log(subscriptionLabel, disabled); result.push({ id: type, name: I18n.t(`admin.wizard.action.${type}.label`), diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 3559c98d..14970334 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -223,7 +223,7 @@ export function actionsAvailableWithAdditionalSubscription( return []; case "basic": return action.actionTypesWithSubscription["advanced"]; - case "none", "": + case "none": return action.actionTypesWithSubscription["basic"].concat( action.actionTypesWithSubscription["advanced"] ); @@ -239,7 +239,7 @@ export function actionsAvailableWithCurrentSubscription( action.actionTypesWithSubscription["basic"]); case "basic": return action.actionTypesWithSubscription["basic"]; - case "none", "": + case "none": return []; } } From b4f463778db7b127c384839e0affe383dcee3c68 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 19 Oct 2021 13:49:06 +0100 Subject: [PATCH 067/556] fix linting issues --- .../components/subscription-container.js.es6 | 20 ++++++----- .../components/wizard-custom-action.js.es6 | 14 +++++--- .../discourse/components/wizard-notice.js.es6 | 36 +++++++++++-------- .../wizard-subscription-selector.js.es6 | 3 +- .../wizard-subscription-selector-row.js.es6 | 2 +- .../components/wizard-subscription.js.es6 | 4 ++- .../custom-wizard-important-notice.js.es6 | 10 +++--- .../controllers/admin-wizards.js.es6 | 10 +++--- .../initializers/custom-wizard-edits.js.es6 | 26 +++++++++----- .../discourse/lib/wizard-schema.js.es6 | 7 ++-- .../models/custom-wizard-notice.js.es6 | 20 ++++++----- .../discourse/routes/admin-wizards.js.es6 | 8 ++--- assets/stylesheets/admin/admin.scss | 10 +++--- 13 files changed, 99 insertions(+), 71 deletions(-) diff --git a/assets/javascripts/discourse/components/subscription-container.js.es6 b/assets/javascripts/discourse/components/subscription-container.js.es6 index 620c6d88..a12b8949 100644 --- a/assets/javascripts/discourse/components/subscription-container.js.es6 +++ b/assets/javascripts/discourse/components/subscription-container.js.es6 @@ -4,18 +4,22 @@ import discourseComputed from "discourse-common/utils/decorators"; export default Component.extend({ classNameBindings: [":subscription-container", "subscribed"], - @discourseComputed('subscribed') + @discourseComputed("subscribed") subscribedIcon(subscribed) { - return subscribed ? 'check' : 'dash'; + return subscribed ? "check" : "dash"; }, - @discourseComputed('subscribed') + @discourseComputed("subscribed") subscribedLabel(subscribed) { - return `admin.wizard.subscription_container.${subscribed ? 'subscribed' : 'not_subscribed'}.label`; + return `admin.wizard.subscription_container.${ + subscribed ? "subscribed" : "not_subscribed" + }.label`; }, - @discourseComputed('subscribed') + @discourseComputed("subscribed") subscribedTitle(subscribed) { - return `admin.wizard.subscription_container.${subscribed ? 'subscribed' : 'not_subscribed'}.title`; - } -}); \ No newline at end of file + return `admin.wizard.subscription_container.${ + subscribed ? "subscribed" : "not_subscribed" + }.title`; + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index f9d90a8c..4ee28d17 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,7 +1,7 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; import wizardSchema, { actionsAvailableWithAdditionalSubscription, - actionsAvailableWithCurrentSubscription + actionsAvailableWithCurrentSubscription, } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; @@ -99,11 +99,15 @@ export default Component.extend(UndoChanges, { @discourseComputed("subscribed", "subscription") actionTypes(subscribed, subscription) { - let unsubscribedActions = - actionsAvailableWithAdditionalSubscription(subscription); - let subscribedActions = actionsAvailableWithCurrentSubscription(subscription); + let unsubscribedActions = actionsAvailableWithAdditionalSubscription( + subscription + ); + let subscribedActions = actionsAvailableWithCurrentSubscription( + subscription + ); return Object.keys(wizardSchema.action.types).reduce((result, type) => { - let subscriptionLabel = (subscribedActions.includes(type) || unsubscribedActions.includes(type)); + let subscriptionLabel = + subscribedActions.includes(type) || unsubscribedActions.includes(type); let disabled = unsubscribedActions.includes(type); result.push({ id: type, diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 index 15da3f35..fcd77606 100644 --- a/assets/javascripts/discourse/components/wizard-notice.js.es6 +++ b/assets/javascripts/discourse/components/wizard-notice.js.es6 @@ -4,33 +4,39 @@ import { not, notEmpty } from "@ember/object/computed"; import I18n from "I18n"; export default Component.extend({ - classNameBindings: [':wizard-notice', 'notice.type', 'dismissed', 'expired', 'resolved'], + classNameBindings: [ + ":wizard-notice", + "notice.type", + "dismissed", + "expired", + "resolved", + ], showFull: false, - resolved: notEmpty('notice.expired_at'), - dismissed: notEmpty('notice.dismissed_at'), - canDismiss: not('dismissed'), + resolved: notEmpty("notice.expired_at"), + dismissed: notEmpty("notice.dismissed_at"), + canDismiss: not("dismissed"), - @discourseComputed('notice.type') + @discourseComputed("notice.type") title(type) { return I18n.t(`admin.wizard.notice.title.${type}`); }, - @discourseComputed('notice.type') + @discourseComputed("notice.type") icon(type) { return { - plugin_status_warning: 'exclamation-circle', - plugin_status_connection_error: 'bolt', - subscription_messages_connection_error: 'bolt', - info: 'info-circle' + plugin_status_warning: "exclamation-circle", + plugin_status_connection_error: "bolt", + subscription_messages_connection_error: "bolt", + info: "info-circle", }[type]; }, actions: { dismiss() { - this.set('dismissing', true); + this.set("dismissing", true); this.notice.dismiss().then(() => { - this.set('dismissing', false); + this.set("dismissing", false); }); - } - } -}); \ No newline at end of file + }, + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 index ec94bce5..ea1aa5d9 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 @@ -7,7 +7,8 @@ export default SingleSelectComponent.extend({ autoFilterable: false, filterable: false, showFullTitle: true, - headerComponent: "wizard-subscription-selector/wizard-subscription-selector-header", + headerComponent: + "wizard-subscription-selector/wizard-subscription-selector-header", caretUpIcon: "caret-up", caretDownIcon: "caret-down", }, diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 index 11dd314a..1d43047a 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector/wizard-subscription-selector-row.js.es6 @@ -16,5 +16,5 @@ export default SelectKitRowComponent.extend({ this.selectKit.select(this.rowValue, this.item); } return false; - } + }, }); diff --git a/assets/javascripts/discourse/components/wizard-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-subscription.js.es6 index 0d839a5f..29e47bc2 100644 --- a/assets/javascripts/discourse/components/wizard-subscription.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription.js.es6 @@ -25,7 +25,9 @@ export default Component.extend({ @discourseComputed("stateClass") stateLabel(stateClass) { - return I18n.t(`admin.wizard.subscription.subscription.status.${stateClass}`); + return I18n.t( + `admin.wizard.subscription.subscription.status.${stateClass}` + ); }, actions: { diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 index 43a2152b..5962f255 100644 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 @@ -6,11 +6,11 @@ export default { }, setupComponent() { - const controller = getOwner(this).lookup('controller:admin-dashboard'); - const importantNotice = controller.get('customWizardImportantNotice'); + const controller = getOwner(this).lookup("controller:admin-dashboard"); + const importantNotice = controller.get("customWizardImportantNotice"); if (importantNotice) { - this.set('importantNotice', importantNotice); + this.set("importantNotice", importantNotice); } - } -}; \ No newline at end of file + }, +}; diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index a94222c1..d128d851 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -8,13 +8,13 @@ export default Controller.extend({ ajax(`/admin/wizards/notice/${this.id}`, { type: "DELETE", }) - .then(result => { + .then((result) => { if (result.success) { const notices = this.notices; - notices.removeObject(notices.findBy('id', noticeId)); + notices.removeObject(notices.findBy("id", noticeId)); } }) .catch(popupAjaxError); - } - } -}); \ No newline at end of file + }, + }, +}); diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index f53dc2cd..21a9d745 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -21,27 +21,37 @@ export default { }; withPluginApi("0.8.36", (api) => { - api.modifyClass('route:admin-dashboard', { + api.modifyClass("route:admin-dashboard", { afterModel() { - return CustomWizardNotice.list().then(result => { + return CustomWizardNotice.list().then((result) => { if (result && result.length) { - this.set('notices', A(result.map(n => CustomWizardNotice.create(n)))); + this.set( + "notices", + A(result.map((n) => CustomWizardNotice.create(n))) + ); } - }); + }); }, setupController(controller) { if (this.notices) { - let pluginStatusConnectionError = this.notices.filter(n => n.type === 'plugin_status_connection_error')[0]; - let pluginStatusWarning = this.notices.filter(n => n.type === 'plugin_status_warning')[0]; + let pluginStatusConnectionError = this.notices.filter( + (n) => n.type === "plugin_status_connection_error" + )[0]; + let pluginStatusWarning = this.notices.filter( + (n) => n.type === "plugin_status_warning" + )[0]; if (pluginStatusConnectionError || pluginStatusWarning) { - controller.set('customWizardImportantNotice', pluginStatusConnectionError || pluginStatusWarning); + controller.set( + "customWizardImportantNotice", + pluginStatusConnectionError || pluginStatusWarning + ); } } this._super(...arguments); - } + }, }); }); }, diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 14970334..76943e2b 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -230,13 +230,12 @@ export function actionsAvailableWithAdditionalSubscription( } } -export function actionsAvailableWithCurrentSubscription( - currentSubscription -) { +export function actionsAvailableWithCurrentSubscription(currentSubscription) { switch (currentSubscription) { case "advanced": return action.actionTypesWithSubscription["advanced"].concat( - action.actionTypesWithSubscription["basic"]); + action.actionTypesWithSubscription["basic"] + ); case "basic": return action.actionTypesWithSubscription["basic"]; case "none": diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 index a6b47c40..1eb21e73 100644 --- a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 @@ -6,18 +6,20 @@ const CustomWizardNotice = EmberObject.extend(); CustomWizardNotice.reopen({ dismiss() { - return ajax(`/admin/wizards/notice/${this.id}`, { type: 'PUT' }).then(result => { - if (result.success) { - this.set('dismissed_at', result.dismissed_at); - } - }).catch(popupAjaxError); - } + return ajax(`/admin/wizards/notice/${this.id}`, { type: "PUT" }) + .then((result) => { + if (result.success) { + this.set("dismissed_at", result.dismissed_at); + } + }) + .catch(popupAjaxError); + }, }); CustomWizardNotice.reopenClass({ list() { - return ajax('/admin/wizards/notice').catch(popupAjaxError); - } + return ajax("/admin/wizards/notice").catch(popupAjaxError); + }, }); -export default CustomWizardNotice; \ No newline at end of file +export default CustomWizardNotice; diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index 4e4e3c40..de67a8b9 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -4,17 +4,17 @@ import { A } from "@ember/array"; export default DiscourseRoute.extend({ model() { - return ajax('/admin/wizards'); + return ajax("/admin/wizards"); }, setupController(controller, model) { - controller.set('notices', A(model.notices)); - controller.set('api_section', model.api_section); + controller.set("notices", A(model.notices)); + controller.set("api_section", model.api_section); }, afterModel(model, transition) { if (transition.targetName === "adminWizards.index") { this.transitionTo("adminWizardsWizard"); } - } + }, }); diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index 8a8c7239..5973a1e7 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -928,7 +928,7 @@ } .d-icon { - margin-right: .4em; + margin-right: 0.4em; } .notice-header { @@ -939,9 +939,9 @@ border: 1px solid var(--primary); display: inline-flex; align-items: center; - padding: 0 .5em; + padding: 0 0.5em; margin-right: 1em; - font-size: .9em; + font-size: 0.9em; line-height: 25px; min-height: 25px; box-sizing: border-box; @@ -967,12 +967,12 @@ .notice-issued, .notice-resolved { - margin-right: .3em; + margin-right: 0.3em; } .notice-message { p { - margin: .5em 0; + margin: 0.5em 0; } p:last-of-type { From 30fe73570a437c268c9a794d902ed51c3831eefe Mon Sep 17 00:00:00 2001 From: merefield Date: Fri, 22 Oct 2021 15:38:38 +0100 Subject: [PATCH 068/556] remove triple curlies --- .../discourse/templates/components/wizard-notice.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/discourse/templates/components/wizard-notice.hbs b/assets/javascripts/discourse/templates/components/wizard-notice.hbs index 6a7d0afb..89bbf5d4 100644 --- a/assets/javascripts/discourse/templates/components/wizard-notice.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-notice.hbs @@ -33,7 +33,7 @@
- {{{notice.message}}} + {{html-safe notice.message}}
{{#if importantOnDashboard}} From 0b8fced879373e699f6d5d2ac8fcb42f270b49da Mon Sep 17 00:00:00 2001 From: merefield Date: Fri, 22 Oct 2021 19:04:37 +0100 Subject: [PATCH 069/556] rubocop changes --- spec/components/custom_wizard/action_spec.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 230a1760..94192aa7 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -14,8 +14,8 @@ describe CustomWizard::Action do let(:send_message) { get_wizard_fixture("actions/send_message") } let(:send_message_multi) { get_wizard_fixture("actions/send_message_multi") } let(:api_test_endpoint) { get_wizard_fixture("endpoints/test_endpoint") } - let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body")} - let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization")} + let(:api_test_endpoint_body) { get_wizard_fixture("endpoints/test_endpoint_body") } + let(:api_test_no_authorization) { get_wizard_fixture("api/no_authorization") } def update_template(template) CustomWizard::Template.save(template, skip_jobs: true) @@ -271,12 +271,12 @@ describe CustomWizard::Action do it '#send_to_api successful' do stub_request(:put, "https://myexternalapi.com/update"). - with( + with( body: "some_body", headers: { - 'Host'=>'myexternalapi.com' + 'Host' => 'myexternalapi.com' }). - to_return(status: 200, body: "success", headers: {}) + to_return(status: 200, body: "success", headers: {}) new_api = CustomWizard::Api.new("my_api") CustomWizard::Api.set("my_api", title: "Mocked external api") @@ -294,12 +294,12 @@ describe CustomWizard::Action do it '#send_to_api failure' do stub_request(:put, "https://myexternalapi.com/update"). - with( + with( body: "some_body", headers: { - 'Host'=>'myexternalapi.com' + 'Host' => 'myexternalapi.com' }). - to_return(status: 500, body: "failure", headers: {}) + to_return(status: 500, body: "failure", headers: {}) new_api = CustomWizard::Api.new("my_api") CustomWizard::Api.set("my_api", title: "Mocked external api") @@ -311,9 +311,8 @@ describe CustomWizard::Action do result = CustomWizard::Api::Endpoint.request("my_api", endpoint_id, "some_body") log_entry = CustomWizard::Api::LogEntry.list("my_api").first - expect(result).to eq({:error=>"API request failed"}) + expect(result).to eq({ error: "API request failed" }) expect(log_entry.status).to eq('FAILURE') end end end - From 50176b400d6c068674b57e01de5b4654804b0b9a Mon Sep 17 00:00:00 2001 From: merefield Date: Fri, 22 Oct 2021 19:07:45 +0100 Subject: [PATCH 070/556] more rubocop --- lib/custom_wizard/api/endpoint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_wizard/api/endpoint.rb b/lib/custom_wizard/api/endpoint.rb index b82dd39a..38000106 100644 --- a/lib/custom_wizard/api/endpoint.rb +++ b/lib/custom_wizard/api/endpoint.rb @@ -74,7 +74,7 @@ class CustomWizard::Api::Endpoint headers["Authorization"] = auth_string if auth_string headers["Content-Type"] = content_type if content_type - connection = Excon.new(URI.encode(endpoint.url), headers: headers) + connection = Excon.new(CGI.escape(endpoint.url), headers: headers) params = { method: endpoint.method } From 41e0f13b255441409988889549715a36fcb59bcf Mon Sep 17 00:00:00 2001 From: merefield Date: Fri, 22 Oct 2021 19:24:28 +0100 Subject: [PATCH 071/556] update subscription types --- controllers/custom_wizard/admin/admin.rb | 2 +- lib/custom_wizard/subscription/subscription.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 2bd5c86e..8a3b69ac 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -5,7 +5,7 @@ class CustomWizard::AdminController < ::Admin::AdminController def index render_json_dump( #TODO replace with appropriate static? - api_section: ["advanced"].include?(CustomWizard::Subscription.type), + api_section: ["business"].include?(CustomWizard::Subscription.type), notices: ActiveModel::ArraySerializer.new( CustomWizard::Notice.list, each_serializer: CustomWizard::NoticeSerializer diff --git a/lib/custom_wizard/subscription/subscription.rb b/lib/custom_wizard/subscription/subscription.rb index 4c804513..0b3bb84d 100644 --- a/lib/custom_wizard/subscription/subscription.rb +++ b/lib/custom_wizard/subscription/subscription.rb @@ -13,7 +13,7 @@ class CustomWizard::Subscription::Subscription end def types - %w(none basic advanced) + %w(none standard business) end def active? From fbab8d89a52adc600e3f2e55b01de21b15e4d82f Mon Sep 17 00:00:00 2001 From: merefield Date: Fri, 22 Oct 2021 19:56:00 +0100 Subject: [PATCH 072/556] Update action dropdown behaviour to show sub level --- .../components/wizard-custom-action.js.es6 | 13 ++----- .../discourse/lib/wizard-schema.js.es6 | 37 +++++++++---------- .../wizard-subscription-selector-row.hbs | 6 +-- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 4ee28d17..9024b401 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,7 +1,7 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; import wizardSchema, { - actionsAvailableWithAdditionalSubscription, - actionsAvailableWithCurrentSubscription, + actionsRequiringAdditionalSubscription, + actionSubscriptionLevel, } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; @@ -99,20 +99,15 @@ export default Component.extend(UndoChanges, { @discourseComputed("subscribed", "subscription") actionTypes(subscribed, subscription) { - let unsubscribedActions = actionsAvailableWithAdditionalSubscription( - subscription - ); - let subscribedActions = actionsAvailableWithCurrentSubscription( + let unsubscribedActions = actionsRequiringAdditionalSubscription( subscription ); return Object.keys(wizardSchema.action.types).reduce((result, type) => { - let subscriptionLabel = - subscribedActions.includes(type) || unsubscribedActions.includes(type); let disabled = unsubscribedActions.includes(type); result.push({ id: type, name: I18n.t(`admin.wizard.action.${type}.label`), - subscription: subscriptionLabel, + subscription: actionSubscriptionLevel(type), disabled: disabled, }); return result; diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 76943e2b..e55e8a44 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -201,8 +201,8 @@ const action = { "send_to_api", ], actionTypesWithSubscription: { - basic: ["send_message", "add_to_group", "watch_categories"], - advanced: ["create_category", "create_group", "send_to_api"], + standard: ["send_message", "add_to_group", "watch_categories"], + business: ["create_category", "create_group", "send_to_api"], }, dependent: {}, objectArrays: {}, @@ -215,31 +215,30 @@ const wizardSchema = { action, }; -export function actionsAvailableWithAdditionalSubscription( +export function actionsRequiringAdditionalSubscription( currentSubscription ) { switch (currentSubscription) { - case "advanced": + case "business": return []; - case "basic": - return action.actionTypesWithSubscription["advanced"]; - case "none": - return action.actionTypesWithSubscription["basic"].concat( - action.actionTypesWithSubscription["advanced"] + case "standard": + return action.actionTypesWithSubscription["business"]; + default: + return action.actionTypesWithSubscription["standard"].concat( + action.actionTypesWithSubscription["business"] ); } } -export function actionsAvailableWithCurrentSubscription(currentSubscription) { - switch (currentSubscription) { - case "advanced": - return action.actionTypesWithSubscription["advanced"].concat( - action.actionTypesWithSubscription["basic"] - ); - case "basic": - return action.actionTypesWithSubscription["basic"]; - case "none": - return []; +export function actionSubscriptionLevel(type) { + if (action.actionTypesWithSubscription["business"].includes(type)) { + return "business" + } else { + if (action.actionTypesWithSubscription["standard"].includes(type)) { + return "standard" + } else { + return "" + } } } diff --git a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs index 92cb2750..87f2a1e1 100644 --- a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs @@ -11,11 +11,7 @@ {{html-safe label}} {{#if item.subscription}} - {{#if item.disabled}} - {{i18n "admin.wizard.subscription.additional_label"}} - {{else}} - {{i18n "admin.wizard.subscription.label"}} - {{/if}} + {{item.subscription}} {{/if}}
From 5334d12f10555fbcb78fd33f9f8108f92f85afdb Mon Sep 17 00:00:00 2001 From: merefield Date: Fri, 22 Oct 2021 19:57:16 +0100 Subject: [PATCH 073/556] format code --- assets/javascripts/discourse/lib/wizard-schema.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index e55e8a44..250df371 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -232,7 +232,7 @@ export function actionsRequiringAdditionalSubscription( export function actionSubscriptionLevel(type) { if (action.actionTypesWithSubscription["business"].includes(type)) { - return "business" + return "business" } else { if (action.actionTypesWithSubscription["standard"].includes(type)) { return "standard" From a806e14c64e097987e6a175255b1cda229ccb23c Mon Sep 17 00:00:00 2001 From: merefield Date: Sat, 23 Oct 2021 13:06:12 +0100 Subject: [PATCH 074/556] Admin custom fields subscription differentiation --- .../components/custom-field-input.js.es6 | 83 +++++++++++-------- .../discourse/lib/wizard-schema.js.es6 | 66 +++++++++++++++ .../routes/admin-wizards-custom-fields.js.es6 | 2 + .../templates/admin-wizards-custom-fields.hbs | 3 +- .../components/custom-field-input.hbs | 4 +- assets/stylesheets/admin/admin.scss | 22 +++-- .../custom_wizard/admin/custom_fields.rb | 3 +- 7 files changed, 132 insertions(+), 51 deletions(-) diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index a673112b..bd6c78a1 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -4,27 +4,12 @@ import { alias, equal, or } from "@ember/object/computed"; import { computed } from "@ember/object"; import I18n from "I18n"; -const klasses = ["topic", "post", "group", "category"]; -const types = ["string", "boolean", "integer", "json"]; -const subscriptionTypes = { - klass: ["group", "category"], - type: ["json"], -}; - -const generateContent = function (array, type, subscribed = false) { - return array.reduce((result, key) => { - let subArr = subscriptionTypes[type]; - let subscription = subArr && subArr.includes(key); - if (!subscription || subscribed) { - result.push({ - id: key, - name: I18n.t(`admin.wizard.custom_field.${type}.${key}`), - subscription, - }); - } - return result; - }, []); -}; +import wizardSchema, { + customFieldsKlassesRequiringAdditionalSubscription, + customFieldsKlassSubscriptionLevel, + customFieldsTypesRequiringAdditionalSubscription, + customFieldsTypeSubscriptionLevel, +} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; export default Component.extend({ tagName: "tr", @@ -32,12 +17,6 @@ export default Component.extend({ postSerializers: ["post"], groupSerializers: ["basic_group"], categorySerializers: ["basic_category"], - klassContent: computed("subscribed", function () { - return generateContent(klasses, "klass", this.subscribed); - }), - typeContent: computed("subscribed", function () { - return generateContent(types, "type", this.subscribed); - }), showInputs: or("field.new", "field.edit"), classNames: ["custom-field-input"], loading: or("saving", "destroying"), @@ -49,15 +28,49 @@ export default Component.extend({ this.set("originalField", JSON.parse(JSON.stringify(this.field))); }, - @discourseComputed("field.klass") - serializerContent(klass) { - const serializers = this.get(`${klass}Serializers`); + // @discourseComputed("field.klass") + // serializerContent(klass) { + // const serializers = this.get(`${klass}Serializers`); - if (serializers) { - return generateContent(serializers, "serializers", this.subscribed); - } else { - return []; - } + // if (serializers) { + // return generateContent(serializers, "serializers", this.subscribed); + // } else { + // return []; + // } + // }, + + @discourseComputed("subscription") + customFieldTypes(subscription) { + let unsubscribedCustomFields = customFieldsTypesRequiringAdditionalSubscription( + subscription + ); + return wizardSchema.custom_field.types.reduce((result, type) => { + let disabled = unsubscribedCustomFields.includes(type); + result.push({ + id: type, + name: I18n.t(`admin.wizard.custom_field.type.${type}`), + subscription: customFieldsTypeSubscriptionLevel(type), + disabled: disabled, + }); + return result; + }, []); + }, + + @discourseComputed("subscription") + customFieldKlasses(subscription) { + let unsubscribedCustomFields = customFieldsKlassesRequiringAdditionalSubscription( + subscription + ); + return wizardSchema.custom_field.klasses.reduce((result, klass) => { + let disabled = unsubscribedCustomFields.includes(klass); + result.push({ + id: klass, + name: I18n.t(`admin.wizard.custom_field.klass.${klass}`), + subscription: customFieldsKlassSubscriptionLevel(klass), + disabled: disabled, + }); + return result; + }, []); }, @observes("field.klass") diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 250df371..a49dec14 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -208,10 +208,24 @@ const action = { objectArrays: {}, }; +const custom_field = { + klasses: ["topic", "post", "group", "category"], + types: ["string", "boolean", "integer", "json"], + customFieldKlassWithSubscription: { + standard: [], + business: ["group", "category"], + }, + customFieldTypeWithSubscription: { + standard: ["json"], + business: [], + }, +} + const wizardSchema = { wizard, step, field, + custom_field, action, }; @@ -242,6 +256,58 @@ export function actionSubscriptionLevel(type) { } } + + +export function customFieldsKlassesRequiringAdditionalSubscription( + currentSubscription +) { + switch (currentSubscription) { + case "business": + return []; + case "standard": + return custom_field.customFieldKlassWithSubscription["business"]; + default: + return custom_field.customFieldKlassWithSubscription["business"].concat(custom_field.customFieldKlassWithSubscription["standard"]); + } +} + +export function customFieldsKlassSubscriptionLevel(type) { + if (custom_field.customFieldKlassWithSubscription["business"].includes(type)) { + return "business" + } else { + if (custom_field.customFieldKlassWithSubscription["standard"].includes(type)) { + return "standard" + } else { + return "" + } + } +} + +export function customFieldsTypesRequiringAdditionalSubscription( + currentSubscription +) { + switch (currentSubscription) { + case "business": + return []; + case "standard": + return custom_field.customFieldTypeWithSubscription["business"]; + default: + return custom_field.customFieldTypeWithSubscription["business"].concat(custom_field.customFieldTypeWithSubscription["standard"]); + } +} + +export function customFieldsTypeSubscriptionLevel(type) { + if (custom_field.customFieldTypeWithSubscription["business"].includes(type)) { + return "business" + } else { + if (custom_field.customFieldTypeWithSubscription["standard"].includes(type)) { + return "standard" + } else { + return "" + } + } +} + export function buildFieldTypes(types) { wizardSchema.field.types = types; } diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index ca9c4c40..4992f92d 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -10,10 +10,12 @@ export default DiscourseRoute.extend({ setupController(controller, model) { const customFields = A(model.custom_fields || []); const subscribed = model.subscribed; + const subscription = model.subscription; controller.setProperties({ customFields, subscribed, + subscription, }); }, }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index 361d2d44..dceed458 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -33,7 +33,8 @@ field=field removeField=(action "removeField") saveField=(action "saveField") - subscribed=subscribed}} + subscribed=subscribed + subscription=subscription}} {{/each}} diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index ac7e8689..b4ddcc81 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -2,14 +2,14 @@ {{wizard-subscription-selector value=field.klass - content=klassContent + content=customFieldKlasses none="admin.wizard.custom_field.klass.select" onChange=(action (mut field.klass))}} {{wizard-subscription-selector value=field.type - content=typeContent + content=customFieldTypes none="admin.wizard.custom_field.type.select" onChange=(action (mut field.type))}} diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index 5973a1e7..94fe6839 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -427,10 +427,6 @@ margin-bottom: 0; } - .wizard-custom-action .select-kit-row.disabled { - background: var(--primary-low); - } - .select-box-kit-header { height: initial; } @@ -792,11 +788,6 @@ vertical-align: middle; } -.subscription-label { - color: var(--tertiary); - font-size: 0.75em; -} - .admin-wizards-subscription { .admin-wizard-controls { h3, @@ -857,14 +848,21 @@ } .wizard-subscription-selector.select-kit.single-select { - .select-kit-row .texts { - display: flex; - align-items: center; + .select-kit-row { + .texts { + display: flex; + align-items: center; + } + &.disabled { + background: var(--primary-low); + } } .subscription-label { margin-left: 0.75em; padding-top: 0.25em; + color: var(--tertiary); + font-size: 0.75em; } } diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/controllers/custom_wizard/admin/custom_fields.rb index d10f82ed..1cd20f5d 100644 --- a/controllers/custom_wizard/admin/custom_fields.rb +++ b/controllers/custom_wizard/admin/custom_fields.rb @@ -3,7 +3,8 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController def index render_json_dump( custom_fields: custom_field_list, - subscribed: CustomWizard::Subscription.subscribed? + subscribed: CustomWizard::Subscription.subscribed?, + subscription: CustomWizard::Subscription.type ) end From 7b129debacdf174e9b390e788ea8c43d74f72451 Mon Sep 17 00:00:00 2001 From: merefield Date: Mon, 25 Oct 2021 15:31:44 +0100 Subject: [PATCH 075/556] partial generalisation of subscription logic --- .../components/wizard-custom-action.js.es6 | 5 +- .../discourse/lib/wizard-schema.js.es6 | 64 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 9024b401..122df003 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,6 +1,7 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; import wizardSchema, { actionsRequiringAdditionalSubscription, + requiringAdditionalSubscription, actionSubscriptionLevel, } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { empty, equal, or } from "@ember/object/computed"; @@ -99,7 +100,9 @@ export default Component.extend(UndoChanges, { @discourseComputed("subscribed", "subscription") actionTypes(subscribed, subscription) { - let unsubscribedActions = actionsRequiringAdditionalSubscription( + let unsubscribedActions = requiringAdditionalSubscription (subscription, "actions"); + debugger; + let unsubscribedActionslong = actionsRequiringAdditionalSubscription( subscription ); return Object.keys(wizardSchema.action.types).reduce((result, type) => { diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index a49dec14..d8befe97 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -221,13 +221,75 @@ const custom_field = { }, } +const subscription_levels = { + standard: { + actions: ["send_message", "add_to_group", "watch_categories"], + custom_fields: { + klasses: [], + types: ["json"] + } + }, + + business: { + actions: ["create_category", "create_group", "send_to_api"], + custom_fields: { + klasses: ["group", "category"], + types: [] + } + } +} + const wizardSchema = { wizard, step, field, custom_field, action, -}; + subscription_levels +} + +export function requiringAdditionalSubscription( + currentSubscription, category +) { + switch (currentSubscription) { + case "business": + return []; + case "standard": + return subscription_levels["business"].[category]; + default: + return subscription_levels["standard"].[category].concat( + subscription_levels["business"].[category] + ); + } +} + + +export function SubscriptionLevel(category, type, subCategory) { + switch (category) { + case "actions": + if (subscription_levels.["business"].actions.includes(type)) { + return "business" + } else { + if (subscription_levels.["standard"].actions.includes(type)) { + return "standard" + } else { + return "" + } + } + case "custom_fields": + if (subscription_levels.["business"].custom_fields[subCategory].includes(type)) { + return "business" + } else { + if (subscription_levels.["standard"].custom_fields[subCategory].includes(type)) { + return "standard" + } else { + return "" + } + } + default: + return ""; + } +} export function actionsRequiringAdditionalSubscription( currentSubscription From 9350db5424e2043d2ad2fa13c8f27baf65766714 Mon Sep 17 00:00:00 2001 From: merefield Date: Mon, 25 Oct 2021 18:55:28 +0100 Subject: [PATCH 076/556] REFACTOR: abstract subscription logic to reduce code --- .../components/custom-field-input.js.es6 | 18 ++- .../components/wizard-custom-action.js.es6 | 15 +- .../discourse/lib/wizard-schema.js.es6 | 134 ++++-------------- 3 files changed, 39 insertions(+), 128 deletions(-) diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index bd6c78a1..51064974 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -5,10 +5,8 @@ import { computed } from "@ember/object"; import I18n from "I18n"; import wizardSchema, { - customFieldsKlassesRequiringAdditionalSubscription, - customFieldsKlassSubscriptionLevel, - customFieldsTypesRequiringAdditionalSubscription, - customFieldsTypeSubscriptionLevel, + requiringAdditionalSubscription, + subscriptionLevel, } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; export default Component.extend({ @@ -41,15 +39,15 @@ export default Component.extend({ @discourseComputed("subscription") customFieldTypes(subscription) { - let unsubscribedCustomFields = customFieldsTypesRequiringAdditionalSubscription( - subscription + let unsubscribedCustomFields = requiringAdditionalSubscription( + subscription, "custom_fields", "types" ); return wizardSchema.custom_field.types.reduce((result, type) => { let disabled = unsubscribedCustomFields.includes(type); result.push({ id: type, name: I18n.t(`admin.wizard.custom_field.type.${type}`), - subscription: customFieldsTypeSubscriptionLevel(type), + subscription: subscriptionLevel(type, "custom_fields", "types"), disabled: disabled, }); return result; @@ -58,15 +56,15 @@ export default Component.extend({ @discourseComputed("subscription") customFieldKlasses(subscription) { - let unsubscribedCustomFields = customFieldsKlassesRequiringAdditionalSubscription( - subscription + let unsubscribedCustomFields = requiringAdditionalSubscription( + subscription, "custom_fields", "klasses" ); return wizardSchema.custom_field.klasses.reduce((result, klass) => { let disabled = unsubscribedCustomFields.includes(klass); result.push({ id: klass, name: I18n.t(`admin.wizard.custom_field.klass.${klass}`), - subscription: customFieldsKlassSubscriptionLevel(klass), + subscription: subscriptionLevel(klass, "custom_fields", "klasses"), disabled: disabled, }); return result; diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 122df003..31d2099f 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,8 +1,7 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; import wizardSchema, { - actionsRequiringAdditionalSubscription, requiringAdditionalSubscription, - actionSubscriptionLevel, + subscriptionLevel, } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; @@ -98,19 +97,15 @@ export default Component.extend(UndoChanges, { return apis.find((a) => a.name === api).endpoints; }, - @discourseComputed("subscribed", "subscription") - actionTypes(subscribed, subscription) { - let unsubscribedActions = requiringAdditionalSubscription (subscription, "actions"); - debugger; - let unsubscribedActionslong = actionsRequiringAdditionalSubscription( - subscription - ); + @discourseComputed("subscription") + actionTypes(subscription) { + let unsubscribedActions = requiringAdditionalSubscription (subscription, "actions", ""); return Object.keys(wizardSchema.action.types).reduce((result, type) => { let disabled = unsubscribedActions.includes(type); result.push({ id: type, name: I18n.t(`admin.wizard.action.${type}.label`), - subscription: actionSubscriptionLevel(type), + subscription: subscriptionLevel(type, "actions", ""), disabled: disabled, }); return result; diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index d8befe97..d62a2df8 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -193,17 +193,6 @@ const action = { "members_visibility_level", ], required: ["id", "type"], - subscriptionTypes: [ - "send_message", - "add_to_group", - "create_category", - "create_group", - "send_to_api", - ], - actionTypesWithSubscription: { - standard: ["send_message", "add_to_group", "watch_categories"], - business: ["create_category", "create_group", "send_to_api"], - }, dependent: {}, objectArrays: {}, }; @@ -211,14 +200,6 @@ const action = { const custom_field = { klasses: ["topic", "post", "group", "category"], types: ["string", "boolean", "integer", "json"], - customFieldKlassWithSubscription: { - standard: [], - business: ["group", "category"], - }, - customFieldTypeWithSubscription: { - standard: ["json"], - business: [], - }, } const subscription_levels = { @@ -249,22 +230,38 @@ const wizardSchema = { } export function requiringAdditionalSubscription( - currentSubscription, category + currentSubscription, category, subCategory ) { - switch (currentSubscription) { - case "business": - return []; - case "standard": - return subscription_levels["business"].[category]; + switch (category) { + case "actions": + switch (currentSubscription) { + case "business": + return []; + case "standard": + return subscription_levels["business"].[category]; + default: + return subscription_levels["standard"].[category].concat( + subscription_levels["business"].[category] + ); + } + case "custom_fields": + switch (currentSubscription) { + case "business": + return []; + case "standard": + return subscription_levels["business"].[category].[subCategory]; + default: + return subscription_levels["standard"].[category].[subCategory].concat( + subscription_levels["business"].[category].[subCategory] + ); + } default: - return subscription_levels["standard"].[category].concat( - subscription_levels["business"].[category] - ); + return []; } } -export function SubscriptionLevel(category, type, subCategory) { +export function subscriptionLevel(type, category, subCategory) { switch (category) { case "actions": if (subscription_levels.["business"].actions.includes(type)) { @@ -291,85 +288,6 @@ export function SubscriptionLevel(category, type, subCategory) { } } -export function actionsRequiringAdditionalSubscription( - currentSubscription -) { - switch (currentSubscription) { - case "business": - return []; - case "standard": - return action.actionTypesWithSubscription["business"]; - default: - return action.actionTypesWithSubscription["standard"].concat( - action.actionTypesWithSubscription["business"] - ); - } -} - -export function actionSubscriptionLevel(type) { - if (action.actionTypesWithSubscription["business"].includes(type)) { - return "business" - } else { - if (action.actionTypesWithSubscription["standard"].includes(type)) { - return "standard" - } else { - return "" - } - } -} - - - -export function customFieldsKlassesRequiringAdditionalSubscription( - currentSubscription -) { - switch (currentSubscription) { - case "business": - return []; - case "standard": - return custom_field.customFieldKlassWithSubscription["business"]; - default: - return custom_field.customFieldKlassWithSubscription["business"].concat(custom_field.customFieldKlassWithSubscription["standard"]); - } -} - -export function customFieldsKlassSubscriptionLevel(type) { - if (custom_field.customFieldKlassWithSubscription["business"].includes(type)) { - return "business" - } else { - if (custom_field.customFieldKlassWithSubscription["standard"].includes(type)) { - return "standard" - } else { - return "" - } - } -} - -export function customFieldsTypesRequiringAdditionalSubscription( - currentSubscription -) { - switch (currentSubscription) { - case "business": - return []; - case "standard": - return custom_field.customFieldTypeWithSubscription["business"]; - default: - return custom_field.customFieldTypeWithSubscription["business"].concat(custom_field.customFieldTypeWithSubscription["standard"]); - } -} - -export function customFieldsTypeSubscriptionLevel(type) { - if (custom_field.customFieldTypeWithSubscription["business"].includes(type)) { - return "business" - } else { - if (custom_field.customFieldTypeWithSubscription["standard"].includes(type)) { - return "standard" - } else { - return "" - } - } -} - export function buildFieldTypes(types) { wizardSchema.field.types = types; } From 36257fbdfeac4bf5284e8475215719a2d1d9de5b Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 13:59:50 +0100 Subject: [PATCH 077/556] fix excon call --- lib/custom_wizard/api/endpoint.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_wizard/api/endpoint.rb b/lib/custom_wizard/api/endpoint.rb index 38000106..97fc1127 100644 --- a/lib/custom_wizard/api/endpoint.rb +++ b/lib/custom_wizard/api/endpoint.rb @@ -74,7 +74,7 @@ class CustomWizard::Api::Endpoint headers["Authorization"] = auth_string if auth_string headers["Content-Type"] = content_type if content_type - connection = Excon.new(CGI.escape(endpoint.url), headers: headers) + connection = Excon.new(endpoint.url, headers: headers) params = { method: endpoint.method } From 791eab6c470d32952d1b9de8925baa75f2581a79 Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 14:02:21 +0100 Subject: [PATCH 078/556] Fix action spec example --- spec/components/custom_wizard/action_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 94192aa7..0c677300 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -312,7 +312,7 @@ describe CustomWizard::Action do log_entry = CustomWizard::Api::LogEntry.list("my_api").first expect(result).to eq({ error: "API request failed" }) - expect(log_entry.status).to eq('FAILURE') + expect(log_entry.status).to eq('FAIL') end end end From 0752f8068a492cfbec9b8682313943a6865986e3 Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 14:09:34 +0100 Subject: [PATCH 079/556] fix subscription spec --- .../subscription/subscription_serializer_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb b/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb index 63caf363..91dc59b1 100644 --- a/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb +++ b/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb @@ -4,11 +4,11 @@ require_relative '../../../plugin_helper' describe CustomWizard::Subscription::SubscriptionSerializer do it 'should return subscription attributes' do - sub = CustomWizard::Subscription::Subscription.new(OpenStruct.new(type: 'community', updated_at: Time.now)) + sub = CustomWizard::Subscription::Subscription.new(OpenStruct.new(type: 'standard', updated_at: Time.now)) serialized = described_class.new(sub, root: false).as_json expect(serialized[:active]).to eq(true) - expect(serialized[:type]).to eq('community') + expect(serialized[:type]).to eq('standard') expect(serialized[:updated_at]).to eq(sub.updated_at) end end From 0c9acb9b200de60766d46f4f2d26ccacb7337696 Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 14:31:24 +0100 Subject: [PATCH 080/556] Update subscription titles --- 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 0b318922..7c3b449b 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -475,7 +475,7 @@ en: not_subscribed: You're not currently subscribed subscription: title: - community: Community Subscription + standard: Standard Subscription business: Business Subscription status: active: Active From 31b4663b3f66533a31de08fa14caf2efb0557fc5 Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 14:41:49 +0100 Subject: [PATCH 081/556] Fix linting errors --- .../javascripts/discourse/lib/wizard-schema.js.es6 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index d62a2df8..3dd69be0 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -238,10 +238,10 @@ export function requiringAdditionalSubscription( case "business": return []; case "standard": - return subscription_levels["business"].[category]; + return subscription_levels["business"][category]; default: - return subscription_levels["standard"].[category].concat( - subscription_levels["business"].[category] + return subscription_levels["standard"][category].concat( + subscription_levels["business"][category] ); } case "custom_fields": @@ -249,10 +249,10 @@ export function requiringAdditionalSubscription( case "business": return []; case "standard": - return subscription_levels["business"].[category].[subCategory]; + return subscription_levels["business"][category][subCategory]; default: - return subscription_levels["standard"].[category].[subCategory].concat( - subscription_levels["business"].[category].[subCategory] + return subscription_levels["standard"][category][subCategory].concat( + subscription_levels["business"][category][subCategory] ); } default: From 60388b7dab2e0fc1f208d4208aa2f36606810959 Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 14:52:22 +0100 Subject: [PATCH 082/556] fix more linting errors --- assets/javascripts/discourse/lib/wizard-schema.js.es6 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 3dd69be0..b482ca2b 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -264,20 +264,20 @@ export function requiringAdditionalSubscription( export function subscriptionLevel(type, category, subCategory) { switch (category) { case "actions": - if (subscription_levels.["business"].actions.includes(type)) { + if (subscription_levels["business"].actions.includes(type)) { return "business" } else { - if (subscription_levels.["standard"].actions.includes(type)) { + if (subscription_levels["standard"].actions.includes(type)) { return "standard" } else { return "" } } case "custom_fields": - if (subscription_levels.["business"].custom_fields[subCategory].includes(type)) { + if (subscription_levels["business"].custom_fields[subCategory].includes(type)) { return "business" } else { - if (subscription_levels.["standard"].custom_fields[subCategory].includes(type)) { + if (subscription_levels["standard"].custom_fields[subCategory].includes(type)) { return "standard" } else { return "" From 203876e9271d10d7b20a2f6cfb576b376916cfe5 Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 15:01:29 +0100 Subject: [PATCH 083/556] linting fixes --- .../components/custom-field-input.js.es6 | 5 ++--- .../components/wizard-custom-action.js.es6 | 2 +- .../discourse/lib/wizard-schema.js.es6 | 19 +++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index 51064974..6d4c6366 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -1,7 +1,6 @@ import Component from "@ember/component"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { alias, equal, or } from "@ember/object/computed"; -import { computed } from "@ember/object"; import I18n from "I18n"; import wizardSchema, { @@ -48,7 +47,7 @@ export default Component.extend({ id: type, name: I18n.t(`admin.wizard.custom_field.type.${type}`), subscription: subscriptionLevel(type, "custom_fields", "types"), - disabled: disabled, + disabled, }); return result; }, []); @@ -65,7 +64,7 @@ export default Component.extend({ id: klass, name: I18n.t(`admin.wizard.custom_field.klass.${klass}`), subscription: subscriptionLevel(klass, "custom_fields", "klasses"), - disabled: disabled, + disabled, }); return result; }, []); diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 31d2099f..53d79098 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -106,7 +106,7 @@ export default Component.extend(UndoChanges, { id: type, name: I18n.t(`admin.wizard.action.${type}.label`), subscription: subscriptionLevel(type, "actions", ""), - disabled: disabled, + disabled, }); return result; }, []); diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index b482ca2b..65e003a8 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -200,7 +200,7 @@ const action = { const custom_field = { klasses: ["topic", "post", "group", "category"], types: ["string", "boolean", "integer", "json"], -} +}; const subscription_levels = { standard: { @@ -218,7 +218,7 @@ const subscription_levels = { types: [] } } -} +}; const wizardSchema = { wizard, @@ -227,7 +227,7 @@ const wizardSchema = { custom_field, action, subscription_levels -} +}; export function requiringAdditionalSubscription( currentSubscription, category, subCategory @@ -258,8 +258,7 @@ export function requiringAdditionalSubscription( default: return []; } -} - +}; export function subscriptionLevel(type, category, subCategory) { switch (category) { @@ -286,15 +285,15 @@ export function subscriptionLevel(type, category, subCategory) { default: return ""; } -} +}; export function buildFieldTypes(types) { wizardSchema.field.types = types; -} +}; export function buildFieldValidations(validations) { wizardSchema.field.validations = validations; -} +}; const siteSettings = getOwner(this).lookup("site-settings:main"); if (siteSettings.wizard_apis_enabled) { @@ -303,7 +302,7 @@ if (siteSettings.wizard_apis_enabled) { api_endpoint: null, api_body: null, }; -} +}; export function setWizardDefaults(obj, itemType) { const objSchema = wizardSchema[itemType]; @@ -329,6 +328,6 @@ export function setWizardDefaults(obj, itemType) { } return obj; -} +}; export default wizardSchema; From a3d59caee81917398ddd8eb402e6e3fbc237dbbd Mon Sep 17 00:00:00 2001 From: merefield Date: Wed, 27 Oct 2021 15:05:09 +0100 Subject: [PATCH 084/556] yet more linting fixes --- .../javascripts/discourse/lib/wizard-schema.js.es6 | 12 ++++++------ assets/javascripts/wizard/lib/text-lite.js.es6 | 2 +- assets/javascripts/wizard/lib/user-search.js.es6 | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 65e003a8..fcdd37b4 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -264,22 +264,22 @@ export function subscriptionLevel(type, category, subCategory) { switch (category) { case "actions": if (subscription_levels["business"].actions.includes(type)) { - return "business" + return "business"; } else { if (subscription_levels["standard"].actions.includes(type)) { - return "standard" + return "standard"; } else { - return "" + return ""; } } case "custom_fields": if (subscription_levels["business"].custom_fields[subCategory].includes(type)) { - return "business" + return "business"; } else { if (subscription_levels["standard"].custom_fields[subCategory].includes(type)) { - return "standard" + return "standard"; } else { - return "" + return ""; } } default: diff --git a/assets/javascripts/wizard/lib/text-lite.js.es6 b/assets/javascripts/wizard/lib/text-lite.js.es6 index c93f6708..cc161426 100644 --- a/assets/javascripts/wizard/lib/text-lite.js.es6 +++ b/assets/javascripts/wizard/lib/text-lite.js.es6 @@ -7,7 +7,7 @@ import { getOwner } from "discourse-common/lib/get-owner"; export function cook(text, options) { if (!options) { options = buildOptions({ - getURL: getURL, + getURL, siteSettings: getOwner(this).lookup("site-settings:main"), }); } diff --git a/assets/javascripts/wizard/lib/user-search.js.es6 b/assets/javascripts/wizard/lib/user-search.js.es6 index 8ef96266..e7171f18 100644 --- a/assets/javascripts/wizard/lib/user-search.js.es6 +++ b/assets/javascripts/wizard/lib/user-search.js.es6 @@ -27,12 +27,12 @@ function performSearch( // need to be able to cancel this oldSearch = $.ajax(getUrl("/u/search/users"), { data: { - term: term, + term, topic_id: topicId, include_groups: includeGroups, include_mentionable_groups: includeMentionableGroups, include_messageable_groups: includeMessageableGroups, - group: group, + group, topic_allowed_users: allowedUsers, }, }); From bd5edaffe99027385df62ebc79903150b4b6f647 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 2 Nov 2021 08:58:15 +0000 Subject: [PATCH 085/556] custom field input: further make generic, fix serializers dropdown --- .../components/custom-field-input.js.es6 | 66 +++++++++---------- .../discourse/lib/wizard-schema.js.es6 | 12 ++-- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index 6d4c6366..f60052c9 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -8,6 +8,22 @@ import wizardSchema, { subscriptionLevel, } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; +const generateContent = function (kategory, subscription) { + let unsubscribedCustomFields = requiringAdditionalSubscription( + subscription, "custom_fields", kategory + ); + return wizardSchema.custom_field[kategory].reduce((result, item) => { + let disabled = unsubscribedCustomFields.includes(item); + result.push({ + id: item, + name: I18n.t(`admin.wizard.custom_field.${kategory}.${item}`), + subscription: subscriptionLevel(item, "custom_fields", kategory), + disabled, + }); + return result; + }, []); +}; + export default Component.extend({ tagName: "tr", topicSerializers: ["topic_view", "topic_list_item"], @@ -25,49 +41,29 @@ export default Component.extend({ this.set("originalField", JSON.parse(JSON.stringify(this.field))); }, - // @discourseComputed("field.klass") - // serializerContent(klass) { - // const serializers = this.get(`${klass}Serializers`); + @discourseComputed("field.klass") + serializerContent(klass) { + const serializers = this.get(`${klass}Serializers`); - // if (serializers) { - // return generateContent(serializers, "serializers", this.subscribed); - // } else { - // return []; - // } - // }, + if (serializers) { + return serializers.reduce((result, key) => { + result.push({ + id: key, + name: I18n.t(`admin.wizard.custom_field.serializers.${key}`), + }); + return result; + }, []); + } + }, @discourseComputed("subscription") customFieldTypes(subscription) { - let unsubscribedCustomFields = requiringAdditionalSubscription( - subscription, "custom_fields", "types" - ); - return wizardSchema.custom_field.types.reduce((result, type) => { - let disabled = unsubscribedCustomFields.includes(type); - result.push({ - id: type, - name: I18n.t(`admin.wizard.custom_field.type.${type}`), - subscription: subscriptionLevel(type, "custom_fields", "types"), - disabled, - }); - return result; - }, []); + return generateContent("type", this.subscription); }, @discourseComputed("subscription") customFieldKlasses(subscription) { - let unsubscribedCustomFields = requiringAdditionalSubscription( - subscription, "custom_fields", "klasses" - ); - return wizardSchema.custom_field.klasses.reduce((result, klass) => { - let disabled = unsubscribedCustomFields.includes(klass); - result.push({ - id: klass, - name: I18n.t(`admin.wizard.custom_field.klass.${klass}`), - subscription: subscriptionLevel(klass, "custom_fields", "klasses"), - disabled, - }); - return result; - }, []); + return generateContent("klass", this.subscription); }, @observes("field.klass") diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index fcdd37b4..fb2e697d 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -198,24 +198,24 @@ const action = { }; const custom_field = { - klasses: ["topic", "post", "group", "category"], - types: ["string", "boolean", "integer", "json"], + klass: ["topic", "post", "group", "category"], + type: ["string", "boolean", "integer", "json"], }; const subscription_levels = { standard: { actions: ["send_message", "add_to_group", "watch_categories"], custom_fields: { - klasses: [], - types: ["json"] + klass: [], + type: ["json"] } }, business: { actions: ["create_category", "create_group", "send_to_api"], custom_fields: { - klasses: ["group", "category"], - types: [] + klass: ["group", "category"], + type: [] } } }; From 8cbc8745b90efe66375117df6128478631580734 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 2 Nov 2021 09:06:00 +0000 Subject: [PATCH 086/556] prettier --- .../components/custom-field-input.js.es6 | 4 +- .../components/wizard-custom-action.js.es6 | 6 ++- .../discourse/lib/wizard-schema.js.es6 | 40 ++++++++++++------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index f60052c9..83f668d0 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -10,7 +10,9 @@ import wizardSchema, { const generateContent = function (kategory, subscription) { let unsubscribedCustomFields = requiringAdditionalSubscription( - subscription, "custom_fields", kategory + subscription, + "custom_fields", + kategory ); return wizardSchema.custom_field[kategory].reduce((result, item) => { let disabled = unsubscribedCustomFields.includes(item); diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 53d79098..6cf22942 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -99,7 +99,11 @@ export default Component.extend(UndoChanges, { @discourseComputed("subscription") actionTypes(subscription) { - let unsubscribedActions = requiringAdditionalSubscription (subscription, "actions", ""); + let unsubscribedActions = requiringAdditionalSubscription( + subscription, + "actions", + "" + ); return Object.keys(wizardSchema.action.types).reduce((result, type) => { let disabled = unsubscribedActions.includes(type); result.push({ diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index fb2e697d..af4fa758 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -207,17 +207,17 @@ const subscription_levels = { actions: ["send_message", "add_to_group", "watch_categories"], custom_fields: { klass: [], - type: ["json"] - } + type: ["json"], + }, }, business: { actions: ["create_category", "create_group", "send_to_api"], custom_fields: { klass: ["group", "category"], - type: [] - } - } + type: [], + }, + }, }; const wizardSchema = { @@ -226,11 +226,13 @@ const wizardSchema = { field, custom_field, action, - subscription_levels + subscription_levels, }; export function requiringAdditionalSubscription( - currentSubscription, category, subCategory + currentSubscription, + category, + subCategory ) { switch (category) { case "actions": @@ -258,7 +260,7 @@ export function requiringAdditionalSubscription( default: return []; } -}; +} export function subscriptionLevel(type, category, subCategory) { switch (category) { @@ -273,10 +275,18 @@ export function subscriptionLevel(type, category, subCategory) { } } case "custom_fields": - if (subscription_levels["business"].custom_fields[subCategory].includes(type)) { + if ( + subscription_levels["business"].custom_fields[subCategory].includes( + type + ) + ) { return "business"; } else { - if (subscription_levels["standard"].custom_fields[subCategory].includes(type)) { + if ( + subscription_levels["standard"].custom_fields[subCategory].includes( + type + ) + ) { return "standard"; } else { return ""; @@ -285,15 +295,15 @@ export function subscriptionLevel(type, category, subCategory) { default: return ""; } -}; +} export function buildFieldTypes(types) { wizardSchema.field.types = types; -}; +} export function buildFieldValidations(validations) { wizardSchema.field.validations = validations; -}; +} const siteSettings = getOwner(this).lookup("site-settings:main"); if (siteSettings.wizard_apis_enabled) { @@ -302,7 +312,7 @@ if (siteSettings.wizard_apis_enabled) { api_endpoint: null, api_body: null, }; -}; +} export function setWizardDefaults(obj, itemType) { const objSchema = wizardSchema[itemType]; @@ -328,6 +338,6 @@ export function setWizardDefaults(obj, itemType) { } return obj; -}; +} export default wizardSchema; From 3add43e81b312137383c226b3bcd236b0233c31c Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 2 Nov 2021 09:09:06 +0000 Subject: [PATCH 087/556] eslint --- .../discourse/components/custom-field-input.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index 83f668d0..7ee70716 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -60,12 +60,12 @@ export default Component.extend({ @discourseComputed("subscription") customFieldTypes(subscription) { - return generateContent("type", this.subscription); + return generateContent("type", subscription); }, @discourseComputed("subscription") customFieldKlasses(subscription) { - return generateContent("klass", this.subscription); + return generateContent("klass", subscription); }, @observes("field.klass") From 14a337e00c20c07387d020019c42d5dce16a4874 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 2 Nov 2021 09:11:30 +0000 Subject: [PATCH 088/556] template lint --- .../wizard-subscription-selector-row.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs index 87f2a1e1..ecd77cb1 100644 --- a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs @@ -11,7 +11,7 @@ {{html-safe label}} {{#if item.subscription}} - {{item.subscription}} + {{item.subscription}} {{/if}}
From 9ab441f8880e2f39fad565cf1edc48e7d2d102df Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 9 Nov 2021 20:38:53 +0800 Subject: [PATCH 089/556] Update plugin metadata --- plugin.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugin.rb b/plugin.rb index 97d5472b..6845bf3a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true # name: discourse-custom-wizard -# about: Create custom wizards +# about: Create custom wizards for topic creation, onboarding, user surveys and much more. # version: 0.8.1 -# authors: Angus McLeod +# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George +# contact_emails: support@thepavilion.io # url: https://github.com/paviliondev/discourse-custom-wizard -# contact emails: angus@thepavilion.io +# tests_passed_test_url: https://plugins.discourse.pavilion.tech/w/super-mega-fun-wizard +# stable_test_url: https://stable.plugins.discourse.pavilion.tech/w/super-mega-fun-wizard gem 'liquid', '5.0.1', require: true register_asset 'stylesheets/admin/admin.scss', :desktop From c6b8e08e38e516d3da13b84ae635deb23c5992a4 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 9 Nov 2021 14:57:33 +0000 Subject: [PATCH 090/556] Add subscription level logic to back-end validation --- lib/custom_wizard/custom_field.rb | 8 ++-- lib/custom_wizard/subscription.rb | 46 +++++++++++++++++++ spec/components/custom_wizard/action_spec.rb | 2 +- spec/components/custom_wizard/builder_spec.rb | 12 ++--- .../custom_wizard/custom_field_spec.rb | 2 +- spec/components/custom_wizard/mapper_spec.rb | 2 +- .../custom_wizard/template_validator_spec.rb | 2 +- .../custom_field_extensions_spec.rb | 2 +- spec/plugin_helper.rb | 4 +- .../custom_field_extensions_spec.rb | 3 +- .../custom_wizard/steps_controller_spec.rb | 2 +- .../wizard_step_serializer_spec.rb | 2 +- 12 files changed, 66 insertions(+), 21 deletions(-) diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index eb93e292..99dcfbbb 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -17,9 +17,8 @@ class ::CustomWizard::CustomField category: ["basic_category"], post: ["post"] } - SUBSCRIPTION_CLASSES ||= ['category', 'group'] + TYPES ||= ["string", "boolean", "integer", "json"] - SUBSCRIPTION_TYPES ||= ["json"] LIST_CACHE_KEY ||= 'custom_field_list' def self.serializers @@ -84,8 +83,7 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_class", class: value)) next end - - if attr == 'klass' && SUBSCRIPTION_CLASSES.include?(value) && !@subscription.subscribed? + if attr == 'klass' && @subscription.requires_additional_subscription("custom_fields", "klass").include?(value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end @@ -100,7 +98,7 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end - if attr == 'type' && SUBSCRIPTION_TYPES.include?(value) && !@subscription.subscribed? + if attr == 'type' && @subscription.requires_additional_subscription("custom_fields", "type").include?(value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index 6c535602..7d94eb89 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -6,6 +6,23 @@ class CustomWizard::Subscription attr_accessor :authentication, :subscription + SUBSCRIPTION_LEVELS = { + standard: { + actions: ["send_message", "add_to_group", "watch_categories"], + custom_fields: { + klass: [], + type: ["json"], + }, + }, + business: { + actions: ["create_category", "create_group", "send_to_api"], + custom_fields: { + klass: ["group", "category"], + type: [], + }, + }, + } + def initialize @authentication = CustomWizard::Subscription::Authentication.new(get_authentication) @subscription = CustomWizard::Subscription::Subscription.new(get_subscription) @@ -39,6 +56,31 @@ class CustomWizard::Subscription "discourse-subscription-server:user_subscription" end + def requires_additional_subscription(kategory, sub_kategory) + case kategory + when "actions" + case self.type + when "business" + return [] + when "standard" + return SUBSCRIPTION_LEVELS[:business][kategory.to_sym] + else + return SUBSCRIPTION_LEVELS[:standard][kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym] + end + when "custom_fields" + case self.type + when "business" + return [] + when "standard" + return SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]; + else + return SUBSCRIPTION_LEVELS[:standard][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym] + end + else + return [] + end + end + def update if @authentication.active? response = Excon.get( @@ -127,6 +169,10 @@ class CustomWizard::Subscription self.new.type end + def self.requires_additional_subscription(kategory, sub_kategory) + self.new.requires_additional_subscription(kategory, sub_kategory) + end + def self.authorized? self.new.authorized? end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 0c677300..284f63d3 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -179,7 +179,7 @@ describe CustomWizard::Action do context "subscription actions" do before do - enable_subscription + enable_subscription("standard") end it '#send_message' do diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 28880409..f4a6d2fc 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -176,7 +176,7 @@ describe CustomWizard::Builder do context "restart is enabled" do before do - enable_subscription + enable_subscription("standard") @template[:restart_on_revisit] = true CustomWizard::Template.save(@template.as_json) end @@ -205,7 +205,7 @@ describe CustomWizard::Builder do context 'with required data' do before do - enable_subscription + enable_subscription("standard") @template[:steps][0][:required_data] = required_data_json['required_data'] @template[:steps][0][:required_data_message] = required_data_json['required_data_message'] CustomWizard::Template.save(@template.as_json) @@ -241,7 +241,7 @@ describe CustomWizard::Builder do context "with permitted params" do before do - enable_subscription + enable_subscription("standard") @template[:steps][0][:permitted_params] = permitted_param_json['permitted_params'] CustomWizard::Template.save(@template.as_json) end @@ -256,7 +256,7 @@ describe CustomWizard::Builder do context "with condition" do before do - enable_subscription + enable_subscription("standard") @template[:steps][0][:condition] = user_condition_json['condition'] CustomWizard::Template.save(@template.as_json) end @@ -295,7 +295,7 @@ describe CustomWizard::Builder do context "with condition" do before do - enable_subscription + enable_subscription("standard") @template[:steps][0][:fields][0][:condition] = user_condition_json['condition'] CustomWizard::Template.save(@template.as_json) end @@ -327,7 +327,7 @@ describe CustomWizard::Builder do context 'save submissions disabled' do before do - enable_subscription + enable_subscription("standard") @template[:save_submissions] = false CustomWizard::Template.save(@template.as_json) @wizard = CustomWizard::Builder.new(@template[:id], user).build diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index a30ec02b..fd0fa4fc 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -217,7 +217,7 @@ describe CustomWizard::CustomField do context "with a subscription" do before do - enable_subscription + enable_subscription("business") end it "saves subscription field types" do diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index 1ac945d0..71c1e7de 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -359,7 +359,7 @@ describe CustomWizard::Mapper do context "with a subscription" do before do - enable_subscription + enable_subscription("standard") end it "treats replaced values as string literals" do diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb index e8b8af28..14214e4d 100644 --- a/spec/components/custom_wizard/template_validator_spec.rb +++ b/spec/components/custom_wizard/template_validator_spec.rb @@ -75,7 +75,7 @@ describe CustomWizard::TemplateValidator do context "with subscription" do before do - enable_subscription + enable_subscription("standard") end it "validates wizard attributes" do diff --git a/spec/extensions/custom_field_extensions_spec.rb b/spec/extensions/custom_field_extensions_spec.rb index 7bba27a0..3660644b 100644 --- a/spec/extensions/custom_field_extensions_spec.rb +++ b/spec/extensions/custom_field_extensions_spec.rb @@ -74,7 +74,7 @@ describe "custom field extensions" do context "subscription custom fields" do before do - enable_subscription + enable_subscription("business") subscription_custom_field_json['custom_fields'].each do |field_json| custom_field = CustomWizard::CustomField.new(nil, field_json) diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index e72fe355..232a2411 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -29,8 +29,10 @@ def authenticate_subscription CustomWizard::Subscription::Authentication.any_instance.stubs(:active?).returns(true) end -def enable_subscription +def enable_subscription(type) + # CustomWizard::Subscription.new CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(true) + CustomWizard::Subscription.any_instance.stubs(:type).returns(type) end def disable_subscription diff --git a/spec/requests/custom_wizard/custom_field_extensions_spec.rb b/spec/requests/custom_wizard/custom_field_extensions_spec.rb index 775e3ee0..19a8417a 100644 --- a/spec/requests/custom_wizard/custom_field_extensions_spec.rb +++ b/spec/requests/custom_wizard/custom_field_extensions_spec.rb @@ -40,8 +40,7 @@ describe "custom field extensions" do context "with a subscription" do before do - enable_subscription - + enable_subscription("business") subscription_custom_field_json['custom_fields'].each do |field_json| custom_field = CustomWizard::CustomField.new(nil, field_json) custom_field.save diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 8396135c..b55de419 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -121,7 +121,7 @@ describe CustomWizard::StepsController do context "subscription" do before do - enable_subscription + enable_subscription("standard") end it "raises an error when user cant see the step due to conditions" do diff --git a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb index 0a9f7e89..f0128765 100644 --- a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb @@ -34,7 +34,7 @@ describe CustomWizard::StepSerializer do context 'with required data' do before do - enable_subscription + enable_subscription("standard") wizard_template['steps'][0]['required_data'] = required_data_json['required_data'] wizard_template['steps'][0]['required_data_message'] = required_data_json['required_data_message'] CustomWizard::Template.save(wizard_template) From 584ee6d24edb0937d7a8846dc0bfb0d0bf4496e2 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 9 Nov 2021 15:01:17 +0000 Subject: [PATCH 091/556] remove redundant returns --- lib/custom_wizard/subscription.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index 7d94eb89..1ab53dc9 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -61,23 +61,23 @@ class CustomWizard::Subscription when "actions" case self.type when "business" - return [] + [] when "standard" - return SUBSCRIPTION_LEVELS[:business][kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym] else - return SUBSCRIPTION_LEVELS[:standard][kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym] + SUBSCRIPTION_LEVELS[:standard][kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym] end when "custom_fields" case self.type when "business" - return [] + [] when "standard" - return SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]; + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]; else - return SUBSCRIPTION_LEVELS[:standard][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:standard][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym] end else - return [] + [] end end From e37b2a6e852544e71622366a24fcacfe31736ff7 Mon Sep 17 00:00:00 2001 From: merefield Date: Tue, 9 Nov 2021 15:04:04 +0000 Subject: [PATCH 092/556] Remove semi colon! --- lib/custom_wizard/subscription.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index 1ab53dc9..7bac7f59 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -72,7 +72,7 @@ class CustomWizard::Subscription when "business" [] when "standard" - SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym]; + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym] else SUBSCRIPTION_LEVELS[:standard][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym] end From 457463f7c76fc79accff00b8509165f4d7df70c4 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 10 Nov 2021 22:25:42 +0800 Subject: [PATCH 093/556] Add blankspace --- lib/custom_wizard/custom_field.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index 99dcfbbb..ed0c9594 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -83,6 +83,7 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_class", class: value)) next end + if attr == 'klass' && @subscription.requires_additional_subscription("custom_fields", "klass").include?(value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end From 81bb7e56c254c9912882f6b27434e00cdb9aef53 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 1 Nov 2021 21:52:29 +0800 Subject: [PATCH 094/556] WIP --- .../components/wizard-notice-row.js.es6 | 14 + .../discourse/components/wizard-notice.js.es6 | 43 +-- .../custom-wizard-critical-notice.hbs | 5 + .../custom-wizard-critical-notice.js.es6 | 19 ++ .../custom-wizard-important-notice.hbs | 3 - .../custom-wizard-important-notice.js.es6 | 16 -- .../controllers/admin-wizards-notices.js.es6 | 67 +++++ .../controllers/admin-wizards.js.es6 | 38 +-- .../custom-wizard-admin-route-map.js.es6 | 5 + .../discourse/helpers/notice-badge.js.es6 | 41 +++ .../initializers/custom-wizard-edits.js.es6 | 62 +++-- .../discourse/mixins/notice-message.js.es6 | 65 +++++ .../models/custom-wizard-notice.js.es6 | 67 ++++- .../routes/admin-wizards-notices.js.es6 | 15 ++ .../discourse/routes/admin-wizards.js.es6 | 13 +- .../templates/admin-wizards-notices.hbs | 49 ++++ .../discourse/templates/admin-wizards.hbs | 9 +- .../components/wizard-notice-row.hbs | 30 +++ .../templates/components/wizard-notice.hbs | 80 +++--- assets/stylesheets/admin/admin.scss | 183 ++++++++----- assets/stylesheets/admin/wizard/manager.scss | 4 - config/locales/client.en.yml | 36 ++- config/locales/server.en.yml | 22 +- config/routes.rb | 5 +- controllers/custom_wizard/admin/admin.rb | 8 +- controllers/custom_wizard/admin/notice.rb | 52 +++- lib/custom_wizard/notice.rb | 244 +++++++++++++----- lib/custom_wizard/notice/connection_error.rb | 61 ++--- plugin.rb | 5 +- .../custom_wizard/notice_serializer.rb | 10 +- 30 files changed, 930 insertions(+), 341 deletions(-) create mode 100644 assets/javascripts/discourse/components/wizard-notice-row.js.es6 create mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs create mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 delete mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs delete mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 create mode 100644 assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 create mode 100644 assets/javascripts/discourse/helpers/notice-badge.js.es6 create mode 100644 assets/javascripts/discourse/mixins/notice-message.js.es6 create mode 100644 assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 create mode 100644 assets/javascripts/discourse/templates/admin-wizards-notices.hbs create mode 100644 assets/javascripts/discourse/templates/components/wizard-notice-row.hbs diff --git a/assets/javascripts/discourse/components/wizard-notice-row.js.es6 b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 new file mode 100644 index 00000000..9c099b39 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 @@ -0,0 +1,14 @@ +import Component from "@ember/component"; +import NoticeMessage from "../mixins/notice-message"; + +export default Component.extend(NoticeMessage, { + tagName: "tr", + attributeBindings: ["notice.id:data-notice-id"], + classNameBindings: [":wizard-notice-row", "notice.typeClass", "notice.expired:expired", "notice.dismissed:dismissed"], + + actions: { + dismiss() { + this.notice.dismiss(); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 index fcd77606..cac3e4eb 100644 --- a/assets/javascripts/discourse/components/wizard-notice.js.es6 +++ b/assets/javascripts/discourse/components/wizard-notice.js.es6 @@ -1,35 +1,9 @@ import Component from "@ember/component"; -import discourseComputed from "discourse-common/utils/decorators"; -import { not, notEmpty } from "@ember/object/computed"; -import I18n from "I18n"; +import NoticeMessage from "../mixins/notice-message"; -export default Component.extend({ - classNameBindings: [ - ":wizard-notice", - "notice.type", - "dismissed", - "expired", - "resolved", - ], - showFull: false, - resolved: notEmpty("notice.expired_at"), - dismissed: notEmpty("notice.dismissed_at"), - canDismiss: not("dismissed"), - - @discourseComputed("notice.type") - title(type) { - return I18n.t(`admin.wizard.notice.title.${type}`); - }, - - @discourseComputed("notice.type") - icon(type) { - return { - plugin_status_warning: "exclamation-circle", - plugin_status_connection_error: "bolt", - subscription_messages_connection_error: "bolt", - info: "info-circle", - }[type]; - }, +export default Component.extend(NoticeMessage, { + attributeBindings: ["notice.id:data-notice-id"], + classNameBindings: [':wizard-notice', 'notice.typeClass', 'notice.dismissed:dismissed', 'notice.expired:expired', 'notice.hidden:hidden'], actions: { dismiss() { @@ -38,5 +12,12 @@ export default Component.extend({ this.set("dismissing", false); }); }, - }, + + hide() { + this.set('hiding', true); + this.notice.hide().then(() => { + this.set('hiding', false); + }); + }, + } }); diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs new file mode 100644 index 00000000..9d96bed9 --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs @@ -0,0 +1,5 @@ +{{#if notices}} + {{#each notices as |notice|}} + {{wizard-notice notice=notice showPlugin=true}} + {{/each}} +{{/if}} diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 new file mode 100644 index 00000000..0bb252e9 --- /dev/null +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 @@ -0,0 +1,19 @@ +import { getOwner } from "discourse-common/lib/get-owner"; + +export default { + shouldRender(attrs, ctx) { + return ctx.siteSettings.wizard_critical_notices_on_dashboard; + }, + + setupComponent(attrs, component) { + const controller = getOwner(this).lookup('controller:admin-dashboard'); + + component.set('notices', controller.get('customWizardCriticalNotices')); + controller.addObserver('customWizardCriticalNotices.[]', () => { + if (this._state === "destroying") { + return; + } + component.set('notices', controller.get('customWizardCriticalNotices')); + }); + } +}; \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs deleted file mode 100644 index 9b01c468..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#if importantNotice}} - {{wizard-notice notice=importantNotice importantOnDashboard=true}} -{{/if}} diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 deleted file mode 100644 index 5962f255..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-important-notice.js.es6 +++ /dev/null @@ -1,16 +0,0 @@ -import { getOwner } from "discourse-common/lib/get-owner"; - -export default { - shouldRender(attrs, ctx) { - return ctx.siteSettings.wizard_important_notices_on_dashboard; - }, - - setupComponent() { - const controller = getOwner(this).lookup("controller:admin-dashboard"); - const importantNotice = controller.get("customWizardImportantNotice"); - - if (importantNotice) { - this.set("importantNotice", importantNotice); - } - }, -}; diff --git a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 new file mode 100644 index 00000000..0f67f878 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 @@ -0,0 +1,67 @@ +import Controller from "@ember/controller"; +import CustomWizardNotice from "../models/custom-wizard-notice"; +import discourseComputed from "discourse-common/utils/decorators"; +import { notEmpty } from "@ember/object/computed"; +import { A } from "@ember/array"; +import I18n from "I18n"; + +export default Controller.extend({ + messageUrl: "https://thepavilion.io/t/3652", + messageKey: "info", + messageIcon: "info-circle", + messageClass: "info", + hasNotices: notEmpty("notices"), + page: 0, + loadingMore: false, + canLoadMore: true, + + @discourseComputed('notices.[]', 'notices.@each.dismissed') + allDismisssed(notices) { + return notices.every(n => !n.canDismiss || n.dismissed); + }, + + loadMoreNotices() { + if (!this.canLoadMore) { + return; + } + const page = this.get("page"); + this.set("loadingMore", true); + + CustomWizardNotice.list({ page, include_all: true }) + .then((result) => { + if (result.notices.length === 0) { + this.set("canLoadMore", false); + return; + } + + this.get("notices").pushObjects( + A(result.notices.map(notice => CustomWizardNotice.create(notice))) + ); + }) + .finally(() => this.set("loadingMore", false)); + }, + + actions: { + loadMore() { + if (this.canLoadMore) { + this.set("page", this.page + 1); + this.loadMoreNotices(); + } + }, + + dismissAll() { + bootbox.confirm( + I18n.t("admin.wizard.notice.dismiss_all.confirm"), + I18n.t("no_value"), + I18n.t("yes_value"), + (result) => { + if (result) { + this.set('loadingMore', true); + CustomWizardNotice.dismissAll() + .finally(() => this.set("loadingMore", false)); + } + } + ); + } + } +}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index d128d851..e2672fe4 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -1,20 +1,26 @@ -import Controller from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import { ajax } from "discourse/lib/ajax"; +import Controller, { inject as controller } from "@ember/controller"; +import { isPresent } from "@ember/utils"; +import { A } from "@ember/array"; export default Controller.extend({ - actions: { - dismissNotice(noticeId) { - ajax(`/admin/wizards/notice/${this.id}`, { - type: "DELETE", - }) - .then((result) => { - if (result.success) { - const notices = this.notices; - notices.removeObject(notices.findBy("id", noticeId)); - } - }) - .catch(popupAjaxError); - }, + adminWizardsNotices: controller(), + + unsubscribe() { + this.messageBus.unsubscribe("/custom-wizard/notices"); }, + + subscribe() { + this.unsubscribe(); + this.messageBus.subscribe("/custom-wizard/notices", (data) => { + if (isPresent(data.active_notice_count)) { + this.set("activeNoticeCount", data.active_notice_count); + this.adminWizardsNotices.setProperties({ + notices: A(), + page: 0, + canLoadMore: true + }); + this.adminWizardsNotices.loadMoreNotices(); + } + }); + } }); diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 67b91f87..c3c95e48 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -63,6 +63,11 @@ export default { path: "/subscription", resetNamespace: true, }); + + this.route("adminWizardsNotices", { + path: "/notices", + resetNamespace: true, + }); } ); }, diff --git a/assets/javascripts/discourse/helpers/notice-badge.js.es6 b/assets/javascripts/discourse/helpers/notice-badge.js.es6 new file mode 100644 index 00000000..bc5df4a6 --- /dev/null +++ b/assets/javascripts/discourse/helpers/notice-badge.js.es6 @@ -0,0 +1,41 @@ +import { autoUpdatingRelativeAge } from "discourse/lib/formatter"; +import { iconHTML } from "discourse-common/lib/icon-library"; +import I18n from "I18n"; +import { registerUnbound } from "discourse-common/lib/helpers"; +import { htmlSafe } from "@ember/template"; + +registerUnbound("notice-badge", function(attrs) { + let tag = attrs.url ? 'a' : 'div'; + let attrStr = ''; + if (attrs.title) { + attrStr += `title='${I18n.t(attrs.title)}'`; + } + if (attrs.url) { + attrStr += `href='${attrs.url}'`; + } + let html = `<${tag} class="${attrs.class ? `${attrs.class} ` : ''}notice-badge" ${attrStr}>`; + if (attrs.icon) { + html += iconHTML(attrs.icon); + } + if (attrs.label) { + if (attrs.icon) { + html += ' '; + } + html += `${I18n.t(attrs.label)}`; + } + if (attrs.date) { + if (attrs.icon || attrs.label) { + html += ' '; + } + let dateAttrs = {}; + if (attrs.leaveAgo) { + dateAttrs = { + format: "medium", + leaveAgo: true + }; + } + html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs); + } + html += ``; + return htmlSafe(html); +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 21a9d745..8208cd20 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -1,6 +1,7 @@ import DiscourseURL from "discourse/lib/url"; import { withPluginApi } from "discourse/lib/plugin-api"; import CustomWizardNotice from "../models/custom-wizard-notice"; +import { isPresent } from "@ember/utils"; import { A } from "@ember/array"; export default { @@ -21,37 +22,46 @@ export default { }; withPluginApi("0.8.36", (api) => { - api.modifyClass("route:admin-dashboard", { - afterModel() { - return CustomWizardNotice.list().then((result) => { - if (result && result.length) { - this.set( - "notices", - A(result.map((n) => CustomWizardNotice.create(n))) - ); + api.modifyClass('route:admin-dashboard', { + setupController(controller) { + this._super(...arguments); + + controller.loadCriticalNotices(); + controller.subscribe(); + } + }); + + api.modifyClass('controller:admin-dashboard', { + criticalNotices: A(), + + unsubscribe() { + this.messageBus.unsubscribe("/custom-wizard/notices"); + }, + + subscribe() { + this.unsubscribe(); + this.messageBus.subscribe("/custom-wizard/notices", (data) => { + if (isPresent(data.active_notice_count)) { + this.loadCriticalNotices(); } }); }, - setupController(controller) { - if (this.notices) { - let pluginStatusConnectionError = this.notices.filter( - (n) => n.type === "plugin_status_connection_error" - )[0]; - let pluginStatusWarning = this.notices.filter( - (n) => n.type === "plugin_status_warning" - )[0]; - - if (pluginStatusConnectionError || pluginStatusWarning) { - controller.set( - "customWizardImportantNotice", - pluginStatusConnectionError || pluginStatusWarning - ); + loadCriticalNotices() { + CustomWizardNotice.list({ + type: [ + 'connection_error', + 'warning' + ], + archetype: 'plugin_status', + visible: true + }).then(result => { + if (result.notices && result.notices.length) { + const criticalNotices = A(result.notices.map(n => CustomWizardNotice.create(n))); + this.set('customWizardCriticalNotices', criticalNotices); } - } - - this._super(...arguments); - }, + }); + } }); }); }, diff --git a/assets/javascripts/discourse/mixins/notice-message.js.es6 b/assets/javascripts/discourse/mixins/notice-message.js.es6 new file mode 100644 index 00000000..492df643 --- /dev/null +++ b/assets/javascripts/discourse/mixins/notice-message.js.es6 @@ -0,0 +1,65 @@ +import Mixin from "@ember/object/mixin"; +import { bind, scheduleOnce } from "@ember/runloop"; +import { cookAsync } from "discourse/lib/text"; +import { createPopper } from "@popperjs/core"; + +export default Mixin.create({ + showCookedMessage: false, + + didReceiveAttrs(){ + const message = this.notice.message; + cookAsync(message).then((cooked) => { + this.set("cookedMessage", cooked); + }); + }, + + createMessageModal() { + let container = this.element.querySelector('.notice-message'); + let modal = this.element.querySelector('.cooked-notice-message'); + + this._popper = createPopper( + container, + modal, { + strategy: "absolute", + placement: "bottom-start", + modifiers: [ + { + name: "preventOverflow", + }, + { + name: "offset", + options: { + offset: [0, 5], + }, + }, + ], + } + ); + }, + + didInsertElement() { + $(document).on("click", bind(this, this.documentClick)); + }, + + willDestroyElement() { + $(document).off("click", bind(this, this.documentClick)); + }, + + documentClick(event) { + if (this._state === "destroying") { return; } + + if (!event.target.closest(`[data-notice-id="${this.notice.id}"] .notice-message`)) { + this.set('showCookedMessage', false); + } + }, + + actions: { + toggleCookedMessage() { + this.toggleProperty("showCookedMessage"); + + if (this.showCookedMessage) { + scheduleOnce("afterRender", this, this.createMessageModal); + } + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 index 1eb21e73..29e30628 100644 --- a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 @@ -1,25 +1,68 @@ import EmberObject from "@ember/object"; +import discourseComputed from "discourse-common/utils/decorators"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; +import { and, not, notEmpty } from "@ember/object/computed"; +import { dasherize } from "@ember/string"; +import I18n from "I18n"; -const CustomWizardNotice = EmberObject.extend(); +const CustomWizardNotice = EmberObject.extend({ + expired: notEmpty('expired_at'), + dismissed: notEmpty('dismissed_at'), + hidden: notEmpty('hidden_at'), + notHidden: not('hidden'), + notDismissed: not('dismissed'), + canDismiss: and('dismissable', 'notDismissed'), + canHide: and('can_hide', 'notHidden'), -CustomWizardNotice.reopen({ - dismiss() { - return ajax(`/admin/wizards/notice/${this.id}`, { type: "PUT" }) - .then((result) => { - if (result.success) { - this.set("dismissed_at", result.dismissed_at); - } - }) - .catch(popupAjaxError); + @discourseComputed('type') + typeClass(type) { + return dasherize(type); }, + + @discourseComputed('type') + typeLabel(type) { + return I18n.t(`admin.wizard.notice.type.${type}`); + }, + + dismiss() { + if (!this.get('canDismiss')) { + return; + } + + return ajax(`/admin/wizards/notice/${this.get('id')}/dismiss`, { type: 'PUT' }).then(result => { + if (result.success) { + this.set('dismissed_at', result.dismissed_at); + } + }).catch(popupAjaxError); + }, + + hide() { + if (!this.get('canHide')) { + return; + } + + return ajax(`/admin/wizards/notice/${this.get('id')}/hide`, { type: 'PUT' }).then(result => { + if (result.success) { + this.set('hidden_at', result.hidden_at); + } + }).catch(popupAjaxError); + } }); CustomWizardNotice.reopenClass({ - list() { - return ajax("/admin/wizards/notice").catch(popupAjaxError); + list(data = {}) { + return ajax('/admin/wizards/notice', { + type: "GET", + data + }).catch(popupAjaxError); }, + + dismissAll() { + return ajax('/admin/wizards/notice/dismiss', { + type: "PUT" + }).catch(popupAjaxError); + } }); export default CustomWizardNotice; diff --git a/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 new file mode 100644 index 00000000..a329ce95 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 @@ -0,0 +1,15 @@ +import CustomWizardNotice from "../models/custom-wizard-notice"; +import DiscourseRoute from "discourse/routes/discourse"; +import { A } from "@ember/array"; + +export default DiscourseRoute.extend({ + model() { + return CustomWizardNotice.list({ include_all: true }); + }, + + setupController(controller, model) { + controller.setProperties({ + notices: A(model.notices.map(notice => CustomWizardNotice.create(notice))), + }); + }, +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index de67a8b9..2bbc73a9 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -1,6 +1,5 @@ import DiscourseRoute from "discourse/routes/discourse"; import { ajax } from "discourse/lib/ajax"; -import { A } from "@ember/array"; export default DiscourseRoute.extend({ model() { @@ -8,13 +7,21 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - controller.set("notices", A(model.notices)); controller.set("api_section", model.api_section); + + if (model.active_notice_count) { + controller.set("activeNoticeCount", model.active_notice_count); + } + if (model.featured_notices) { + controller.set("featuredNotices", model.featured_notices); + } + + controller.subscribe(); }, afterModel(model, transition) { if (transition.targetName === "adminWizards.index") { this.transitionTo("adminWizardsWizard"); } - }, + } }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-notices.hbs b/assets/javascripts/discourse/templates/admin-wizards-notices.hbs new file mode 100644 index 00000000..d522c1a5 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-notices.hbs @@ -0,0 +1,49 @@ +
+

{{i18n "admin.wizard.notices.title"}}

+ +
+ {{d-button + label="admin.wizard.notice.dismiss_all.label" + title="admin.wizard.notice.dismiss_all.title" + action=(action "dismissAll") + disabled=allDismisssed + icon="check"}} +
+
+ +{{wizard-message + key=messageKey + url=messageUrl + type=messageType + opts=messageOpts + items=messageItems + loading=loading + component="notices"}} + +
+ {{#load-more selector=".wizard-table tr" action=(action "loadMore")}} + {{#if hasNotices}} + + + + + + + + + + + {{#each notices as |notice|}} + {{wizard-notice-row notice=notice}} + {{/each}} + +
{{I18n "admin.wizard.notice.time"}}{{I18n "admin.wizard.notice.type.label"}}{{I18n "admin.wizard.notice.title"}}{{I18n "admin.wizard.notice.status"}}
+ {{else}} + {{#unless loadingMore}} +

{{i18n "search.no_results"}}

+ {{/unless}} + {{/if}} + + {{conditional-loading-spinner condition=loadingMore}} + {{/load-more}} +
\ No newline at end of file diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index aa3b2cf5..4447380f 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -10,6 +10,12 @@ {{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}}
+ {{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}} @@ -17,8 +23,5 @@ {{/admin-nav}}
- {{#each notices as |notice|}} - {{wizard-notice notice=notice}} - {{/each}} {{outlet}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs b/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs new file mode 100644 index 00000000..7f97b250 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs @@ -0,0 +1,30 @@ + + {{#if notice.updated_at}} + {{notice-badge class="notice-updated-at" date=notice.updated_at label="admin.wizard.notice.updated_at" leaveAgo=true}} + {{else}} + {{notice-badge class="notice-created-at" date=notice.created_at label="admin.wizard.notice.created_at" leaveAgo=true}} + {{/if}} + +{{notice.typeLabel}} + + {{notice.title}} + {{#if showCookedMessage}} + {{cookedMessage}} + {{/if}} + + + {{#if notice.canDismiss}} + {{d-button + action="dismiss" + label="admin.wizard.notice.dismiss.label" + title="admin.wizard.notice.dismiss.title" + class="btn-dismiss" + icon="check"}} + {{else if notice.dismissed}} + {{i18n "admin.wizard.notice.dismissed_at"}} {{format-date notice.dismissed_at leaveAgo="true"}} + {{else if notice.expired}} + {{i18n "admin.wizard.notice.expired_at"}} {{format-date notice.expired_at leaveAgo="true"}} + {{else}} + {{i18n "admin.wizard.notice.active"}} + {{/if}} + \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-notice.hbs b/assets/javascripts/discourse/templates/components/wizard-notice.hbs index 89bbf5d4..24a853d3 100644 --- a/assets/javascripts/discourse/templates/components/wizard-notice.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-notice.hbs @@ -1,51 +1,39 @@
- {{#if resolved}} -
- {{d-icon "check"}} - {{i18n "admin.wizard.notice.resolved"}} - {{format-date notice.expired_at leaveAgo="true"}} -
- {{/if}} - -
- {{d-icon icon}} - {{title}} +
+ {{notice.title}} + {{#if showCookedMessage}} + {{cookedMessage}} + {{/if}}
+
+ {{#if notice.expired}} + {{notice-badge class="notice-expired-at" icon="check" label="admin.wizard.notice.expired_at" date=notice.expired_at}} + {{/if}} + {{#if showPlugin}} + {{notice-badge class="notice-plugin" icon="plug" title="admin.wizard.notice.plugin" label="admin.wizard.notice.plugin" url="/admin/wizards/notices"}} + {{/if}} + {{notice-badge class="notice-created-at" icon="far-clock" label="admin.wizard.notice.created_at" date=notice.created_at leaveAgo=true}} + {{#if notice.updated_at}} + {{notice-badge class="notice-updated-at" icon="far-clock" label="admin.wizard.notice.updated_at" date=notice.updated_at}} + {{/if}} -
- {{d-icon "far-clock"}} - {{i18n "admin.wizard.notice.issued"}} - {{format-date notice.created_at leaveAgo="true"}} -
- - {{#if notice.updated_at}} -
- {{d-icon "calendar-alt"}} - {{i18n "admin.wizard.notice.updated"}} - {{format-date notice.updated_at leaveAgo="true"}} -
- {{/if}} - -
- {{d-icon "plug"}} - {{i18n "admin.wizard.notice.plugin"}} + {{#if notice.canDismiss}} +
+ {{#if dismissing}} + {{loading-spinner size="small"}} + {{else}} + {{d-icon "times"}} + {{/if}} +
+ {{/if}} + {{#if notice.canHide}} +
+ {{#if hiding}} + {{loading-spinner size="small"}} + {{else}} + {{d-icon "far-eye-slash"}} + {{/if}} +
+ {{/if}}
- -
- {{html-safe notice.message}} -
- -{{#if importantOnDashboard}} - - {{i18n "admin.wizard.notice.disable_important_on_dashboard"}} - -{{/if}} - -{{#if canDismiss}} - {{#if dismissing}} - {{loading-spinner size="small"}} - {{else}} - {{d-icon "times"}} - {{/if}} -{{/if}} diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index 94fe6839..e3790465 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -4,15 +4,25 @@ @import "common/components/buttons"; @import "wizard/variables"; +$expired: #339b18; +$info: #038ae7; +$warning: #d47e00; +$error: #ef1700; + .admin-wizard-controls { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; + min-height: 34px; & + .wizard-message + div { margin-top: 20px; } + + h3 { + margin-bottom: 0; + } } .wizard-message { @@ -909,87 +919,142 @@ } } +.admin-wizards-notices { + .wizard-table { + overflow: unset; + } +} + .wizard-notice { - padding: 1em; + padding: .75em; margin-bottom: 1em; - border: 1px solid var(--primary); - position: relative; + border: 1px solid var(--primary-low); &.dismissed { display: none; } - &.resolved .notice-badge:not(.notice-expired-at), - &.resolved a, - &.resolved p { + &.expired .notice-badge:not(.notice-expired-at), + &.expired a, + &.expired p { color: var(--primary-medium) !important; } - .d-icon { - margin-right: 0.4em; + .notice-badge { + padding: 0 .5em; } .notice-header { display: flex; - } - - .notice-badge { - border: 1px solid var(--primary); - display: inline-flex; - align-items: center; - padding: 0 0.5em; - margin-right: 1em; - font-size: 0.9em; - line-height: 25px; - min-height: 25px; - box-sizing: border-box; - - &:last-of-type { - margin-right: 0; - } - } - - &.warning { - .notice-expired-at { - border: 1px solid var(--success); - background-color: rgba($success, 0.1); - color: var(--success); - } .notice-title { - border: 1px solid var(--pavilion-warning); - background-color: rgba($pavilion_warning, 0.1); - color: var(--pavilion-warning); + padding: 0; + } + + .notice-header-right { + margin-left: auto; + display: flex; + align-items: center; + + .notice-badge { + margin-left: .5em; + } } } - .notice-issued, - .notice-resolved { - margin-right: 0.3em; - } - - .notice-message { - p { - margin: 0.5em 0; - } - - p:last-of-type { - margin-bottom: 0; - } + .dismiss-notice-container, + .hide-notice-container { + width: 40px; + display: flex; + justify-content: center; + align-items: center; } .dismiss-notice, - .spinner { - position: absolute; - top: 1em; - right: 1em; - color: var(--primary-medium); - } + .hide-notice { + display: flex; + align-items: center; - .disable-important { - position: absolute; - right: 3em; - top: 1em; - color: var(--primary-medium); + .d-icon { + margin-right: 0; + color: var(--primary); + } } } + +.notice-badge { + display: inline-flex; + align-items: center; + min-height: 25px; + box-sizing: border-box; + color: var(--primary) !important; +} + +.admin-actions { + display: flex; + align-items: center; +} + +.wizard-notices-link { + position: relative; + margin-right: 10px; + + div > a { + @include btn; + color: var(--secondary) !important; + background-color: var(--primary-medium); + + &.active { + background-color: var(--tertiary) !important; + color: var(--secondary) !important; + } + } +} + +.active-notice-count { + background-color: $danger; + color: $secondary; + border-radius: 50%; + width: 18px; + height: 18px; + line-height: 18px; + text-align: center; + position: absolute; + top: -8px; + right: -8px; + font-size: .7em; +} + +a.show-notice-message { + padding: .25em .5em; + color: var(--primary); +} + +.wizard-notice, +.wizard-notice-row:not(.expired):not(.dismissed) { + &.info { + background-color: rgba($info, 0.1); + border: 1px solid rgba($info, 0.5); + } + &.warning, + &.connection-error { + background-color: rgba($warning, 0.1); + border: 1px solid rgba($warning, 0.5); + } +} + +.notice-message { + position: relative; + + .cooked-notice-message { + background-color: var(--secondary); + padding: 1em; + z-index: 1; + box-shadow: shadow("dropdown"); + border-top: 1px solid var(--primary-low); + + p { + margin: 0; + } + } +} \ No newline at end of file diff --git a/assets/stylesheets/admin/wizard/manager.scss b/assets/stylesheets/admin/wizard/manager.scss index a3b30c98..69f963fe 100644 --- a/assets/stylesheets/admin/wizard/manager.scss +++ b/assets/stylesheets/admin/wizard/manager.scss @@ -2,10 +2,6 @@ display: flex; justify-content: flex-start; - h3 { - margin-bottom: 0; - } - .buttons { display: flex; margin-left: auto; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 7c3b449b..9ffeda37 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -112,6 +112,9 @@ en: select: "Select a wizard to see its logs" viewing: "View recent logs for wizards on the forum" documentation: "Check out the logs documentation" + notices: + info: "Plugin status and subscription notices" + documentation: Check out the notices documentation editor: show: "Show" @@ -485,14 +488,31 @@ en: notice: plugin: Custom Wizard Plugin - issued: Issued - update: Updated - resolved: Resolved - title: - plugin_status_warning: Warning Notice - plugin_status_connection_error: Connection Notice - subscription_messages_connection_error: Connection Notice - disable_important_on_dashboard: disable + message: Message + time: Time + status: Status + title: Title + dismiss: + label: Dismiss + title: Dismiss notice + dismiss_all: + label: Dismiss All + title: Dismiss all informational Custom Wizard notices + confirm: Are you sure you want to dismiss all informational Custom Wizard notices? + active: active + created_at: issued + updated_at: updated + expired_at: expired + dismissed_at: dismissed + type: + label: Type + info: Information + warning: Warning + connection_error: Connection Error + + notices: + nav_label: Notices + title: Plugin Notices wizard_js: group: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 5fd7c076..5c25690b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -54,21 +54,21 @@ en: notice: connection_error: "Failed to connect to http://%{domain}" - compatibility_issue: > - The Custom Wizard Plugin has a compatibility issue with the latest version of Discourse. - Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. + compatibility_issue: + title: The Custom Wizard Plugin is incompatibile with the latest version of Discourse. + message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. plugin_status: - connection_error_limit: > - We're unable to connect to the Pavilion Plugin Status Server. Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse or the plugin. - If this connection issue persists please contact support@thepavilion.io for further assistance. - subscription_messages: - connection_error_limit: > - We're unable to connect to the Pavilion Subscription Server. This will not affect the operation of the plugin. - If this connection issue persists please contact support@thepavilion.io for further assistance. + connection_error: + title: Unable to connect to the Custom Wizard Plugin status server + message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. If this issue persists contact support@thepavilion.io for further assistance. + subscription_message: + connection_error: + title: Unable to connect to the Custom Wizard Plugin subscription server + message: If this issue persists contact support@thepavilion.io for further assistance. site_settings: custom_wizard_enabled: "Enable custom wizards." wizard_redirect_exclude_paths: "Routes excluded from wizard redirects." wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview" wizard_apis_enabled: "Enable API features (experimental)." - wizard_important_notices_on_dashboard: "Show important notices about the custom wizard plugin on the admin dashboard." + wizard_critical_notices_on_dashboard: "Show critical notices about the custom wizard plugin on the admin dashboard." diff --git a/config/routes.rb b/config/routes.rb index 0d59b200..3b5f8ca6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -52,6 +52,9 @@ Discourse::Application.routes.append do delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication' get 'admin/wizards/notice' => 'admin_notice#index' - put 'admin/wizards/notice/:notice_id' => 'admin_notice#dismiss' + put 'admin/wizards/notice/:notice_id/dismiss' => 'admin_notice#dismiss' + put 'admin/wizards/notice/:notice_id/hide' => 'admin_notice#hide' + put 'admin/wizards/notice/dismiss' => 'admin_notice#dismiss_all' + get 'admin/wizards/notices' => 'admin_notice#index' end end diff --git a/controllers/custom_wizard/admin/admin.rb b/controllers/custom_wizard/admin/admin.rb index 8a3b69ac..63a89833 100644 --- a/controllers/custom_wizard/admin/admin.rb +++ b/controllers/custom_wizard/admin/admin.rb @@ -6,8 +6,12 @@ class CustomWizard::AdminController < ::Admin::AdminController render_json_dump( #TODO replace with appropriate static? api_section: ["business"].include?(CustomWizard::Subscription.type), - notices: ActiveModel::ArraySerializer.new( - CustomWizard::Notice.list, + active_notice_count: CustomWizard::Notice.active_count, + featured_notices: ActiveModel::ArraySerializer.new( + CustomWizard::Notice.list( + type: CustomWizard::Notice.types[:info], + archetype: CustomWizard::Notice.archetypes[:subscription_message] + ), each_serializer: CustomWizard::NoticeSerializer ) ) diff --git a/controllers/custom_wizard/admin/notice.rb b/controllers/custom_wizard/admin/notice.rb index f28240e3..d6c43b5f 100644 --- a/controllers/custom_wizard/admin/notice.rb +++ b/controllers/custom_wizard/admin/notice.rb @@ -1,20 +1,66 @@ # frozen_string_literal: true class CustomWizard::AdminNoticeController < CustomWizard::AdminController - before_action :find_notice, only: [:dismiss] + before_action :find_notice, only: [:dismiss, :hide] def index - render_serialized(CustomWizard::Notice.list(include_recently_expired: true), CustomWizard::NoticeSerializer) + type = params[:type] + archetype = params[:archtype] + page = params[:page].to_i + include_all = ActiveRecord::Type::Boolean.new.cast(params[:include_all]) + visible = ActiveRecord::Type::Boolean.new.cast(params[:visible]) + + if type + if type.is_a?(Array) + type = type.map { |type| CustomWizard::Notice.types[type.to_sym] } + else + type = CustomWizard::Notice.types[type.to_sym] + end + end + + if archetype + if archetype.is_a?(Array) + archetype = archetype.map { |type| CustomWizard::Notice.archetypes[archetype.to_sym] } + else + archetype = CustomWizard::Notice.archetypes[archetype.to_sym] + end + end + + notices = CustomWizard::Notice.list( + include_all: include_all, + page: page, + type: type, + archetype: archetype, + visible: visible + ) + + render_serialized(notices, CustomWizard::NoticeSerializer, root: :notices) end def dismiss - if @notice.dismissable? && @notice.dismiss + if @notice.dismissable? && @notice.dismiss! render json: success_json.merge(dismissed_at: @notice.dismissed_at) else render json: failed_json end end + def hide + if @notice.can_hide? && @notice.hide! + render json: success_json.merge(hidden_at: @notice.hidden_at) + else + render json: failed_json + end + end + + def dismiss_all + if CustomWizard::Notice.dismiss_all + render json: success_json + else + render json: failed_json + end + end + def find_notice @notice = CustomWizard::Notice.find(params[:notice_id]) raise Discourse::InvalidParameters.new(:notice_id) unless @notice diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index 3ba710c5..7266178d 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -7,42 +7,61 @@ class CustomWizard::Notice "tests-passed" => "plugins.discourse.pavilion.tech", "stable" => "stable.plugins.discourse.pavilion.tech" } - SUBSCRIPTION_MESSAGES_DOMAIN = "test.thepavilion.io" + SUBSCRIPTION_MESSAGE_DOMAIN = "test.thepavilion.io" LOCALHOST_DOMAIN = "localhost:3000" PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing) CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable) + PAGE_LIMIT = 30 attr_reader :id, + :title, :message, :type, + :archetype, :created_at attr_accessor :retrieved_at, :updated_at, :dismissed_at, - :expired_at + :expired_at, + :hidden_at def initialize(attrs) - @id = Digest::SHA1.hexdigest(attrs[:message]) + @id = self.class.generate_notice_id(attrs[:title], attrs[:created_at]) + @title = attrs[:title] @message = attrs[:message] @type = attrs[:type].to_i + @archetype = attrs[:archetype].to_i @created_at = attrs[:created_at] @updated_at = attrs[:updated_at] @retrieved_at = attrs[:retrieved_at] @dismissed_at = attrs[:dismissed_at] @expired_at = attrs[:expired_at] + @hidden_at = attrs[:hidden_at] end - def dismiss + def dismiss! if dismissable? self.dismissed_at = Time.now self.save + self.class.publish_notice_count end end - def expire - self.expired_at = Time.now - self.save + def hide! + if can_hide? + self.hidden_at = Time.now + self.save + self.class.publish_notice_count + end + end + + def expire! + if !expired? + self.expired_at = Time.now + self.save + self.class.publish_notice_count + end end def expired? @@ -54,15 +73,33 @@ class CustomWizard::Notice end def dismissable? - true + !expired? && !dismissed? && type === self.class.types[:info] + end + + def hidden? + hidden_at.present? + end + + def can_hide? + !hidden? && ( + type === self.class.types[:connection_error] || + type === self.class.types[:warning] + ) && ( + archetype === self.class.archetypes[:plugin_status] + ) end def save attrs = { expired_at: expired_at, + updated_at: updated_at, + retrieved_at: retrieved_at, created_at: created_at, + hidden_at: hidden_at, + title: title, message: message, - type: type + type: type, + archetype: archetype } if current = self.class.find(self.id) @@ -75,9 +112,15 @@ class CustomWizard::Notice def self.types @types ||= Enum.new( info: 0, - plugin_status_warning: 1, - plugin_status_connection_error: 2, - subscription_messages_connection_error: 3 + warning: 1, + connection_error: 2 + ) + end + + def self.archetypes + @archetypes ||= Enum.new( + subscription_message: 0, + plugin_status: 1 ) end @@ -85,7 +128,7 @@ class CustomWizard::Notice notices = [] if !skip_subscription - subscription_messages = request(:subscription_messages) + subscription_messages = request(:subscription_message) if subscription_messages.present? subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages]) @@ -96,27 +139,46 @@ class CustomWizard::Notice if !skip_plugin && request_plugin_status? plugin_status = request(:plugin_status) - if plugin_status.present? && plugin_status[:status].present? && plugin_status[:status].is_a?(Hash) - plugin_notice = convert_plugin_status_to_notice(plugin_status[:status]) + if plugin_status.present? && plugin_status[:status].present? + plugin_notice = convert_plugin_status_to_notice(plugin_status) notices.push(plugin_notice) if plugin_notice end end - notices.each do |notice_data| - notice = new(notice_data) - notice.retrieved_at = Time.now - notice.save + if notices.any? + + notices.each do |notice_data| + notice = new(notice_data) + notice.retrieved_at = Time.now + notice.save + end + + publish_notice_count end end + def self.publish_notice_count + payload = { + active_notice_count: CustomWizard::Notice.active_count + } + MessageBus.publish("/custom-wizard/notices", payload, group_ids: [Group::AUTO_GROUPS[:admins]]) + end + def self.convert_subscription_messages_to_notices(messages) - messages.map do |message| - { - message: message[:message], - type: types[message[:type].to_sym], - created_at: message[:created_at], - expired_at: message[:expired_at] - } + messages.reduce([]) do |result, message| + notice_id = generate_notice_id(message[:title], message[:created_at]) + + unless exists?(notice_id) + result.push( + title: message[:title], + message: message[:message], + type: types[message[:type].to_sym], + archetype: archetypes[:subscription_message], + created_at: message[:created_at], + expired_at: message[:expired_at] + ) + end + result end end @@ -124,22 +186,32 @@ class CustomWizard::Notice notice = nil if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status]) - notice = { - message: I18n.t('wizard.notice.compatibility_issue', domain: plugin_status_domain), - type: types[:plugin_status_warning], - created_at: plugin_status[:status_changed_at] - } + title = I18n.t('wizard.notice.compatibility_issue.title') + created_at = plugin_status[:status_changed_at] + id = generate_notice_id(title, created_at) + + unless exists?(id) + message = I18n.t('wizard.notice.compatibility_issue.message', domain: plugin_status_domain) + notice = { + id: id, + title: title, + message: message, + type: types[:warning], + archetype: archetypes[:plugin_status], + created_at: created_at + } + end else - expire_notices(types[:plugin_status_warning]) + expire_all(types[:warning], archetypes[:plugin_status]) end notice end - def self.notify_connection_errors(connection_type_key) - domain = self.send("#{connection_type_key.to_s}_domain") - message = I18n.t("wizard.notice.#{connection_type_key.to_s}.connection_error_limit", domain: domain) - notices = list(type: types[:connection_error], message: message) + def self.notify_connection_errors(archetype) + domain = self.send("#{archetype.to_s}_domain") + title = I18n.t("wizard.notice.#{archetype.to_s}.connection_error.title") + notices = list(type: types[:connection_error], archetype: archetypes[archetype.to_sym], title: title) if notices.any? notice = notices.first @@ -147,28 +219,29 @@ class CustomWizard::Notice notice.save else notice = new( - message: message, - type: types["#{connection_type_key}_connection_error".to_sym], - created_at: Time.now + title: title, + message: I18n.t("wizard.notice.#{archetype.to_s}.connection_error.message", domain: domain), + archetype: archetypes[archetype.to_sym], + type: types[:connection_error], + created_at: Time.now, + updated_at: Time.now ) notice.save end - end - def self.expire_notices(type) - list(type: type).each(&:expire) + publish_notice_count end def self.request_plugin_status? CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development? end - def self.subscription_messages_domain - (Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGES_DOMAIN + def self.subscription_message_domain + (Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGE_DOMAIN end - def self.subscription_messages_url - "http://#{subscription_messages_domain}/subscription-server/messages.json" + def self.subscription_message_url + "http://#{subscription_message_domain}/subscription-server/messages.json" end def self.plugin_status_domain @@ -180,14 +253,19 @@ class CustomWizard::Notice "http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard" end - def self.request(type) - url = self.send("#{type.to_s}_url") - response = Excon.get(url) - connection_error = CustomWizard::Notice::ConnectionError.new(type) + def self.request(archetype) + url = self.send("#{archetype.to_s}_url") - if response.status == 200 + begin + response = Excon.get(url) + rescue Excon::Error::Socket, Excon::Error::Timeout => e + response = nil + end + connection_error = CustomWizard::Notice::ConnectionError.new(archetype) + + if response && response.status == 200 connection_error.expire! - expire_notices(types["#{type}_connection_error".to_sym]) + expire_all(types[:connection_error], archetypes[archetype.to_sym]) begin data = JSON.parse(response.body).deep_symbolize_keys @@ -198,8 +276,7 @@ class CustomWizard::Notice data else connection_error.create! - notify_connection_errors(type) if connection_error.reached_limit? - + notify_connection_errors(archetype) if connection_error.reached_limit? nil end end @@ -213,20 +290,65 @@ class CustomWizard::Notice new(raw.symbolize_keys) if raw.present? end + def self.exists?(id) + PluginStoreRow.where(plugin_name: namespace, key: id).exists? + end + def self.store(id, raw_notice) PluginStore.set(namespace, id, raw_notice) end - def self.list_query(type: nil, message: nil, include_recently_expired: false) + def self.list_query(type: nil, archetype: nil, title: nil, include_all: false, include_recently_expired: false, page: nil, visible: false) query = PluginStoreRow.where(plugin_name: namespace) - query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}") - query = query.where("(value::json->>'type')::integer = ?", type) if type - query = query.where("(value::json->>'message')::text = ?", message) if message - query.order("value::json->>'created_at' DESC") + query = query.where("(value::json->>'hidden_at') IS NULL") if visible + query = query.where("(value::json->>'dismissed_at') IS NULL") unless include_all + query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}") unless include_all + query = query.where("(value::json->>'archetype')::integer = ?", archetype) if archetype + if type + type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?" + query = query.where(type_query_str, type) + end + query = query.where("(value::json->>'title')::text = ?", title) if title + query = query.limit(PAGE_LIMIT).offset(page.to_i * PAGE_LIMIT) if !page.nil? + query.order("value::json->>'expired_at' DESC, value::json->>'updated_at' DESC,value::json->>'dismissed_at' DESC, value::json->>'created_at' DESC") end - def self.list(type: nil, message: nil, include_recently_expired: false) - list_query(type: type, message: message, include_recently_expired: include_recently_expired) + def self.list(type: nil, archetype: nil, title: nil, include_all: false, include_recently_expired: false, page: 0, visible: false) + list_query(type: type, archetype: archetype, title: title, include_all: include_all, include_recently_expired: include_recently_expired, page: page, visible: visible) .map { |r| self.new(JSON.parse(r.value).symbolize_keys) } end + + def self.active_count + list_query.count + end + + def self.dismiss_all + dismissed_count = PluginStoreRow.where(" + plugin_name = '#{namespace}' AND + (value::json->>'type')::integer = #{types[:info]} AND + (value::json->>'expired_at') IS NULL AND + (value::json->>'dismissed_at') IS NULL + ").update_all(" + value = jsonb_set(value::jsonb, '{dismissed_at}', (to_json(now())::text)::jsonb, true) + ") + publish_notice_count if dismissed_count.to_i > 0 + dismissed_count + end + + def self.expire_all(type, archetype) + expired_count = PluginStoreRow.where(" + plugin_name = '#{namespace}' AND + (value::json->>'type')::integer = #{type} AND + (value::json->>'archetype')::integer = #{archetype} AND + (value::json->>'expired_at') IS NULL + ").update_all(" + value = jsonb_set(value::jsonb, '{expired_at}', (to_json(now())::text)::jsonb, true) + ") + publish_notice_count if expired_count.to_i > 0 + expired_count + end + + def self.generate_notice_id(title, created_at) + Digest::SHA1.hexdigest("#{title}-#{created_at}") + end end diff --git a/lib/custom_wizard/notice/connection_error.rb b/lib/custom_wizard/notice/connection_error.rb index 8b8b944a..9f78a09e 100644 --- a/lib/custom_wizard/notice/connection_error.rb +++ b/lib/custom_wizard/notice/connection_error.rb @@ -2,80 +2,73 @@ class CustomWizard::Notice::ConnectionError - attr_reader :type_key + attr_reader :archetype - def initialize(type_key) - @type_key = type_key + def initialize(archetype) + @archetype = archetype end def create! - id = "#{type_key.to_s}_error" - - if attrs = PluginStore.get(namespace, id) + if attrs = current_error + key = "#{archetype.to_s}_error_#{attrs["id"]}" attrs['updated_at'] = Time.now attrs['count'] = attrs['count'].to_i + 1 else - domain = CustomWizard::Notice.send("#{type_key.to_s}_domain") + domain = CustomWizard::Notice.send("#{archetype.to_s}_domain") + id = SecureRandom.hex(8) attrs = { + id: id, message: I18n.t("wizard.notice.connection_error", domain: domain), - type: self.class.types[type_key], + archetype: CustomWizard::Notice.archetypes[archetype.to_sym], created_at: Time.now, count: 1 } + key = "#{archetype.to_s}_error_#{id}" end - PluginStore.set(namespace, id, attrs) + PluginStore.set(namespace, key, attrs) @errors = nil end def expire! - if errors.exists? - errors.each do |error_row| - error = JSON.parse(error_row.value) - error['expired_at'] = Time.now - error_row.value = error.to_json - error_row.save - end + if query = current_error(query_only: true) + record = query.first + error = JSON.parse(record.value) + error['expired_at'] = Time.now + record.value = error.to_json + record.save end end - def self.types - @types ||= Enum.new( - plugin_status: 0, - subscription_messages: 1 - ) - end - def plugin_status_limit 5 end - def subscription_messages_limit + def subscription_message_limit 10 end def limit - self.send("#{type_key.to_s}_limit") + self.send("#{archetype.to_s}_limit") end def reached_limit? - return false unless errors.exists? + return false unless current_error.present? current_error['count'].to_i >= limit end - def current_error - JSON.parse(errors.first.value) - end - def namespace "#{CustomWizard::PLUGIN_NAME}_notice_connection" end - def errors - @errors ||= begin + def current_error(query_only: false) + @current_error ||= begin query = PluginStoreRow.where(plugin_name: namespace) - query = query.where("(value::json->>'type')::integer = ?", self.class.types[type_key]) - query.where("(value::json->>'expired_at') IS NULL") + query = query.where("(value::json->>'archetype')::integer = ?", CustomWizard::Notice.archetypes[archetype]) + query = query.where("(value::json->>'expired_at') IS NULL") + return nil if !query.exists? + return query if query_only + JSON.parse(query.first.value) end end end diff --git a/plugin.rb b/plugin.rb index 6845bf3a..bec30637 100644 --- a/plugin.rb +++ b/plugin.rb @@ -245,7 +245,10 @@ after_initialize do end AdminDashboardData.add_problem_check do - warning_notices = CustomWizard::Notice.list(type: CustomWizard::Notice.types[:plugin_status_warning]) + warning_notices = CustomWizard::Notice.list( + type: CustomWizard::Notice.types[:warning], + archetype: CustomWizard::Notice.archetypes[:plugin_status] + ) warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil end diff --git a/serializers/custom_wizard/notice_serializer.rb b/serializers/custom_wizard/notice_serializer.rb index 5564de1f..4354731d 100644 --- a/serializers/custom_wizard/notice_serializer.rb +++ b/serializers/custom_wizard/notice_serializer.rb @@ -2,19 +2,27 @@ class CustomWizard::NoticeSerializer < ApplicationSerializer attributes :id, + :title, :message, :type, + :archetype, :created_at, :expired_at, :updated_at, :dismissed_at, :retrieved_at, - :dismissable + :hidden_at, + :dismissable, + :can_hide def dismissable object.dismissable? end + def can_hide + object.can_hide? + end + def type CustomWizard::Notice.types.key(object.type) end From 98061c14e8ef84e974f1e8162d1ecbc2038e56bf Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 2 Nov 2021 15:29:31 +0800 Subject: [PATCH 095/556] Fix spec (mostly) --- .../initializers/custom-wizard-edits.js.es6 | 1 + lib/custom_wizard/notice.rb | 49 ++++++------ lib/custom_wizard/notice/connection_error.rb | 17 ++-- spec/components/custom_wizard/notice_spec.rb | 80 +++++++++++++------ spec/jobs/update_notices_spec.rb | 2 +- .../admin/notice_controller_spec.rb | 57 +++++++++++-- 6 files changed, 141 insertions(+), 65 deletions(-) diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 8208cd20..774acca3 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -41,6 +41,7 @@ export default { subscribe() { this.unsubscribe(); this.messageBus.subscribe("/custom-wizard/notices", (data) => { + if (isPresent(data.active_notice_count)) { this.loadCriticalNotices(); } diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index 7266178d..a07e1443 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -43,24 +43,30 @@ class CustomWizard::Notice def dismiss! if dismissable? self.dismissed_at = Time.now - self.save - self.class.publish_notice_count + self.save_and_publish end end def hide! if can_hide? self.hidden_at = Time.now - self.save - self.class.publish_notice_count + self.save_and_publish end end def expire! if !expired? self.expired_at = Time.now - self.save + self.save_and_publish + end + end + + def save_and_publish + if self.save self.class.publish_notice_count + true + else + false end end @@ -95,7 +101,6 @@ class CustomWizard::Notice updated_at: updated_at, retrieved_at: retrieved_at, created_at: created_at, - hidden_at: hidden_at, title: title, message: message, type: type, @@ -104,6 +109,7 @@ class CustomWizard::Notice if current = self.class.find(self.id) attrs[:dismissed_at] = current.dismissed_at || self.dismissed_at + attrs[:hidden_at] = current.hidden_at || self.hidden_at end self.class.store(id, attrs) @@ -146,7 +152,6 @@ class CustomWizard::Notice end if notices.any? - notices.each do |notice_data| notice = new(notice_data) notice.retrieved_at = Time.now @@ -166,18 +171,16 @@ class CustomWizard::Notice def self.convert_subscription_messages_to_notices(messages) messages.reduce([]) do |result, message| - notice_id = generate_notice_id(message[:title], message[:created_at]) - - unless exists?(notice_id) - result.push( - title: message[:title], - message: message[:message], - type: types[message[:type].to_sym], - archetype: archetypes[:subscription_message], - created_at: message[:created_at], - expired_at: message[:expired_at] - ) - end + id = generate_notice_id(message[:title], message[:created_at]) + result.push( + id: id, + title: message[:title], + message: message[:message], + type: types[message[:type].to_sym], + archetype: archetypes[:subscription_message], + created_at: message[:created_at], + expired_at: message[:expired_at] + ) result end end @@ -298,11 +301,11 @@ class CustomWizard::Notice PluginStore.set(namespace, id, raw_notice) end - def self.list_query(type: nil, archetype: nil, title: nil, include_all: false, include_recently_expired: false, page: nil, visible: false) + def self.list_query(type: nil, archetype: nil, title: nil, include_all: false, page: nil, visible: false) query = PluginStoreRow.where(plugin_name: namespace) query = query.where("(value::json->>'hidden_at') IS NULL") if visible query = query.where("(value::json->>'dismissed_at') IS NULL") unless include_all - query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}") unless include_all + query = query.where("(value::json->>'expired_at') IS NULL") unless include_all query = query.where("(value::json->>'archetype')::integer = ?", archetype) if archetype if type type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?" @@ -313,8 +316,8 @@ class CustomWizard::Notice query.order("value::json->>'expired_at' DESC, value::json->>'updated_at' DESC,value::json->>'dismissed_at' DESC, value::json->>'created_at' DESC") end - def self.list(type: nil, archetype: nil, title: nil, include_all: false, include_recently_expired: false, page: 0, visible: false) - list_query(type: type, archetype: archetype, title: title, include_all: include_all, include_recently_expired: include_recently_expired, page: page, visible: visible) + def self.list(type: nil, archetype: nil, title: nil, include_all: false, page: 0, visible: false) + list_query(type: type, archetype: archetype, title: title, include_all: include_all, page: page, visible: visible) .map { |r| self.new(JSON.parse(r.value).symbolize_keys) } end diff --git a/lib/custom_wizard/notice/connection_error.rb b/lib/custom_wizard/notice/connection_error.rb index 9f78a09e..a1d834c6 100644 --- a/lib/custom_wizard/notice/connection_error.rb +++ b/lib/custom_wizard/notice/connection_error.rb @@ -10,9 +10,9 @@ class CustomWizard::Notice::ConnectionError def create! if attrs = current_error - key = "#{archetype.to_s}_error_#{attrs["id"]}" - attrs['updated_at'] = Time.now - attrs['count'] = attrs['count'].to_i + 1 + key = "#{archetype.to_s}_error_#{attrs[:id]}" + attrs[:updated_at] = Time.now + attrs[:count] = attrs[:count].to_i + 1 else domain = CustomWizard::Notice.send("#{archetype.to_s}_domain") id = SecureRandom.hex(8) @@ -27,7 +27,8 @@ class CustomWizard::Notice::ConnectionError end PluginStore.set(namespace, key, attrs) - @errors = nil + + @current_error = nil end def expire! @@ -54,7 +55,7 @@ class CustomWizard::Notice::ConnectionError def reached_limit? return false unless current_error.present? - current_error['count'].to_i >= limit + current_error[:count].to_i >= limit end def namespace @@ -64,11 +65,13 @@ class CustomWizard::Notice::ConnectionError def current_error(query_only: false) @current_error ||= begin query = PluginStoreRow.where(plugin_name: namespace) - query = query.where("(value::json->>'archetype')::integer = ?", CustomWizard::Notice.archetypes[archetype]) + query = query.where("(value::json->>'archetype')::integer = ?", CustomWizard::Notice.archetypes[archetype.to_sym]) query = query.where("(value::json->>'expired_at') IS NULL") + return nil if !query.exists? return query if query_only - JSON.parse(query.first.value) + + JSON.parse(query.first.value).deep_symbolize_keys end end end diff --git a/spec/components/custom_wizard/notice_spec.rb b/spec/components/custom_wizard/notice_spec.rb index b51305e1..0b34664d 100644 --- a/spec/components/custom_wizard/notice_spec.rb +++ b/spec/components/custom_wizard/notice_spec.rb @@ -6,6 +6,7 @@ describe CustomWizard::Notice do fab!(:user) { Fabricate(:user) } let(:subscription_message) { { + title: "Title of message about subscription", message: "Message about subscription", type: "info", created_at: Time.now - 3.day, @@ -23,7 +24,7 @@ describe CustomWizard::Notice do context "subscription message" do before do freeze_time - stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) described_class.update(skip_plugin: true) end @@ -36,46 +37,73 @@ describe CustomWizard::Notice do it "expires notice if subscription message is expired" do subscription_message[:expired_at] = Time.now - stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) described_class.update(skip_plugin: true) - notice = described_class.list(include_recently_expired: true).first + notice = described_class.list(include_all: true).first expect(notice.expired?).to eq(true) end + + it "dismisses informational subscription notices" do + notice = described_class.list(include_all: true).first + expect(notice.dismissed?).to eq(false) + + notice.dismiss! + expect(notice.dismissed?).to eq(true) + end + + it "dismisses all informational subscription notices" do + 4.times do |index| + subscription_message[:title] += " #{index}" + stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + described_class.update(skip_plugin: true) + end + expect(described_class.list.count).to eq(5) + described_class.dismiss_all + expect(described_class.list.count).to eq(0) + end end context "plugin status" do before do freeze_time - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) described_class.update(skip_subscription: true) end it "converts warning into notice" do notice = described_class.list.first - expect(notice.type).to eq(described_class.types[:plugin_status_warning]) - expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue", domain: described_class.plugin_status_domain)) + expect(notice.type).to eq(described_class.types[:warning]) + expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue.message", domain: described_class.plugin_status_domain)) expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime) end it "expires warning notices if status is recommended or compatible" do plugin_status[:status] = 'compatible' plugin_status[:status_changed_at] = Time.now - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) described_class.update(skip_subscription: true) - notice = described_class.list(type: described_class.types[:plugin_status_warning], include_recently_expired: true).first + notice = described_class.list(type: described_class.types[:warning], include_all: true).first expect(notice.expired?).to eq(true) end + + it "hides plugin status warnings" do + notice = described_class.list.first + expect(notice.hidden?).to eq(false) + + notice.hide! + expect(notice.hidden?).to eq(true) + end end it "lists notices not expired more than a day ago" do subscription_message[:expired_at] = Time.now - 8.hours - stub_request(:get, described_class.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) described_class.update - expect(described_class.list(include_recently_expired: true).length).to eq(2) + expect(described_class.list(include_all: true).length).to eq(2) end context "connection errors" do @@ -84,47 +112,47 @@ describe CustomWizard::Notice do end it "creates an error if connection to notice server fails" do - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) described_class.update(skip_subscription: true) error = CustomWizard::Notice::ConnectionError.new(:plugin_status) - expect(error.errors.exists?).to eq(true) + expect(error.current_error.present?).to eq(true) end it "only creates one connection error per type at a time" do - stub_request(:get, described_class.subscription_messages_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json) - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.subscription_message_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) 5.times { described_class.update } plugin_status_errors = CustomWizard::Notice::ConnectionError.new(:plugin_status) - subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_messages) + subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_message) - expect(plugin_status_errors.errors.length).to eq(1) - expect(subscription_message_errors.errors.length).to eq(1) + expect(plugin_status_errors.current_error[:count]).to eq(5) + expect(subscription_message_errors.current_error[:count]).to eq(5) end it "creates a connection error notice if connection errors reach limit" do - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) error = CustomWizard::Notice::ConnectionError.new(:plugin_status) error.limit.times { described_class.update(skip_subscription: true) } - notice = described_class.list(type: described_class.types[:plugin_status_connection_error]).first + notice = described_class.list(type: described_class.types[:connection_error]).first - expect(error.current_error['count']).to eq(error.limit) - expect(notice.type).to eq(described_class.types[:plugin_status_connection_error]) + expect(error.current_error[:count]).to eq(error.limit) + expect(notice.type).to eq(described_class.types[:connection_error]) end it "expires a connection error notice if connection succeeds" do - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) error = CustomWizard::Notice::ConnectionError.new(:plugin_status) error.limit.times { described_class.update(skip_subscription: true) } - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) described_class.update(skip_subscription: true) - notice = described_class.list(type: described_class.types[:plugin_status_connection_error], include_recently_expired: true).first + notice = described_class.list(type: described_class.types[:connection_error], include_all: true).first - expect(notice.type).to eq(described_class.types[:plugin_status_connection_error]) + expect(notice.type).to eq(described_class.types[:connection_error]) expect(notice.expired_at.present?).to eq(true) end end diff --git a/spec/jobs/update_notices_spec.rb b/spec/jobs/update_notices_spec.rb index d0e5a468..8ba5587f 100644 --- a/spec/jobs/update_notices_spec.rb +++ b/spec/jobs/update_notices_spec.rb @@ -20,7 +20,7 @@ describe Jobs::CustomWizardUpdateNotices do } it "updates the notices" do - stub_request(:get, CustomWizard::Notice.subscription_messages_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) + stub_request(:get, CustomWizard::Notice.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) described_class.new.execute diff --git a/spec/requests/custom_wizard/admin/notice_controller_spec.rb b/spec/requests/custom_wizard/admin/notice_controller_spec.rb index bd174e90..33d03432 100644 --- a/spec/requests/custom_wizard/admin/notice_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/notice_controller_spec.rb @@ -3,29 +3,70 @@ require_relative '../../../plugin_helper' describe CustomWizard::AdminNoticeController do fab!(:admin_user) { Fabricate(:user, admin: true) } + let(:subscription_message_notice) { + { + title: "Title of message about subscription", + message: "Message about subscription", + type: 0, + created_at: Time.now.iso8601(3), + expired_at: nil + } + } + let(:plugin_status_notice) { + { + title: "The Custom Wizard Plugin is incompatibile with the latest version of Discourse.", + message: "Please check the Custom Wizard Plugin status on [localhost:3000](http://localhost:3000) before updating Discourse.", + type: 1, + archetype: 1, + created_at: Time.now.iso8601(3), + expired_at: nil + } + } before do sign_in(admin_user) - @notice = CustomWizard::Notice.new( - message: "Message about subscription", - type: "info", - created_at: Time.now - 3.day, - expired_at: nil - ) - @notice.save end it "lists notices" do + @notice = CustomWizard::Notice.new(subscription_message_notice) + @notice.save + get "/admin/wizards/notice.json" expect(response.status).to eq(200) expect(response.parsed_body.length).to eq(1) end it "dismisses notices" do - put "/admin/wizards/notice/#{@notice.id}.json" + @notice = CustomWizard::Notice.new(subscription_message_notice) + @notice.save + + put "/admin/wizards/notice/#{@notice.id}/dismiss.json" expect(response.status).to eq(200) updated = CustomWizard::Notice.find(@notice.id) expect(updated.dismissed?).to eq(true) end + + it "dismisses all notices" do + 5.times do |index| + subscription_message_notice[:title] += " #{index}" + @notice = CustomWizard::Notice.new(subscription_message_notice) + @notice.save + end + + put "/admin/wizards/notice/dismiss.json" + expect(response.status).to eq(200) + expect(CustomWizard::Notice.list.size).to eq(0) + end + + it "hides notices" do + @notice = CustomWizard::Notice.new(plugin_status_notice) + @notice.save + + put "/admin/wizards/notice/#{@notice.id}/hide.json" + expect(response.status).to eq(200) + + updated = CustomWizard::Notice.find(@notice.id) + expect(updated.hidden?).to eq(true) + end end From 853634be27963acf154c9653fb9a19f22884af7c Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 2 Nov 2021 15:39:38 +0800 Subject: [PATCH 096/556] Fix failing specs --- spec/components/custom_wizard/wizard_spec.rb | 2 +- spec/jobs/update_notices_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 9cccff97..8ee19c33 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -186,7 +186,7 @@ describe CustomWizard::Wizard do it "lists the site categories" do Site.clear_cache - expect(@wizard.categories.length).to eq(1) + expect(@wizard.categories.length > 0).to eq(true) end context "submissions" do diff --git a/spec/jobs/update_notices_spec.rb b/spec/jobs/update_notices_spec.rb index 8ba5587f..df0697b8 100644 --- a/spec/jobs/update_notices_spec.rb +++ b/spec/jobs/update_notices_spec.rb @@ -21,7 +21,7 @@ describe Jobs::CustomWizardUpdateNotices do it "updates the notices" do stub_request(:get, CustomWizard::Notice.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) - stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: { status: plugin_status }.to_json) + stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) described_class.new.execute expect(CustomWizard::Notice.list.length).to eq(2) From 49538d554df1597141a78005eadde3b13bb7c46c Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Wed, 17 Nov 2021 20:48:11 +0800 Subject: [PATCH 097/556] Linting --- .../components/wizard-notice-row.js.es6 | 13 ++-- .../discourse/components/wizard-notice.js.es6 | 14 +++-- .../custom-wizard-critical-notice.js.es6 | 12 ++-- .../controllers/admin-wizards-notices.js.es6 | 17 +++--- .../controllers/admin-wizards.js.es6 | 4 +- .../discourse/helpers/notice-badge.js.es6 | 18 +++--- .../initializers/custom-wizard-edits.js.es6 | 26 ++++---- .../discourse/mixins/notice-message.js.es6 | 55 +++++++++-------- .../models/custom-wizard-notice.js.es6 | 60 ++++++++++--------- .../routes/admin-wizards-notices.js.es6 | 4 +- .../discourse/routes/admin-wizards.js.es6 | 2 +- .../templates/admin-wizards-notices.hbs | 2 +- .../components/wizard-notice-row.hbs | 2 +- assets/stylesheets/admin/admin.scss | 12 ++-- controllers/custom_wizard/admin/notice.rb | 4 +- lib/custom_wizard/notice.rb | 2 +- 16 files changed, 135 insertions(+), 112 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-notice-row.js.es6 b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 index 9c099b39..ada4384d 100644 --- a/assets/javascripts/discourse/components/wizard-notice-row.js.es6 +++ b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 @@ -4,11 +4,16 @@ import NoticeMessage from "../mixins/notice-message"; export default Component.extend(NoticeMessage, { tagName: "tr", attributeBindings: ["notice.id:data-notice-id"], - classNameBindings: [":wizard-notice-row", "notice.typeClass", "notice.expired:expired", "notice.dismissed:dismissed"], + classNameBindings: [ + ":wizard-notice-row", + "notice.typeClass", + "notice.expired:expired", + "notice.dismissed:dismissed", + ], actions: { dismiss() { this.notice.dismiss(); - } - } -}); \ No newline at end of file + }, + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 index cac3e4eb..ca6b7658 100644 --- a/assets/javascripts/discourse/components/wizard-notice.js.es6 +++ b/assets/javascripts/discourse/components/wizard-notice.js.es6 @@ -3,7 +3,13 @@ import NoticeMessage from "../mixins/notice-message"; export default Component.extend(NoticeMessage, { attributeBindings: ["notice.id:data-notice-id"], - classNameBindings: [':wizard-notice', 'notice.typeClass', 'notice.dismissed:dismissed', 'notice.expired:expired', 'notice.hidden:hidden'], + classNameBindings: [ + ":wizard-notice", + "notice.typeClass", + "notice.dismissed:dismissed", + "notice.expired:expired", + "notice.hidden:hidden", + ], actions: { dismiss() { @@ -14,10 +20,10 @@ export default Component.extend(NoticeMessage, { }, hide() { - this.set('hiding', true); + this.set("hiding", true); this.notice.hide().then(() => { - this.set('hiding', false); + this.set("hiding", false); }); }, - } + }, }); diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 index 0bb252e9..803e58a4 100644 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 +++ b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 @@ -6,14 +6,14 @@ export default { }, setupComponent(attrs, component) { - const controller = getOwner(this).lookup('controller:admin-dashboard'); + const controller = getOwner(this).lookup("controller:admin-dashboard"); - component.set('notices', controller.get('customWizardCriticalNotices')); - controller.addObserver('customWizardCriticalNotices.[]', () => { + component.set("notices", controller.get("customWizardCriticalNotices")); + controller.addObserver("customWizardCriticalNotices.[]", () => { if (this._state === "destroying") { return; } - component.set('notices', controller.get('customWizardCriticalNotices')); + component.set("notices", controller.get("customWizardCriticalNotices")); }); - } -}; \ No newline at end of file + }, +}; diff --git a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 index 0f67f878..1721e699 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 @@ -15,9 +15,9 @@ export default Controller.extend({ loadingMore: false, canLoadMore: true, - @discourseComputed('notices.[]', 'notices.@each.dismissed') + @discourseComputed("notices.[]", "notices.@each.dismissed") allDismisssed(notices) { - return notices.every(n => !n.canDismiss || n.dismissed); + return notices.every((n) => !n.canDismiss || n.dismissed); }, loadMoreNotices() { @@ -35,7 +35,7 @@ export default Controller.extend({ } this.get("notices").pushObjects( - A(result.notices.map(notice => CustomWizardNotice.create(notice))) + A(result.notices.map((notice) => CustomWizardNotice.create(notice))) ); }) .finally(() => this.set("loadingMore", false)); @@ -56,12 +56,13 @@ export default Controller.extend({ I18n.t("yes_value"), (result) => { if (result) { - this.set('loadingMore', true); - CustomWizardNotice.dismissAll() - .finally(() => this.set("loadingMore", false)); + this.set("loadingMore", true); + CustomWizardNotice.dismissAll().finally(() => + this.set("loadingMore", false) + ); } } ); - } - } + }, + }, }); diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index e2672fe4..33841460 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -17,10 +17,10 @@ export default Controller.extend({ this.adminWizardsNotices.setProperties({ notices: A(), page: 0, - canLoadMore: true + canLoadMore: true, }); this.adminWizardsNotices.loadMoreNotices(); } }); - } + }, }); diff --git a/assets/javascripts/discourse/helpers/notice-badge.js.es6 b/assets/javascripts/discourse/helpers/notice-badge.js.es6 index bc5df4a6..ea32b462 100644 --- a/assets/javascripts/discourse/helpers/notice-badge.js.es6 +++ b/assets/javascripts/discourse/helpers/notice-badge.js.es6 @@ -4,38 +4,40 @@ import I18n from "I18n"; import { registerUnbound } from "discourse-common/lib/helpers"; import { htmlSafe } from "@ember/template"; -registerUnbound("notice-badge", function(attrs) { - let tag = attrs.url ? 'a' : 'div'; - let attrStr = ''; +registerUnbound("notice-badge", function (attrs) { + let tag = attrs.url ? "a" : "div"; + let attrStr = ""; if (attrs.title) { attrStr += `title='${I18n.t(attrs.title)}'`; } if (attrs.url) { attrStr += `href='${attrs.url}'`; } - let html = `<${tag} class="${attrs.class ? `${attrs.class} ` : ''}notice-badge" ${attrStr}>`; + let html = `<${tag} class="${ + attrs.class ? `${attrs.class} ` : "" + }notice-badge" ${attrStr}>`; if (attrs.icon) { html += iconHTML(attrs.icon); } if (attrs.label) { if (attrs.icon) { - html += ' '; + html += " "; } html += `${I18n.t(attrs.label)}`; } if (attrs.date) { if (attrs.icon || attrs.label) { - html += ' '; + html += " "; } let dateAttrs = {}; if (attrs.leaveAgo) { dateAttrs = { format: "medium", - leaveAgo: true + leaveAgo: true, }; } html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs); } html += ``; return htmlSafe(html); -}); \ No newline at end of file +}); diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 774acca3..e67f1ebd 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -22,16 +22,16 @@ export default { }; withPluginApi("0.8.36", (api) => { - api.modifyClass('route:admin-dashboard', { + api.modifyClass("route:admin-dashboard", { setupController(controller) { this._super(...arguments); controller.loadCriticalNotices(); controller.subscribe(); - } + }, }); - api.modifyClass('controller:admin-dashboard', { + api.modifyClass("controller:admin-dashboard", { criticalNotices: A(), unsubscribe() { @@ -41,7 +41,6 @@ export default { subscribe() { this.unsubscribe(); this.messageBus.subscribe("/custom-wizard/notices", (data) => { - if (isPresent(data.active_notice_count)) { this.loadCriticalNotices(); } @@ -50,19 +49,18 @@ export default { loadCriticalNotices() { CustomWizardNotice.list({ - type: [ - 'connection_error', - 'warning' - ], - archetype: 'plugin_status', - visible: true - }).then(result => { + type: ["connection_error", "warning"], + archetype: "plugin_status", + visible: true, + }).then((result) => { if (result.notices && result.notices.length) { - const criticalNotices = A(result.notices.map(n => CustomWizardNotice.create(n))); - this.set('customWizardCriticalNotices', criticalNotices); + const criticalNotices = A( + result.notices.map((n) => CustomWizardNotice.create(n)) + ); + this.set("customWizardCriticalNotices", criticalNotices); } }); - } + }, }); }); }, diff --git a/assets/javascripts/discourse/mixins/notice-message.js.es6 b/assets/javascripts/discourse/mixins/notice-message.js.es6 index 492df643..76e311bb 100644 --- a/assets/javascripts/discourse/mixins/notice-message.js.es6 +++ b/assets/javascripts/discourse/mixins/notice-message.js.es6 @@ -6,7 +6,7 @@ import { createPopper } from "@popperjs/core"; export default Mixin.create({ showCookedMessage: false, - didReceiveAttrs(){ + didReceiveAttrs() { const message = this.notice.message; cookAsync(message).then((cooked) => { this.set("cookedMessage", cooked); @@ -14,27 +14,24 @@ export default Mixin.create({ }, createMessageModal() { - let container = this.element.querySelector('.notice-message'); - let modal = this.element.querySelector('.cooked-notice-message'); + let container = this.element.querySelector(".notice-message"); + let modal = this.element.querySelector(".cooked-notice-message"); - this._popper = createPopper( - container, - modal, { - strategy: "absolute", - placement: "bottom-start", - modifiers: [ - { - name: "preventOverflow", + this._popper = createPopper(container, modal, { + strategy: "absolute", + placement: "bottom-start", + modifiers: [ + { + name: "preventOverflow", + }, + { + name: "offset", + options: { + offset: [0, 5], }, - { - name: "offset", - options: { - offset: [0, 5], - }, - }, - ], - } - ); + }, + ], + }); }, didInsertElement() { @@ -46,10 +43,16 @@ export default Mixin.create({ }, documentClick(event) { - if (this._state === "destroying") { return; } + if (this._state === "destroying") { + return; + } - if (!event.target.closest(`[data-notice-id="${this.notice.id}"] .notice-message`)) { - this.set('showCookedMessage', false); + if ( + !event.target.closest( + `[data-notice-id="${this.notice.id}"] .notice-message` + ) + ) { + this.set("showCookedMessage", false); } }, @@ -60,6 +63,6 @@ export default Mixin.create({ if (this.showCookedMessage) { scheduleOnce("afterRender", this, this.createMessageModal); } - } - } -}); \ No newline at end of file + }, + }, +}); diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 index 29e30628..035e2ad5 100644 --- a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 @@ -7,62 +7,68 @@ import { dasherize } from "@ember/string"; import I18n from "I18n"; const CustomWizardNotice = EmberObject.extend({ - expired: notEmpty('expired_at'), - dismissed: notEmpty('dismissed_at'), - hidden: notEmpty('hidden_at'), - notHidden: not('hidden'), - notDismissed: not('dismissed'), - canDismiss: and('dismissable', 'notDismissed'), - canHide: and('can_hide', 'notHidden'), + expired: notEmpty("expired_at"), + dismissed: notEmpty("dismissed_at"), + hidden: notEmpty("hidden_at"), + notHidden: not("hidden"), + notDismissed: not("dismissed"), + canDismiss: and("dismissable", "notDismissed"), + canHide: and("can_hide", "notHidden"), - @discourseComputed('type') + @discourseComputed("type") typeClass(type) { return dasherize(type); }, - @discourseComputed('type') + @discourseComputed("type") typeLabel(type) { return I18n.t(`admin.wizard.notice.type.${type}`); }, dismiss() { - if (!this.get('canDismiss')) { + if (!this.get("canDismiss")) { return; } - return ajax(`/admin/wizards/notice/${this.get('id')}/dismiss`, { type: 'PUT' }).then(result => { - if (result.success) { - this.set('dismissed_at', result.dismissed_at); - } - }).catch(popupAjaxError); + return ajax(`/admin/wizards/notice/${this.get("id")}/dismiss`, { + type: "PUT", + }) + .then((result) => { + if (result.success) { + this.set("dismissed_at", result.dismissed_at); + } + }) + .catch(popupAjaxError); }, hide() { - if (!this.get('canHide')) { + if (!this.get("canHide")) { return; } - return ajax(`/admin/wizards/notice/${this.get('id')}/hide`, { type: 'PUT' }).then(result => { - if (result.success) { - this.set('hidden_at', result.hidden_at); - } - }).catch(popupAjaxError); - } + return ajax(`/admin/wizards/notice/${this.get("id")}/hide`, { type: "PUT" }) + .then((result) => { + if (result.success) { + this.set("hidden_at", result.hidden_at); + } + }) + .catch(popupAjaxError); + }, }); CustomWizardNotice.reopenClass({ list(data = {}) { - return ajax('/admin/wizards/notice', { + return ajax("/admin/wizards/notice", { type: "GET", - data + data, }).catch(popupAjaxError); }, dismissAll() { - return ajax('/admin/wizards/notice/dismiss', { - type: "PUT" + return ajax("/admin/wizards/notice/dismiss", { + type: "PUT", }).catch(popupAjaxError); - } + }, }); export default CustomWizardNotice; diff --git a/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 index a329ce95..1d8b7cc8 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 @@ -9,7 +9,9 @@ export default DiscourseRoute.extend({ setupController(controller, model) { controller.setProperties({ - notices: A(model.notices.map(notice => CustomWizardNotice.create(notice))), + notices: A( + model.notices.map((notice) => CustomWizardNotice.create(notice)) + ), }); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index 2bbc73a9..5c39c0d6 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -23,5 +23,5 @@ export default DiscourseRoute.extend({ if (transition.targetName === "adminWizards.index") { this.transitionTo("adminWizardsWizard"); } - } + }, }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-notices.hbs b/assets/javascripts/discourse/templates/admin-wizards-notices.hbs index d522c1a5..039afe49 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-notices.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-notices.hbs @@ -46,4 +46,4 @@ {{conditional-loading-spinner condition=loadingMore}} {{/load-more}} -
\ No newline at end of file +
diff --git a/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs b/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs index 7f97b250..cc22a42e 100644 --- a/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs @@ -27,4 +27,4 @@ {{else}} {{i18n "admin.wizard.notice.active"}} {{/if}} - \ No newline at end of file + diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index e3790465..36184088 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -926,7 +926,7 @@ $error: #ef1700; } .wizard-notice { - padding: .75em; + padding: 0.75em; margin-bottom: 1em; border: 1px solid var(--primary-low); @@ -941,7 +941,7 @@ $error: #ef1700; } .notice-badge { - padding: 0 .5em; + padding: 0 0.5em; } .notice-header { @@ -957,7 +957,7 @@ $error: #ef1700; align-items: center; .notice-badge { - margin-left: .5em; + margin-left: 0.5em; } } } @@ -1022,11 +1022,11 @@ $error: #ef1700; position: absolute; top: -8px; right: -8px; - font-size: .7em; + font-size: 0.7em; } a.show-notice-message { - padding: .25em .5em; + padding: 0.25em 0.5em; color: var(--primary); } @@ -1057,4 +1057,4 @@ a.show-notice-message { margin: 0; } } -} \ No newline at end of file +} diff --git a/controllers/custom_wizard/admin/notice.rb b/controllers/custom_wizard/admin/notice.rb index d6c43b5f..81ae00da 100644 --- a/controllers/custom_wizard/admin/notice.rb +++ b/controllers/custom_wizard/admin/notice.rb @@ -12,7 +12,7 @@ class CustomWizard::AdminNoticeController < CustomWizard::AdminController if type if type.is_a?(Array) - type = type.map { |type| CustomWizard::Notice.types[type.to_sym] } + type = type.map { |t| CustomWizard::Notice.types[t.to_sym] } else type = CustomWizard::Notice.types[type.to_sym] end @@ -20,7 +20,7 @@ class CustomWizard::AdminNoticeController < CustomWizard::AdminController if archetype if archetype.is_a?(Array) - archetype = archetype.map { |type| CustomWizard::Notice.archetypes[archetype.to_sym] } + archetype = archetype.map { |t| CustomWizard::Notice.archetypes[archetype.to_sym] } else archetype = CustomWizard::Notice.archetypes[archetype.to_sym] end diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index a07e1443..1fcb5041 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -308,7 +308,7 @@ class CustomWizard::Notice query = query.where("(value::json->>'expired_at') IS NULL") unless include_all query = query.where("(value::json->>'archetype')::integer = ?", archetype) if archetype if type - type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?" + type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?" query = query.where(type_query_str, type) end query = query.where("(value::json->>'title')::text = ?", title) if title From 559d3f4f193369a537c2777b1c89c73f0f7ec503 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Thu, 18 Nov 2021 16:05:32 +0800 Subject: [PATCH 098/556] Use iso times for the notice timestamps --- lib/custom_wizard/notice.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index 1fcb5041..6f5c3045 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -4,6 +4,8 @@ class CustomWizard::Notice include ActiveModel::Serialization PLUGIN_STATUS_DOMAINS = { + "main" => "plugins.discourse.pavilion.tech", + "master" => "plugins.discourse.pavilion.tech", "tests-passed" => "plugins.discourse.pavilion.tech", "stable" => "stable.plugins.discourse.pavilion.tech" } @@ -42,21 +44,21 @@ class CustomWizard::Notice def dismiss! if dismissable? - self.dismissed_at = Time.now + self.dismissed_at = DateTime.now.iso8601(3) self.save_and_publish end end def hide! if can_hide? - self.hidden_at = Time.now + self.hidden_at = DateTime.now.iso8601(3) self.save_and_publish end end def expire! if !expired? - self.expired_at = Time.now + self.expired_at = DateTime.now.iso8601(3) self.save_and_publish end end @@ -154,7 +156,7 @@ class CustomWizard::Notice if notices.any? notices.each do |notice_data| notice = new(notice_data) - notice.retrieved_at = Time.now + notice.retrieved_at = DateTime.now.iso8601(3) notice.save end @@ -218,7 +220,7 @@ class CustomWizard::Notice if notices.any? notice = notices.first - notice.updated_at = Time.now + notice.updated_at = DateTime.now.iso8601(3) notice.save else notice = new( @@ -226,8 +228,8 @@ class CustomWizard::Notice message: I18n.t("wizard.notice.#{archetype.to_s}.connection_error.message", domain: domain), archetype: archetypes[archetype.to_sym], type: types[:connection_error], - created_at: Time.now, - updated_at: Time.now + created_at: DateTime.now.iso8601(3), + updated_at: DateTime.now.iso8601(3) ) notice.save end @@ -240,7 +242,8 @@ class CustomWizard::Notice end def self.subscription_message_domain - (Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGE_DOMAIN + return LOCALHOST_DOMAIN if (Rails.env.test? || Rails.env.development?) + SUBSCRIPTION_MESSAGE_DOMAIN end def self.subscription_message_url From e1a746ca28d5f1516e193034f287c5e85f7d55e3 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Thu, 18 Nov 2021 16:32:23 +0800 Subject: [PATCH 099/556] Add pluginId and console.logs --- .../discourse/controllers/admin-wizards-notices.js.es6 | 1 + assets/javascripts/discourse/controllers/admin-wizards.js.es6 | 1 + .../discourse/initializers/custom-wizard-edits.js.es6 | 3 +++ 3 files changed, 5 insertions(+) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 index 1721e699..06c61054 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 @@ -29,6 +29,7 @@ export default Controller.extend({ CustomWizardNotice.list({ page, include_all: true }) .then((result) => { + console.log('loadMoreNotices result', result); if (result.notices.length === 0) { this.set("canLoadMore", false); return; diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index 33841460..15a98525 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -14,6 +14,7 @@ export default Controller.extend({ this.messageBus.subscribe("/custom-wizard/notices", (data) => { if (isPresent(data.active_notice_count)) { this.set("activeNoticeCount", data.active_notice_count); + console.log('adminWizards resetting'); this.adminWizardsNotices.setProperties({ notices: A(), page: 0, diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index e67f1ebd..a74dafdc 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -23,6 +23,8 @@ export default { withPluginApi("0.8.36", (api) => { api.modifyClass("route:admin-dashboard", { + pluginId: "custom-wizard", + setupController(controller) { this._super(...arguments); @@ -32,6 +34,7 @@ export default { }); api.modifyClass("controller:admin-dashboard", { + pluginId: "custom-wizard", criticalNotices: A(), unsubscribe() { From fac8d821cf9ede74e170be058844bb44c3dd656c Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Thu, 18 Nov 2021 17:07:15 +0800 Subject: [PATCH 100/556] publish notice count at the end of the update cycle --- .../discourse/controllers/admin-wizards-notices.js.es6 | 1 - .../javascripts/discourse/controllers/admin-wizards.js.es6 | 1 - lib/custom_wizard/notice.rb | 6 ++---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 index 06c61054..1721e699 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 @@ -29,7 +29,6 @@ export default Controller.extend({ CustomWizardNotice.list({ page, include_all: true }) .then((result) => { - console.log('loadMoreNotices result', result); if (result.notices.length === 0) { this.set("canLoadMore", false); return; diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index 15a98525..33841460 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -14,7 +14,6 @@ export default Controller.extend({ this.messageBus.subscribe("/custom-wizard/notices", (data) => { if (isPresent(data.active_notice_count)) { this.set("activeNoticeCount", data.active_notice_count); - console.log('adminWizards resetting'); this.adminWizardsNotices.setProperties({ notices: A(), page: 0, diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index 6f5c3045..abd27559 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -159,9 +159,9 @@ class CustomWizard::Notice notice.retrieved_at = DateTime.now.iso8601(3) notice.save end - - publish_notice_count end + + publish_notice_count end def self.publish_notice_count @@ -233,8 +233,6 @@ class CustomWizard::Notice ) notice.save end - - publish_notice_count end def self.request_plugin_status? From 7af77533bacccbc97a5d5619bbbcd233187e3ead Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Thu, 25 Nov 2021 14:38:16 +0800 Subject: [PATCH 101/556] Add https to notice requests --- lib/custom_wizard/notice.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb index abd27559..9d5c1edf 100644 --- a/lib/custom_wizard/notice.rb +++ b/lib/custom_wizard/notice.rb @@ -245,7 +245,7 @@ class CustomWizard::Notice end def self.subscription_message_url - "http://#{subscription_message_domain}/subscription-server/messages.json" + "https://#{subscription_message_domain}/subscription-server/messages.json" end def self.plugin_status_domain @@ -254,7 +254,7 @@ class CustomWizard::Notice end def self.plugin_status_url - "http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard" + "https://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard" end def self.request(archetype) From f0f16ce4dc3edc53fba83605d5602a7e4b5b1954 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:09 -0800 Subject: [PATCH 102/556] New translations client.en.yml (Romanian) --- config/locales/client.ro.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index a5136d01..170ae794 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -179,6 +179,7 @@ ro: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 47df65292c70dbfc6fd129b547cb2977962ea162 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:10 -0800 Subject: [PATCH 103/556] New translations client.en.yml (Ukrainian) --- config/locales/client.uk.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 7c1bf2b1..1e01bd64 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -179,6 +179,7 @@ uk: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 87f7b2210399e9008017a055516e86d2b2c66b0f Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:11 -0800 Subject: [PATCH 104/556] New translations client.en.yml (Polish) --- config/locales/client.pl.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.pl.yml b/config/locales/client.pl.yml index b665aaf5..2327c227 100644 --- a/config/locales/client.pl.yml +++ b/config/locales/client.pl.yml @@ -179,6 +179,7 @@ pl: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 71c101290c8b920e5dca8a114969c9f429c5b346 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:12 -0800 Subject: [PATCH 105/556] New translations client.en.yml (Portuguese) --- config/locales/client.pt.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index eba3a5ac..d7975b3b 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -179,6 +179,7 @@ pt: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 91c9644fb103cb6bbd01d357610fc65e4718a60b Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:13 -0800 Subject: [PATCH 106/556] New translations client.en.yml (Russian) --- config/locales/client.ru.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 85e8ff50..a236ff0b 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -179,6 +179,7 @@ ru: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From b1d5c888c4dac353ea085986621262c2d1190ddd Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:14 -0800 Subject: [PATCH 107/556] New translations client.en.yml (Slovak) --- config/locales/client.sk.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index c762d3bb..5882aca4 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -179,6 +179,7 @@ sk: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 5b3550bdde9fd344bfb6f1147a6977aa2188abf8 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:15 -0800 Subject: [PATCH 108/556] New translations client.en.yml (Slovenian) --- config/locales/client.sl.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml index 80912cb1..f5ad31f9 100644 --- a/config/locales/client.sl.yml +++ b/config/locales/client.sl.yml @@ -179,6 +179,7 @@ sl: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From bed0483e6427cda2c6337336340a78dbe2b3f447 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:16 -0800 Subject: [PATCH 109/556] New translations client.en.yml (Albanian) --- config/locales/client.sq.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 089e4fce..f22ac487 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -179,6 +179,7 @@ sq: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From ec650a1f67a391b3bfe29705fd3bb9d256a16691 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:18 -0800 Subject: [PATCH 110/556] New translations client.en.yml (Serbian (Cyrillic)) --- config/locales/client.sr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml index 5db98108..19249821 100644 --- a/config/locales/client.sr.yml +++ b/config/locales/client.sr.yml @@ -179,6 +179,7 @@ sr-Cyrl: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 3df6fcbf0c309a40bd85dac14c0a55f9ae5744bb Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:18 -0800 Subject: [PATCH 111/556] New translations client.en.yml (Swedish) --- config/locales/client.sv.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index e19dbb9c..108d898e 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -179,6 +179,7 @@ sv: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 5e231c3e7b4da78620fefa1292e339520dcde768 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:19 -0800 Subject: [PATCH 112/556] New translations client.en.yml (Turkish) --- config/locales/client.tr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.tr.yml b/config/locales/client.tr.yml index b33e9e0b..e35787ca 100644 --- a/config/locales/client.tr.yml +++ b/config/locales/client.tr.yml @@ -179,6 +179,7 @@ tr: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From a5fd54a3ee62916fb601f85f6fa9d585993436ef Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:21 -0800 Subject: [PATCH 113/556] New translations client.en.yml (Chinese Simplified) --- config/locales/client.zh.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/locales/client.zh.yml b/config/locales/client.zh.yml index a8950f7b..6e81737f 100644 --- a/config/locales/client.zh.yml +++ b/config/locales/client.zh.yml @@ -1,4 +1,4 @@ -zh-TW: +zh-CN: js: wizard: complete_custom: "Complete the {{name}}" @@ -179,6 +179,7 @@ zh-TW: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 69942a8a58f06a33c9bd66f3174bad2e700ed88d Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:22 -0800 Subject: [PATCH 114/556] New translations client.en.yml (Norwegian) --- config/locales/client.no.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.no.yml b/config/locales/client.no.yml index 4e0d5cab..3d81a003 100644 --- a/config/locales/client.no.yml +++ b/config/locales/client.no.yml @@ -179,6 +179,7 @@ property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 1d1ac820f5e83edd58e59d4e739858d76d67b58c Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:22 -0800 Subject: [PATCH 115/556] New translations client.en.yml (Chinese Traditional) --- config/locales/client.zh.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/client.zh.yml b/config/locales/client.zh.yml index 6e81737f..e95c7bd1 100644 --- a/config/locales/client.zh.yml +++ b/config/locales/client.zh.yml @@ -1,4 +1,4 @@ -zh-CN: +zh-TW: js: wizard: complete_custom: "Complete the {{name}}" From 8dc59745e63c97cbf5e5699f476b9ad6b9422f32 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:23 -0800 Subject: [PATCH 116/556] New translations client.en.yml (Zulu) --- config/locales/client.zu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.zu.yml b/config/locales/client.zu.yml index b147ac1f..99e40b64 100644 --- a/config/locales/client.zu.yml +++ b/config/locales/client.zu.yml @@ -179,6 +179,7 @@ zu: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From a0688b81a241425f39eaeb325429d4d88e46a893 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:24 -0800 Subject: [PATCH 117/556] New translations client.en.yml (Urdu (Pakistan)) --- config/locales/client.ur.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml index 2469e86c..5d3becad 100644 --- a/config/locales/client.ur.yml +++ b/config/locales/client.ur.yml @@ -179,6 +179,7 @@ ur: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 3a922f7a93da52a4656a7e0854c4d2363f86da5d Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:25 -0800 Subject: [PATCH 118/556] New translations client.en.yml (Vietnamese) --- config/locales/client.vi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index 5dd7ce98..d86819ea 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -179,6 +179,7 @@ vi: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 1b69c1919a5c9413ae415e6606d4685974416d7e Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:26 -0800 Subject: [PATCH 119/556] New translations client.en.yml (Galician) --- config/locales/client.gl.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index 0767376d..6c491a50 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -179,6 +179,7 @@ gl: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From a75fc1f2d4cd4a5d9bcb22fd843ef9ceec113ca8 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:28 -0800 Subject: [PATCH 120/556] New translations client.en.yml (Icelandic) --- config/locales/client.is.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.is.yml b/config/locales/client.is.yml index 383c3f0a..a0c1848d 100644 --- a/config/locales/client.is.yml +++ b/config/locales/client.is.yml @@ -179,6 +179,7 @@ is: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 7bea8c1c129077cd160b66a8547261be0f2345ab Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:29 -0800 Subject: [PATCH 121/556] New translations client.en.yml (Indonesian) --- config/locales/client.id.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index 5a24a279..3d615a44 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -179,6 +179,7 @@ id: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From f967f792d22218999653ab4b3debeefb1e3da9a5 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:30 -0800 Subject: [PATCH 122/556] New translations client.en.yml (Persian) --- config/locales/client.fa.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.fa.yml b/config/locales/client.fa.yml index 68f2a0c8..f0547027 100644 --- a/config/locales/client.fa.yml +++ b/config/locales/client.fa.yml @@ -179,6 +179,7 @@ fa: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 24be61e593090d4a66096825c5c0acd6bb52bafc Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:31 -0800 Subject: [PATCH 123/556] New translations client.en.yml (Khmer) --- config/locales/client.km.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.km.yml b/config/locales/client.km.yml index d64b2d1c..6ead85e8 100644 --- a/config/locales/client.km.yml +++ b/config/locales/client.km.yml @@ -179,6 +179,7 @@ km: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 27396538ad29e897d7cda008b4f320ff71d2071c Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:32 -0800 Subject: [PATCH 124/556] New translations client.en.yml (Punjabi) --- config/locales/client.pa.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.pa.yml b/config/locales/client.pa.yml index 00236d10..69360f32 100644 --- a/config/locales/client.pa.yml +++ b/config/locales/client.pa.yml @@ -179,6 +179,7 @@ pa: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 0f58fe5aab06e1cf19dcb33b4521ab69209985ca Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:33 -0800 Subject: [PATCH 125/556] New translations client.en.yml (Dutch) --- config/locales/client.nl.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 9e8cfabb..409b101c 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -179,6 +179,7 @@ nl: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 681ba4e5b474b23cf8d73d511254a9a1479f7e0b Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:34 -0800 Subject: [PATCH 126/556] New translations client.en.yml (French) --- config/locales/client.fr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 9667ee84..2b2a31c1 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -179,6 +179,7 @@ fr: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 15539eb0c3865d54de8f61e412f28eab708dbac5 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:35 -0800 Subject: [PATCH 127/556] New translations client.en.yml (Basque) --- config/locales/client.eu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.eu.yml b/config/locales/client.eu.yml index a12a029c..37e43f90 100644 --- a/config/locales/client.eu.yml +++ b/config/locales/client.eu.yml @@ -179,6 +179,7 @@ eu: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 1c4422d7e2f2ef23e54042f7cf41d6ea88a3da5a Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:36 -0800 Subject: [PATCH 128/556] New translations client.en.yml (Spanish) --- config/locales/client.es.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index b9d08e0b..7160f527 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -179,6 +179,7 @@ es: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 22b9698f2b9be71ebff14228d76175e6556ab667 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:37 -0800 Subject: [PATCH 129/556] New translations client.en.yml (Afrikaans) --- config/locales/client.af.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.af.yml b/config/locales/client.af.yml index 2f399b99..2f25e7be 100644 --- a/config/locales/client.af.yml +++ b/config/locales/client.af.yml @@ -179,6 +179,7 @@ af: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From c57c55112905e203e79bdf186c65626133fcefd4 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:38 -0800 Subject: [PATCH 130/556] New translations client.en.yml (Arabic) --- config/locales/client.ar.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index b7253bba..ae29c2b7 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -179,6 +179,7 @@ ar: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From df9494e461d00f62191ef1579393f608173a063c Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:39 -0800 Subject: [PATCH 131/556] New translations client.en.yml (Belarusian) --- config/locales/client.be.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.be.yml b/config/locales/client.be.yml index f6fd6764..0467b14f 100644 --- a/config/locales/client.be.yml +++ b/config/locales/client.be.yml @@ -179,6 +179,7 @@ be: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From eb9c44348461e1172c93e4f5ff4da825c21ce521 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:40 -0800 Subject: [PATCH 132/556] New translations client.en.yml (Bulgarian) --- config/locales/client.bg.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml index 058baf4d..038ccabd 100644 --- a/config/locales/client.bg.yml +++ b/config/locales/client.bg.yml @@ -179,6 +179,7 @@ bg: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 76bf5631e14ea3e01a390177e6ab33f6316b461f Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:41 -0800 Subject: [PATCH 133/556] New translations client.en.yml (Catalan) --- config/locales/client.ca.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index 7d80c17b..869469a7 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -179,6 +179,7 @@ ca: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 421e64ac463a6a30de9b5a27078bf76ea5da6400 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:42 -0800 Subject: [PATCH 134/556] New translations client.en.yml (Czech) --- config/locales/client.cs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 018e4236..dac08b72 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -179,6 +179,7 @@ cs: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 18b28f5b804f88f2e41f4beca02247dc794a0f97 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:43 -0800 Subject: [PATCH 135/556] New translations client.en.yml (Danish) --- config/locales/client.da.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 4b254517..7f297ab0 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -179,6 +179,7 @@ da: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 3f56dc9134b33103d019ad179548e868e4cf909c Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:44 -0800 Subject: [PATCH 136/556] New translations client.en.yml (German) --- config/locales/client.de.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index a69bf37f..bfec2666 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -179,6 +179,7 @@ de: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From c07337de2e3dc7820b6602970e2281324e0fe689 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:45 -0800 Subject: [PATCH 137/556] New translations client.en.yml (Greek) --- config/locales/client.el.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index 5a49f0c8..ac589276 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -179,6 +179,7 @@ el: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From a57d1b50438b265c5a08633213792a641145ee70 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:46 -0800 Subject: [PATCH 138/556] New translations client.en.yml (Finnish) --- config/locales/client.fi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 95a82ae9..ba2998cc 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -179,6 +179,7 @@ fi: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 12c37bd733bfc8b1351ee217f3cc1743261ede51 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:47 -0800 Subject: [PATCH 139/556] New translations client.en.yml (Mongolian) --- config/locales/client.mn.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.mn.yml b/config/locales/client.mn.yml index f6493025..8c369e43 100644 --- a/config/locales/client.mn.yml +++ b/config/locales/client.mn.yml @@ -179,6 +179,7 @@ mn: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 9d95ec881151c6865675fdfa7dda584b41ac86cb Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:48 -0800 Subject: [PATCH 140/556] New translations client.en.yml (Hebrew) --- config/locales/client.he.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 4fc73f73..a6045c25 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -179,6 +179,7 @@ he: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 0dc9cdd86a091867d777361af9ed74e868d44165 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:49 -0800 Subject: [PATCH 141/556] New translations client.en.yml (Hungarian) --- config/locales/client.hu.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml index a70682f0..0b525b90 100644 --- a/config/locales/client.hu.yml +++ b/config/locales/client.hu.yml @@ -179,6 +179,7 @@ hu: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 462b8fef724f292166e5828e741481d5b385af51 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:50 -0800 Subject: [PATCH 142/556] New translations client.en.yml (Armenian) --- config/locales/client.hy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.hy.yml b/config/locales/client.hy.yml index 028b9f42..29ec7207 100644 --- a/config/locales/client.hy.yml +++ b/config/locales/client.hy.yml @@ -179,6 +179,7 @@ hy: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 14f79bd4c860a065210945543a5fc2c895739051 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:51 -0800 Subject: [PATCH 143/556] New translations client.en.yml (Italian) --- config/locales/client.it.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 6a4afdec..daab0e61 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -179,6 +179,7 @@ it: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From eb3c19064983ef76e645cba1ee9d70c5ac38e28b Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:52 -0800 Subject: [PATCH 144/556] New translations client.en.yml (Japanese) --- config/locales/client.ja.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 3b18dfe7..241f102f 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -179,6 +179,7 @@ ja: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 24752365f70226060a7e694fa0518d4cd9713907 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:53 -0800 Subject: [PATCH 145/556] New translations client.en.yml (Georgian) --- config/locales/client.ka.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ka.yml b/config/locales/client.ka.yml index ee95af36..a18db0e8 100644 --- a/config/locales/client.ka.yml +++ b/config/locales/client.ka.yml @@ -179,6 +179,7 @@ ka: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 56af532903fa5bbae0ae64bea58ed5eb43589d28 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:54 -0800 Subject: [PATCH 146/556] New translations client.en.yml (Korean) --- config/locales/client.ko.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 4d3edcc8..97a82979 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -179,6 +179,7 @@ ko: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 15a653ddd9f33b910cf756382276e047a17996e5 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:55 -0800 Subject: [PATCH 147/556] New translations client.en.yml (Kurdish) --- config/locales/client.ku.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ku.yml b/config/locales/client.ku.yml index 69694c34..f50cc6a2 100644 --- a/config/locales/client.ku.yml +++ b/config/locales/client.ku.yml @@ -179,6 +179,7 @@ ku: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From b5022cc07bb98f20cbbacc5fea7910c7c27ac8d1 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:56 -0800 Subject: [PATCH 148/556] New translations client.en.yml (Lithuanian) --- config/locales/client.lt.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml index 01de5326..08442713 100644 --- a/config/locales/client.lt.yml +++ b/config/locales/client.lt.yml @@ -179,6 +179,7 @@ lt: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 9f251255102b387b03c52d3423c731d4de182dbf Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:57 -0800 Subject: [PATCH 149/556] New translations client.en.yml (Macedonian) --- config/locales/client.mk.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.mk.yml b/config/locales/client.mk.yml index aacd434f..0e11eb12 100644 --- a/config/locales/client.mk.yml +++ b/config/locales/client.mk.yml @@ -179,6 +179,7 @@ mk: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 7563608b45036e81d7167e0994bccbcb778b4120 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:58 -0800 Subject: [PATCH 150/556] New translations client.en.yml (Tamil) --- config/locales/client.ta.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ta.yml b/config/locales/client.ta.yml index 4cfc9a90..231576b6 100644 --- a/config/locales/client.ta.yml +++ b/config/locales/client.ta.yml @@ -179,6 +179,7 @@ ta: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 3ee4aaf083e51259130574ab34c87e66f87d449e Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:48:59 -0800 Subject: [PATCH 151/556] New translations client.en.yml (Bengali) --- config/locales/client.bn.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.bn.yml b/config/locales/client.bn.yml index ca21e418..d74e4148 100644 --- a/config/locales/client.bn.yml +++ b/config/locales/client.bn.yml @@ -179,6 +179,7 @@ bn: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 56b795af5d7d52539205a424d38fcc451c389443 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:01 -0800 Subject: [PATCH 152/556] New translations client.en.yml (Esperanto) --- config/locales/client.eo.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.eo.yml b/config/locales/client.eo.yml index ee59949b..23991394 100644 --- a/config/locales/client.eo.yml +++ b/config/locales/client.eo.yml @@ -179,6 +179,7 @@ eo: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 2f2072509d97c52d92e0de3eac0a061a7bf5689e Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:02 -0800 Subject: [PATCH 153/556] New translations client.en.yml (Oromo) --- config/locales/client.om.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.om.yml b/config/locales/client.om.yml index 3c7a7e46..5e88a621 100644 --- a/config/locales/client.om.yml +++ b/config/locales/client.om.yml @@ -179,6 +179,7 @@ om: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 9aefb688a95a1dd0af8691124866fbf922296e9e Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:03 -0800 Subject: [PATCH 154/556] New translations client.en.yml (Lao) --- config/locales/client.lo.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.lo.yml b/config/locales/client.lo.yml index 56206ebb..7ddec86f 100644 --- a/config/locales/client.lo.yml +++ b/config/locales/client.lo.yml @@ -179,6 +179,7 @@ lo: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From ba235bac6745dfeab7b4c6d024a2accbe30ada84 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:04 -0800 Subject: [PATCH 155/556] New translations client.en.yml (Nepali) --- config/locales/client.ne.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ne.yml b/config/locales/client.ne.yml index 33f52ead..78de44bf 100644 --- a/config/locales/client.ne.yml +++ b/config/locales/client.ne.yml @@ -179,6 +179,7 @@ ne: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From ccc5103e2116473278f494dd30556243634aecac Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:05 -0800 Subject: [PATCH 156/556] New translations client.en.yml (Swahili) --- config/locales/client.sw.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml index 5ebb0624..96e970d5 100644 --- a/config/locales/client.sw.yml +++ b/config/locales/client.sw.yml @@ -179,6 +179,7 @@ sw: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From fe56e3beff4338bb5e53844724282ff927372f35 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:06 -0800 Subject: [PATCH 157/556] New translations client.en.yml (Kannada) --- config/locales/client.kn.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.kn.yml b/config/locales/client.kn.yml index 8950065b..b6416c60 100644 --- a/config/locales/client.kn.yml +++ b/config/locales/client.kn.yml @@ -179,6 +179,7 @@ kn: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 01a4469af56f2f83afa65d21accf2aa8c87dddcf Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:07 -0800 Subject: [PATCH 158/556] New translations client.en.yml (Bosnian) --- config/locales/client.bs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.bs.yml b/config/locales/client.bs.yml index e188aece..1c8be5a6 100644 --- a/config/locales/client.bs.yml +++ b/config/locales/client.bs.yml @@ -179,6 +179,7 @@ bs: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 18bba7a92b502cc7c582ff07e49d03b86ac2c788 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:08 -0800 Subject: [PATCH 159/556] New translations client.en.yml (Tibetan) --- config/locales/client.bo.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.bo.yml b/config/locales/client.bo.yml index cd8c7924..a5520c42 100644 --- a/config/locales/client.bo.yml +++ b/config/locales/client.bo.yml @@ -179,6 +179,7 @@ bo: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 90a7512a33204e39a8d9edc19d5d6202cea5e6c9 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:09 -0800 Subject: [PATCH 160/556] New translations client.en.yml (Malayalam) --- config/locales/client.ml.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ml.yml b/config/locales/client.ml.yml index f417dd95..01b170dd 100644 --- a/config/locales/client.ml.yml +++ b/config/locales/client.ml.yml @@ -179,6 +179,7 @@ ml: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 6c1d2df7e1b4851a1a476bf0f1c9917d663fef1a Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:10 -0800 Subject: [PATCH 161/556] New translations client.en.yml (Tatar) --- config/locales/client.tt.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.tt.yml b/config/locales/client.tt.yml index a48bac61..b6a7adb7 100644 --- a/config/locales/client.tt.yml +++ b/config/locales/client.tt.yml @@ -179,6 +179,7 @@ tt: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 649f014466e34d00ee280504ee21e84450a9ac59 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:11 -0800 Subject: [PATCH 162/556] New translations client.en.yml (Welsh) --- config/locales/client.cy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.cy.yml b/config/locales/client.cy.yml index 4510470b..d612abf2 100644 --- a/config/locales/client.cy.yml +++ b/config/locales/client.cy.yml @@ -179,6 +179,7 @@ cy: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From f1007b14838e7ff5a414b25340ba1194060a1d42 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:12 -0800 Subject: [PATCH 163/556] New translations client.en.yml (Thai) --- config/locales/client.th.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index 68b139ee..ea9cc751 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -179,6 +179,7 @@ th: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From bf5ce61ede5e721b12fc1ac182d1749277e6eb8f Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:13 -0800 Subject: [PATCH 164/556] New translations client.en.yml (Yiddish) --- config/locales/client.yi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.yi.yml b/config/locales/client.yi.yml index 94cd0b5d..a964bcdb 100644 --- a/config/locales/client.yi.yml +++ b/config/locales/client.yi.yml @@ -179,6 +179,7 @@ yi: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 61e2817503a29be6382ab2567b53a925921870bb Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:14 -0800 Subject: [PATCH 165/556] New translations client.en.yml (Tagalog) --- config/locales/client.tl.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.tl.yml b/config/locales/client.tl.yml index bc3a2557..88c864be 100644 --- a/config/locales/client.tl.yml +++ b/config/locales/client.tl.yml @@ -179,6 +179,7 @@ tl: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 57e3bc7421f7d430e5098278ae2c4f8c5bf12506 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:15 -0800 Subject: [PATCH 166/556] New translations client.en.yml (Telugu) --- config/locales/client.te.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index a3b7c377..f18a335c 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -179,6 +179,7 @@ te: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From d71808048d66c540331f934f469eaaaa44d256fc Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:16 -0800 Subject: [PATCH 167/556] New translations client.en.yml (Malay) --- config/locales/client.ms.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.ms.yml b/config/locales/client.ms.yml index 4f8c9cad..426a122f 100644 --- a/config/locales/client.ms.yml +++ b/config/locales/client.ms.yml @@ -179,6 +179,7 @@ ms: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From c1b4971bd9f968f55d3af19335c0234fd5a47390 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:17 -0800 Subject: [PATCH 168/556] New translations client.en.yml (Hindi) --- config/locales/client.hi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.hi.yml b/config/locales/client.hi.yml index 0bdd2a8f..84eff7a4 100644 --- a/config/locales/client.hi.yml +++ b/config/locales/client.hi.yml @@ -179,6 +179,7 @@ hi: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From a6a87b8471a1b00ca597e9497606fe6f18a4bd7a Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:18 -0800 Subject: [PATCH 169/556] New translations client.en.yml (Azerbaijani) --- config/locales/client.az.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.az.yml b/config/locales/client.az.yml index 990f6a37..f390766c 100644 --- a/config/locales/client.az.yml +++ b/config/locales/client.az.yml @@ -179,6 +179,7 @@ az: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 3ccfeced5d6dfed82800a03b87d2768483a03565 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:19 -0800 Subject: [PATCH 170/556] New translations client.en.yml (Latvian) --- config/locales/client.lv.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index eaaf4e35..c61d7071 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -179,6 +179,7 @@ lv: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 4c0e95bddb9ae63b92821a3a9b847457d2bff93d Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:20 -0800 Subject: [PATCH 171/556] New translations client.en.yml (Estonian) --- config/locales/client.et.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index 490c3afc..17a2b9b6 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -179,6 +179,7 @@ et: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From be684274a3663db2d881f1ab0797c770bc9637fb Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:21 -0800 Subject: [PATCH 172/556] New translations client.en.yml (Kazakh) --- config/locales/client.kk.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.kk.yml b/config/locales/client.kk.yml index d268cbfd..0e816ccd 100644 --- a/config/locales/client.kk.yml +++ b/config/locales/client.kk.yml @@ -179,6 +179,7 @@ kk: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 294878d376210864f4c1354f8887bb838d8a06da Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:22 -0800 Subject: [PATCH 173/556] New translations client.en.yml (Croatian) --- config/locales/client.hr.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.hr.yml b/config/locales/client.hr.yml index a9b9e8ff..90d47d54 100644 --- a/config/locales/client.hr.yml +++ b/config/locales/client.hr.yml @@ -179,6 +179,7 @@ hr: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From 363ce6a83f1557892eacb8ca2cd45cd0032b2ca7 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Sun, 6 Feb 2022 21:49:23 -0800 Subject: [PATCH 174/556] New translations client.en.yml (Sindhi) --- config/locales/client.sd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/client.sd.yml b/config/locales/client.sd.yml index add951c7..5ceeec11 100644 --- a/config/locales/client.sd.yml +++ b/config/locales/client.sd.yml @@ -179,6 +179,7 @@ sd: property: "Property" prefill: "Prefill" content: "Content" + tag_groups: "Tag Groups" date_time_format: label: "Format" instructions: "Moment.js format" From b62aee8a48e48eb25ad89988e2732898feb6183b Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 15 Feb 2022 17:16:21 +1100 Subject: [PATCH 175/556] WIP --- test/javascripts/wizard/test_helper.js | 74 ++++++++++++++ test/javascripts/wizard/wizard-pretender.js | 106 ++++++++++++++++++++ test/javascripts/wizard/wizard-test.js | 78 ++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 test/javascripts/wizard/test_helper.js create mode 100644 test/javascripts/wizard/wizard-pretender.js create mode 100644 test/javascripts/wizard/wizard-test.js diff --git a/test/javascripts/wizard/test_helper.js b/test/javascripts/wizard/test_helper.js new file mode 100644 index 00000000..7d851f1b --- /dev/null +++ b/test/javascripts/wizard/test_helper.js @@ -0,0 +1,74 @@ +// discourse-skip-module +/*global document, Logster, QUnit */ + +window.Discourse = {}; +window.Wizard = {}; +Wizard.SiteSettings = {}; +Discourse.__widget_helpers = {}; +Discourse.SiteSettings = Wizard.SiteSettings; + +//= require env +//= require jquery.debug +//= require ember.debug +//= require locales/i18n +//= require locales/en +//= require route-recognizer +//= require fake_xml_http_request +//= require pretender +//= require qunit +//= require ember-qunit +//= require discourse-loader +//= require jquery.debug +//= require handlebars +//= require ember-template-compiler +//= require wizard-application +//= require wizard-vendor +//= require_tree ./helpers +//= require_tree ./acceptance +//= require_tree ./models +//= require_tree ./components +//= require ./wizard-pretender +//= require test-shims + +document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); +}); + +if (window.Logster) { + Logster.enabled = false; +} else { + window.Logster = { enabled: false }; +} +Ember.Test.adapter = window.QUnitAdapter.create(); + +let createPretendServer = requirejs( + "wizard/test/wizard-pretender", + null, + null, + false +).default; + +let server; +QUnit.testStart(function () { + server = createPretendServer(); +}); + +QUnit.testDone(function () { + server.shutdown(); +}); + +let _testApp = requirejs("wizard/test/helpers/start-app").default(); +let _buildResolver = requirejs("discourse-common/resolver").buildResolver; +window.setResolver(_buildResolver("wizard").create({ namespace: _testApp })); + +Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry, null, null, true); + } +}); diff --git a/test/javascripts/wizard/wizard-pretender.js b/test/javascripts/wizard/wizard-pretender.js new file mode 100644 index 00000000..e9dccfb3 --- /dev/null +++ b/test/javascripts/wizard/wizard-pretender.js @@ -0,0 +1,106 @@ +import Pretender from "pretender"; + +// TODO: This file has some copied and pasted functions from `create-pretender` - would be good +// to centralize that code at some point. + +function parsePostData(query) { + const result = {}; + query.split("&").forEach(function (part) { + const item = part.split("="); + const firstSeg = decodeURIComponent(item[0]); + const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg); + + const val = decodeURIComponent(item[1]).replace(/\+/g, " "); + if (m) { + result[m[1]] = result[m[1]] || {}; + result[m[1]][m[2]] = val; + } else { + result[firstSeg] = val; + } + }); + return result; +} + +function response(code, obj) { + if (typeof code === "object") { + obj = code; + code = 200; + } + return [code, { "Content-Type": "application/json" }, obj]; +} + +export default function () { + const server = new Pretender(function () { + this.get("/wizard.json", () => { + return response(200, { + wizard: { + start: "hello-world", + completed: true, + steps: [ + { + id: "hello-world", + title: "hello there", + index: 0, + description: "hello!", + fields: [ + { + id: "full_name", + type: "text", + required: true, + description: "Your name", + }, + ], + next: "second-step", + }, + { + id: "second-step", + title: "Second step", + index: 1, + fields: [{ id: "some-title", type: "text" }], + previous: "hello-world", + next: "last-step", + }, + { + id: "last-step", + index: 2, + fields: [ + { id: "snack", type: "dropdown", required: true }, + { id: "theme-preview", type: "component" }, + { id: "an-image", type: "image" }, + ], + previous: "second-step", + }, + ], + }, + }); + }); + + this.put("/wizard/steps/:id", (request) => { + const body = parsePostData(request.requestBody); + + if (body.fields.full_name === "Server Fail") { + return response(422, { + errors: [{ field: "full_name", description: "Invalid name" }], + }); + } else { + return response(200, { success: true }); + } + }); + }); + + server.prepareBody = function (body) { + if (body && typeof body === "object") { + return JSON.stringify(body); + } + return body; + }; + + server.unhandledRequest = function (verb, path) { + const error = + "Unhandled request in test environment: " + path + " (" + verb + ")"; + window.console.error(error); + throw error; + }; + + return server; +} diff --git a/test/javascripts/wizard/wizard-test.js b/test/javascripts/wizard/wizard-test.js new file mode 100644 index 00000000..ea550cf2 --- /dev/null +++ b/test/javascripts/wizard/wizard-test.js @@ -0,0 +1,78 @@ +import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers"; +import { module, test } from "qunit"; +import { run } from "@ember/runloop"; +import startApp from "wizard/test/helpers/start-app"; + +let wizard; +module("Acceptance: wizard", { + beforeEach() { + wizard = startApp(); + }, + + afterEach() { + run(wizard, "destroy"); + }, +}); + +function exists(selector) { + return document.querySelector(selector) !== null; +} + +test("Wizard starts", async function (assert) { + await visit("/"); + assert.ok(exists(".wizard-column-contents")); + assert.strictEqual(currentRouteName(), "step"); +}); + +test("Going back and forth in steps", async function (assert) { + await visit("/steps/hello-world"); + assert.ok(exists(".wizard-step")); + assert.ok( + exists(".wizard-step-hello-world"), + "it adds a class for the step id" + ); + assert.ok(!exists(".wizard-btn.finish"), "can’t finish on first step"); + assert.ok(exists(".wizard-progress")); + assert.ok(exists(".wizard-step-title")); + assert.ok(exists(".wizard-step-description")); + assert.ok( + !exists(".invalid .field-full-name"), + "don't show it as invalid until the user does something" + ); + assert.ok(exists(".wizard-field .field-description")); + assert.ok(!exists(".wizard-btn.back")); + assert.ok(!exists(".wizard-field .field-error-description")); + + // invalid data + await click(".wizard-btn.next"); + assert.ok(exists(".invalid .field-full-name")); + + // server validation fail + await fillIn("input.field-full-name", "Server Fail"); + await click(".wizard-btn.next"); + assert.ok(exists(".invalid .field-full-name")); + assert.ok(exists(".wizard-field .field-error-description")); + + // server validation ok + await fillIn("input.field-full-name", "Evil Trout"); + await click(".wizard-btn.next"); + assert.ok(!exists(".wizard-field .field-error-description")); + assert.ok(!exists(".wizard-step-description")); + assert.ok( + exists(".wizard-btn.finish"), + "shows finish on an intermediate step" + ); + + await click(".wizard-btn.next"); + assert.ok(exists(".select-kit.field-snack"), "went to the next step"); + assert.ok(exists(".preview-area"), "renders the component field"); + assert.ok(exists(".wizard-btn.done"), "last step shows a done button"); + assert.ok(exists(".action-link.back"), "shows the back button"); + assert.ok(!exists(".wizard-step-title")); + assert.ok(!exists(".wizard-btn.finish"), "can’t finish on last step"); + + await click(".action-link.back"); + assert.ok(exists(".wizard-step-title")); + assert.ok(exists(".wizard-btn.next")); + assert.ok(!exists(".wizard-prev")); +}); From b20b8ce3335af792b38b7d6319783e688c4eea39 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 17 Feb 2022 17:08:14 +1100 Subject: [PATCH 176/556] WIP 2 --- .github/workflows/plugin-tests.yml | 2 +- .../javascripts/acceptance/wizard-test.js.es6 | 30 ++ test/javascripts/fixtures/wizard.js | 340 ++++++++++++++++++ test/javascripts/helpers/start-app.js | 23 ++ .../test_helper.js => plugin_helper.js} | 18 +- test/javascripts/wizard/wizard-test.js | 78 ---- .../wizard => views}/wizard-pretender.js | 45 +-- 7 files changed, 404 insertions(+), 132 deletions(-) create mode 100644 test/javascripts/acceptance/wizard-test.js.es6 create mode 100644 test/javascripts/fixtures/wizard.js create mode 100644 test/javascripts/helpers/start-app.js rename test/javascripts/{wizard/test_helper.js => plugin_helper.js} (86%) delete mode 100644 test/javascripts/wizard/wizard-test.js rename {test/javascripts/wizard => views}/wizard-pretender.js (57%) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 855bdfec..ef042fbc 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -31,7 +31,7 @@ jobs: build_type: ["backend", "frontend"] ruby: ["2.7"] postgres: ["12"] - redis: ["4.x"] + redis: ["6.x"] services: postgres: diff --git a/test/javascripts/acceptance/wizard-test.js.es6 b/test/javascripts/acceptance/wizard-test.js.es6 new file mode 100644 index 00000000..d7aa706a --- /dev/null +++ b/test/javascripts/acceptance/wizard-test.js.es6 @@ -0,0 +1,30 @@ +import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers"; +import { module, test } from "qunit"; +import { run } from "@ember/runloop"; +import startApp from "../helpers/start-app"; +console.log("STARTING TEST"); +let wizard; +window.onerror = function (msg, url, lineNo, columnNo, error) { + console.log(error); + return false +} +module("Acceptance: Custom Wizard", { + beforeEach() { + console.log("BEFORE EACH") + wizard = startApp(); + }, + + afterEach() { + run(wizard, "destroy"); + }, +}); + +function exists(selector) { + return document.querySelector(selector) !== null; +} + +test("Wizard starts", async function (assert) { + console.log("TEST") + await visit("/w/wizard"); + assert.ok(exists(".wizard-column")); +}); diff --git a/test/javascripts/fixtures/wizard.js b/test/javascripts/fixtures/wizard.js new file mode 100644 index 00000000..0b3a219c --- /dev/null +++ b/test/javascripts/fixtures/wizard.js @@ -0,0 +1,340 @@ +export default { + "id": "super_mega_fun_wizard", + "name": "Super Mega Fun Wizard", + "background": "#333333", + "save_submissions": true, + "after_signup": false, + "prompt_completion": false, + "theme_id": 2, + "steps": [ + { + "id": "step_1", + "title": "Text", + "raw_description": "Text inputs!", + "fields": [ + { + "id": "step_1_field_1", + "label": "Text", + "description": "Text field description.", + "type": "text", + "min_length": "3", + "prefill": [ + { + "type": "assignment", + "output": "I am prefilled", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "step_1_field_2", + "label": "Textarea", + "type": "textarea", + "min_length": "" + }, + { + "id": "step_1_field_3", + "label": "Composer", + "type": "composer" + }, + { + "id": "step_1_field_4", + "label": "I'm only text", + "description": "", + "type": "text_only" + } + ], + "description": "Text inputs!" + }, + { + "id": "step_2", + "title": "Values", + "raw_description": "Because I couldn't think of another name for this step :)", + "fields": [ + { + "id": "step_2_field_1", + "label": "Date", + "type": "date", + "format": "YYYY-MM-DD" + }, + { + "id": "step_2_field_2", + "label": "Time", + "type": "time", + "format": "HH:mm" + }, + { + "id": "step_2_field_3", + "label": "Date & Time", + "type": "date_time", + "format": "" + }, + { + "id": "step_2_field_4", + "label": "Number", + "type": "number" + }, + { + "id": "step_2_field_5", + "label": "Checkbox", + "type": "checkbox" + }, + { + "id": "step_2_field_6", + "label": "Url", + "type": "url" + }, + { + "id": "step_2_field_7", + "label": "Upload", + "type": "upload", + "file_types": ".jpg,.jpeg,.png" + } + ], + "description": "Because I couldn't think of another name for this step :)" + }, + { + "id": "step_3", + "title": "Combo-boxes", + "raw_description": "Unfortunately not the edible type :sushi: ", + "fields": [ + { + "id": "step_3_field_1", + "label": "Custom Dropdown", + "type": "dropdown", + "content": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "choice1", + "key_type": "text", + "value": "Choice 1", + "value_type": "text", + "connector": "equal" + }, + { + "index": 1, + "key": "choice2", + "key_type": "text", + "value": "Choice 2", + "value_type": "text", + "connector": "association" + }, + { + "index": 2, + "key": "choice3", + "key_type": "text", + "value": "Choice 3", + "value_type": "text", + "connector": "association" + } + ] + } + ] + }, + { + "id": "step_3_field_2", + "label": "Tag", + "type": "tag" + }, + { + "id": "step_3_field_3", + "label": "Category", + "type": "category", + "limit": 1, + "property": "id" + }, + { + "id": "step_3_field_4", + "label": "Group", + "type": "group" + }, + { + "id": "step_3_field_5", + "label": "User Selector", + "description": "", + "type": "user_selector" + }, + { + "id": "step_3_field_6", + "label": "Conditional User Selector", + "description": "Shown when checkbox in step_2_field_5 is true", + "type": "user_selector" + } + ], + "description": "Unfortunately not the edible type :sushi: " + } + ], + "actions": [ + { + "id": "action_1", + "run_after": "step_3", + "type": "create_topic", + "skip_redirect": true, + "post": "step_1_field_2", + "title": [ + { + "type": "assignment", + "output": "step_1_field_1", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "category": [ + { + "type": "assignment", + "output": "step_3_field_3", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "tags": [ + { + "type": "assignment", + "output": "step_3_field_2", + "output_type": "wizard_field", + "output_connector": "set" + } + ], + "custom_fields": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "post_field", + "key_type": "text", + "value": "Post custom field value", + "value_type": "text", + "connector": "association" + }, + { + "index": 1, + "key": "topic.topic_field", + "key_type": "text", + "value": "Topic custom field value", + "value_type": "text", + "connector": "association" + }, + { + "index": 2, + "key": "topic.topic_json_field{}.key_1", + "key_type": "text", + "value": "Key 1 value", + "value_type": "text", + "connector": "association" + }, + { + "index": 3, + "key": "topic.topic_json_field{}.key_2", + "key_type": "text", + "value": "Key 2 value", + "value_type": "text", + "connector": "association" + } + ] + } + ], + "visible": [ + { + "type": "conditional", + "output": "true", + "output_type": "text", + "output_connector": "then", + "pairs": [ + { + "index": 0, + "key": "name", + "key_type": "user_field", + "value": "Angus", + "value_type": "text", + "connector": "equal" + } + ] + } + ] + }, + { + "id": "action_5", + "run_after": "step_1", + "type": "watch_categories", + "notification_level": "tracking", + "wizard_user": true, + "categories": [ + { + "type": "assignment", + "output": "action_8", + "output_type": "wizard_action", + "output_connector": "set" + } + ], + "mute_remainder": [ + { + "type": "assignment", + "output": "true", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "action_4", + "run_after": "step_2", + "type": "update_profile", + "profile_updates": [ + { + "type": "association", + "pairs": [ + { + "index": 0, + "key": "profile_background", + "key_type": "user_field", + "value": "step_2_field_7", + "value_type": "wizard_field", + "connector": "association" + } + ] + } + ] + }, + { + "id": "action_3", + "run_after": "step_2", + "type": "open_composer", + "post_builder": true, + "post_template": "I am interpolating some user fields u{name} u{username} u{email}", + "title": [ + { + "type": "assignment", + "output": "Title of the composer topic", + "output_type": "text", + "output_connector": "set" + } + ], + "tags": [ + { + "type": "assignment", + "output": "tag1", + "output_type": "text", + "output_connector": "set" + } + ] + }, + { + "id": "action_10", + "run_after": "step_3", + "type": "route_to", + "url": [ + { + "type": "assignment", + "output": "https://google.com", + "output_type": "text", + "output_connector": "set" + } + ] + } + ] +} diff --git a/test/javascripts/helpers/start-app.js b/test/javascripts/helpers/start-app.js new file mode 100644 index 00000000..6f5dbcc6 --- /dev/null +++ b/test/javascripts/helpers/start-app.js @@ -0,0 +1,23 @@ +import CustomWizard from "discourse/plugins/discourse-custom-wizard/wizard/custom-wizard"; +import wizardInitializer from "discourse/plugins/discourse-custom-wizard/wizard/initializers/custom-wizard"; +import stepInitializer from "discourse/plugins/discourse-custom-wizard/wizard/initializers/custom-wizard-step"; +import fieldInitializer from "discourse/plugins/discourse-custom-wizard/wizard/initializers/custom-wizard-field"; +import { run } from "@ember/runloop"; + +let app; +let started = false; + +export default function () { + run(() => (app = CustomWizard.create({ rootElement: "#ember-testing" }))); + + if (!started) { + wizardInitializer.initialize(app); + stepInitializer.initialize(app); + fieldInitializer.initialize(app); + app.start(); + started = true; + } + app.setupForTesting(); + app.injectTestHelpers(); + return app; +} diff --git a/test/javascripts/wizard/test_helper.js b/test/javascripts/plugin_helper.js similarity index 86% rename from test/javascripts/wizard/test_helper.js rename to test/javascripts/plugin_helper.js index 7d851f1b..9b1558cd 100644 --- a/test/javascripts/wizard/test_helper.js +++ b/test/javascripts/plugin_helper.js @@ -1,6 +1,6 @@ // discourse-skip-module /*global document, Logster, QUnit */ - +console.log('starting test_helper') window.Discourse = {}; window.Wizard = {}; Wizard.SiteSettings = {}; @@ -21,7 +21,7 @@ Discourse.SiteSettings = Wizard.SiteSettings; //= require jquery.debug //= require handlebars //= require ember-template-compiler -//= require wizard-application +//= require wizard-custom //= require wizard-vendor //= require_tree ./helpers //= require_tree ./acceptance @@ -29,7 +29,7 @@ Discourse.SiteSettings = Wizard.SiteSettings; //= require_tree ./components //= require ./wizard-pretender //= require test-shims - +console.log ("end of require") document.addEventListener("DOMContentLoaded", function () { document.body.insertAdjacentHTML( "afterbegin", @@ -47,12 +47,7 @@ if (window.Logster) { } Ember.Test.adapter = window.QUnitAdapter.create(); -let createPretendServer = requirejs( - "wizard/test/wizard-pretender", - null, - null, - false -).default; +/*let createPretendServer = requirejs("./wizard-pretender", null, null, false).default; let server; QUnit.testStart(function () { @@ -61,9 +56,9 @@ QUnit.testStart(function () { QUnit.testDone(function () { server.shutdown(); -}); +});*/ -let _testApp = requirejs("wizard/test/helpers/start-app").default(); +let _testApp = requirejs("./helpers/start-app").default(); let _buildResolver = requirejs("discourse-common/resolver").buildResolver; window.setResolver(_buildResolver("wizard").create({ namespace: _testApp })); @@ -72,3 +67,4 @@ Object.keys(requirejs.entries).forEach(function (entry) { requirejs(entry, null, null, true); } }); +console.log ("end of helper") diff --git a/test/javascripts/wizard/wizard-test.js b/test/javascripts/wizard/wizard-test.js deleted file mode 100644 index ea550cf2..00000000 --- a/test/javascripts/wizard/wizard-test.js +++ /dev/null @@ -1,78 +0,0 @@ -import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers"; -import { module, test } from "qunit"; -import { run } from "@ember/runloop"; -import startApp from "wizard/test/helpers/start-app"; - -let wizard; -module("Acceptance: wizard", { - beforeEach() { - wizard = startApp(); - }, - - afterEach() { - run(wizard, "destroy"); - }, -}); - -function exists(selector) { - return document.querySelector(selector) !== null; -} - -test("Wizard starts", async function (assert) { - await visit("/"); - assert.ok(exists(".wizard-column-contents")); - assert.strictEqual(currentRouteName(), "step"); -}); - -test("Going back and forth in steps", async function (assert) { - await visit("/steps/hello-world"); - assert.ok(exists(".wizard-step")); - assert.ok( - exists(".wizard-step-hello-world"), - "it adds a class for the step id" - ); - assert.ok(!exists(".wizard-btn.finish"), "can’t finish on first step"); - assert.ok(exists(".wizard-progress")); - assert.ok(exists(".wizard-step-title")); - assert.ok(exists(".wizard-step-description")); - assert.ok( - !exists(".invalid .field-full-name"), - "don't show it as invalid until the user does something" - ); - assert.ok(exists(".wizard-field .field-description")); - assert.ok(!exists(".wizard-btn.back")); - assert.ok(!exists(".wizard-field .field-error-description")); - - // invalid data - await click(".wizard-btn.next"); - assert.ok(exists(".invalid .field-full-name")); - - // server validation fail - await fillIn("input.field-full-name", "Server Fail"); - await click(".wizard-btn.next"); - assert.ok(exists(".invalid .field-full-name")); - assert.ok(exists(".wizard-field .field-error-description")); - - // server validation ok - await fillIn("input.field-full-name", "Evil Trout"); - await click(".wizard-btn.next"); - assert.ok(!exists(".wizard-field .field-error-description")); - assert.ok(!exists(".wizard-step-description")); - assert.ok( - exists(".wizard-btn.finish"), - "shows finish on an intermediate step" - ); - - await click(".wizard-btn.next"); - assert.ok(exists(".select-kit.field-snack"), "went to the next step"); - assert.ok(exists(".preview-area"), "renders the component field"); - assert.ok(exists(".wizard-btn.done"), "last step shows a done button"); - assert.ok(exists(".action-link.back"), "shows the back button"); - assert.ok(!exists(".wizard-step-title")); - assert.ok(!exists(".wizard-btn.finish"), "can’t finish on last step"); - - await click(".action-link.back"); - assert.ok(exists(".wizard-step-title")); - assert.ok(exists(".wizard-btn.next")); - assert.ok(!exists(".wizard-prev")); -}); diff --git a/test/javascripts/wizard/wizard-pretender.js b/views/wizard-pretender.js similarity index 57% rename from test/javascripts/wizard/wizard-pretender.js rename to views/wizard-pretender.js index e9dccfb3..ac0ef1e8 100644 --- a/test/javascripts/wizard/wizard-pretender.js +++ b/views/wizard-pretender.js @@ -1,4 +1,5 @@ import Pretender from "pretender"; +import WizardJson from "./fixtures/wizard"; // TODO: This file has some copied and pasted functions from `create-pretender` - would be good // to centralize that code at some point. @@ -31,48 +32,8 @@ function response(code, obj) { export default function () { const server = new Pretender(function () { - this.get("/wizard.json", () => { - return response(200, { - wizard: { - start: "hello-world", - completed: true, - steps: [ - { - id: "hello-world", - title: "hello there", - index: 0, - description: "hello!", - fields: [ - { - id: "full_name", - type: "text", - required: true, - description: "Your name", - }, - ], - next: "second-step", - }, - { - id: "second-step", - title: "Second step", - index: 1, - fields: [{ id: "some-title", type: "text" }], - previous: "hello-world", - next: "last-step", - }, - { - id: "last-step", - index: 2, - fields: [ - { id: "snack", type: "dropdown", required: true }, - { id: "theme-preview", type: "component" }, - { id: "an-image", type: "image" }, - ], - previous: "second-step", - }, - ], - }, - }); + this.get("/w/wizard.json", () => { + return response(200, cloneJSON(WizardJson); }); this.put("/wizard/steps/:id", (request) => { From f9b35a25410606c454275905a461fdc79d6644a7 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 7 Mar 2022 09:02:39 +0100 Subject: [PATCH 177/556] COMPATIBILITY: Remove lodash --- assets/javascripts/wizard-custom.js | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index de0804a9..99a96a00 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -61,7 +61,6 @@ //= require polyfills //= require markdown-it-bundle -//= require lodash.js //= require template_include.js //= require itsatrap.js //= require caret_position.js From 46c86cda58826b9e60118a660055bc0804b84bb8 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 12 Mar 2022 14:00:07 +0100 Subject: [PATCH 178/556] Move to new coverage approach --- .github/workflows/plugin-tests.yml | 7 +- .simplecov | 6 ++ .../controllers}/custom_wizard/admin/admin.rb | 0 .../controllers}/custom_wizard/admin/api.rb | 0 .../custom_wizard/admin/custom_fields.rb | 0 .../controllers}/custom_wizard/admin/logs.rb | 0 .../custom_wizard/admin/manager.rb | 0 .../custom_wizard/admin/submissions.rb | 0 .../custom_wizard/admin/wizard.rb | 0 .../custom_wizard/realtime_validations.rb | 0 .../controllers}/custom_wizard/steps.rb | 0 .../controllers}/custom_wizard/wizard.rb | 2 +- .../jobs}/refresh_api_access_token.rb | 0 {jobs => app/jobs}/set_after_time_wizard.rb | 0 .../api/authorization_serializer.rb | 0 .../api/basic_endpoint_serializer.rb | 0 .../custom_wizard/api/endpoint_serializer.rb | 0 .../custom_wizard/api/log_serializer.rb | 0 .../custom_wizard/api_serializer.rb | 0 .../custom_wizard/basic_api_serializer.rb | 0 .../custom_wizard/basic_wizard_serializer.rb | 0 .../custom_wizard/custom_field_serializer.rb | 0 .../custom_wizard/log_serializer.rb | 0 .../similar_topics_serializer.rb | 0 .../custom_wizard/submission_serializer.rb | 0 .../custom_wizard/wizard_field_serializer.rb | 0 .../custom_wizard/wizard_serializer.rb | 0 .../custom_wizard/wizard_step_serializer.rb | 0 {views => app/views}/layouts/wizard.html.erb | 0 coverage/.last_run.json | 2 +- .../extensions}/custom_field/extension.rb | 0 .../extensions}/custom_field/preloader.rb | 0 .../extensions}/custom_field/serializer.rb | 0 .../extensions}/discourse_tagging.rb | 0 .../extensions}/extra_locales_controller.rb | 0 .../custom_wizard/extensions}/guardian.rb | 0 .../extensions}/invites_controller.rb | 0 .../extensions}/tags_controller.rb | 0 .../extensions}/users_controller.rb | 0 plugin.rb | 70 +++++++++---------- spec/components/custom_wizard/action_spec.rb | 1 - spec/components/custom_wizard/builder_spec.rb | 1 - spec/components/custom_wizard/cache_spec.rb | 2 - .../custom_wizard/custom_field_spec.rb | 2 - spec/components/custom_wizard/field_spec.rb | 1 - spec/components/custom_wizard/log_spec.rb | 1 - spec/components/custom_wizard/mapper_spec.rb | 1 - .../custom_wizard/realtime_validation_spec.rb | 2 - .../similar_topics_spec.rb | 2 - spec/components/custom_wizard/step_spec.rb | 1 - .../custom_wizard/submission_spec.rb | 1 - .../components/custom_wizard/template_spec.rb | 1 - .../custom_wizard/template_validator_spec.rb | 1 - .../custom_wizard/update_validator_spec.rb | 1 - spec/components/custom_wizard/wizard_spec.rb | 2 - .../custom_field_extensions_spec.rb | 2 - .../extra_locales_controller_spec.rb | 1 - spec/extensions/guardian_extension_spec.rb | 2 - spec/extensions/invites_controller_spec.rb | 1 - spec/extensions/sprockets_directive_spec.rb | 2 - spec/extensions/tags_controller_spec.rb | 2 - spec/extensions/users_controller_spec.rb | 1 - spec/jobs/set_after_time_wizard_spec.rb | 2 - spec/plugin_helper.rb | 17 ----- .../admin/custom_fields_controller_spec.rb | 1 - .../admin/logs_controller_spec.rb | 1 - .../admin/manager_controller_spec.rb | 1 - .../admin/submissions_controller_spec.rb | 1 - .../admin/wizard_controller_spec.rb | 1 - .../application_controller_spec.rb | 1 - .../custom_field_extensions_spec.rb | 2 - .../realtime_validations_spec.rb | 2 - .../custom_wizard/steps_controller_spec.rb | 1 - .../custom_wizard/wizard_controller_spec.rb | 1 - .../basic_wizard_serializer_spec.rb | 2 - .../custom_field_serializer_spec.rb | 2 - .../custom_wizard/log_serializer_spec.rb | 2 - .../wizard_field_serializer_spec.rb | 2 - .../custom_wizard/wizard_serializer_spec.rb | 2 - .../wizard_step_serializer_spec.rb | 2 - 80 files changed, 49 insertions(+), 112 deletions(-) create mode 100644 .simplecov rename {controllers => app/controllers}/custom_wizard/admin/admin.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/api.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/custom_fields.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/logs.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/manager.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/submissions.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/wizard.rb (100%) rename {controllers => app/controllers}/custom_wizard/realtime_validations.rb (100%) rename {controllers => app/controllers}/custom_wizard/steps.rb (100%) rename {controllers => app/controllers}/custom_wizard/wizard.rb (98%) rename {jobs => app/jobs}/refresh_api_access_token.rb (100%) rename {jobs => app/jobs}/set_after_time_wizard.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/authorization_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/basic_endpoint_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/endpoint_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/log_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/basic_api_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/basic_wizard_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/custom_field_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/log_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/realtime_validation/similar_topics_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/submission_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/wizard_field_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/wizard_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/wizard_step_serializer.rb (100%) rename {views => app/views}/layouts/wizard.html.erb (100%) rename {extensions => lib/custom_wizard/extensions}/custom_field/extension.rb (100%) rename {extensions => lib/custom_wizard/extensions}/custom_field/preloader.rb (100%) rename {extensions => lib/custom_wizard/extensions}/custom_field/serializer.rb (100%) rename {extensions => lib/custom_wizard/extensions}/discourse_tagging.rb (100%) rename {extensions => lib/custom_wizard/extensions}/extra_locales_controller.rb (100%) rename {extensions => lib/custom_wizard/extensions}/guardian.rb (100%) rename {extensions => lib/custom_wizard/extensions}/invites_controller.rb (100%) rename {extensions => lib/custom_wizard/extensions}/tags_controller.rb (100%) rename {extensions => lib/custom_wizard/extensions}/users_controller.rb (100%) delete mode 100644 spec/plugin_helper.rb diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index aec4fa6c..18f09d5d 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -141,7 +141,12 @@ jobs: - name: Plugin RSpec with Coverage if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' - run: SIMPLECOV=1 bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] + run: | + if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ] + cp plugins/${{ steps.repo-name.outputs.value }}/.simplecov .simplecov + export COVERAGE=1 + fi + bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] - name: Plugin QUnit if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' diff --git a/.simplecov b/.simplecov new file mode 100644 index 00000000..18b59116 --- /dev/null +++ b/.simplecov @@ -0,0 +1,6 @@ +plugin = "discourse-custom-wizard" + +SimpleCov.configure do + track_files "plugins/#{plugin}/**/*.rb" + add_filter { |src| !(src.filename =~ /(\/#{plugin}\/app\/|\/#{plugin}\/lib\/)/) } +end diff --git a/controllers/custom_wizard/admin/admin.rb b/app/controllers/custom_wizard/admin/admin.rb similarity index 100% rename from controllers/custom_wizard/admin/admin.rb rename to app/controllers/custom_wizard/admin/admin.rb diff --git a/controllers/custom_wizard/admin/api.rb b/app/controllers/custom_wizard/admin/api.rb similarity index 100% rename from controllers/custom_wizard/admin/api.rb rename to app/controllers/custom_wizard/admin/api.rb diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/app/controllers/custom_wizard/admin/custom_fields.rb similarity index 100% rename from controllers/custom_wizard/admin/custom_fields.rb rename to app/controllers/custom_wizard/admin/custom_fields.rb diff --git a/controllers/custom_wizard/admin/logs.rb b/app/controllers/custom_wizard/admin/logs.rb similarity index 100% rename from controllers/custom_wizard/admin/logs.rb rename to app/controllers/custom_wizard/admin/logs.rb diff --git a/controllers/custom_wizard/admin/manager.rb b/app/controllers/custom_wizard/admin/manager.rb similarity index 100% rename from controllers/custom_wizard/admin/manager.rb rename to app/controllers/custom_wizard/admin/manager.rb diff --git a/controllers/custom_wizard/admin/submissions.rb b/app/controllers/custom_wizard/admin/submissions.rb similarity index 100% rename from controllers/custom_wizard/admin/submissions.rb rename to app/controllers/custom_wizard/admin/submissions.rb diff --git a/controllers/custom_wizard/admin/wizard.rb b/app/controllers/custom_wizard/admin/wizard.rb similarity index 100% rename from controllers/custom_wizard/admin/wizard.rb rename to app/controllers/custom_wizard/admin/wizard.rb diff --git a/controllers/custom_wizard/realtime_validations.rb b/app/controllers/custom_wizard/realtime_validations.rb similarity index 100% rename from controllers/custom_wizard/realtime_validations.rb rename to app/controllers/custom_wizard/realtime_validations.rb diff --git a/controllers/custom_wizard/steps.rb b/app/controllers/custom_wizard/steps.rb similarity index 100% rename from controllers/custom_wizard/steps.rb rename to app/controllers/custom_wizard/steps.rb diff --git a/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb similarity index 98% rename from controllers/custom_wizard/wizard.rb rename to app/controllers/custom_wizard/wizard.rb index 854d1e39..12e6bdff 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class CustomWizard::WizardController < ::ApplicationController include ApplicationHelper - prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) + prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views')) layout 'wizard' before_action :ensure_plugin_enabled diff --git a/jobs/refresh_api_access_token.rb b/app/jobs/refresh_api_access_token.rb similarity index 100% rename from jobs/refresh_api_access_token.rb rename to app/jobs/refresh_api_access_token.rb diff --git a/jobs/set_after_time_wizard.rb b/app/jobs/set_after_time_wizard.rb similarity index 100% rename from jobs/set_after_time_wizard.rb rename to app/jobs/set_after_time_wizard.rb diff --git a/serializers/custom_wizard/api/authorization_serializer.rb b/app/serializers/custom_wizard/api/authorization_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/authorization_serializer.rb rename to app/serializers/custom_wizard/api/authorization_serializer.rb diff --git a/serializers/custom_wizard/api/basic_endpoint_serializer.rb b/app/serializers/custom_wizard/api/basic_endpoint_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/basic_endpoint_serializer.rb rename to app/serializers/custom_wizard/api/basic_endpoint_serializer.rb diff --git a/serializers/custom_wizard/api/endpoint_serializer.rb b/app/serializers/custom_wizard/api/endpoint_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/endpoint_serializer.rb rename to app/serializers/custom_wizard/api/endpoint_serializer.rb diff --git a/serializers/custom_wizard/api/log_serializer.rb b/app/serializers/custom_wizard/api/log_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/log_serializer.rb rename to app/serializers/custom_wizard/api/log_serializer.rb diff --git a/serializers/custom_wizard/api_serializer.rb b/app/serializers/custom_wizard/api_serializer.rb similarity index 100% rename from serializers/custom_wizard/api_serializer.rb rename to app/serializers/custom_wizard/api_serializer.rb diff --git a/serializers/custom_wizard/basic_api_serializer.rb b/app/serializers/custom_wizard/basic_api_serializer.rb similarity index 100% rename from serializers/custom_wizard/basic_api_serializer.rb rename to app/serializers/custom_wizard/basic_api_serializer.rb diff --git a/serializers/custom_wizard/basic_wizard_serializer.rb b/app/serializers/custom_wizard/basic_wizard_serializer.rb similarity index 100% rename from serializers/custom_wizard/basic_wizard_serializer.rb rename to app/serializers/custom_wizard/basic_wizard_serializer.rb diff --git a/serializers/custom_wizard/custom_field_serializer.rb b/app/serializers/custom_wizard/custom_field_serializer.rb similarity index 100% rename from serializers/custom_wizard/custom_field_serializer.rb rename to app/serializers/custom_wizard/custom_field_serializer.rb diff --git a/serializers/custom_wizard/log_serializer.rb b/app/serializers/custom_wizard/log_serializer.rb similarity index 100% rename from serializers/custom_wizard/log_serializer.rb rename to app/serializers/custom_wizard/log_serializer.rb diff --git a/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb b/app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb similarity index 100% rename from serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb rename to app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb diff --git a/serializers/custom_wizard/submission_serializer.rb b/app/serializers/custom_wizard/submission_serializer.rb similarity index 100% rename from serializers/custom_wizard/submission_serializer.rb rename to app/serializers/custom_wizard/submission_serializer.rb diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/app/serializers/custom_wizard/wizard_field_serializer.rb similarity index 100% rename from serializers/custom_wizard/wizard_field_serializer.rb rename to app/serializers/custom_wizard/wizard_field_serializer.rb diff --git a/serializers/custom_wizard/wizard_serializer.rb b/app/serializers/custom_wizard/wizard_serializer.rb similarity index 100% rename from serializers/custom_wizard/wizard_serializer.rb rename to app/serializers/custom_wizard/wizard_serializer.rb diff --git a/serializers/custom_wizard/wizard_step_serializer.rb b/app/serializers/custom_wizard/wizard_step_serializer.rb similarity index 100% rename from serializers/custom_wizard/wizard_step_serializer.rb rename to app/serializers/custom_wizard/wizard_step_serializer.rb diff --git a/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb similarity index 100% rename from views/layouts/wizard.html.erb rename to app/views/layouts/wizard.html.erb diff --git a/coverage/.last_run.json b/coverage/.last_run.json index 4c7365e8..13a74541 100644 --- a/coverage/.last_run.json +++ b/coverage/.last_run.json @@ -1,5 +1,5 @@ { "result": { - "line": 92.87 + "line": 84.4 } } diff --git a/extensions/custom_field/extension.rb b/lib/custom_wizard/extensions/custom_field/extension.rb similarity index 100% rename from extensions/custom_field/extension.rb rename to lib/custom_wizard/extensions/custom_field/extension.rb diff --git a/extensions/custom_field/preloader.rb b/lib/custom_wizard/extensions/custom_field/preloader.rb similarity index 100% rename from extensions/custom_field/preloader.rb rename to lib/custom_wizard/extensions/custom_field/preloader.rb diff --git a/extensions/custom_field/serializer.rb b/lib/custom_wizard/extensions/custom_field/serializer.rb similarity index 100% rename from extensions/custom_field/serializer.rb rename to lib/custom_wizard/extensions/custom_field/serializer.rb diff --git a/extensions/discourse_tagging.rb b/lib/custom_wizard/extensions/discourse_tagging.rb similarity index 100% rename from extensions/discourse_tagging.rb rename to lib/custom_wizard/extensions/discourse_tagging.rb diff --git a/extensions/extra_locales_controller.rb b/lib/custom_wizard/extensions/extra_locales_controller.rb similarity index 100% rename from extensions/extra_locales_controller.rb rename to lib/custom_wizard/extensions/extra_locales_controller.rb diff --git a/extensions/guardian.rb b/lib/custom_wizard/extensions/guardian.rb similarity index 100% rename from extensions/guardian.rb rename to lib/custom_wizard/extensions/guardian.rb diff --git a/extensions/invites_controller.rb b/lib/custom_wizard/extensions/invites_controller.rb similarity index 100% rename from extensions/invites_controller.rb rename to lib/custom_wizard/extensions/invites_controller.rb diff --git a/extensions/tags_controller.rb b/lib/custom_wizard/extensions/tags_controller.rb similarity index 100% rename from extensions/tags_controller.rb rename to lib/custom_wizard/extensions/tags_controller.rb diff --git a/extensions/users_controller.rb b/lib/custom_wizard/extensions/users_controller.rb similarity index 100% rename from extensions/users_controller.rb rename to lib/custom_wizard/extensions/users_controller.rb diff --git a/plugin.rb b/plugin.rb index b5eb348d..d819e9c4 100644 --- a/plugin.rb +++ b/plugin.rb @@ -57,18 +57,18 @@ after_initialize do %w[ ../lib/custom_wizard/engine.rb ../config/routes.rb - ../controllers/custom_wizard/admin/admin.rb - ../controllers/custom_wizard/admin/wizard.rb - ../controllers/custom_wizard/admin/submissions.rb - ../controllers/custom_wizard/admin/api.rb - ../controllers/custom_wizard/admin/logs.rb - ../controllers/custom_wizard/admin/manager.rb - ../controllers/custom_wizard/admin/custom_fields.rb - ../controllers/custom_wizard/wizard.rb - ../controllers/custom_wizard/steps.rb - ../controllers/custom_wizard/realtime_validations.rb - ../jobs/refresh_api_access_token.rb - ../jobs/set_after_time_wizard.rb + ../app/controllers/custom_wizard/admin/admin.rb + ../app/controllers/custom_wizard/admin/wizard.rb + ../app/controllers/custom_wizard/admin/submissions.rb + ../app/controllers/custom_wizard/admin/api.rb + ../app/controllers/custom_wizard/admin/logs.rb + ../app/controllers/custom_wizard/admin/manager.rb + ../app/controllers/custom_wizard/admin/custom_fields.rb + ../app/controllers/custom_wizard/wizard.rb + ../app/controllers/custom_wizard/steps.rb + ../app/controllers/custom_wizard/realtime_validations.rb + ../app/jobs/refresh_api_access_token.rb + ../app/jobs/set_after_time_wizard.rb ../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/action_result.rb @@ -93,29 +93,29 @@ after_initialize do ../lib/custom_wizard/api/log_entry.rb ../lib/custom_wizard/liquid_extensions/first_non_empty.rb ../lib/custom_wizard/exceptions/exceptions.rb - ../serializers/custom_wizard/api/authorization_serializer.rb - ../serializers/custom_wizard/api/basic_endpoint_serializer.rb - ../serializers/custom_wizard/api/endpoint_serializer.rb - ../serializers/custom_wizard/api/log_serializer.rb - ../serializers/custom_wizard/api_serializer.rb - ../serializers/custom_wizard/basic_api_serializer.rb - ../serializers/custom_wizard/basic_wizard_serializer.rb - ../serializers/custom_wizard/custom_field_serializer.rb - ../serializers/custom_wizard/wizard_field_serializer.rb - ../serializers/custom_wizard/wizard_step_serializer.rb - ../serializers/custom_wizard/wizard_serializer.rb - ../serializers/custom_wizard/log_serializer.rb - ../serializers/custom_wizard/submission_serializer.rb - ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb - ../extensions/extra_locales_controller.rb - ../extensions/invites_controller.rb - ../extensions/guardian.rb - ../extensions/users_controller.rb - ../extensions/tags_controller.rb - ../extensions/custom_field/preloader.rb - ../extensions/custom_field/serializer.rb - ../extensions/custom_field/extension.rb - ../extensions/discourse_tagging.rb + ../app/serializers/custom_wizard/api/authorization_serializer.rb + ../app/serializers/custom_wizard/api/basic_endpoint_serializer.rb + ../app/serializers/custom_wizard/api/endpoint_serializer.rb + ../app/serializers/custom_wizard/api/log_serializer.rb + ../app/serializers/custom_wizard/api_serializer.rb + ../app/serializers/custom_wizard/basic_api_serializer.rb + ../app/serializers/custom_wizard/basic_wizard_serializer.rb + ../app/serializers/custom_wizard/custom_field_serializer.rb + ../app/serializers/custom_wizard/wizard_field_serializer.rb + ../app/serializers/custom_wizard/wizard_step_serializer.rb + ../app/serializers/custom_wizard/wizard_serializer.rb + ../app/serializers/custom_wizard/log_serializer.rb + ../app/serializers/custom_wizard/submission_serializer.rb + ../app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb + ../lib/custom_wizard/extensions/extra_locales_controller.rb + ../lib/custom_wizard/extensions/invites_controller.rb + ../lib/custom_wizard/extensions/guardian.rb + ../lib/custom_wizard/extensions/users_controller.rb + ../lib/custom_wizard/extensions/tags_controller.rb + ../lib/custom_wizard/extensions/custom_field/preloader.rb + ../lib/custom_wizard/extensions/custom_field/serializer.rb + ../lib/custom_wizard/extensions/custom_field/extension.rb + ../lib/custom_wizard/extensions/discourse_tagging.rb ].each do |path| load File.expand_path(path, __FILE__) end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 8b617c39..248c44a5 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Action do fab!(:user) { Fabricate(:user, name: "Angus", username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) } diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 099d8681..e140931c 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Builder do fab!(:trusted_user) { diff --git a/spec/components/custom_wizard/cache_spec.rb b/spec/components/custom_wizard/cache_spec.rb index 2cc3b81a..2d7dd832 100644 --- a/spec/components/custom_wizard/cache_spec.rb +++ b/spec/components/custom_wizard/cache_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper.rb' - describe CustomWizard::Cache do it "writes and reads values to the cache" do CustomWizard::Cache.new('list').write([1, 2, 3]) diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index b17e26c6..155a6526 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::CustomField do let(:custom_field_json) { diff --git a/spec/components/custom_wizard/field_spec.rb b/spec/components/custom_wizard/field_spec.rb index 871c42cd..2386a004 100644 --- a/spec/components/custom_wizard/field_spec.rb +++ b/spec/components/custom_wizard/field_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Field do let(:field_hash) do diff --git a/spec/components/custom_wizard/log_spec.rb b/spec/components/custom_wizard/log_spec.rb index 30fd0173..d5c0de5d 100644 --- a/spec/components/custom_wizard/log_spec.rb +++ b/spec/components/custom_wizard/log_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Log do before do diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index ed66d7c1..422ffbf5 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Mapper do fab!(:user1) { diff --git a/spec/components/custom_wizard/realtime_validation_spec.rb b/spec/components/custom_wizard/realtime_validation_spec.rb index 819ac2ae..22e36dd1 100644 --- a/spec/components/custom_wizard/realtime_validation_spec.rb +++ b/spec/components/custom_wizard/realtime_validation_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::RealtimeValidation do validation_names = CustomWizard::RealtimeValidation.types.keys diff --git a/spec/components/custom_wizard/realtime_validations/similar_topics_spec.rb b/spec/components/custom_wizard/realtime_validations/similar_topics_spec.rb index eb81509e..6ea07684 100644 --- a/spec/components/custom_wizard/realtime_validations/similar_topics_spec.rb +++ b/spec/components/custom_wizard/realtime_validations/similar_topics_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../../plugin_helper' - describe ::CustomWizard::RealtimeValidation::SimilarTopics do let(:post) { create_post(title: "matching similar topic") } let(:topic) { post.topic } diff --git a/spec/components/custom_wizard/step_spec.rb b/spec/components/custom_wizard/step_spec.rb index bf4613a4..ac0abbb7 100644 --- a/spec/components/custom_wizard/step_spec.rb +++ b/spec/components/custom_wizard/step_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Step do let(:step_hash) do diff --git a/spec/components/custom_wizard/submission_spec.rb b/spec/components/custom_wizard/submission_spec.rb index b85af243..5a86eca6 100644 --- a/spec/components/custom_wizard/submission_spec.rb +++ b/spec/components/custom_wizard/submission_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Submission do fab!(:user) { Fabricate(:user) } diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb index 0e3dbdbe..06a7bcb7 100644 --- a/spec/components/custom_wizard/template_spec.rb +++ b/spec/components/custom_wizard/template_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::Template do fab!(:user) { Fabricate(:user) } diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb index 0ff0d1e7..8e730140 100644 --- a/spec/components/custom_wizard/template_validator_spec.rb +++ b/spec/components/custom_wizard/template_validator_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::TemplateValidator do fab!(:user) { Fabricate(:user) } diff --git a/spec/components/custom_wizard/update_validator_spec.rb b/spec/components/custom_wizard/update_validator_spec.rb index e976e1ff..b8aea789 100644 --- a/spec/components/custom_wizard/update_validator_spec.rb +++ b/spec/components/custom_wizard/update_validator_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::UpdateValidator do fab!(:user) { Fabricate(:user) } diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 67905f5a..a3f86f3e 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::Wizard do fab!(:user) { Fabricate(:user) } fab!(:trusted_user) { Fabricate(:user, trust_level: TrustLevel[3]) } diff --git a/spec/extensions/custom_field_extensions_spec.rb b/spec/extensions/custom_field_extensions_spec.rb index f0ce32f5..1b7ec6bf 100644 --- a/spec/extensions/custom_field_extensions_spec.rb +++ b/spec/extensions/custom_field_extensions_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../plugin_helper' - describe "custom field extensions" do fab!(:topic) { Fabricate(:topic) } fab!(:post) { Fabricate(:post) } diff --git a/spec/extensions/extra_locales_controller_spec.rb b/spec/extensions/extra_locales_controller_spec.rb index a71e39c4..32d3940c 100644 --- a/spec/extensions/extra_locales_controller_spec.rb +++ b/spec/extensions/extra_locales_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../plugin_helper' describe ExtraLocalesControllerCustomWizard, type: :request do let(:new_user) { Fabricate(:user, trust_level: TrustLevel[0]) } diff --git a/spec/extensions/guardian_extension_spec.rb b/spec/extensions/guardian_extension_spec.rb index d779fe11..ddfeb9ef 100644 --- a/spec/extensions/guardian_extension_spec.rb +++ b/spec/extensions/guardian_extension_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../plugin_helper' - describe ::Guardian do fab!(:user) { Fabricate(:user, name: "Angus", username: 'angus', email: "angus@email.com") diff --git a/spec/extensions/invites_controller_spec.rb b/spec/extensions/invites_controller_spec.rb index 47c4ca84..42e0ece7 100644 --- a/spec/extensions/invites_controller_spec.rb +++ b/spec/extensions/invites_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../plugin_helper' describe InvitesControllerCustomWizard, type: :request do fab!(:topic) { Fabricate(:topic) } diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb index 5a074040..db54c5dc 100644 --- a/spec/extensions/sprockets_directive_spec.rb +++ b/spec/extensions/sprockets_directive_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../plugin_helper' - describe "Sprockets: require_tree_discourse directive" do let(:discourse_asset_path) { "#{Rails.root}/app/assets/javascripts/" diff --git a/spec/extensions/tags_controller_spec.rb b/spec/extensions/tags_controller_spec.rb index 6df00d9a..b3c1ccc8 100644 --- a/spec/extensions/tags_controller_spec.rb +++ b/spec/extensions/tags_controller_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../plugin_helper' - describe ::TagsController, type: :request do fab!(:tag_1) { Fabricate(:tag, name: "Angus") } fab!(:tag_2) { Fabricate(:tag, name: "Faizaan") } diff --git a/spec/extensions/users_controller_spec.rb b/spec/extensions/users_controller_spec.rb index f4ba8e51..0c220a62 100644 --- a/spec/extensions/users_controller_spec.rb +++ b/spec/extensions/users_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../plugin_helper' describe CustomWizardUsersController, type: :request do let(:template) do diff --git a/spec/jobs/set_after_time_wizard_spec.rb b/spec/jobs/set_after_time_wizard_spec.rb index 35576f01..40418d13 100644 --- a/spec/jobs/set_after_time_wizard_spec.rb +++ b/spec/jobs/set_after_time_wizard_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../plugin_helper' - describe Jobs::SetAfterTimeWizard do fab!(:user1) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) } diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb deleted file mode 100644 index 9e4bbbbe..00000000 --- a/spec/plugin_helper.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -if ENV['SIMPLECOV'] - require 'simplecov' - - SimpleCov.start do - root "plugins/discourse-custom-wizard" - track_files "plugins/discourse-custom-wizard/**/*.rb" - add_filter { |src| src.filename =~ /(\/spec\/|\/db\/|plugin\.rb|api|gems)/ } - SimpleCov.minimum_coverage 80 - end -end - -require 'oj' -Oj.default_options = Oj.default_options.merge(cache_str: -1) - -require 'rails_helper' diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb index 8c1a8550..6f1aea12 100644 --- a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../../plugin_helper' describe CustomWizard::AdminCustomFieldsController do fab!(:admin_user) { Fabricate(:user, admin: true) } diff --git a/spec/requests/custom_wizard/admin/logs_controller_spec.rb b/spec/requests/custom_wizard/admin/logs_controller_spec.rb index 28b7d785..a2f619e4 100644 --- a/spec/requests/custom_wizard/admin/logs_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/logs_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../../plugin_helper' describe CustomWizard::AdminLogsController do fab!(:admin_user) { Fabricate(:user, admin: true) } diff --git a/spec/requests/custom_wizard/admin/manager_controller_spec.rb b/spec/requests/custom_wizard/admin/manager_controller_spec.rb index 7d087e3e..5c7b3ec3 100644 --- a/spec/requests/custom_wizard/admin/manager_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/manager_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../../plugin_helper' describe CustomWizard::AdminManagerController do fab!(:admin_user) { Fabricate(:user, admin: true) } diff --git a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb index 36296e95..c35b5365 100644 --- a/spec/requests/custom_wizard/admin/submissions_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/submissions_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../../plugin_helper' describe CustomWizard::AdminSubmissionsController do fab!(:admin_user) { Fabricate(:user, admin: true) } diff --git a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb index 82aa4fc5..de287374 100644 --- a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../../plugin_helper' describe CustomWizard::AdminWizardController do fab!(:admin_user) { Fabricate(:user, admin: true) } diff --git a/spec/requests/custom_wizard/application_controller_spec.rb b/spec/requests/custom_wizard/application_controller_spec.rb index 0b5513aa..23679c39 100644 --- a/spec/requests/custom_wizard/application_controller_spec.rb +++ b/spec/requests/custom_wizard/application_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe ApplicationController do fab!(:user) { diff --git a/spec/requests/custom_wizard/custom_field_extensions_spec.rb b/spec/requests/custom_wizard/custom_field_extensions_spec.rb index b991769a..5bb7ffd9 100644 --- a/spec/requests/custom_wizard/custom_field_extensions_spec.rb +++ b/spec/requests/custom_wizard/custom_field_extensions_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe "custom field extensions" do let!(:topic) { Fabricate(:topic) } let!(:post) { Fabricate(:post) } diff --git a/spec/requests/custom_wizard/realtime_validations_spec.rb b/spec/requests/custom_wizard/realtime_validations_spec.rb index 0d59e885..a57333b8 100644 --- a/spec/requests/custom_wizard/realtime_validations_spec.rb +++ b/spec/requests/custom_wizard/realtime_validations_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::RealtimeValidationsController do fab!(:validation_type) { "test_stub" } diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 5da75d8d..dc8d0130 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::StepsController do fab!(:user) { diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index f5bcd5ac..44e48ed7 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' describe CustomWizard::WizardController do fab!(:user) { diff --git a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb index bf575827..59647477 100644 --- a/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb +++ b/spec/serializers/custom_wizard/basic_wizard_serializer_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::BasicWizardSerializer do fab!(:user) { Fabricate(:user) } diff --git a/spec/serializers/custom_wizard/custom_field_serializer_spec.rb b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb index 4f5ffd72..0f6f5564 100644 --- a/spec/serializers/custom_wizard/custom_field_serializer_spec.rb +++ b/spec/serializers/custom_wizard/custom_field_serializer_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::CustomFieldSerializer do fab!(:user) { Fabricate(:user) } diff --git a/spec/serializers/custom_wizard/log_serializer_spec.rb b/spec/serializers/custom_wizard/log_serializer_spec.rb index bde16199..895cab54 100644 --- a/spec/serializers/custom_wizard/log_serializer_spec.rb +++ b/spec/serializers/custom_wizard/log_serializer_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::LogSerializer do fab!(:user) { Fabricate(:user) } diff --git a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb index a5a5e721..5f225f44 100644 --- a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::FieldSerializer do fab!(:user) { Fabricate(:user) } diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb index 2052639a..1b26ce24 100644 --- a/spec/serializers/custom_wizard/wizard_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_serializer_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::WizardSerializer do fab!(:user) { Fabricate(:user) } fab!(:category) { Fabricate(:category) } diff --git a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb index 21345352..0df76baf 100644 --- a/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_step_serializer_spec.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../../plugin_helper' - describe CustomWizard::StepSerializer do fab!(:user) { Fabricate(:user) } From 8cac2a596003ee99f11e32e8a7a7fe3095aaa9e3 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 12 Mar 2022 14:01:39 +0100 Subject: [PATCH 179/556] Update plugin.rb --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index d819e9c4..a1b377ac 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards -# version: 1.18.3 +# version: 1.18.4 # authors: Angus McLeod # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io From 7a56b9d3902a7f2bbebb08652a28c523accc5a9c Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 12 Mar 2022 14:02:11 +0100 Subject: [PATCH 180/556] Rubocop --- .simplecov | 1 + 1 file changed, 1 insertion(+) diff --git a/.simplecov b/.simplecov index 18b59116..c7b6143b 100644 --- a/.simplecov +++ b/.simplecov @@ -1,3 +1,4 @@ +# frozen_string_literal: true plugin = "discourse-custom-wizard" SimpleCov.configure do From 684a2a3801fcbe7b213fa1de1a9810ddf2720a51 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 12 Mar 2022 14:26:27 +0100 Subject: [PATCH 181/556] Update plugin-tests.yml --- .github/workflows/plugin-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 18f09d5d..782ebc4f 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -143,6 +143,7 @@ jobs: if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' run: | if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ] + then cp plugins/${{ steps.repo-name.outputs.value }}/.simplecov .simplecov export COVERAGE=1 fi From cb7bb4e12f95c6a3894ef8bb61ac532d118d0629 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 12 Mar 2022 14:49:41 +0100 Subject: [PATCH 182/556] Move to proper folder structure --- .../controllers}/custom_wizard/admin/admin.rb | 0 .../controllers}/custom_wizard/admin/api.rb | 0 .../custom_wizard/admin/custom_fields.rb | 0 .../controllers}/custom_wizard/admin/logs.rb | 0 .../custom_wizard/admin/manager.rb | 0 .../custom_wizard/admin/notice.rb | 0 .../custom_wizard/admin/submissions.rb | 0 .../custom_wizard/admin/subscription.rb | 0 .../custom_wizard/admin/wizard.rb | 0 .../custom_wizard/realtime_validations.rb | 0 .../controllers}/custom_wizard/steps.rb | 0 .../controllers}/custom_wizard/wizard.rb | 0 .../jobs}/regular/refresh_api_access_token.rb | 0 .../jobs}/regular/set_after_time_wizard.rb | 0 .../scheduled/custom_wizard/update_notices.rb | 0 .../custom_wizard/update_subscription.rb | 0 .../api/authorization_serializer.rb | 0 .../api/basic_endpoint_serializer.rb | 0 .../custom_wizard/api/endpoint_serializer.rb | 0 .../custom_wizard/api/log_serializer.rb | 0 .../custom_wizard/api_serializer.rb | 0 .../custom_wizard/basic_api_serializer.rb | 0 .../custom_wizard/basic_wizard_serializer.rb | 0 .../custom_wizard/custom_field_serializer.rb | 0 .../custom_wizard/log_serializer.rb | 0 .../custom_wizard/notice_serializer.rb | 0 .../similar_topics_serializer.rb | 0 .../custom_wizard/submission_serializer.rb | 0 .../subscription/authentication_serializer.rb | 0 .../subscription/subscription_serializer.rb | 0 .../custom_wizard/subscription_serializer.rb | 0 .../custom_wizard/wizard_field_serializer.rb | 0 .../custom_wizard/wizard_serializer.rb | 0 .../custom_wizard/wizard_step_serializer.rb | 0 {views => app/views}/layouts/wizard.html.erb | 0 .../extensions}/custom_field/extension.rb | 0 .../extensions}/custom_field/preloader.rb | 0 .../extensions}/custom_field/serializer.rb | 0 .../extensions}/extra_locales_controller.rb | 0 .../extensions}/invites_controller.rb | 0 .../extensions}/users_controller.rb | 0 plugin.rb | 80 +++++++++---------- 42 files changed, 40 insertions(+), 40 deletions(-) rename {controllers => app/controllers}/custom_wizard/admin/admin.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/api.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/custom_fields.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/logs.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/manager.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/notice.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/submissions.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/subscription.rb (100%) rename {controllers => app/controllers}/custom_wizard/admin/wizard.rb (100%) rename {controllers => app/controllers}/custom_wizard/realtime_validations.rb (100%) rename {controllers => app/controllers}/custom_wizard/steps.rb (100%) rename {controllers => app/controllers}/custom_wizard/wizard.rb (100%) rename {jobs => app/jobs}/regular/refresh_api_access_token.rb (100%) rename {jobs => app/jobs}/regular/set_after_time_wizard.rb (100%) rename {jobs => app/jobs}/scheduled/custom_wizard/update_notices.rb (100%) rename {jobs => app/jobs}/scheduled/custom_wizard/update_subscription.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/authorization_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/basic_endpoint_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/endpoint_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api/log_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/api_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/basic_api_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/basic_wizard_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/custom_field_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/log_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/notice_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/realtime_validation/similar_topics_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/submission_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/subscription/authentication_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/subscription/subscription_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/subscription_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/wizard_field_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/wizard_serializer.rb (100%) rename {serializers => app/serializers}/custom_wizard/wizard_step_serializer.rb (100%) rename {views => app/views}/layouts/wizard.html.erb (100%) rename {extensions => lib/custom_wizard/extensions}/custom_field/extension.rb (100%) rename {extensions => lib/custom_wizard/extensions}/custom_field/preloader.rb (100%) rename {extensions => lib/custom_wizard/extensions}/custom_field/serializer.rb (100%) rename {extensions => lib/custom_wizard/extensions}/extra_locales_controller.rb (100%) rename {extensions => lib/custom_wizard/extensions}/invites_controller.rb (100%) rename {extensions => lib/custom_wizard/extensions}/users_controller.rb (100%) diff --git a/controllers/custom_wizard/admin/admin.rb b/app/controllers/custom_wizard/admin/admin.rb similarity index 100% rename from controllers/custom_wizard/admin/admin.rb rename to app/controllers/custom_wizard/admin/admin.rb diff --git a/controllers/custom_wizard/admin/api.rb b/app/controllers/custom_wizard/admin/api.rb similarity index 100% rename from controllers/custom_wizard/admin/api.rb rename to app/controllers/custom_wizard/admin/api.rb diff --git a/controllers/custom_wizard/admin/custom_fields.rb b/app/controllers/custom_wizard/admin/custom_fields.rb similarity index 100% rename from controllers/custom_wizard/admin/custom_fields.rb rename to app/controllers/custom_wizard/admin/custom_fields.rb diff --git a/controllers/custom_wizard/admin/logs.rb b/app/controllers/custom_wizard/admin/logs.rb similarity index 100% rename from controllers/custom_wizard/admin/logs.rb rename to app/controllers/custom_wizard/admin/logs.rb diff --git a/controllers/custom_wizard/admin/manager.rb b/app/controllers/custom_wizard/admin/manager.rb similarity index 100% rename from controllers/custom_wizard/admin/manager.rb rename to app/controllers/custom_wizard/admin/manager.rb diff --git a/controllers/custom_wizard/admin/notice.rb b/app/controllers/custom_wizard/admin/notice.rb similarity index 100% rename from controllers/custom_wizard/admin/notice.rb rename to app/controllers/custom_wizard/admin/notice.rb diff --git a/controllers/custom_wizard/admin/submissions.rb b/app/controllers/custom_wizard/admin/submissions.rb similarity index 100% rename from controllers/custom_wizard/admin/submissions.rb rename to app/controllers/custom_wizard/admin/submissions.rb diff --git a/controllers/custom_wizard/admin/subscription.rb b/app/controllers/custom_wizard/admin/subscription.rb similarity index 100% rename from controllers/custom_wizard/admin/subscription.rb rename to app/controllers/custom_wizard/admin/subscription.rb diff --git a/controllers/custom_wizard/admin/wizard.rb b/app/controllers/custom_wizard/admin/wizard.rb similarity index 100% rename from controllers/custom_wizard/admin/wizard.rb rename to app/controllers/custom_wizard/admin/wizard.rb diff --git a/controllers/custom_wizard/realtime_validations.rb b/app/controllers/custom_wizard/realtime_validations.rb similarity index 100% rename from controllers/custom_wizard/realtime_validations.rb rename to app/controllers/custom_wizard/realtime_validations.rb diff --git a/controllers/custom_wizard/steps.rb b/app/controllers/custom_wizard/steps.rb similarity index 100% rename from controllers/custom_wizard/steps.rb rename to app/controllers/custom_wizard/steps.rb diff --git a/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb similarity index 100% rename from controllers/custom_wizard/wizard.rb rename to app/controllers/custom_wizard/wizard.rb diff --git a/jobs/regular/refresh_api_access_token.rb b/app/jobs/regular/refresh_api_access_token.rb similarity index 100% rename from jobs/regular/refresh_api_access_token.rb rename to app/jobs/regular/refresh_api_access_token.rb diff --git a/jobs/regular/set_after_time_wizard.rb b/app/jobs/regular/set_after_time_wizard.rb similarity index 100% rename from jobs/regular/set_after_time_wizard.rb rename to app/jobs/regular/set_after_time_wizard.rb diff --git a/jobs/scheduled/custom_wizard/update_notices.rb b/app/jobs/scheduled/custom_wizard/update_notices.rb similarity index 100% rename from jobs/scheduled/custom_wizard/update_notices.rb rename to app/jobs/scheduled/custom_wizard/update_notices.rb diff --git a/jobs/scheduled/custom_wizard/update_subscription.rb b/app/jobs/scheduled/custom_wizard/update_subscription.rb similarity index 100% rename from jobs/scheduled/custom_wizard/update_subscription.rb rename to app/jobs/scheduled/custom_wizard/update_subscription.rb diff --git a/serializers/custom_wizard/api/authorization_serializer.rb b/app/serializers/custom_wizard/api/authorization_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/authorization_serializer.rb rename to app/serializers/custom_wizard/api/authorization_serializer.rb diff --git a/serializers/custom_wizard/api/basic_endpoint_serializer.rb b/app/serializers/custom_wizard/api/basic_endpoint_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/basic_endpoint_serializer.rb rename to app/serializers/custom_wizard/api/basic_endpoint_serializer.rb diff --git a/serializers/custom_wizard/api/endpoint_serializer.rb b/app/serializers/custom_wizard/api/endpoint_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/endpoint_serializer.rb rename to app/serializers/custom_wizard/api/endpoint_serializer.rb diff --git a/serializers/custom_wizard/api/log_serializer.rb b/app/serializers/custom_wizard/api/log_serializer.rb similarity index 100% rename from serializers/custom_wizard/api/log_serializer.rb rename to app/serializers/custom_wizard/api/log_serializer.rb diff --git a/serializers/custom_wizard/api_serializer.rb b/app/serializers/custom_wizard/api_serializer.rb similarity index 100% rename from serializers/custom_wizard/api_serializer.rb rename to app/serializers/custom_wizard/api_serializer.rb diff --git a/serializers/custom_wizard/basic_api_serializer.rb b/app/serializers/custom_wizard/basic_api_serializer.rb similarity index 100% rename from serializers/custom_wizard/basic_api_serializer.rb rename to app/serializers/custom_wizard/basic_api_serializer.rb diff --git a/serializers/custom_wizard/basic_wizard_serializer.rb b/app/serializers/custom_wizard/basic_wizard_serializer.rb similarity index 100% rename from serializers/custom_wizard/basic_wizard_serializer.rb rename to app/serializers/custom_wizard/basic_wizard_serializer.rb diff --git a/serializers/custom_wizard/custom_field_serializer.rb b/app/serializers/custom_wizard/custom_field_serializer.rb similarity index 100% rename from serializers/custom_wizard/custom_field_serializer.rb rename to app/serializers/custom_wizard/custom_field_serializer.rb diff --git a/serializers/custom_wizard/log_serializer.rb b/app/serializers/custom_wizard/log_serializer.rb similarity index 100% rename from serializers/custom_wizard/log_serializer.rb rename to app/serializers/custom_wizard/log_serializer.rb diff --git a/serializers/custom_wizard/notice_serializer.rb b/app/serializers/custom_wizard/notice_serializer.rb similarity index 100% rename from serializers/custom_wizard/notice_serializer.rb rename to app/serializers/custom_wizard/notice_serializer.rb diff --git a/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb b/app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb similarity index 100% rename from serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb rename to app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb diff --git a/serializers/custom_wizard/submission_serializer.rb b/app/serializers/custom_wizard/submission_serializer.rb similarity index 100% rename from serializers/custom_wizard/submission_serializer.rb rename to app/serializers/custom_wizard/submission_serializer.rb diff --git a/serializers/custom_wizard/subscription/authentication_serializer.rb b/app/serializers/custom_wizard/subscription/authentication_serializer.rb similarity index 100% rename from serializers/custom_wizard/subscription/authentication_serializer.rb rename to app/serializers/custom_wizard/subscription/authentication_serializer.rb diff --git a/serializers/custom_wizard/subscription/subscription_serializer.rb b/app/serializers/custom_wizard/subscription/subscription_serializer.rb similarity index 100% rename from serializers/custom_wizard/subscription/subscription_serializer.rb rename to app/serializers/custom_wizard/subscription/subscription_serializer.rb diff --git a/serializers/custom_wizard/subscription_serializer.rb b/app/serializers/custom_wizard/subscription_serializer.rb similarity index 100% rename from serializers/custom_wizard/subscription_serializer.rb rename to app/serializers/custom_wizard/subscription_serializer.rb diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/app/serializers/custom_wizard/wizard_field_serializer.rb similarity index 100% rename from serializers/custom_wizard/wizard_field_serializer.rb rename to app/serializers/custom_wizard/wizard_field_serializer.rb diff --git a/serializers/custom_wizard/wizard_serializer.rb b/app/serializers/custom_wizard/wizard_serializer.rb similarity index 100% rename from serializers/custom_wizard/wizard_serializer.rb rename to app/serializers/custom_wizard/wizard_serializer.rb diff --git a/serializers/custom_wizard/wizard_step_serializer.rb b/app/serializers/custom_wizard/wizard_step_serializer.rb similarity index 100% rename from serializers/custom_wizard/wizard_step_serializer.rb rename to app/serializers/custom_wizard/wizard_step_serializer.rb diff --git a/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb similarity index 100% rename from views/layouts/wizard.html.erb rename to app/views/layouts/wizard.html.erb diff --git a/extensions/custom_field/extension.rb b/lib/custom_wizard/extensions/custom_field/extension.rb similarity index 100% rename from extensions/custom_field/extension.rb rename to lib/custom_wizard/extensions/custom_field/extension.rb diff --git a/extensions/custom_field/preloader.rb b/lib/custom_wizard/extensions/custom_field/preloader.rb similarity index 100% rename from extensions/custom_field/preloader.rb rename to lib/custom_wizard/extensions/custom_field/preloader.rb diff --git a/extensions/custom_field/serializer.rb b/lib/custom_wizard/extensions/custom_field/serializer.rb similarity index 100% rename from extensions/custom_field/serializer.rb rename to lib/custom_wizard/extensions/custom_field/serializer.rb diff --git a/extensions/extra_locales_controller.rb b/lib/custom_wizard/extensions/extra_locales_controller.rb similarity index 100% rename from extensions/extra_locales_controller.rb rename to lib/custom_wizard/extensions/extra_locales_controller.rb diff --git a/extensions/invites_controller.rb b/lib/custom_wizard/extensions/invites_controller.rb similarity index 100% rename from extensions/invites_controller.rb rename to lib/custom_wizard/extensions/invites_controller.rb diff --git a/extensions/users_controller.rb b/lib/custom_wizard/extensions/users_controller.rb similarity index 100% rename from extensions/users_controller.rb rename to lib/custom_wizard/extensions/users_controller.rb diff --git a/plugin.rb b/plugin.rb index 8c6be4db..e41fcee8 100644 --- a/plugin.rb +++ b/plugin.rb @@ -63,22 +63,22 @@ after_initialize do %w[ ../lib/custom_wizard/engine.rb ../config/routes.rb - ../controllers/custom_wizard/admin/admin.rb - ../controllers/custom_wizard/admin/wizard.rb - ../controllers/custom_wizard/admin/submissions.rb - ../controllers/custom_wizard/admin/api.rb - ../controllers/custom_wizard/admin/logs.rb - ../controllers/custom_wizard/admin/manager.rb - ../controllers/custom_wizard/admin/custom_fields.rb - ../controllers/custom_wizard/admin/subscription.rb - ../controllers/custom_wizard/admin/notice.rb - ../controllers/custom_wizard/wizard.rb - ../controllers/custom_wizard/steps.rb - ../controllers/custom_wizard/realtime_validations.rb - ../jobs/regular/refresh_api_access_token.rb - ../jobs/regular/set_after_time_wizard.rb - ../jobs/scheduled/custom_wizard/update_subscription.rb - ../jobs/scheduled/custom_wizard/update_notices.rb + ../app/controllers/custom_wizard/admin/admin.rb + ../app/controllers/custom_wizard/admin/wizard.rb + ../app/controllers/custom_wizard/admin/submissions.rb + ../app/controllers/custom_wizard/admin/api.rb + ../app/controllers/custom_wizard/admin/logs.rb + ../app/controllers/custom_wizard/admin/manager.rb + ../app/controllers/custom_wizard/admin/custom_fields.rb + ../app/controllers/custom_wizard/admin/subscription.rb + ../app/controllers/custom_wizard/admin/notice.rb + ../app/controllers/custom_wizard/wizard.rb + ../app/controllers/custom_wizard/steps.rb + ../app/controllers/custom_wizard/realtime_validations.rb + ../app/jobs/regular/refresh_api_access_token.rb + ../app/jobs/regular/set_after_time_wizard.rb + ../app/jobs/scheduled/custom_wizard/update_subscription.rb + ../app/jobs/scheduled/custom_wizard/update_notices.rb ../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/action_result.rb @@ -108,30 +108,30 @@ after_initialize do ../lib/custom_wizard/api/log_entry.rb ../lib/custom_wizard/liquid_extensions/first_non_empty.rb ../lib/custom_wizard/exceptions/exceptions.rb - ../serializers/custom_wizard/api/authorization_serializer.rb - ../serializers/custom_wizard/api/basic_endpoint_serializer.rb - ../serializers/custom_wizard/api/endpoint_serializer.rb - ../serializers/custom_wizard/api/log_serializer.rb - ../serializers/custom_wizard/api_serializer.rb - ../serializers/custom_wizard/basic_api_serializer.rb - ../serializers/custom_wizard/basic_wizard_serializer.rb - ../serializers/custom_wizard/custom_field_serializer.rb - ../serializers/custom_wizard/wizard_field_serializer.rb - ../serializers/custom_wizard/wizard_step_serializer.rb - ../serializers/custom_wizard/wizard_serializer.rb - ../serializers/custom_wizard/log_serializer.rb - ../serializers/custom_wizard/submission_serializer.rb - ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb - ../serializers/custom_wizard/subscription/authentication_serializer.rb - ../serializers/custom_wizard/subscription/subscription_serializer.rb - ../serializers/custom_wizard/subscription_serializer.rb - ../serializers/custom_wizard/notice_serializer.rb - ../extensions/extra_locales_controller.rb - ../extensions/invites_controller.rb - ../extensions/users_controller.rb - ../extensions/custom_field/preloader.rb - ../extensions/custom_field/serializer.rb - ../extensions/custom_field/extension.rb + ../app/serializers/custom_wizard/api/authorization_serializer.rb + ../app/serializers/custom_wizard/api/basic_endpoint_serializer.rb + ../app/serializers/custom_wizard/api/endpoint_serializer.rb + ../app/serializers/custom_wizard/api/log_serializer.rb + ../app/serializers/custom_wizard/api_serializer.rb + ../app/serializers/custom_wizard/basic_api_serializer.rb + ../app/serializers/custom_wizard/basic_wizard_serializer.rb + ../app/serializers/custom_wizard/custom_field_serializer.rb + ../app/serializers/custom_wizard/wizard_field_serializer.rb + ../app/serializers/custom_wizard/wizard_step_serializer.rb + ../app/serializers/custom_wizard/wizard_serializer.rb + ../app/serializers/custom_wizard/log_serializer.rb + ../app/serializers/custom_wizard/submission_serializer.rb + ../app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb + ../app/serializers/custom_wizard/subscription/authentication_serializer.rb + ../app/serializers/custom_wizard/subscription/subscription_serializer.rb + ../app/serializers/custom_wizard/subscription_serializer.rb + ../app/serializers/custom_wizard/notice_serializer.rb + ..//lib/custom_wizard/extensions/extra_locales_controller.rb + ..//lib/custom_wizard/extensions/invites_controller.rb + ..//lib/custom_wizard/extensions/users_controller.rb + ..//lib/custom_wizard/extensions/custom_field/preloader.rb + ..//lib/custom_wizard/extensions/custom_field/serializer.rb + ..//lib/custom_wizard/extensions/custom_field/extension.rb ].each do |path| load File.expand_path(path, __FILE__) end From d57f260defa01052fa0e9f544add48efc3606982 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 12 Mar 2022 15:20:54 +0100 Subject: [PATCH 183/556] Cleanup after merge --- app/controllers/custom_wizard/admin/logs.rb | 41 +++++++++++++++++-- .../custom_wizard/log_serializer.rb | 8 +++- .../custom_wizard/submission_serializer.rb | 34 +++++++++++---- spec/plugin_helper.rb | 17 -------- 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/app/controllers/custom_wizard/admin/logs.rb b/app/controllers/custom_wizard/admin/logs.rb index 976814f8..7ca37bb2 100644 --- a/app/controllers/custom_wizard/admin/logs.rb +++ b/app/controllers/custom_wizard/admin/logs.rb @@ -1,9 +1,44 @@ # frozen_string_literal: true class CustomWizard::AdminLogsController < CustomWizard::AdminController + before_action :find_wizard, except: [:index] + def index - render_serialized( - CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i), - CustomWizard::LogSerializer + render json: ActiveModel::ArraySerializer.new( + CustomWizard::Wizard.list(current_user), + each_serializer: CustomWizard::BasicWizardSerializer ) end + + def show + render_json_dump( + wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), + logs: ActiveModel::ArraySerializer.new( + log_list.logs, + each_serializer: CustomWizard::LogSerializer + ), + total: log_list.total + ) + end + + protected + + def log_list + @log_list ||= begin + list = CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i, params[:wizard_id]) + + if list.logs.any? && (usernames = list.logs.map(&:username)).present? + user_map = User.where(username: usernames) + .reduce({}) do |result, user| + result[user.username] = user + result + end + + list.logs.each do |log_item| + log_item.user = user_map[log_item.username] + end + end + + list + end + end end diff --git a/app/serializers/custom_wizard/log_serializer.rb b/app/serializers/custom_wizard/log_serializer.rb index e521c573..56c5fd8f 100644 --- a/app/serializers/custom_wizard/log_serializer.rb +++ b/app/serializers/custom_wizard/log_serializer.rb @@ -1,4 +1,10 @@ # frozen_string_literal: true + class CustomWizard::LogSerializer < ApplicationSerializer - attributes :message, :date + attributes :date, + :action, + :username, + :message + + has_one :user, serializer: ::BasicUserSerializer, embed: :objects end diff --git a/app/serializers/custom_wizard/submission_serializer.rb b/app/serializers/custom_wizard/submission_serializer.rb index 52f0cb32..9c2f8133 100644 --- a/app/serializers/custom_wizard/submission_serializer.rb +++ b/app/serializers/custom_wizard/submission_serializer.rb @@ -1,16 +1,32 @@ # frozen_string_literal: true class CustomWizard::SubmissionSerializer < ApplicationSerializer attributes :id, - :username, :fields, - :submitted_at, - :route_to, - :redirect_on_complete, - :redirect_to + :submitted_at - def username - object.user.present? ? - object.user.username : - I18n.t('admin.wizard.submission.no_user', user_id: object.user_id) + has_one :user, serializer: ::BasicUserSerializer, embed: :objects + + def include_user? + object.user.present? + end + + def fields + @fields ||= begin + result = {} + + object.wizard.template['steps'].each do |step| + step['fields'].each do |field| + if value = object.fields[field['id']] + result[field['id']] = { + value: value, + type: field['type'], + label: field['label'] + } + end + end + end + + result + end end end diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 232a2411..6e340ccf 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -1,22 +1,5 @@ # frozen_string_literal: true -if ENV['SIMPLECOV'] - require 'simplecov' - - SimpleCov.start do - root "plugins/discourse-custom-wizard" - track_files "plugins/discourse-custom-wizard/**/*.rb" - add_filter { |src| src.filename =~ /(\/spec\/|\/db\/|plugin\.rb|api|gems)/ } - SimpleCov.minimum_coverage 80 - end -end - -require 'oj' -Oj.default_options = Oj.default_options.merge(cache_str: -1) - -require 'rails_helper' -require 'webmock/rspec' - def get_wizard_fixture(path) JSON.parse( File.open( From 8893e6caf1dc97c7351023451f7916a86fbd9c3d Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 12:33:34 +0100 Subject: [PATCH 184/556] Refactor wizard client and add tests --- assets/javascripts/wizard-custom-globals.js | 6 - assets/javascripts/wizard-custom-start.js | 2 +- assets/javascripts/wizard-custom.js | 15 +- assets/javascripts/wizard-qunit.js | 16 + assets/javascripts/wizard/application.js.es6 | 19 + .../components/custom-user-selector.js.es6 | 9 +- .../wizard/components/field-validators.js.es6 | 3 + .../similar-topics-validator.js.es6 | 1 + .../wizard/components/validator.js.es6 | 3 +- .../components/wizard-composer-editor.js.es6 | 1 + .../wizard-composer-hyperlink.js.es6 | 1 + .../components/wizard-date-input.js.es6 | 1 + .../components/wizard-date-time-input.js.es6 | 2 + .../components/wizard-field-category.js.es6 | 2 + .../components/wizard-field-checkbox.js.es6 | 5 + .../wizard-field-composer-preview.js.es6 | 2 + .../components/wizard-field-composer.js.es6 | 2 + .../components/wizard-field-date-time.js.es6 | 2 + .../components/wizard-field-date.js.es6 | 2 + .../components/wizard-field-dropdown.js.es6 | 15 + .../components/wizard-field-group.js.es6 | 5 + .../components/wizard-field-number.js.es6 | 5 + .../wizard/components/wizard-field-tag.js.es6 | 5 + .../components/wizard-field-text.js.es6 | 9 + .../components/wizard-field-textarea.js.es6 | 9 + .../components/wizard-field-time.js.es6 | 2 + .../components/wizard-field-upload.js.es6 | 4 + .../wizard/components/wizard-field-url.js.es6 | 5 + .../wizard-field-user-selector.js.es6 | 5 + .../wizard/components/wizard-field.js.es6 | 34 ++ .../components/wizard-group-selector.js.es6 | 1 + .../wizard/components/wizard-no-access.js.es6 | 23 +- .../components/wizard-similar-topics.js.es6 | 1 + .../wizard/components/wizard-step-form.js.es6 | 9 + .../wizard/components/wizard-step.js.es6 | 247 +++++++++ .../components/wizard-text-field.js.es6 | 6 +- .../components/wizard-time-input.js.es6 | 4 +- .../wizard/controllers/custom.js.es6 | 3 - .../{custom-step.js.es6 => step.js.es6} | 11 +- .../wizard/controllers/wizard-index.js.es6 | 24 + .../wizard/controllers/wizard.js.es6 | 5 + .../javascripts/wizard/custom-wizard.js.es6 | 39 -- .../initializers/custom-wizard-step.js.es6 | 219 -------- .../wizard/initializers/custom-wizard.js.es6 | 126 ----- .../lib/initialize/create-contexts.js.es6 | 12 + .../lib/initialize/inject-objects.js.es6 | 49 ++ .../initialize/patch-components.js.es6} | 140 ++---- .../lib/initialize/register-files.js.es6 | 26 + .../wizard/lib/initialize/wizard.js.es6 | 49 ++ .../wizard/lib/utilities-lite.js.es6 | 71 --- assets/javascripts/wizard/models/field.js.es6 | 79 +++ assets/javascripts/wizard/models/site.js.es6 | 3 +- assets/javascripts/wizard/models/step.js.es6 | 114 +++++ .../models/{custom.js.es6 => wizard.js.es6} | 6 +- assets/javascripts/wizard/router.js.es6 | 17 + .../wizard/routes/application.js.es6 | 3 + .../wizard/routes/custom-steps.js.es6 | 5 - assets/javascripts/wizard/routes/index.js.es6 | 9 + .../{custom-step.js.es6 => step.js.es6} | 11 +- assets/javascripts/wizard/routes/steps.js.es6 | 7 + ...ustom-index.js.es6 => wizard-index.js.es6} | 26 +- .../routes/{custom.js.es6 => wizard.js.es6} | 59 ++- .../components/wizard-field-dropdown.hbs | 1 + .../wizard/templates/custom.index.hbs | 15 - assets/javascripts/wizard/templates/index.hbs | 1 + .../templates/{custom.step.hbs => step.hbs} | 0 .../wizard/templates/wizard-index.hbs | 3 + .../templates/{custom.hbs => wizard.hbs} | 4 - .../wizard/tests/acceptance/field-test.js.es6 | 135 +++++ .../wizard/tests/acceptance/step-test.js.es6 | 47 ++ .../tests/acceptance/wizard-test.js.es6 | 72 +++ .../javascripts/wizard/tests/bootstrap.js.es6 | 17 + .../wizard/tests/fixtures/categories.js.es6 | 209 ++++++++ .../wizard/tests/fixtures/groups.js.es6 | 313 ++++++++++++ .../tests/fixtures/site-settings.js.es6 | 283 +++++++++++ .../wizard/tests/fixtures/tags.js.es6 | 22 + .../wizard/tests/fixtures/update.js.es6 | 5 + .../wizard/tests/fixtures/user.js.es6 | 34 ++ .../wizard/tests/fixtures/users.js.es6 | 14 + .../wizard/tests/fixtures/wizard.js.es6 | 469 ++++++++++++++++++ .../wizard/tests/helpers/acceptance.js.es6 | 53 ++ .../wizard/tests/helpers/start-app.js.es6 | 19 + .../wizard/tests/helpers/step.js.es6 | 20 + .../wizard/tests/helpers/test.js.es6 | 7 + .../wizard/tests/helpers/wizard.js.es6 | 52 ++ .../javascripts/wizard/tests/pretender.js.es6 | 28 +- .../stylesheets/wizard/custom/composer.scss | 2 +- config/routes.rb | 1 + controllers/custom_wizard/wizard.rb | 112 +++-- extensions/extra_locales_controller.rb | 1 + lib/custom_wizard/validators/update.rb | 2 +- plugin.rb | 3 +- .../javascripts/acceptance/wizard-test.js.es6 | 30 -- test/javascripts/fixtures/wizard.js | 340 ------------- test/javascripts/helpers/start-app.js | 23 - test/javascripts/plugin_helper.js | 70 --- views/layouts/qunit.html.erb | 28 ++ views/layouts/wizard.html.erb | 6 +- 98 files changed, 2834 insertions(+), 1203 deletions(-) delete mode 100644 assets/javascripts/wizard-custom-globals.js create mode 100644 assets/javascripts/wizard-qunit.js create mode 100644 assets/javascripts/wizard/application.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-group.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-number.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-tag.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-text.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-textarea.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-url.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-step-form.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-step.js.es6 delete mode 100644 assets/javascripts/wizard/controllers/custom.js.es6 rename assets/javascripts/wizard/controllers/{custom-step.js.es6 => step.js.es6} (76%) create mode 100644 assets/javascripts/wizard/controllers/wizard-index.js.es6 create mode 100644 assets/javascripts/wizard/controllers/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/custom-wizard.js.es6 delete mode 100644 assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 delete mode 100644 assets/javascripts/wizard/initializers/custom-wizard.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 rename assets/javascripts/wizard/{initializers/custom-wizard-field.js.es6 => lib/initialize/patch-components.js.es6} (52%) create mode 100644 assets/javascripts/wizard/lib/initialize/register-files.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/lib/utilities-lite.js.es6 create mode 100644 assets/javascripts/wizard/models/field.js.es6 create mode 100644 assets/javascripts/wizard/models/step.js.es6 rename assets/javascripts/wizard/models/{custom.js.es6 => wizard.js.es6} (96%) create mode 100644 assets/javascripts/wizard/router.js.es6 create mode 100644 assets/javascripts/wizard/routes/application.js.es6 delete mode 100644 assets/javascripts/wizard/routes/custom-steps.js.es6 create mode 100644 assets/javascripts/wizard/routes/index.js.es6 rename assets/javascripts/wizard/routes/{custom-step.js.es6 => step.js.es6} (78%) create mode 100644 assets/javascripts/wizard/routes/steps.js.es6 rename assets/javascripts/wizard/routes/{custom-index.js.es6 => wizard-index.js.es6} (50%) rename assets/javascripts/wizard/routes/{custom.js.es6 => wizard.js.es6} (59%) delete mode 100644 assets/javascripts/wizard/templates/custom.index.hbs create mode 100644 assets/javascripts/wizard/templates/index.hbs rename assets/javascripts/wizard/templates/{custom.step.hbs => step.hbs} (100%) create mode 100644 assets/javascripts/wizard/templates/wizard-index.hbs rename assets/javascripts/wizard/templates/{custom.hbs => wizard.hbs} (86%) create mode 100644 assets/javascripts/wizard/tests/acceptance/field-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/acceptance/step-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/bootstrap.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/categories.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/groups.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/tags.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/update.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/user.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/users.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/wizard.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/acceptance.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/start-app.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/step.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/test.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/wizard.js.es6 rename views/wizard-pretender.js => assets/javascripts/wizard/tests/pretender.js.es6 (56%) delete mode 100644 test/javascripts/acceptance/wizard-test.js.es6 delete mode 100644 test/javascripts/fixtures/wizard.js delete mode 100644 test/javascripts/helpers/start-app.js delete mode 100644 test/javascripts/plugin_helper.js create mode 100644 views/layouts/qunit.html.erb diff --git a/assets/javascripts/wizard-custom-globals.js b/assets/javascripts/wizard-custom-globals.js deleted file mode 100644 index 83923cba..00000000 --- a/assets/javascripts/wizard-custom-globals.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint no-undef: 0*/ -window.Discourse = {}; -window.Wizard = {}; -Wizard.SiteSettings = {}; -Discourse.__widget_helpers = {}; -Discourse.SiteSettings = Wizard.SiteSettings; diff --git a/assets/javascripts/wizard-custom-start.js b/assets/javascripts/wizard-custom-start.js index 3ffa9c5a..b92d2871 100644 --- a/assets/javascripts/wizard-custom-start.js +++ b/assets/javascripts/wizard-custom-start.js @@ -1,4 +1,4 @@ (function () { - let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/custom-wizard").default.create(); + let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/application").default.create(); wizard.start(); })(); diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 826cdf2d..2a5eb97a 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,10 +1,13 @@ +//= require_tree_discourse truth-helpers/addon +//= require_tree_discourse discourse-common/addon +//= require_tree_discourse select-kit/addon +//= require_tree_discourse wizard/lib +//= require_tree_discourse wizard/mixins //= require_tree_discourse discourse/app/lib //= require_tree_discourse discourse/app/mixins //= require discourse/app/adapters/rest - //= require message-bus - //= require_tree_discourse discourse/app/models //= require discourse/app/helpers/category-link @@ -12,9 +15,6 @@ //= require discourse/app/helpers/format-username //= require discourse/app/helpers/share-url //= require discourse/app/helpers/decorate-username-selector -//= require discourse-common/addon/helpers/component-for-collection -//= require discourse-common/addon/helpers/component-for-row -//= require discourse-common/addon/lib/raw-templates //= require discourse/app/helpers/discourse-tag //= require discourse/app/services/app-events @@ -59,7 +59,6 @@ //= require polyfills //= require markdown-it-bundle -//= require lodash.js //= require template_include.js //= require itsatrap.js //= require caret_position.js @@ -69,11 +68,11 @@ //= require bootbox.js //= require discourse-shims -//= require ./wizard/custom-wizard +//= require ./wizard/application +//= require ./wizard/router //= require_tree ./wizard/components //= require_tree ./wizard/controllers //= require_tree ./wizard/helpers -//= require_tree ./wizard/initializers //= require_tree ./wizard/lib //= require_tree ./wizard/models //= require_tree ./wizard/routes diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js new file mode 100644 index 00000000..2866cb5d --- /dev/null +++ b/assets/javascripts/wizard-qunit.js @@ -0,0 +1,16 @@ +//= require route-recognizer +//= require fake_xml_http_request +//= require pretender +//= require qunit +//= require ember-qunit +//= require test-shims +//= require jquery.debug +//= require ember.debug +//= require ember-template-compiler + +//= require_tree ./wizard/tests/fixtures +//= require ./wizard/tests/pretender +//= require_tree ./wizard/tests/helpers +//= require_tree ./wizard/tests/acceptance + +//= require ./wizard/tests/bootstrap diff --git a/assets/javascripts/wizard/application.js.es6 b/assets/javascripts/wizard/application.js.es6 new file mode 100644 index 00000000..012949b3 --- /dev/null +++ b/assets/javascripts/wizard/application.js.es6 @@ -0,0 +1,19 @@ +import { buildResolver } from "discourse-common/resolver"; +import Application from "@ember/application"; +import WizardInitializer from "./lib/initialize/wizard"; +import { isTesting } from "discourse-common/config/environment"; + +export default Application.extend({ + rootElement: "#custom-wizard-main", + Resolver: buildResolver("discourse/plugins/discourse-custom-wizard/wizard"), + + customEvents: { + paste: "paste", + }, + + start() { + if (!isTesting()) { + this.initializer(WizardInitializer); + } + }, +}); diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index b2f08ede..1698a008 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -1,6 +1,6 @@ import { default as computed, - observes, + observes } from "discourse-common/utils/decorators"; import { renderAvatar } from "discourse/helpers/user-avatar"; import userSearch from "../lib/user-search"; @@ -55,7 +55,6 @@ export default Ember.TextField.extend({ let self = this, selected = [], groups = [], - currentUser = this.currentUser, includeMentionableGroups = this.get("includeMentionableGroups") === "true", includeMessageableGroups = @@ -66,13 +65,8 @@ export default Ember.TextField.extend({ function excludedUsernames() { // hack works around some issues with allowAny eventing const usernames = self.get("single") ? [] : selected; - - if (currentUser && self.get("excludeCurrentUser")) { - return usernames.concat([currentUser.get("username")]); - } return usernames; } - $(this.element) .val(this.get("usernames")) .autocomplete({ @@ -84,7 +78,6 @@ export default Ember.TextField.extend({ dataSource(term) { const termRegex = /[^a-zA-Z0-9_\-\.@\+]/; - let results = userSearch({ term: term.replace(termRegex, ""), topicId: self.get("topicId"), diff --git a/assets/javascripts/wizard/components/field-validators.js.es6 b/assets/javascripts/wizard/components/field-validators.js.es6 index a315020d..15cfc181 100644 --- a/assets/javascripts/wizard/components/field-validators.js.es6 +++ b/assets/javascripts/wizard/components/field-validators.js.es6 @@ -1,5 +1,8 @@ import Component from "@ember/component"; + export default Component.extend({ + layoutName: 'wizard/templates/components/field-validators', + actions: { perform() { this.appEvents.trigger("custom-wizard:validate"); diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 index 98ea9270..e5133d4f 100644 --- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 +++ b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 @@ -10,6 +10,7 @@ import { dasherize } from "@ember/string"; export default WizardFieldValidator.extend({ classNames: ["similar-topics-validator"], + layoutName: 'wizard/templates/components/similar-topics-validator', similarTopics: null, hasInput: notEmpty("field.value"), hasSimilarTopics: notEmpty("similarTopics"), diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index 6227cc64..ab442d97 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -6,11 +6,12 @@ import { getToken } from "wizard/lib/ajax"; export default Component.extend({ classNames: ["validator"], classNameBindings: ["isValid", "isInvalid"], + layoutName: 'wizard/templates/components/validator', validMessageKey: null, invalidMessageKey: null, isValid: null, isInvalid: equal("isValid", false), - layoutName: "components/validator", // useful for sharing the template with extending components + layoutName: "wizard/templates/components/validator", init() { this._super(...arguments); diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 5e1d1aac..0bd003f5 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -13,6 +13,7 @@ import { uploadIcon } from "discourse/lib/uploads"; import { dasherize } from "@ember/string"; export default ComposerEditor.extend({ + layoutName: 'wizard/templates/components/wizard-composer-editor', classNameBindings: ["fieldClass"], allowUpload: true, showLink: false, diff --git a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 index a56b7aff..c700d3ce 100644 --- a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 @@ -2,6 +2,7 @@ import Component from "@ember/component"; export default Component.extend({ classNames: ["wizard-composer-hyperlink"], + layoutName: 'wizard/templates/components/wizard-composer-hyperlink', actions: { addLink() { diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index 9c8e4bff..375b7195 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -3,6 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DateInput.extend({ useNativePicker: false, + layoutName: 'wizard/templates/components/wizard-date-input', @discourseComputed() placeholder() { diff --git a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 index 44b675b0..0fe1e68a 100644 --- a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 @@ -2,6 +2,8 @@ import DateTimeInput from "discourse/components/date-time-input"; import discourseComputed from "discourse-common/utils/decorators"; export default DateTimeInput.extend({ + layoutName: 'wizard/templates/components/wizard-date-time-input', + @discourseComputed("timeFirst", "tabindex") timeTabindex(timeFirst, tabindex) { return timeFirst ? tabindex : tabindex + 1; diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index a7452214..dc20538e 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -2,6 +2,8 @@ import { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; export default Ember.Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-category', + didInsertElement() { const property = this.field.property || "id"; const value = this.field.value; diff --git a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 new file mode 100644 index 00000000..f9653cd2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-checkbox' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 index b49233f2..0aee0d13 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 @@ -7,6 +7,8 @@ import { ajax } from "discourse/lib/ajax"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-composer-preview', + @on("init") updatePreview() { if (this.isDestroyed) { diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 8b9ecb82..0ef5fe20 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -5,6 +5,8 @@ import { import EmberObject from "@ember/object"; export default Ember.Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-composer', + showPreview: false, classNameBindings: [ ":wizard-field-composer", diff --git a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 index 2d918636..a916f18e 100644 --- a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-date-time', + @observes("dateTime") setValue() { this.set("field.value", this.dateTime.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-date.js.es6 b/assets/javascripts/wizard/components/wizard-field-date.js.es6 index d5d0a830..a06d582a 100644 --- a/assets/javascripts/wizard/components/wizard-field-date.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-date', + @observes("date") setValue() { this.set("field.value", this.date.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 new file mode 100644 index 00000000..4b8b7e63 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 @@ -0,0 +1,15 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-dropdown', + + keyPress(e) { + e.stopPropagation(); + }, + + actions: { + onChangeValue(value) { + this.set("field.value", value); + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-group.js.es6 b/assets/javascripts/wizard/components/wizard-field-group.js.es6 new file mode 100644 index 00000000..93538071 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-group.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-group' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-number.js.es6 b/assets/javascripts/wizard/components/wizard-field-number.js.es6 new file mode 100644 index 00000000..e7c4d77f --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-number.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-number' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 new file mode 100644 index 00000000..45343522 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-tag' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-text.js.es6 b/assets/javascripts/wizard/components/wizard-field-text.js.es6 new file mode 100644 index 00000000..f9d5b056 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-text.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-text', + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 new file mode 100644 index 00000000..54865d3c --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-textarea', + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-time.js.es6 index 82f9c68b..bf954ec4 100644 --- a/assets/javascripts/wizard/components/wizard-field-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-time', + @observes("time") setValue() { this.set("field.value", this.time.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 index 876cdf0e..edadb4f0 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 @@ -3,12 +3,16 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend(UppyUploadMixin, { + layoutName: 'wizard/templates/components/wizard-field-upload', classNames: ["wizard-field-upload"], classNameBindings: ["isImage"], uploading: false, type: computed(function () { return `wizard_${this.field.id}`; }), + id: computed(function () { + return `wizard_field_upload_${this.field.id}`; + }), isImage: computed("field.value.extension", function () { return ( this.field.value && diff --git a/assets/javascripts/wizard/components/wizard-field-url.js.es6 b/assets/javascripts/wizard/components/wizard-field-url.js.es6 new file mode 100644 index 00000000..411b5fe9 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-url.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-url' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 new file mode 100644 index 00000000..c2a32f44 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-user-selector' +}); diff --git a/assets/javascripts/wizard/components/wizard-field.js.es6 b/assets/javascripts/wizard/components/wizard-field.js.es6 new file mode 100644 index 00000000..372ee182 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -0,0 +1,34 @@ +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; +import discourseComputed from "discourse-common/utils/decorators"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field', + classNameBindings: [":wizard-field", "typeClasses", "field.invalid", "field.id"], + + @discourseComputed("field.type", "field.id") + typeClasses: (type, id) => + `${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`, + + @discourseComputed("field.id") + fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`, + + @discourseComputed("field.type", "field.id") + inputComponentName(type, id) { + if (["text_only"].includes(type)) { + return false; + } + return dasherize(type === "component" ? id : `wizard-field-${type}`); + }, + + @discourseComputed("field.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed("field.type") + textType(fieldType) { + return ["text", "textarea"].includes(fieldType); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 index cb613107..b7523f9a 100644 --- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 @@ -3,6 +3,7 @@ import { computed } from "@ember/object"; import { makeArray } from "discourse-common/lib/helpers"; export default ComboBox.extend({ + layoutName: 'wizard/templates/components/wizard-group-selector', content: computed("groups.[]", "field.content.[]", function () { const whitelist = makeArray(this.field.content); return this.groups diff --git a/assets/javascripts/wizard/components/wizard-no-access.js.es6 b/assets/javascripts/wizard/components/wizard-no-access.js.es6 index 57fe9111..78a23aa8 100644 --- a/assets/javascripts/wizard/components/wizard-no-access.js.es6 +++ b/assets/javascripts/wizard/components/wizard-no-access.js.es6 @@ -1,10 +1,21 @@ -import CustomWizard from "../models/custom"; +import CustomWizard from "../models/wizard"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; -export default Ember.Component.extend({ - siteName: function () { - /*eslint no-undef:0*/ - return Wizard.SiteSettings.title; - }.property(), +export default Component.extend({ + classNameBindings: [':wizard-no-access', 'reasonClass'], + layoutName: 'wizard/templates/components/wizard-no-access', + + @discourseComputed('reason') + reasonClass(reason) { + return dasherize(reason); + }, + + @discourseComputed + siteName() { + return (this.siteSettings.title || ''); + }, actions: { skip() { diff --git a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 index 687cfa86..c52bfeae 100644 --- a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 +++ b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 @@ -4,6 +4,7 @@ import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["wizard-similar-topics"], + layoutName: 'wizard/templates/components/wizard-similar-topics', showTopics: true, didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-step-form.js.es6 b/assets/javascripts/wizard/components/wizard-step-form.js.es6 new file mode 100644 index 00000000..73406b4f --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step-form.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [":wizard-step-form", "customStepClass"], + + @discourseComputed("step.id") + customStepClass: (stepId) => `wizard-step-${stepId}`, +}); diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 new file mode 100644 index 00000000..4518afee --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -0,0 +1,247 @@ +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import I18n from "I18n"; +import getUrl from "discourse-common/lib/get-url"; +import { htmlSafe } from "@ember/template"; +import { schedule } from "@ember/runloop"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; +import { updateCachedWizard } from "discourse/plugins/discourse-custom-wizard/wizard/models/wizard"; +import { alias, not } from "@ember/object/computed"; +import CustomWizard from "../models/wizard"; + +const alreadyWarned = {}; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-step', + classNameBindings: [":wizard-step", "step.id"], + saving: null, + + init() { + this._super(...arguments); + this.set("stylingDropdown", {}); + }, + + didInsertElement() { + this._super(...arguments); + this.autoFocus(); + }, + + @discourseComputed("step.index", "wizard.required") + showQuitButton: (index, required) => (index === 0 && !required), + + showNextButton: not("step.final"), + showDoneButton: alias("step.final"), + + @discourseComputed("step.translatedTitle") + cookedTitle(title) { + return cook(title); + }, + + @discourseComputed("step.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed( + "step.index", + "step.displayIndex", + "wizard.totalSteps", + "wizard.completed" + ) + showFinishButton: (index, displayIndex, total, completed) => { + return index !== 0 && displayIndex !== total && completed; + }, + + @discourseComputed("step.index") + showBackButton: (index) => index > 0, + + @discourseComputed("step.banner") + bannerImage(src) { + if (!src) { + return; + } + return getUrl(src); + }, + + @discourseComputed("step.id") + bannerAndDescriptionClass(id) { + return `wizard-banner-and-description wizard-banner-and-description-${id}`; + }, + + @discourseComputed("step.fields.[]") + primaryButtonIndex(fields) { + return fields.length + 1; + }, + + @discourseComputed("step.fields.[]") + secondaryButtonIndex(fields) { + return fields.length + 2; + }, + + @observes("step.id") + _stepChanged() { + this.set("saving", false); + this.autoFocus(); + }, + + @observes("step.message") + _handleMessage: function () { + const message = this.get("step.message"); + this.sendAction("showMessage", message); + }, + + keyPress(event) { + if (event.key === "Enter") { + if (this.showDoneButton) { + this.send("quit"); + } else { + this.send("nextStep"); + } + } + }, + + @discourseComputed("step.index", "wizard.totalSteps") + barStyle(displayIndex, totalSteps) { + let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1); + if (ratio < 0) { + ratio = 0; + } + if (ratio > 1) { + ratio = 1; + } + + return htmlSafe(`width: ${ratio * 200}px`); + }, + + @discourseComputed("step.fields") + includeSidebar(fields) { + return !!fields.findBy("show_in_sidebar"); + }, + + autoFocus() { + schedule("afterRender", () => { + const $invalid = $( + ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" + ); + + if ($invalid.length) { + return $invalid.focus(); + } + + $(".wizard-focusable:first").focus(); + }); + }, + + animateInvalidFields() { + schedule("afterRender", () => { + let $element = $( + ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" + ); + + if ($element.length) { + $([document.documentElement, document.body]).animate( + { + scrollTop: $element.offset().top - 200, + }, + 400, + function () { + $element.wiggle(2, 100); + } + ); + } + }); + }, + + advance() { + this.set("saving", true); + this.get("step") + .save() + .then((response) => { + updateCachedWizard(CustomWizard.build(response["wizard"])); + + if (response["final"]) { + CustomWizard.finished(response); + } else { + this.sendAction("goNext", response); + } + }) + .catch(() => this.animateInvalidFields()) + .finally(() => this.set("saving", false)); + }, + + keyPress() {}, + + actions: { + quit() { + this.get("wizard").skip(); + }, + + done() { + this.send("nextStep"); + }, + + showMessage(message) { + this.sendAction("showMessage", message); + }, + + stylingDropdownChanged(id, value) { + this.set("stylingDropdown", { id, value }); + }, + + exitEarly() { + const step = this.step; + step.validate(); + + if (step.get("valid")) { + this.set("saving", true); + + step + .save() + .then(() => this.send("quit")) + .finally(() => this.set("saving", false)); + } else { + this.autoFocus(); + } + }, + + backStep() { + if (this.saving) { + return; + } + + this.goBack(); + }, + + nextStep() { + if (this.saving) { + return; + } + + const step = this.step; + const result = step.validate(); + + if (result.warnings.length) { + const unwarned = result.warnings.filter((w) => !alreadyWarned[w]); + if (unwarned.length) { + unwarned.forEach((w) => (alreadyWarned[w] = true)); + return window.bootbox.confirm( + unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"), + I18n.t("no_value"), + I18n.t("yes_value"), + (confirmed) => { + if (confirmed) { + this.advance(); + } + } + ); + } + } + + if (step.get("valid")) { + this.advance(); + } else { + this.autoFocus(); + } + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/wizard/components/wizard-text-field.js.es6 index c522eb2c..3d87be09 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-text-field.js.es6 @@ -1,5 +1,3 @@ -/* eslint no-undef: 0*/ - import computed from "discourse-common/utils/decorators"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import WizardI18n from "../lib/wizard-i18n"; @@ -15,7 +13,7 @@ export default Ember.TextField.extend({ @computed dir() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (val) { return isRTL(val) ? "rtl" : "ltr"; @@ -26,7 +24,7 @@ export default Ember.TextField.extend({ }, keyUp() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (isRTL(val)) { this.set("dir", "rtl"); diff --git a/assets/javascripts/wizard/components/wizard-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-time-input.js.es6 index 0bca244d..ec121002 100644 --- a/assets/javascripts/wizard/components/wizard-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-time-input.js.es6 @@ -1,3 +1,5 @@ import TimeInput from "discourse/components/time-input"; -export default TimeInput.extend(); +export default TimeInput.extend({ + layoutName: 'wizard/templates/components/wizard-time-input' +}); diff --git a/assets/javascripts/wizard/controllers/custom.js.es6 b/assets/javascripts/wizard/controllers/custom.js.es6 deleted file mode 100644 index 5f168f3a..00000000 --- a/assets/javascripts/wizard/controllers/custom.js.es6 +++ /dev/null @@ -1,3 +0,0 @@ -export default Ember.Controller.extend({ - queryParams: ["reset"], -}); diff --git a/assets/javascripts/wizard/controllers/custom-step.js.es6 b/assets/javascripts/wizard/controllers/step.js.es6 similarity index 76% rename from assets/javascripts/wizard/controllers/custom-step.js.es6 rename to assets/javascripts/wizard/controllers/step.js.es6 index b44c0fca..4b321173 100644 --- a/assets/javascripts/wizard/controllers/custom-step.js.es6 +++ b/assets/javascripts/wizard/controllers/step.js.es6 @@ -1,7 +1,10 @@ -import StepController from "wizard/controllers/step"; +import Controller from "@ember/controller"; import getUrl from "discourse-common/lib/get-url"; -export default StepController.extend({ +export default Controller.extend({ + wizard: null, + step: null, + actions: { goNext(response) { let nextStepId = response["next_step_id"]; @@ -12,12 +15,12 @@ export default StepController.extend({ const wizardId = this.get("wizard.id"); window.location.href = getUrl(`/w/${wizardId}/steps/${nextStepId}`); } else { - this.transitionToRoute("custom.step", nextStepId); + this.transitionToRoute("step", nextStepId); } }, goBack() { - this.transitionToRoute("custom.step", this.get("step.previous")); + this.transitionToRoute("step", this.get("step.previous")); }, showMessage(message) { diff --git a/assets/javascripts/wizard/controllers/wizard-index.js.es6 b/assets/javascripts/wizard/controllers/wizard-index.js.es6 new file mode 100644 index 00000000..2dfdee40 --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard-index.js.es6 @@ -0,0 +1,24 @@ +import Controller from "@ember/controller"; +import { or } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; + +const reasons = { + noWizard: "none", + requiresLogin: "requires_login", + notPermitted: "not_permitted", + completed: "completed" +} + +export default Controller.extend({ + noAccess: or('noWizard', 'requiresLogin', 'notPermitted', 'completed'), + + @discourseComputed('noAccessReason') + noAccessI18nKey(reason) { + return reason ? `wizard.${reasons[reason]}` : 'wizard.none'; + }, + + @discourseComputed + noAccessReason() { + return Object.keys(reasons).find(reason => this.get(reason)); + } +}); diff --git a/assets/javascripts/wizard/controllers/wizard.js.es6 b/assets/javascripts/wizard/controllers/wizard.js.es6 new file mode 100644 index 00000000..35f964e2 --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard.js.es6 @@ -0,0 +1,5 @@ +import Controller from "@ember/controller"; + +export default Controller.extend({ + queryParams: ["reset"], +}); diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6 deleted file mode 100644 index 8c0a473c..00000000 --- a/assets/javascripts/wizard/custom-wizard.js.es6 +++ /dev/null @@ -1,39 +0,0 @@ -import { buildResolver } from "discourse-common/resolver"; - -export default Ember.Application.extend({ - rootElement: "#custom-wizard-main", - Resolver: buildResolver("wizard"), - - customEvents: { - paste: "paste", - }, - - start() { - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/pre\-initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - - const init = module.default; - const oldInitialize = init.initialize; - init.initialize = () => { - oldInitialize.call(this, this.__container__, this); - }; - - this.initializer(init); - } - }); - - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - this.initializer(module.default); - } - }); - }, -}); diff --git a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 deleted file mode 100644 index 6a679796..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 +++ /dev/null @@ -1,219 +0,0 @@ -export default { - name: "custom-wizard-step", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const CustomWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).default; - const updateCachedWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).updateCachedWizard; - const StepModel = requirejs("wizard/models/step").default; - const StepComponent = requirejs("wizard/components/wizard-step").default; - const ajax = requirejs("wizard/lib/ajax").ajax; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const discourseComputed = requirejs("discourse-common/utils/decorators") - .default; - const cook = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ).cook; - const { schedule } = requirejs("@ember/runloop"); - const { alias, not } = requirejs("@ember/object/computed"); - const { translatedText } = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" - ); - - StepModel.reopen({ - @discourseComputed("wizardId", "id") - i18nKey(wizardId, stepId) { - return `${wizardId}.${stepId}`; - }, - - @discourseComputed("i18nKey", "title") - translatedTitle(i18nKey, title) { - return translatedText(`${i18nKey}.title`, title); - }, - - @discourseComputed("i18nKey", "description") - translatedDescription(i18nKey, description) { - return translatedText(`${i18nKey}.description`, description); - }, - - save() { - const wizardId = this.get("wizardId"); - const fields = {}; - - this.get("fields").forEach((f) => { - if (f.type !== "text_only") { - fields[f.id] = f.value; - } - }); - - return ajax({ - url: `/w/${wizardId}/steps/${this.get("id")}`, - type: "PUT", - data: { fields }, - }).catch((response) => { - if ( - response && - response.responseJSON && - response.responseJSON.errors - ) { - let wizardErrors = []; - response.responseJSON.errors.forEach((err) => { - if (err.field === wizardId) { - wizardErrors.push(err.description); - } else if (err.field) { - this.fieldError(err.field, err.description); - } else if (err) { - wizardErrors.push(err); - } - }); - if (wizardErrors.length) { - this.handleWizardError(wizardErrors.join("\n")); - } - this.animateInvalidFields(); - throw response; - } - - if (response && response.responseText) { - const responseText = response.responseText; - const start = responseText.indexOf(">") + 1; - const end = responseText.indexOf("plugins"); - const message = responseText.substring(start, end); - this.handleWizardError(message); - throw message; - } - }); - }, - - handleWizardError(message) { - this.set("message", { - state: "error", - text: message, - }); - Ember.run.later(() => this.set("message", null), 6000); - }, - }); - - StepComponent.reopen({ - classNameBindings: ["step.id"], - - autoFocus() { - schedule("afterRender", () => { - const $invalid = $( - ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" - ); - - if ($invalid.length) { - return $invalid.focus(); - } - - $(".wizard-focusable:first").focus(); - }); - }, - - animateInvalidFields() { - schedule("afterRender", () => { - let $element = $( - ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" - ); - - if ($element.length) { - $([document.documentElement, document.body]).animate( - { - scrollTop: $element.offset().top - 200, - }, - 400, - function () { - $element.wiggle(2, 100); - } - ); - } - }); - }, - - ensureStartsAtTop: function () { - window.scrollTo(0, 0); - }.observes("step.id"), - - showQuitButton: function () { - const index = this.get("step.index"); - const required = this.get("wizard.required"); - return index === 0 && !required; - }.property("step.index", "wizard.required"), - - @discourseComputed("step.translatedTitle") - cookedTitle(title) { - return cook(title); - }, - - @discourseComputed("step.translatedDescription") - cookedDescription(description) { - return cook(description); - }, - - bannerImage: function () { - const src = this.get("step.banner"); - if (!src) { - return; - } - return getUrl(src); - }.property("step.banner"), - - @discourseComputed("step.fields.[]") - primaryButtonIndex(fields) { - return fields.length + 1; - }, - - @discourseComputed("step.fields.[]") - secondaryButtonIndex(fields) { - return fields.length + 2; - }, - - handleMessage: function () { - const message = this.get("step.message"); - this.sendAction("showMessage", message); - }.observes("step.message"), - - showNextButton: not("step.final"), - showDoneButton: alias("step.final"), - - advance() { - this.set("saving", true); - this.get("step") - .save() - .then((response) => { - updateCachedWizard(CustomWizard.build(response["wizard"])); - - if (response["final"]) { - CustomWizard.finished(response); - } else { - this.sendAction("goNext", response); - } - }) - .catch(() => this.animateInvalidFields()) - .finally(() => this.set("saving", false)); - }, - - keyPress() {}, - - actions: { - quit() { - this.get("wizard").skip(); - }, - - done() { - this.send("nextStep"); - }, - - showMessage(message) { - this.sendAction("showMessage", message); - }, - }, - }); - }, -}; diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 deleted file mode 100644 index f2095827..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 +++ /dev/null @@ -1,126 +0,0 @@ -export default { - name: "custom-routes", - initialize(app) { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const EmberObject = requirejs("@ember/object").default; - const Router = requirejs("wizard/router").default; - const ApplicationRoute = requirejs("wizard/routes/application").default; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const Store = requirejs("discourse/services/store").default; - const registerRawHelpers = requirejs( - "discourse-common/lib/raw-handlebars-helpers" - ).registerRawHelpers; - const createHelperContext = requirejs("discourse-common/lib/helpers") - .createHelperContext; - const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") - .default; - const Handlebars = requirejs("handlebars").default; - const Site = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/site" - ).default; - const RestAdapter = requirejs("discourse/adapters/rest").default; - const Session = requirejs("discourse/models/session").default; - const setDefaultOwner = requirejs("discourse-common/lib/get-owner") - .setDefaultOwner; - const messageBus = requirejs("message-bus-client").default; - const getToken = requirejs("wizard/lib/ajax").getToken; - const setEnvironment = requirejs("discourse-common/config/environment") - .setEnvironment; - const container = app.__container__; - Discourse.Model = EmberObject.extend(); - Discourse.__container__ = container; - setDefaultOwner(container); - registerRawHelpers(RawHandlebars, Handlebars); - - // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill - if (!Object.entries) { - Object.entries = function (obj) { - let ownProps = Object.keys(obj), - i = ownProps.length, - resArray = new Array(i); // preallocate the Array - while (i--) { - resArray[i] = [ownProps[i], obj[ownProps[i]]]; - } - - return resArray; - }; - } - - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - const targets = ["controller", "component", "route", "model", "adapter"]; - /*eslint no-undef: 0*/ - const siteSettings = Wizard.SiteSettings; - app.register("site-settings:main", siteSettings, { instantiate: false }); - createHelperContext({ siteSettings }); - targets.forEach((t) => app.inject(t, "siteSettings", "site-settings:main")); - - app.register("message-bus:main", messageBus, { instantiate: false }); - targets.forEach((t) => app.inject(t, "messageBus", "message-bus:main")); - - app.register("service:store", Store); - targets.forEach((t) => app.inject(t, "store", "service:store")); - targets.forEach((t) => app.inject(t, "appEvents", "service:app-events")); - - app.register("adapter:rest", RestAdapter); - - const site = Site.current(); - app.register("site:main", site, { instantiate: false }); - targets.forEach((t) => app.inject(t, "site", "site:main")); - - site.set("can_create_tag", false); - app.register("session:main", Session.current(), { instantiate: false }); - targets.forEach((t) => app.inject(t, "session", "session:main")); - - createHelperContext({ - siteSettings: container.lookup("site-settings:main"), - currentUser: container.lookup("current-user:main"), - site: container.lookup("site:main"), - session: container.lookup("session:main"), - capabilities: container.lookup("capabilities:main"), - }); - - const session = container.lookup("session:main"); - const setupData = document.getElementById("data-discourse-setup").dataset; - session.set("highlightJsPath", setupData.highlightJsPath); - setEnvironment(setupData.environment); - - Router.reopen({ - rootURL: getUrl("/w/"), - }); - - Router.map(function () { - this.route("custom", { path: "/:wizard_id" }, function () { - this.route("steps"); - this.route("step", { path: "/steps/:step_id" }); - }); - }); - - ApplicationRoute.reopen({ - redirect() { - this.transitionTo("custom"); - }, - model() {}, - }); - - // Add a CSRF token to all AJAX requests - let token = getToken(); - session.set("csrfToken", token); - let callbacks = $.Callbacks(); - $.ajaxPrefilter(callbacks.fire); - - callbacks.add(function (options, originalOptions, xhr) { - if (!options.crossDomain) { - xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); - } - }); - }, -}; diff --git a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 new file mode 100644 index 00000000..022ac48e --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 @@ -0,0 +1,12 @@ +export default { + run(app, container) { + const { createHelperContext } = requirejs("discourse-common/lib/helpers"); + + createHelperContext({ + siteSettings: container.lookup('site-settings:main'), + site: container.lookup("site:main"), + session: container.lookup("session:main"), + capabilities: container.lookup("capabilities:main"), + }); + } +} diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 new file mode 100644 index 00000000..d31efb4d --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -0,0 +1,49 @@ +export default { + run(app, container) { + const Store = requirejs("discourse/services/store").default; + const Site = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/models/site" + ).default; + const Session = requirejs("discourse/models/session").default; + const RestAdapter = requirejs("discourse/adapters/rest").default; + const messageBus = requirejs("message-bus-client").default; + const sniffCapabilites = requirejs("discourse/pre-initializers/sniff-capabilities").default; + const site = Site.current(); + const session = Session.current(); + + const registrations = [ + ["site-settings:main", app.SiteSettings, false], + ["message-bus:main", messageBus, false], + ["site:main", site, false], + ["session:main", session, false], + ["service:store", Store, true], + ["adapter:rest", RestAdapter, true] + ]; + + registrations.forEach(registration => { + if (!app.hasRegistration(registration[0])) { + app.register(registration[0], registration[1], { instantiate: registration[2] }); + } + }); + + const targets = ["controller", "component", "route", "model", "adapter", "mixin"]; + const injections = [ + ["siteSettings", "site-settings:main"], + ["messageBus", "message-bus:main"], + ["site", "site:main"], + ["session", "session:main"], + ["store", "service:store"], + ["appEvents", "service:app-events"] + ]; + + injections.forEach(injection => { + targets.forEach((t) => app.inject(t, injection[0], injection[1])); + }); + + if (!app.hasRegistration("capabilities:main")) { + sniffCapabilites.initialize(null, app); + } + + site.set("can_create_tag", false); + } +} diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 similarity index 52% rename from assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 rename to assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 6f2a80b7..65086e53 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,117 +1,25 @@ -import { dasherize } from "@ember/string"; -import discourseComputed from "discourse-common/utils/decorators"; - export default { - name: "custom-wizard-field", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; + run(app, container) { + const getToken = requirejs("wizard/lib/ajax").getToken; + const isTesting = requirejs("discourse-common/config/environment").isTesting; + + if (!isTesting) { + // Add a CSRF token to all AJAX requests + let token = getToken(); + session.set("csrfToken", token); + let callbacks = $.Callbacks(); + $.ajaxPrefilter(callbacks.fire); + + callbacks.add(function (options, originalOptions, xhr) { + if (!options.crossDomain) { + xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); + } + }); } - const FieldComponent = requirejs("wizard/components/wizard-field").default; - const FieldModel = requirejs("wizard/models/wizard-field").default; - const { cook } = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ); const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const toMarkdown = requirejs("discourse/lib/to-markdown").default; - const { translatedText } = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" - ); - - FieldComponent.reopen({ - classNameBindings: ["field.id"], - - @discourseComputed("field.translatedDescription") - cookedDescription(description) { - return cook(description); - }, - - @discourseComputed("field.type") - textType(fieldType) { - return ["text", "textarea"].includes(fieldType); - }, - - inputComponentName: function () { - const type = this.get("field.type"); - const id = this.get("field.id"); - if (["text_only"].includes(type)) { - return false; - } - return dasherize(type === "component" ? id : `wizard-field-${type}`); - }.property("field.type", "field.id"), - }); - - const StandardFieldValidation = [ - "text", - "number", - "textarea", - "dropdown", - "tag", - "image", - "user_selector", - "text_only", - "composer", - "category", - "group", - "date", - "time", - "date_time", - ]; - - FieldModel.reopen({ - @discourseComputed("wizardId", "stepId", "id") - i18nKey(wizardId, stepId, id) { - return `${wizardId}.${stepId}.${id}`; - }, - - @discourseComputed("i18nKey", "label") - translatedLabel(i18nKey, label) { - return translatedText(`${i18nKey}.label`, label); - }, - - @discourseComputed("i18nKey", "placeholder") - translatedPlaceholder(i18nKey, placeholder) { - return translatedText(`${i18nKey}.placeholder`, placeholder); - }, - - @discourseComputed("i18nKey", "description") - translatedDescription(i18nKey, description) { - return translatedText(`${i18nKey}.description`, description); - }, - - check() { - if (this.customCheck) { - return this.customCheck(); - } - - let valid = this.valid; - - if (!this.required) { - this.setValid(true); - return true; - } - - const val = this.get("value"); - const type = this.get("type"); - - if (type === "checkbox") { - valid = val; - } else if (type === "upload") { - valid = val && val.id > 0; - } else if (StandardFieldValidation.indexOf(type) > -1) { - valid = val && val.toString().length > 0; - } else if (type === "url") { - valid = true; - } - - this.setValid(valid); - - return valid; - }, - }); - const isInside = (text, regex) => { const matches = text.match(regex); return matches && matches.length % 2; @@ -218,5 +126,19 @@ export default { } }, }); - }, + + // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill + if (!Object.entries) { + Object.entries = function (obj) { + let ownProps = Object.keys(obj), + i = ownProps.length, + resArray = new Array(i); // preallocate the Array + while (i--) { + resArray[i] = [ownProps[i], obj[ownProps[i]]]; + } + + return resArray; + }; + } + } }; diff --git a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 new file mode 100644 index 00000000..8d4b850e --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 @@ -0,0 +1,26 @@ +export default { + run(app, container) { + const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default; + const Handlebars = requirejs("handlebars").default; + const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers; + const { registerHelpers } = requirejs("discourse-common/lib/helpers"); + const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins").default; + + Object.keys(Ember.TEMPLATES).forEach((k) => { + if (k.indexOf("select-kit") === 0) { + let template = Ember.TEMPLATES[k]; + define(k, () => template); + } + }); + + Object.keys(requirejs.entries).forEach((entry) => { + if (/\/helpers\//.test(entry)) { + requirejs(entry, null, null, true); + } + }); + + registerRawHelpers(RawHandlebars, Handlebars); + registerHelpers(app); + jqueryPlugins.initialize(container, app); + } +} diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 new file mode 100644 index 00000000..134257a1 --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -0,0 +1,49 @@ +export default { + name: "custom-wizard", + initialize(app) { + const isTesting = requirejs("discourse-common/config/environment").isTesting; + const isWizard = window.location.pathname.indexOf("/w/") > -1; + + if (!isWizard && !isTesting()) { + return; + } + + const container = app.__container__; + const setDefaultOwner = requirejs("discourse-common/lib/get-owner").setDefaultOwner; + setDefaultOwner(container); + + if (!isTesting()) { + const PreloadStore = requirejs("discourse/lib/preload-store").default; + + let preloaded; + const preloadedDataElement = document.getElementById("data-preloaded-wizard"); + if (preloadedDataElement) { + preloaded = JSON.parse(preloadedDataElement.dataset.preloadedWizard); + } + + Object.keys(preloaded).forEach(function (key) { + PreloadStore.store(key, JSON.parse(preloaded[key])); + }); + + app.SiteSettings = PreloadStore.get("siteSettings"); + } + + const setEnvironment = requirejs("discourse-common/config/environment").setEnvironment; + const setupData = document.getElementById("data-discourse-setup").dataset; + setEnvironment(setupData.environment); + + const Session = requirejs("discourse/models/session").default; + const session = Session.current(); + session.set("highlightJsPath", setupData.highlightJsPath); + + [ + 'register-files', + 'inject-objects', + 'create-contexts', + 'patch-components' + ].forEach(fileName => { + const initializer = requirejs(`discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}`).default; + initializer.run(app, container); + }); + } +}; diff --git a/assets/javascripts/wizard/lib/utilities-lite.js.es6 b/assets/javascripts/wizard/lib/utilities-lite.js.es6 deleted file mode 100644 index 19517b2b..00000000 --- a/assets/javascripts/wizard/lib/utilities-lite.js.es6 +++ /dev/null @@ -1,71 +0,0 @@ -// lite version of discourse/lib/utilities - -export function determinePostReplaceSelection({ - selection, - needle, - replacement, -}) { - const diff = - replacement.end - replacement.start - (needle.end - needle.start); - - if (selection.end <= needle.start) { - // Selection ends (and starts) before needle. - return { start: selection.start, end: selection.end }; - } else if (selection.start <= needle.start) { - // Selection starts before needle... - if (selection.end < needle.end) { - // ... and ends inside needle. - return { start: selection.start, end: needle.start }; - } else { - // ... and spans needle completely. - return { start: selection.start, end: selection.end + diff }; - } - } else if (selection.start < needle.end) { - // Selection starts inside needle... - if (selection.end <= needle.end) { - // ... and ends inside needle. - return { start: replacement.end, end: replacement.end }; - } else { - // ... and spans end of needle. - return { start: replacement.end, end: selection.end + diff }; - } - } else { - // Selection starts (and ends) behind needle. - return { start: selection.start + diff, end: selection.end + diff }; - } -} - -const toArray = (items) => { - items = items || []; - - if (!Array.isArray(items)) { - return Array.from(items); - } - - return items; -}; - -export function clipboardData(e, canUpload) { - const clipboard = - e.clipboardData || - e.originalEvent.clipboardData || - e.delegatedEvent.originalEvent.clipboardData; - - const types = toArray(clipboard.types); - let files = toArray(clipboard.files); - - if (types.includes("Files") && files.length === 0) { - // for IE - files = toArray(clipboard.items).filter((i) => i.kind === "file"); - } - - canUpload = files && canUpload && !types.includes("text/plain"); - const canUploadImage = - canUpload && files.filter((f) => f.type.match("^image/"))[0]; - const canPasteHtml = - Discourse.SiteSettings.enable_rich_text_paste && - types.includes("text/html") && - !canUploadImage; - - return { clipboard, types, canUpload, canPasteHtml }; -} diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/wizard/models/field.js.es6 new file mode 100644 index 00000000..5f76074e --- /dev/null +++ b/assets/javascripts/wizard/models/field.js.es6 @@ -0,0 +1,79 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; + +const StandardFieldValidation = [ + "text", + "number", + "textarea", + "dropdown", + "tag", + "image", + "user_selector", + "text_only", + "composer", + "category", + "group", + "date", + "time", + "date_time", +]; + +export default EmberObject.extend(ValidState, { + id: null, + type: null, + value: null, + required: null, + warning: null, + + @discourseComputed("wizardId", "stepId", "id") + i18nKey(wizardId, stepId, id) { + return `${wizardId}.${stepId}.${id}`; + }, + + @discourseComputed("i18nKey", "label") + translatedLabel(i18nKey, label) { + return translatedText(`${i18nKey}.label`, label); + }, + + @discourseComputed("i18nKey", "placeholder") + translatedPlaceholder(i18nKey, placeholder) { + return translatedText(`${i18nKey}.placeholder`, placeholder); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + check() { + if (this.customCheck) { + return this.customCheck(); + } + + let valid = this.valid; + + if (!this.required) { + this.setValid(true); + return true; + } + + const val = this.get("value"); + const type = this.get("type"); + + if (type === "checkbox") { + valid = val; + } else if (type === "upload") { + valid = val && val.id > 0; + } else if (StandardFieldValidation.indexOf(type) > -1) { + valid = val && val.toString().length > 0; + } else if (type === "url") { + valid = true; + } + + this.setValid(valid); + + return valid; + } +}); diff --git a/assets/javascripts/wizard/models/site.js.es6 b/assets/javascripts/wizard/models/site.js.es6 index c15ce98f..96837ff2 100644 --- a/assets/javascripts/wizard/models/site.js.es6 +++ b/assets/javascripts/wizard/models/site.js.es6 @@ -1,10 +1,11 @@ import Site from "discourse/models/site"; +import { getOwner } from "discourse-common/lib/get-owner"; export default Site.reopenClass({ // There is no site data actually loaded by the CW yet. This placeholder is // needed by imported classes createCurrent() { - const store = Discourse.__container__.lookup("service:store"); + const store = getOwner(this).lookup("service:store"); return store.createRecord("site", {}); }, }); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 new file mode 100644 index 00000000..e18657b5 --- /dev/null +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -0,0 +1,114 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import { ajax } from "wizard/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; + +export default EmberObject.extend(ValidState, { + id: null, + + @discourseComputed("wizardId", "id") + i18nKey(wizardId, stepId) { + return `${wizardId}.${stepId}`; + }, + + @discourseComputed("i18nKey", "title") + translatedTitle(i18nKey, title) { + return translatedText(`${i18nKey}.title`, title); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + @discourseComputed("index") + displayIndex: (index) => index + 1, + + @discourseComputed("fields.[]") + fieldsById(fields) { + const lookup = {}; + fields.forEach((field) => (lookup[field.get("id")] = field)); + return lookup; + }, + + validate() { + let allValid = true; + const result = { warnings: [] }; + + this.fields.forEach((field) => { + allValid = allValid && field.check(); + const warning = field.get("warning"); + if (warning) { + result.warnings.push(warning); + } + }); + + this.setValid(allValid); + + return result; + }, + + fieldError(id, description) { + const field = this.fields.findBy("id", id); + if (field) { + field.setValid(false, description); + } + }, + + save() { + const wizardId = this.get("wizardId"); + const fields = {}; + + this.get("fields").forEach((f) => { + if (f.type !== "text_only") { + fields[f.id] = f.value; + } + }); + + return ajax({ + url: `/w/${wizardId}/steps/${this.get("id")}`, + type: "PUT", + data: { fields }, + }).catch((response) => { + if ( + response && + response.responseJSON && + response.responseJSON.errors + ) { + let wizardErrors = []; + response.responseJSON.errors.forEach((err) => { + if (err.field === wizardId) { + wizardErrors.push(err.description); + } else if (err.field) { + this.fieldError(err.field, err.description); + } else if (err) { + wizardErrors.push(err); + } + }); + if (wizardErrors.length) { + this.handleWizardError(wizardErrors.join("\n")); + } + this.animateInvalidFields(); + throw response; + } + + if (response && response.responseText) { + const responseText = response.responseText; + const start = responseText.indexOf(">") + 1; + const end = responseText.indexOf("plugins"); + const message = responseText.substring(start, end); + this.handleWizardError(message); + throw message; + } + }); + }, + + handleWizardError(message) { + this.set("message", { + state: "error", + text: message, + }); + Ember.run.later(() => this.set("message", null), 6000); + }, +}); diff --git a/assets/javascripts/wizard/models/custom.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 similarity index 96% rename from assets/javascripts/wizard/models/custom.js.es6 rename to assets/javascripts/wizard/models/wizard.js.es6 index 4cbd5706..b2e26b25 100644 --- a/assets/javascripts/wizard/models/custom.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -1,9 +1,9 @@ import { default as computed } from "discourse-common/utils/decorators"; import getUrl from "discourse-common/lib/get-url"; -import WizardField from "wizard/models/wizard-field"; +import Field from "./field"; import { ajax } from "wizard/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import Step from "wizard/models/step"; +import Step from "./step"; import EmberObject from "@ember/object"; import Site from "./site"; @@ -77,7 +77,7 @@ CustomWizard.reopenClass({ stepObj.fields = stepObj.fields.map((f) => { f.wizardId = wizardJson.id; f.stepId = stepObj.id; - return WizardField.create(f); + return Field.create(f); }); return stepObj; diff --git a/assets/javascripts/wizard/router.js.es6 b/assets/javascripts/wizard/router.js.es6 new file mode 100644 index 00000000..5178c91f --- /dev/null +++ b/assets/javascripts/wizard/router.js.es6 @@ -0,0 +1,17 @@ +import EmberRouter from "@ember/routing/router"; +import getUrl from "discourse-common/lib/get-url"; +import { isTesting } from "discourse-common/config/environment"; + +const Router = EmberRouter.extend({ + rootURL: isTesting() ? getUrl("/") : getUrl("/w/"), + location: isTesting() ? "none" : "history", +}); + +Router.map(function () { + this.route("wizard", { path: "/:wizard_id" }, function () { + this.route("steps", { path: "/steps", resetNamespace: true }); + this.route("step", { path: "/steps/:step_id", resetNamespace: true }); + }); +}); + +export default Router; diff --git a/assets/javascripts/wizard/routes/application.js.es6 b/assets/javascripts/wizard/routes/application.js.es6 new file mode 100644 index 00000000..0051f5ce --- /dev/null +++ b/assets/javascripts/wizard/routes/application.js.es6 @@ -0,0 +1,3 @@ +import Route from "@ember/routing/route"; + +export default Route.extend(); diff --git a/assets/javascripts/wizard/routes/custom-steps.js.es6 b/assets/javascripts/wizard/routes/custom-steps.js.es6 deleted file mode 100644 index c58d1251..00000000 --- a/assets/javascripts/wizard/routes/custom-steps.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -export default Ember.Route.extend({ - redirect() { - this.transitionTo("custom.index"); - }, -}); diff --git a/assets/javascripts/wizard/routes/index.js.es6 b/assets/javascripts/wizard/routes/index.js.es6 new file mode 100644 index 00000000..b24b3631 --- /dev/null +++ b/assets/javascripts/wizard/routes/index.js.es6 @@ -0,0 +1,9 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + beforeModel(transition) { + if (transition.intent.params) { + this.transitionTo("wizard"); + } + }, +}); diff --git a/assets/javascripts/wizard/routes/custom-step.js.es6 b/assets/javascripts/wizard/routes/step.js.es6 similarity index 78% rename from assets/javascripts/wizard/routes/custom-step.js.es6 rename to assets/javascripts/wizard/routes/step.js.es6 index 8088727a..2454fc95 100644 --- a/assets/javascripts/wizard/routes/custom-step.js.es6 +++ b/assets/javascripts/wizard/routes/step.js.es6 @@ -1,7 +1,8 @@ import WizardI18n from "../lib/wizard-i18n"; -import { getCachedWizard } from "../models/custom"; +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel() { this.set("wizard", getCachedWizard()); }, @@ -19,11 +20,15 @@ export default Ember.Route.extend({ afterModel(model) { if (model.completed) { - return this.transitionTo("index"); + return this.transitionTo("wizard.index"); } return model.set("wizardId", this.wizard.id); }, + renderTemplate() { + this.render('wizard/templates/step'); + }, + setupController(controller, model) { let props = { step: model, diff --git a/assets/javascripts/wizard/routes/steps.js.es6 b/assets/javascripts/wizard/routes/steps.js.es6 new file mode 100644 index 00000000..6f35d152 --- /dev/null +++ b/assets/javascripts/wizard/routes/steps.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + redirect() { + this.transitionTo("wizard.index"); + }, +}); diff --git a/assets/javascripts/wizard/routes/custom-index.js.es6 b/assets/javascripts/wizard/routes/wizard-index.js.es6 similarity index 50% rename from assets/javascripts/wizard/routes/custom-index.js.es6 rename to assets/javascripts/wizard/routes/wizard-index.js.es6 index a8abc152..16b1140a 100644 --- a/assets/javascripts/wizard/routes/custom-index.js.es6 +++ b/assets/javascripts/wizard/routes/wizard-index.js.es6 @@ -1,10 +1,11 @@ -import { getCachedWizard } from "../models/custom"; +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel() { const wizard = getCachedWizard(); - if (wizard && wizard.permitted && !wizard.completed && wizard.start) { - this.replaceWith("custom.step", wizard.start); + if (wizard && wizard.user && wizard.permitted && !wizard.completed && wizard.start) { + this.replaceWith("step", wizard.start); } }, @@ -12,6 +13,10 @@ export default Ember.Route.extend({ return getCachedWizard(); }, + renderTemplate() { + this.render('wizard/templates/wizard-index'); + }, + setupController(controller, model) { if (model && model.id) { const completed = model.get("completed"); @@ -19,17 +24,20 @@ export default Ember.Route.extend({ const wizardId = model.get("id"); const user = model.get("user"); const name = model.get("name"); + const requiresLogin = !user; + const notPermitted = !permitted; - controller.setProperties({ - requiresLogin: !user, + const props = { + requiresLogin, user, name, completed, - notPermitted: !permitted, + notPermitted, wizardId, - }); + }; + controller.setProperties(props); } else { controller.set("noWizard", true); } - }, + } }); diff --git a/assets/javascripts/wizard/routes/custom.js.es6 b/assets/javascripts/wizard/routes/wizard.js.es6 similarity index 59% rename from assets/javascripts/wizard/routes/custom.js.es6 rename to assets/javascripts/wizard/routes/wizard.js.es6 index a312db3a..2910ee6d 100644 --- a/assets/javascripts/wizard/routes/custom.js.es6 +++ b/assets/javascripts/wizard/routes/wizard.js.es6 @@ -1,37 +1,26 @@ -/* eslint no-undef: 0*/ - -import { findCustomWizard, updateCachedWizard } from "../models/custom"; +import { findCustomWizard, updateCachedWizard } from "../models/wizard"; import { ajax } from "wizard/lib/ajax"; import WizardI18n from "../lib/wizard-i18n"; +import Route from "@ember/routing/route"; +import { scheduleOnce } from "@ember/runloop"; +import { getOwner } from "discourse-common/lib/get-owner"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel(transition) { - this.set("queryParams", transition.intent.queryParams); + if (transition.intent.queryParams) { + this.set("queryParams", transition.intent.queryParams); + } }, model(params) { return findCustomWizard(params.wizard_id, this.get("queryParams")); }, - renderTemplate() { - this.render("custom"); - const wizardModel = this.modelFor("custom"); - const stepModel = this.modelFor("custom.step"); - - if ( - wizardModel.resume_on_revisit && - wizardModel.submission_last_updated_at && - stepModel.index > 0 - ) { - this.showDialog(wizardModel); - } - }, - showDialog(wizardModel) { const title = WizardI18n("wizard.incomplete_submission.title", { date: moment(wizardModel.submission_last_updated_at).format( "MMMM Do YYYY" - ), + ) }); const buttons = [ @@ -57,28 +46,36 @@ export default Ember.Route.extend({ afterModel(model) { updateCachedWizard(model); + }, - return ajax({ - url: `/site/settings`, - type: "GET", - }).then((result) => { - $.extend(Wizard.SiteSettings, result); - }); + renderTemplate() { + this.render('wizard/templates/wizard'); }, setupController(controller, model) { - const background = model ? model.get("background") : "AliceBlue"; - Ember.run.scheduleOnce("afterRender", this, function () { - $("body.custom-wizard").css("background", background); + const background = model ? model.get("background") : ""; + + scheduleOnce("afterRender", this, function () { + $("body").css("background", background); if (model && model.id) { - $("#custom-wizard-main").addClass(model.id.dasherize()); + $(getOwner(this).rootElement).addClass(model.id.dasherize()); } }); + controller.setProperties({ customWizard: true, - logoUrl: Wizard.SiteSettings.logo_small, + logoUrl: this.siteSettings.logo_small, reset: null, }); + + const stepModel = this.modelFor("step"); + if ( + model.resume_on_revisit && + model.submission_last_updated_at && + stepModel.index > 0 + ) { + this.showDialog(model); + } }, }); diff --git a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index 7ce4c298..42fc63e8 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -3,6 +3,7 @@ value=field.value content=field.content tabindex=field.tabindex + onChange=(action "onChangeValue") options=(hash none="select_kit.default_header_text" )}} diff --git a/assets/javascripts/wizard/templates/custom.index.hbs b/assets/javascripts/wizard/templates/custom.index.hbs deleted file mode 100644 index 3cd02e05..00000000 --- a/assets/javascripts/wizard/templates/custom.index.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#if noWizard}} - {{wizard-no-access text=(wizard-i18n "wizard.none") wizardId=wizardId}} -{{else}} - {{#if requiresLogin}} - {{wizard-no-access text=(wizard-i18n "wizard.requires_login") wizardId=wizardId}} - {{else}} - {{#if notPermitted}} - {{wizard-no-access text=(wizard-i18n "wizard.not_permitted") wizardId=wizardId}} - {{else}} - {{#if completed}} - {{wizard-no-access text=(wizard-i18n "wizard.completed") wizardId=wizardId}} - {{/if}} - {{/if}} - {{/if}} -{{/if}} diff --git a/assets/javascripts/wizard/templates/index.hbs b/assets/javascripts/wizard/templates/index.hbs new file mode 100644 index 00000000..c24cd689 --- /dev/null +++ b/assets/javascripts/wizard/templates/index.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/assets/javascripts/wizard/templates/custom.step.hbs b/assets/javascripts/wizard/templates/step.hbs similarity index 100% rename from assets/javascripts/wizard/templates/custom.step.hbs rename to assets/javascripts/wizard/templates/step.hbs diff --git a/assets/javascripts/wizard/templates/wizard-index.hbs b/assets/javascripts/wizard/templates/wizard-index.hbs new file mode 100644 index 00000000..a8f1b9ba --- /dev/null +++ b/assets/javascripts/wizard/templates/wizard-index.hbs @@ -0,0 +1,3 @@ +{{#if noAccess}} + {{wizard-no-access text=(wizard-i18n noAccessI18nKey) wizardId=wizardId reason=noAccessReason}} +{{/if}} diff --git a/assets/javascripts/wizard/templates/custom.hbs b/assets/javascripts/wizard/templates/wizard.hbs similarity index 86% rename from assets/javascripts/wizard/templates/custom.hbs rename to assets/javascripts/wizard/templates/wizard.hbs index 4701fec2..f6d6127e 100644 --- a/assets/javascripts/wizard/templates/custom.hbs +++ b/assets/javascripts/wizard/templates/wizard.hbs @@ -1,7 +1,3 @@ -{{#if showCanvas}} - {{wizard-canvas}} -{{/if}} -
{{outlet}} diff --git a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 new file mode 100644 index 00000000..f73d1ab7 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 @@ -0,0 +1,135 @@ +import { + visit, + click, + fillIn, + triggerKeyEvent +} from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible, + server +} from "../helpers/acceptance"; +import { + allFieldsWizard, + getWizard +} from "../helpers/wizard"; +import tagsJson from "../fixtures/tags"; +import usersJson from "../fixtures/users"; +import { response } from "../pretender"; + +acceptance("Field | Fields", [ getWizard(allFieldsWizard) ], + function(hooks) { + test("Text", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); + }); + + test("Textarea", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.textarea-field textarea.wizard-focusable")); + }); + + test("Composer", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.composer-field .wizard-field-composer textarea")); + assert.strictEqual(count(".wizard-field.composer-field .d-editor-button-bar button"), 8); + assert.ok(visible(".wizard-btn.toggle-preview")); + + await fillIn(".wizard-field.composer-field .wizard-field-composer textarea", "Input in composer"); + await click(".wizard-btn.toggle-preview"); + assert.strictEqual(query('.wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p').textContent.trim(), "Input in composer"); + }); + + test("Text Only", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.text-only-field label.field-label")); + }); + + test("Date", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-field input.date-picker")); + await click(".wizard-field.date-field input.date-picker"); + assert.ok(visible(".wizard-field.date-field .pika-single")); + }); + + test("Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); + await click(".wizard-field.time-field .d-time-input .select-kit .select-kit-header"); + assert.ok(visible(".wizard-field.time-field .select-kit-collection")); + }); + + test("Date Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-time-field .d-date-time-input .select-kit")); + await click(".wizard-field.date-time-field .d-date-input input.date-picker"); + assert.ok(visible(".wizard-field.date-time-field .d-date-input .pika-single")); + await click(".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header"); + assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); + }); + + test("Number", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.number-field input[type='number']")); + }); + + test("Checkbox", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); + }); + + test("Url", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.url-field input[type='text']")); + }); + + test("Upload", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.upload-field label.wizard-btn-upload-file")); + assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); + }); + + test("Dropdown", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); + await click(".wizard-field.dropdown-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.dropdown-field .select-kit-collection li"), 3); + }); + + test("Tag", async function (assert) { + server.get("/tags/filter/search", () => (response(200, { results: tagsJson['tags']}))); + await visit("/wizard"); + assert.ok(visible(".wizard-field.tag-field .multi-select-header")); + await click(".wizard-field.tag-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.tag-field .select-kit-collection li"), 2); + }); + + test("Category", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.category-field .multi-select-header")); + await click(".wizard-field.category-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.category-field .select-kit-collection li"), 5); + }); + + test("Group", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.group-field .single-select-header")); + await click(".wizard-field.group-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.group-field .select-kit-collection li"), 10); + }); + + test("User", async function (assert) { + server.get("/u/search/users", () => (response(200, usersJson))); + + await visit("/wizard"); + await fillIn(".wizard-field.user-selector-field input.ember-text-field", "a"); + await triggerKeyEvent(".wizard-field.user-selector-field input.ember-text-field", "keyup", "a".charCodeAt(0)); + + assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); + // TODO: add assertion for ac results. autocomplete does not appear in time. + }); + } +); diff --git a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 new file mode 100644 index 00000000..731c9b76 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 @@ -0,0 +1,47 @@ +import { visit, click } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible +} from "../helpers/acceptance"; +import { + stepNotPermitted, + wizard, + getWizard +} from "../helpers/wizard"; +import { + saveStep, + update +} from "../helpers/step"; + +acceptance("Step | Not permitted", [ getWizard(stepNotPermitted) ], + function(hooks) { + test("Shows not permitted message", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".step-message.not-permitted")); + }); + } +); + +acceptance("Step | Step", [ getWizard(wizard), saveStep(update) ], + function(hooks) { + test("Renders the step", async function (assert) { + await visit("/wizard"); + assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); + assert.ok(visible('.wizard-step-footer .wizard-progress'), true); + assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); + }); + + test("Goes to the next step", async function (assert) { + await visit("/wizard"); + assert.ok(visible('.wizard-step.step_1'), true); + await click('.wizard-btn.next'); + assert.ok(visible('.wizard-step.step_2'), true); + }); + } +); diff --git a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 new file mode 100644 index 00000000..e68f59ae --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 @@ -0,0 +1,72 @@ +import { visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible +} from "../helpers/acceptance"; +import { + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + wizard, + getWizard +} from "../helpers/wizard"; + +acceptance("Wizard | Not logged in", [ getWizard(wizardNoUser) ], + function(hooks) { + test("Wizard no access requires login", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.requires-login")); + }); + } +); + +acceptance("Wizard | Not permitted", [ getWizard(wizardNotPermitted) ], + function(hooks) { + test("Wizard no access not permitted", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.not-permitted")); + }); + } +); + +acceptance("Wizard | Completed", [ getWizard(wizardCompleted) ], + function(hooks) { + test("Wizard no access completed", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.completed")); + }); + } +); + +acceptance("Wizard | Wizard", [ getWizard(wizard) ], + function(hooks) { + test("Starts", async function (assert) { + await visit("/wizard"); + assert.ok(query('.wizard-column'), true); + }); + + test("Applies the body background color", async function (assert) { + await visit("/wizard"); + assert.ok($("body")[0].style.background); + }); + + test("Renders the wizard form", async function (assert) { + await visit("/wizard"); + assert.ok(visible('.wizard-column-contents .wizard-step'), true); + assert.ok(visible('.wizard-footer img'), true); + }); + + test("Renders the first step", async function (assert) { + await visit("/wizard"); + assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); + assert.ok(visible('.wizard-step-footer .wizard-progress'), true); + assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); + }); + } +); diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 new file mode 100644 index 00000000..d2c503ad --- /dev/null +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -0,0 +1,17 @@ +// discourse-skip-module + +document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); +}); + +Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); + } +}); diff --git a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 new file mode 100644 index 00000000..e862f54a --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 @@ -0,0 +1,209 @@ +export default { + "categories": [ + { + "id": 1, + "name": "Uncategorized", + "color": "0088CC", + "text_color": "FFFFFF", + "slug": "uncategorized", + "topic_count": 1, + "post_count": 1, + "position": 0, + "description": "Topics that don't need a category, or don't fit into any other existing category.", + "description_text": "Topics that don't need a category, or don't fit into any other existing category.", + "description_excerpt": "Topics that don't need a category, or don't fit into any other existing category.", + "topic_url": "/t/", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 2, + "name": "Site Feedback", + "color": "808281", + "text_color": "FFFFFF", + "slug": "site-feedback", + "topic_count": 20, + "post_count": 21, + "position": 1, + "description": "

Discussion about this site, its organization, how it works, and how we can improve it.

", + "description_text": "Discussion about this site, its organization, how it works, and how we can improve it.", + "description_excerpt": "Discussion about this site, its organization, how it works, and how we can improve it.", + "topic_url": "/t/about-the-site-feedback-category/1", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 3, + "name": "Staff", + "color": "E45735", + "text_color": "FFFFFF", + "slug": "staff", + "topic_count": 4, + "post_count": 7, + "position": 2, + "description": "

Private category for staff discussions. Topics are only visible to admins and moderators.

", + "description_text": "Private category for staff discussions. Topics are only visible to admins and moderators.", + "description_excerpt": "Private category for staff discussions. Topics are only visible to admins and moderators.", + "topic_url": "/t/about-the-staff-category/2", + "read_restricted": true, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 4, + "name": "Lounge", + "color": "A461EF", + "text_color": "652D90", + "slug": "lounge", + "topic_count": 1, + "post_count": 1, + "position": 3, + "description": "

A category exclusive to members with trust level 3 and higher.

", + "description_text": "A category exclusive to members with trust level 3 and higher.", + "description_excerpt": "A category exclusive to members with trust level 3 and higher.", + "topic_url": "/t/about-the-lounge-category/3", + "read_restricted": true, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 5, + "name": "Custom Categories", + "color": "0088CC", + "text_color": "FFFFFF", + "slug": "custom-category", + "topic_count": 0, + "post_count": 0, + "position": 10, + "description": "Description of custom category", + "description_text": "Description of custom category", + "description_excerpt": "Description of custom category", + "topic_url": "/t/about-the-custom-category/5", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 new file mode 100644 index 00000000..16f8150d --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 @@ -0,0 +1,313 @@ +export default { + "groups": [ + { + "id": 1, + "automatic": true, + "name": "admins", + "display_name": "admins", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 0, + "automatic": true, + "name": "everyone", + "display_name": "everyone", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 3, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 15, + "automatic": false, + "name": "custom_group", + "user_count": 1, + "mentionable_level": 1, + "messageable_level": 2, + "visibility_level": 3, + "primary_group": false, + "title": "Custom Group", + "grant_trust_level": 3, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": "I am prefilled", + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 99, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 2, + "automatic": true, + "name": "moderators", + "display_name": "moderators", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 99, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 2, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 3, + "automatic": true, + "name": "staff", + "display_name": "staff", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 10, + "automatic": true, + "name": "trust_level_0", + "display_name": "trust_level_0", + "user_count": 2, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 11, + "automatic": true, + "name": "trust_level_1", + "display_name": "trust_level_1", + "user_count": 2, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 12, + "automatic": true, + "name": "trust_level_2", + "display_name": "trust_level_2", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 13, + "automatic": true, + "name": "trust_level_3", + "display_name": "trust_level_3", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 14, + "automatic": true, + "name": "trust_level_4", + "display_name": "trust_level_4", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 new file mode 100644 index 00000000..f71ace8e --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 @@ -0,0 +1,283 @@ +export default { + "default_locale": "en", + "title": "Discourse", + "short_site_description": "", + "exclude_rel_nofollow_domains": "", + "logo": "/images/discourse-logo-sketch.png", + "logo_small": "/images/discourse-logo-sketch-small.png", + "digest_logo": "", + "mobile_logo": "", + "logo_dark": "", + "logo_small_dark": "", + "mobile_logo_dark": "", + "large_icon": "", + "favicon": "", + "apple_touch_icon": "", + "display_local_time_in_user_card": false, + "allow_user_locale": false, + "set_locale_from_accept_language_header": false, + "support_mixed_text_direction": false, + "suggested_topics": 5, + "ga_universal_tracking_code": "", + "ga_universal_domain_name": "auto", + "gtm_container_id": "", + "top_menu": "categories|latest", + "post_menu": "read|like|share|flag|edit|bookmark|delete|admin|reply", + "post_menu_hidden_items": "flag|bookmark|edit|delete|admin", + "share_links": "twitter|facebook|email", + "share_quote_visibility": "anonymous", + "share_quote_buttons": "twitter|email", + "desktop_category_page_style": "categories_and_latest_topics", + "category_colors": "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", + "category_style": "bullet", + "max_category_nesting": 2, + "enable_mobile_theme": true, + "enable_direct_s3_uploads": false, + "enable_upload_debug_mode": false, + "default_dark_mode_color_scheme_id": 1, + "relative_date_duration": 30, + "fixed_category_positions": false, + "fixed_category_positions_on_create": false, + "enable_badges": true, + "enable_badge_sql": true, + "max_favorite_badges": 2, + "enable_whispers": false, + "enable_bookmarks_with_reminders": true, + "push_notifications_prompt": true, + "vapid_public_key_bytes": "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", + "invite_only": false, + "login_required": false, + "must_approve_users": false, + "enable_local_logins": true, + "enable_local_logins_via_email": true, + "allow_new_registrations": true, + "enable_signup_cta": true, + "facebook_app_id": "", + "auth_skip_create_confirm": false, + "auth_overrides_email": false, + "enable_discourse_connect": true, + "discourse_connect_overrides_avatar": false, + "hide_email_address_taken": false, + "min_username_length": 3, + "max_username_length": 20, + "unicode_usernames": false, + "min_password_length": 10, + "min_admin_password_length": 15, + "email_editable": true, + "logout_redirect": "", + "full_name_required": false, + "enable_names": true, + "invite_expiry_days": 90, + "invites_per_page": 40, + "delete_user_max_post_age": 60, + "delete_all_posts_max": 15, + "prioritize_username_in_ux": true, + "enable_user_directory": true, + "allow_anonymous_posting": false, + "anonymous_posting_min_trust_level": 1, + "allow_users_to_hide_profile": true, + "hide_user_profiles_from_public": false, + "allow_featured_topic_on_user_profiles": true, + "hide_suspension_reasons": false, + "ignored_users_count_message_threshold": 5, + "ignored_users_message_gap_days": 365, + "user_selected_primary_groups": false, + "gravatar_name": "Gravatar", + "gravatar_base_url": "www.gravatar.com", + "gravatar_login_url": "/emails", + "enable_group_directory": true, + "enable_category_group_moderation": false, + "min_post_length": 20, + "min_first_post_length": 20, + "min_personal_message_post_length": 10, + "max_post_length": 32000, + "topic_featured_link_enabled": true, + "min_topic_views_for_delete_confirm": 5000, + "min_topic_title_length": 15, + "max_topic_title_length": 255, + "enable_filtered_replies_view": false, + "min_personal_message_title_length": 2, + "allow_uncategorized_topics": true, + "min_title_similar_length": 10, + "enable_personal_messages": true, + "edit_history_visible_to_public": true, + "delete_removed_posts_after": 24, + "traditional_markdown_linebreaks": false, + "enable_markdown_typographer": true, + "enable_markdown_linkify": true, + "markdown_linkify_tlds": "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", + "markdown_typographer_quotation_marks": "“|”|‘|’", + "enable_rich_text_paste": true, + "suppress_reply_directly_below": true, + "suppress_reply_directly_above": true, + "max_reply_history": 1, + "enable_mentions": true, + "here_mention": "here", + "newuser_max_embedded_media": 1, + "newuser_max_attachments": 0, + "show_pinned_excerpt_mobile": true, + "show_pinned_excerpt_desktop": true, + "display_name_on_posts": false, + "show_time_gap_days": 7, + "short_progress_text_threshold": 10000, + "default_code_lang": "auto", + "autohighlight_all_code": false, + "highlighted_languages": "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", + "show_copy_button_on_codeblocks": false, + "enable_emoji": true, + "enable_emoji_shortcuts": true, + "emoji_set": "twitter", + "emoji_autocomplete_min_chars": 0, + "enable_inline_emoji_translation": false, + "code_formatting_style": "code-fences", + "allowed_href_schemes": "", + "watched_words_regular_expressions": false, + "enable_diffhtml_preview": false, + "enable_fast_edit": true, + "old_post_notice_days": 14, + "blur_tl0_flagged_posts_media": true, + "email_time_window_mins": 10, + "disable_digest_emails": false, + "email_in": false, + "enable_imap": false, + "enable_smtp": false, + "disable_emails": "no", + "bounce_score_threshold": 4, + "enable_secondary_emails": true, + "max_image_size_kb": 4096, + "max_attachment_size_kb": 4096, + "authorized_extensions": "jpg|jpeg|png|gif|heic|heif|webp", + "authorized_extensions_for_staff": "", + "max_image_width": 690, + "max_image_height": 500, + "prevent_anons_from_downloading_files": false, + "secure_media": false, + "enable_s3_uploads": false, + "allow_profile_backgrounds": true, + "allow_uploaded_avatars": "0", + "default_avatars": "", + "external_system_avatars_enabled": true, + "external_system_avatars_url": "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", + "external_emoji_url": "", + "selectable_avatars_mode": "disabled", + "selectable_avatars": "", + "allow_staff_to_upload_any_file_in_pm": true, + "simultaneous_uploads": 5, + "composer_media_optimization_image_enabled": true, + "composer_media_optimization_image_bytes_optimization_threshold": 524288, + "composer_media_optimization_image_resize_dimensions_threshold": 1920, + "composer_media_optimization_image_resize_width_target": 1920, + "composer_media_optimization_image_resize_pre_multiply": false, + "composer_media_optimization_image_resize_linear_rgb": false, + "composer_media_optimization_image_encode_quality": 75, + "composer_media_optimization_debug_mode": false, + "min_trust_level_to_allow_profile_background": 0, + "min_trust_level_to_allow_user_card_background": 0, + "min_trust_level_to_allow_ignore": 2, + "tl1_requires_read_posts": 30, + "tl3_links_no_follow": false, + "enforce_second_factor": "no", + "moderators_change_post_ownership": false, + "moderators_view_emails": false, + "use_admin_ip_allowlist": false, + "allowed_iframes": "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", + "can_permanently_delete": false, + "max_oneboxes_per_post": 50, + "reviewable_claiming": "disabled", + "reviewable_default_topics": false, + "reviewable_default_visibility": "low", + "alert_admins_if_errors_per_minute": 0, + "alert_admins_if_errors_per_hour": 0, + "max_prints_per_hour_per_user": 5, + "invite_link_max_redemptions_limit": 5000, + "invite_link_max_redemptions_limit_users": 10, + "enable_long_polling": true, + "enable_chunked_encoding": true, + "long_polling_base_url": "/", + "background_polling_interval": 60000, + "polling_interval": 3000, + "anon_polling_interval": 25000, + "flush_timings_secs": 60, + "verbose_localization": false, + "max_new_topics": 500, + "enable_safe_mode": true, + "tos_url": "", + "privacy_policy_url": "", + "faq_url": "", + "enable_backups": true, + "backup_location": "local", + "maximum_backups": 5, + "use_pg_headlines_for_excerpt": false, + "min_search_term_length": 3, + "log_search_queries": true, + "version_checks": true, + "suppress_uncategorized_badge": true, + "header_dropdown_category_count": 8, + "slug_generation_method": "ascii", + "summary_timeline_button": false, + "topic_views_heat_low": 1000, + "topic_views_heat_medium": 2000, + "topic_views_heat_high": 3500, + "topic_post_like_heat_low": 0.5, + "topic_post_like_heat_medium": 1, + "topic_post_like_heat_high": 2, + "history_hours_low": 12, + "history_hours_medium": 24, + "history_hours_high": 48, + "cold_age_days_low": 14, + "cold_age_days_medium": 90, + "cold_age_days_high": 180, + "global_notice": "", + "show_create_topics_notice": true, + "bootstrap_mode_min_users": 50, + "bootstrap_mode_enabled": true, + "automatically_unpin_topics": true, + "read_time_word_count": 500, + "topic_page_title_includes_category": true, + "svg_icon_subset": "", + "allow_bulk_invite": true, + "disable_mailing_list_mode": true, + "default_topics_automatic_unpin": true, + "mute_all_categories_by_default": false, + "tagging_enabled": true, + "tag_style": "simple", + "max_tags_per_topic": 5, + "max_tag_length": 20, + "min_trust_level_to_tag_topics": "0", + "max_tag_search_results": 5, + "max_tags_in_filter_list": 30, + "tags_sort_alphabetically": false, + "tags_listed_by_group": false, + "suppress_overlapping_tags_in_list": false, + "remove_muted_tags_from_latest": "always", + "force_lowercase_tags": true, + "dashboard_hidden_reports": "", + "dashboard_visible_tabs": "moderation|security|reports", + "dashboard_general_tab_activity_metrics": "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", + "discourse_narrative_bot_enabled": true, + "details_enabled": true, + "custom_wizard_enabled": true, + "wizard_redirect_exclude_paths": "admin", + "wizard_recognised_image_upload_formats": "jpg|jpeg|png|gif", + "wizard_important_notices_on_dashboard": true, + "discourse_local_dates_email_format": "YYYY-MM-DDTHH:mm:ss[Z]", + "discourse_local_dates_enabled": true, + "discourse_local_dates_default_formats": "LLL|LTS|LL|LLLL", + "discourse_local_dates_default_timezones": "Europe/Paris|America/Los_Angeles", + "poll_enabled": true, + "poll_maximum_options": 20, + "poll_minimum_trust_level_to_create": 1, + "poll_groupable_user_fields": "", + "poll_export_data_explorer_query_id": -16, + "presence_enabled": true, + "presence_max_users_shown": 5, + "available_locales": "[{\"name\":\"اللغة العربية\",\"value\":\"ar\"},{\"name\":\"беларуская мова\",\"value\":\"be\"},{\"name\":\"български език\",\"value\":\"bg\"},{\"name\":\"bosanski jezik\",\"value\":\"bs_BA\"},{\"name\":\"català\",\"value\":\"ca\"},{\"name\":\"čeština\",\"value\":\"cs\"},{\"name\":\"dansk\",\"value\":\"da\"},{\"name\":\"Deutsch\",\"value\":\"de\"},{\"name\":\"ελληνικά\",\"value\":\"el\"},{\"name\":\"English (US)\",\"value\":\"en\"},{\"name\":\"English (UK)\",\"value\":\"en_GB\"},{\"name\":\"Español\",\"value\":\"es\"},{\"name\":\"eesti\",\"value\":\"et\"},{\"name\":\"فارسی\",\"value\":\"fa_IR\"},{\"name\":\"suomi\",\"value\":\"fi\"},{\"name\":\"Français\",\"value\":\"fr\"},{\"name\":\"galego\",\"value\":\"gl\"},{\"name\":\"עברית\",\"value\":\"he\"},{\"name\":\"magyar\",\"value\":\"hu\"},{\"name\":\"Հայերեն\",\"value\":\"hy\"},{\"name\":\"Indonesian\",\"value\":\"id\"},{\"name\":\"Italiano\",\"value\":\"it\"},{\"name\":\"日本語\",\"value\":\"ja\"},{\"name\":\"한국어\",\"value\":\"ko\"},{\"name\":\"lietuvių kalba\",\"value\":\"lt\"},{\"name\":\"latviešu valoda\",\"value\":\"lv\"},{\"name\":\"Norsk bokmål\",\"value\":\"nb_NO\"},{\"name\":\"Nederlands\",\"value\":\"nl\"},{\"name\":\"polski\",\"value\":\"pl_PL\"},{\"name\":\"Português\",\"value\":\"pt\"},{\"name\":\"Português (BR)\",\"value\":\"pt_BR\"},{\"name\":\"limba română\",\"value\":\"ro\"},{\"name\":\"Русский\",\"value\":\"ru\"},{\"name\":\"slovenčina\",\"value\":\"sk\"},{\"name\":\"slovenščina\",\"value\":\"sl\"},{\"name\":\"Shqip\",\"value\":\"sq\"},{\"name\":\"српски језик\",\"value\":\"sr\"},{\"name\":\"svenska\",\"value\":\"sv\"},{\"name\":\"Kiswahili\",\"value\":\"sw\"},{\"name\":\"తెలుగు\",\"value\":\"te\"},{\"name\":\"ไทย\",\"value\":\"th\"},{\"name\":\"Türkçe\",\"value\":\"tr_TR\"},{\"name\":\"українська мова\",\"value\":\"uk\"},{\"name\":\"اردو\",\"value\":\"ur\"},{\"name\":\"Việt Nam\",\"value\":\"vi\"},{\"name\":\"简体中文\",\"value\":\"zh_CN\"},{\"name\":\"繁體中文\",\"value\":\"zh_TW\"}]", + "require_invite_code": false, + "site_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", + "site_logo_small_url": "http://localhost:3000/images/discourse-logo-sketch-small.png", + "site_mobile_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", + "site_favicon_url": "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", + "site_logo_dark_url": "", + "site_logo_small_dark_url": "", + "site_mobile_logo_dark_url": "" +} diff --git a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 new file mode 100644 index 00000000..a072772e --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 @@ -0,0 +1,22 @@ +export default { + "tags": [ + { + "id": "tag1", + "text": "tag1", + "name": "tag1", + "description": null, + "count": 1, + "pm_count": 0, + "target_tag": null + }, + { + "id": "tag2", + "text": "tag2", + "name": "tag2", + "description": null, + "count": 1, + "pm_count": 0, + "target_tag": null + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/update.js.es6 b/assets/javascripts/wizard/tests/fixtures/update.js.es6 new file mode 100644 index 00000000..3908525c --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/update.js.es6 @@ -0,0 +1,5 @@ +export default { + "final": false, + "next_step_id": "step_2", + "wizard": {} +} diff --git a/assets/javascripts/wizard/tests/fixtures/user.js.es6 b/assets/javascripts/wizard/tests/fixtures/user.js.es6 new file mode 100644 index 00000000..8acd7392 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/user.js.es6 @@ -0,0 +1,34 @@ +export default { + id: 19, + username: "angus", + uploaded_avatar_id: 5275, + avatar_template: "/user_avatar/localhost/angus/{size}/5275.png", + name: "Angus McLeod", + unread_notifications: 0, + unread_private_messages: 0, + unread_high_priority_notifications: 0, + admin: true, + notification_channel_position: null, + site_flagged_posts_count: 1, + moderator: true, + staff: true, + can_create_group: true, + title: "", + reply_count: 859, + topic_count: 36, + enable_quoting: true, + external_links_in_new_tab: false, + dynamic_favicon: true, + trust_level: 4, + can_edit: true, + can_invite_to_forum: true, + should_be_redirected_to_top: false, + custom_fields: {}, + muted_category_ids: [], + dismissed_banner_key: null, + akismet_review_count: 0, + title_count_mode: "notifications", + timezone: "Australia/Perth", + skip_new_user_tips: false, + can_review: true +} diff --git a/assets/javascripts/wizard/tests/fixtures/users.js.es6 b/assets/javascripts/wizard/tests/fixtures/users.js.es6 new file mode 100644 index 00000000..267d4909 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/users.js.es6 @@ -0,0 +1,14 @@ +export default { + "users": [ + { + "username": "angus", + "name": "Angus", + "avatar_template": "/user_avatar/localhost/angus/{size}/12_2.png" + }, + { + "username": "angus_2", + "name": "Angus 2", + "avatar_template": "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png" + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 new file mode 100644 index 00000000..be4fa8b2 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 @@ -0,0 +1,469 @@ +export default { + "id": "wizard", + "name": "Wizard", + "start": "step_1", + "background": "#333333", + "submission_last_updated_at": "2022-03-15T21:11:01+01:00", + "theme_id": 2, + "required": false, + "permitted": true, + "uncategorized_category_id": 1, + "categories": [], + "subscribed": false, + "resume_on_revisit": false, + "steps": [ + { + "id": "step_1", + "index": 0, + "next": "step_2", + "description": "

Text inputs!

", + "title": "Text", + "permitted": true, + "permitted_message": null, + "final": false, + "fields": [ + { + "id": "step_1_field_1", + "index": 0, + "type": "text", + "required": false, + "value": "I am prefilled", + "label": "

Text

", + "description": "Text field description.", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_2", + "index": 0, + "type": "textarea", + "required": false, + "value": "", + "label": "

Textarea

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_3", + "index": 2, + "type": "composer", + "required": false, + "value": "", + "label": "

Composer

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_4", + "index": 3, + "type": "text_only", + "required": false, + "value": null, + "label": "

I’m only text

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 4, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_5", + "index": 4, + "type": "composer_preview", + "required": false, + "value": "", + "label": "

I’m a preview

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": "

I am prefilled

", + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_6", + "index": 5, + "type": "composer_preview", + "required": false, + "value": "", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": "

This is the preview of the composer

", + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + }, + { + "id": "step_2", + "index": 1, + "next": "step_3", + "previous": "step_1", + "description": "

Because I couldn’t think of another name for this step \":slight_smile:\"

", + "title": "Values", + "permitted": true, + "permitted_message": null, + "final": false, + "fields": [ + { + "id": "step_2_field_1", + "index": 0, + "type": "date", + "required": false, + "value": "", + "label": "

Date

", + "file_types": null, + "format": "YYYY-MM-DD", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_2", + "index": 0, + "type": "time", + "required": false, + "value": "", + "label": "

Time

", + "file_types": null, + "format": "HH:mm", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_3", + "index": 2, + "type": "date_time", + "required": false, + "value": "", + "label": "

Date & Time

", + "file_types": null, + "format": "", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_4", + "index": 3, + "type": "number", + "required": false, + "value": "", + "label": "

Number

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_5", + "index": 4, + "type": "checkbox", + "required": false, + "value": false, + "label": "

Checkbox

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_6", + "index": 5, + "type": "url", + "required": false, + "value": "", + "label": "

Url

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 7, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_7", + "index": 6, + "type": "upload", + "required": false, + "value": "", + "label": "

Upload

", + "file_types": ".jpg,.jpeg,.png", + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 8, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + }, + { + "id": "step_3", + "index": 2, + "previous": "step_2", + "description": "

Unfortunately not the edible type \":sushi:\"

", + "title": "Combo-boxes", + "permitted": true, + "permitted_message": null, + "final": true, + "fields": [ + { + "id": "step_3_field_1", + "index": 0, + "type": "dropdown", + "required": false, + "value": "choice1", + "label": "

Custom Dropdown

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": [ + { + "id": "one", + "name": "One" + }, + { + "id": "two", + "name": "Two" + } + ], + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_2", + "index": 0, + "type": "tag", + "required": false, + "value": null, + "label": "

Tag

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_3", + "index": 2, + "type": "category", + "required": false, + "value": null, + "label": "

Category

", + "file_types": null, + "format": null, + "limit": 1, + "property": "id", + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_4", + "index": 3, + "type": "group", + "required": false, + "value": null, + "label": "

Group

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 4, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_5", + "index": 4, + "type": "user_selector", + "required": false, + "value": null, + "label": "

User Selector

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_6", + "index": 5, + "type": "user_selector", + "required": false, + "value": null, + "label": "

Conditional User Selector

", + "description": "Shown when checkbox in step_2_field_5 is true", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + } + ], + "groups": [] +} diff --git a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 new file mode 100644 index 00000000..f5c1175f --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 @@ -0,0 +1,53 @@ +import { module } from "qunit"; +import setupPretender, { response } from "../pretender"; +import startApp from "../helpers/start-app"; + +let server; +let app; + +function acceptance(name, requests, cb) { + module(`Acceptance: ${name}`, function(hooks) { + hooks.beforeEach(function() { + server = setupPretender(function(pretender) { + requests.forEach(req => { + pretender[req.verb](req.path, () => (response(req.status, req.response))); + }); + return pretender; + }); + app = startApp(); + }); + + hooks.afterEach(function() { + app.destroy(); + server.shutdown(); + }); + + cb(hooks); + }); +} + +export default acceptance; + +export { + server +}; + +// The discourse/test/helpers/qunit-helpers file has many functions and imports +// we don't need, so there will be some duplciation here. + +export function queryAll(selector, context) { + context = context || "#ember-testing"; + return $(selector, context); +} + +export function query() { + return document.querySelector("#ember-testing").querySelector(...arguments); +} + +export function visible(selector) { + return queryAll(selector + ":visible").length > 0; +} + +export function count(selector) { + return queryAll(selector).length; +} diff --git a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 new file mode 100644 index 00000000..6afe6eb9 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 @@ -0,0 +1,19 @@ +const CustomWizard = requirejs("discourse/plugins/discourse-custom-wizard/wizard/application").default; +const initializer = requirejs("discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard").default; +const siteSettings = requirejs("discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings").default; +const { cloneJSON } = requirejs("discourse-common/lib/object").default; + +let app; + +export default function () { + app = CustomWizard.create({ rootElement: "#ember-testing" }); + app.start(); + + app.SiteSettings = cloneJSON(siteSettings); + initializer.initialize(app); + + app.setupForTesting(); + app.injectTestHelpers(); + + return app; +} diff --git a/assets/javascripts/wizard/tests/helpers/step.js.es6 b/assets/javascripts/wizard/tests/helpers/step.js.es6 new file mode 100644 index 00000000..a24e04e1 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/step.js.es6 @@ -0,0 +1,20 @@ +import updateJson from "../fixtures/update"; +import { cloneJSON } from "discourse-common/lib/object"; +import wizardJson from "../fixtures/wizard"; + +const update = cloneJSON(updateJson); +update.wizard = cloneJSON(wizardJson); + +const saveStep = function(response) { + return { + verb: "put", + path: '/w/wizard/steps/:step_id', + status: 200, + response + } +} + +export { + saveStep, + update +} diff --git a/assets/javascripts/wizard/tests/helpers/test.js.es6 b/assets/javascripts/wizard/tests/helpers/test.js.es6 new file mode 100644 index 00000000..c7401fd5 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/test.js.es6 @@ -0,0 +1,7 @@ +function exists(selector) { + return document.querySelector(selector) !== null; +} + +export { + exists +} diff --git a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 new file mode 100644 index 00000000..997f6c36 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 @@ -0,0 +1,52 @@ +import wizardJson from "../fixtures/wizard"; +import userJson from "../fixtures/user"; +import categoriesJson from "../fixtures/categories"; +import groupsJson from "../fixtures/groups"; +import { cloneJSON } from "discourse-common/lib/object"; + +const wizardNoUser = cloneJSON(wizardJson); +const wizard = cloneJSON(wizardJson); +wizard.user = cloneJSON(userJson); + +const wizardNotPermitted = cloneJSON(wizard); +wizardNotPermitted.permitted = false; + +const wizardCompleted = cloneJSON(wizard); +wizardCompleted.completed = true; + +wizard.start = "step_1"; +wizard.resume_on_revisit = false; +wizard.submission_last_updated_at = "2022-03-11T20:00:18+01:00"; +wizard.subscribed = false; + +const stepNotPermitted = cloneJSON(wizard); +stepNotPermitted.steps[0].permitted = false; + +const allFieldsWizard = cloneJSON(wizard); +allFieldsWizard.steps[0].fields = [ + ...allFieldsWizard.steps[0].fields, + ...allFieldsWizard.steps[1].fields, + ...allFieldsWizard.steps[2].fields +]; +allFieldsWizard.steps = [cloneJSON(allFieldsWizard.steps[0])]; +allFieldsWizard.categories = cloneJSON(categoriesJson['categories']); +allFieldsWizard.groups = cloneJSON(groupsJson['groups']); + +const getWizard = function(response) { + return { + verb: "get", + path: "/w/wizard", + status: 200, + response + } +} + +export { + getWizard, + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + stepNotPermitted, + allFieldsWizard, + wizard +} diff --git a/views/wizard-pretender.js b/assets/javascripts/wizard/tests/pretender.js.es6 similarity index 56% rename from views/wizard-pretender.js rename to assets/javascripts/wizard/tests/pretender.js.es6 index ac0ef1e8..88eae666 100644 --- a/views/wizard-pretender.js +++ b/assets/javascripts/wizard/tests/pretender.js.es6 @@ -1,8 +1,4 @@ import Pretender from "pretender"; -import WizardJson from "./fixtures/wizard"; - -// TODO: This file has some copied and pasted functions from `create-pretender` - would be good -// to centralize that code at some point. function parsePostData(query) { const result = {}; @@ -30,24 +26,14 @@ function response(code, obj) { return [code, { "Content-Type": "application/json" }, obj]; } -export default function () { - const server = new Pretender(function () { - this.get("/w/wizard.json", () => { - return response(200, cloneJSON(WizardJson); - }); +export { response }; - this.put("/wizard/steps/:id", (request) => { - const body = parsePostData(request.requestBody); +export default function (cb) { + let server = new Pretender(); - if (body.fields.full_name === "Server Fail") { - return response(422, { - errors: [{ field: "full_name", description: "Invalid name" }], - }); - } else { - return response(200, { success: true }); - } - }); - }); + if (cb) { + server = cb(server); + } server.prepareBody = function (body) { if (body && typeof body === "object") { @@ -56,7 +42,7 @@ export default function () { return body; }; - server.unhandledRequest = function (verb, path) { + server.unhandledRequest = function (verb, path, request) { const error = "Unhandled request in test environment: " + path + " (" + verb + ")"; window.console.error(error); diff --git a/assets/stylesheets/wizard/custom/composer.scss b/assets/stylesheets/wizard/custom/composer.scss index be866b8a..aea17d63 100644 --- a/assets/stylesheets/wizard/custom/composer.scss +++ b/assets/stylesheets/wizard/custom/composer.scss @@ -46,7 +46,7 @@ position: relative; } -.wizard-field-composer.show-preview .d-editor-textarea-wrapper { +.wizard-field-composer.show-preview .d-editor-textarea-column { display: none; } diff --git a/config/routes.rb b/config/routes.rb index 3b5f8ca6..514605de 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true CustomWizard::Engine.routes.draw do + get 'qunit' => 'wizard#qunit' get ':wizard_id' => 'wizard#index' put ':wizard_id/skip' => 'wizard#skip' get ':wizard_id/steps' => 'wizard#index' diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb index 1e20adf9..d877f035 100644 --- a/controllers/custom_wizard/wizard.rb +++ b/controllers/custom_wizard/wizard.rb @@ -1,55 +1,40 @@ # frozen_string_literal: true -class CustomWizard::WizardController < ::ApplicationController - include ApplicationHelper - prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) - layout 'wizard' +class CustomWizard::WizardController < ::ActionController::Base + helper ApplicationHelper + include CurrentUser + include CanonicalURL::ControllerExtensions + include GlobalPath + + prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) + layout :set_wizard_layout + + before_action :preload_wizard_json before_action :ensure_plugin_enabled before_action :update_subscription, only: [:index] before_action :ensure_logged_in, only: [:skip] + helper_method :wizard_page_title helper_method :wizard_theme_id helper_method :wizard_theme_lookup helper_method :wizard_theme_translations_lookup - def wizard - @builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - @wizard ||= @builder.build - @wizard - end - - def wizard_page_title - wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') - end - - def wizard_theme_id - wizard ? wizard.theme_id : nil - end - - def wizard_theme_lookup(name) - Theme.lookup_field(wizard_theme_id, mobile_view? ? :mobile : :desktop, name) - end - - def wizard_theme_translations_lookup - Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + def set_wizard_layout + action_name === 'qunit' ? 'qunit' : 'wizard' end def index respond_to do |format| format.json do - builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - - if builder.wizard.present? - builder_opts = {} - builder_opts[:reset] = params[:reset] - built_wizard = builder.build(builder_opts, params) - - render_serialized(built_wizard, ::CustomWizard::WizardSerializer, root: false) + if wizard.present? + render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200 else render json: { error: I18n.t('wizard.none') } end end - format.html {} + format.html do + render "default/empty" + end end end @@ -61,9 +46,8 @@ class CustomWizard::WizardController < ::ApplicationController end result = success_json - user = current_user - if user && wizard.can_access? + if current_user && wizard.can_access? submission = wizard.current_submission if submission.present? && submission.redirect_to @@ -77,6 +61,64 @@ class CustomWizard::WizardController < ::ApplicationController render json: result end + def qunit + raise Discourse::InvalidAccess.new if Rails.env.production? + + respond_to do |format| + format.html do + render "default/empty" + end + end + end + + protected + + def ensure_logged_in + raise Discourse::NotLoggedIn.new unless current_user.present? + end + + def guardian + @guardian ||= Guardian.new(current_user, request) + end + + def wizard + @wizard ||= begin + builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) + return nil unless builder.present? + opts = {} + opts[:reset] = params[:reset] + builder.build(opts, params) + end + end + + def wizard_page_title + wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') + end + + def wizard_theme_id + wizard ? wizard.theme_id : nil + end + + def wizard_theme_lookup(name) + Theme.lookup_field(wizard_theme_id, view_context.mobile_view? ? :mobile : :desktop, name) + end + + def wizard_theme_translations_lookup + Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + end + + def preload_wizard_json + return if request.xhr? || request.format.json? + return if request.method != "GET" + + store_preloaded("siteSettings", SiteSetting.client_settings_json) + end + + def store_preloaded(key, json) + @preloaded ||= {} + @preloaded[key] = json.gsub(" (app = CustomWizard.create({ rootElement: "#ember-testing" }))); - - if (!started) { - wizardInitializer.initialize(app); - stepInitializer.initialize(app); - fieldInitializer.initialize(app); - app.start(); - started = true; - } - app.setupForTesting(); - app.injectTestHelpers(); - return app; -} diff --git a/test/javascripts/plugin_helper.js b/test/javascripts/plugin_helper.js deleted file mode 100644 index 9b1558cd..00000000 --- a/test/javascripts/plugin_helper.js +++ /dev/null @@ -1,70 +0,0 @@ -// discourse-skip-module -/*global document, Logster, QUnit */ -console.log('starting test_helper') -window.Discourse = {}; -window.Wizard = {}; -Wizard.SiteSettings = {}; -Discourse.__widget_helpers = {}; -Discourse.SiteSettings = Wizard.SiteSettings; - -//= require env -//= require jquery.debug -//= require ember.debug -//= require locales/i18n -//= require locales/en -//= require route-recognizer -//= require fake_xml_http_request -//= require pretender -//= require qunit -//= require ember-qunit -//= require discourse-loader -//= require jquery.debug -//= require handlebars -//= require ember-template-compiler -//= require wizard-custom -//= require wizard-vendor -//= require_tree ./helpers -//= require_tree ./acceptance -//= require_tree ./models -//= require_tree ./components -//= require ./wizard-pretender -//= require test-shims -console.log ("end of require") -document.addEventListener("DOMContentLoaded", function () { - document.body.insertAdjacentHTML( - "afterbegin", - ` -
- - ` - ); -}); - -if (window.Logster) { - Logster.enabled = false; -} else { - window.Logster = { enabled: false }; -} -Ember.Test.adapter = window.QUnitAdapter.create(); - -/*let createPretendServer = requirejs("./wizard-pretender", null, null, false).default; - -let server; -QUnit.testStart(function () { - server = createPretendServer(); -}); - -QUnit.testDone(function () { - server.shutdown(); -});*/ - -let _testApp = requirejs("./helpers/start-app").default(); -let _buildResolver = requirejs("discourse-common/resolver").buildResolver; -window.setResolver(_buildResolver("wizard").create({ namespace: _testApp })); - -Object.keys(requirejs.entries).forEach(function (entry) { - if (/\-test/.test(entry)) { - requirejs(entry, null, null, true); - } -}); -console.log ("end of helper") diff --git a/views/layouts/qunit.html.erb b/views/layouts/qunit.html.erb new file mode 100644 index 00000000..c2f5fb5e --- /dev/null +++ b/views/layouts/qunit.html.erb @@ -0,0 +1,28 @@ + + + + Custom Wizard QUnit Test Runner + <%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %> + <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> + <%= discourse_stylesheet_link_tag :wizard_custom %> + <%= preload_script "locales/en" %> + <%= preload_script "ember_jquery" %> + <%= preload_script "wizard-vendor" %> + <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> + <%= preload_script "wizard-plugin" %> + <%= preload_script "pretty-text-bundle" %> + <%= preload_script "wizard-qunit" %> + <%= csrf_meta_tags %> + + + + <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> + + + + +
+
+ + diff --git a/views/layouts/wizard.html.erb b/views/layouts/wizard.html.erb index 16d119b7..f909ae84 100644 --- a/views/layouts/wizard.html.erb +++ b/views/layouts/wizard.html.erb @@ -11,10 +11,8 @@ <%= preload_script "locales/#{I18n.locale}" %> <%= preload_script "ember_jquery" %> <%= preload_script "wizard-vendor" %> - <%= preload_script "wizard-application" %> - <%= preload_script "wizard-custom-globals" %> - <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-plugin" %> <%= preload_script "pretty-text-bundle" %> @@ -58,5 +56,7 @@ <%= raw SvgSprite.bundle %>
+ + From 835600c05498eed68a8f80b33c282aeaee4c8644 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 12:46:16 +0100 Subject: [PATCH 185/556] Fix linting --- .../initializers/custom-wizard-edits.js.es6 | 2 +- .../components/custom-user-selector.js.es6 | 2 +- .../wizard/components/field-validators.js.es6 | 2 +- .../similar-topics-validator.js.es6 | 2 +- .../wizard/components/validator.js.es6 | 2 +- .../components/wizard-composer-editor.js.es6 | 2 +- .../wizard-composer-hyperlink.js.es6 | 2 +- .../components/wizard-date-input.js.es6 | 2 +- .../components/wizard-date-time-input.js.es6 | 2 +- .../components/wizard-field-category.js.es6 | 2 +- .../components/wizard-field-checkbox.js.es6 | 2 +- .../wizard-field-composer-preview.js.es6 | 2 +- .../components/wizard-field-composer.js.es6 | 2 +- .../components/wizard-field-date-time.js.es6 | 2 +- .../components/wizard-field-date.js.es6 | 2 +- .../components/wizard-field-dropdown.js.es6 | 2 +- .../components/wizard-field-group.js.es6 | 2 +- .../components/wizard-field-number.js.es6 | 2 +- .../wizard/components/wizard-field-tag.js.es6 | 2 +- .../components/wizard-field-text.js.es6 | 2 +- .../components/wizard-field-textarea.js.es6 | 2 +- .../components/wizard-field-time.js.es6 | 2 +- .../components/wizard-field-upload.js.es6 | 2 +- .../wizard/components/wizard-field-url.js.es6 | 2 +- .../wizard-field-user-selector.js.es6 | 2 +- .../wizard/components/wizard-field.js.es6 | 9 +- .../components/wizard-group-selector.js.es6 | 2 +- .../wizard/components/wizard-no-access.js.es6 | 8 +- .../components/wizard-similar-topics.js.es6 | 2 +- .../wizard/components/wizard-step.js.es6 | 4 +- .../components/wizard-time-input.js.es6 | 2 +- .../wizard/controllers/wizard-index.js.es6 | 14 +- .../lib/initialize/create-contexts.js.es6 | 6 +- .../lib/initialize/inject-objects.js.es6 | 31 +- .../lib/initialize/patch-components.js.es6 | 8 +- .../lib/initialize/register-files.js.es6 | 14 +- .../wizard/lib/initialize/wizard.js.es6 | 29 +- assets/javascripts/wizard/models/field.js.es6 | 2 +- assets/javascripts/wizard/models/step.js.es6 | 6 +- assets/javascripts/wizard/routes/step.js.es6 | 2 +- .../wizard/routes/wizard-index.js.es6 | 12 +- .../javascripts/wizard/routes/wizard.js.es6 | 5 +- .../wizard/tests/acceptance/field-test.js.es6 | 252 +++--- .../wizard/tests/acceptance/step-test.js.es6 | 78 +- .../tests/acceptance/wizard-test.js.es6 | 105 +-- .../wizard/tests/fixtures/categories.js.es6 | 400 +++++---- .../wizard/tests/fixtures/groups.js.es6 | 586 ++++++------ .../tests/fixtures/site-settings.js.es6 | 575 ++++++------ .../wizard/tests/fixtures/tags.js.es6 | 36 +- .../wizard/tests/fixtures/update.js.es6 | 8 +- .../wizard/tests/fixtures/user.js.es6 | 4 +- .../wizard/tests/fixtures/users.js.es6 | 20 +- .../wizard/tests/fixtures/wizard.js.es6 | 842 +++++++++--------- .../wizard/tests/helpers/acceptance.js.es6 | 18 +- .../wizard/tests/helpers/start-app.js.es6 | 12 +- .../wizard/tests/helpers/step.js.es6 | 15 +- .../wizard/tests/helpers/test.js.es6 | 4 +- .../wizard/tests/helpers/wizard.js.es6 | 18 +- .../javascripts/wizard/tests/pretender.js.es6 | 20 +- 59 files changed, 1635 insertions(+), 1564 deletions(-) diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 035fa6f2..6a027dee 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -64,7 +64,7 @@ export default { this.set("customWizardCriticalNotices", criticalNotices); } }); - } + }, }); api.modifyClass("component:d-navigation", { diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index 1698a008..c53c3bf4 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -1,6 +1,6 @@ import { default as computed, - observes + observes, } from "discourse-common/utils/decorators"; import { renderAvatar } from "discourse/helpers/user-avatar"; import userSearch from "../lib/user-search"; diff --git a/assets/javascripts/wizard/components/field-validators.js.es6 b/assets/javascripts/wizard/components/field-validators.js.es6 index 15cfc181..7284241c 100644 --- a/assets/javascripts/wizard/components/field-validators.js.es6 +++ b/assets/javascripts/wizard/components/field-validators.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/field-validators', + layoutName: "wizard/templates/components/field-validators", actions: { perform() { diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 index e5133d4f..4f722123 100644 --- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 +++ b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 @@ -10,7 +10,7 @@ import { dasherize } from "@ember/string"; export default WizardFieldValidator.extend({ classNames: ["similar-topics-validator"], - layoutName: 'wizard/templates/components/similar-topics-validator', + layoutName: "wizard/templates/components/similar-topics-validator", similarTopics: null, hasInput: notEmpty("field.value"), hasSimilarTopics: notEmpty("similarTopics"), diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index ab442d97..dafbf8c0 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -6,7 +6,7 @@ import { getToken } from "wizard/lib/ajax"; export default Component.extend({ classNames: ["validator"], classNameBindings: ["isValid", "isInvalid"], - layoutName: 'wizard/templates/components/validator', + layoutName: "wizard/templates/components/validator", validMessageKey: null, invalidMessageKey: null, isValid: null, diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 0bd003f5..4f44d439 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -13,7 +13,7 @@ import { uploadIcon } from "discourse/lib/uploads"; import { dasherize } from "@ember/string"; export default ComposerEditor.extend({ - layoutName: 'wizard/templates/components/wizard-composer-editor', + layoutName: "wizard/templates/components/wizard-composer-editor", classNameBindings: ["fieldClass"], allowUpload: true, showLink: false, diff --git a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 index c700d3ce..0eeeb176 100644 --- a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; export default Component.extend({ classNames: ["wizard-composer-hyperlink"], - layoutName: 'wizard/templates/components/wizard-composer-hyperlink', + layoutName: "wizard/templates/components/wizard-composer-hyperlink", actions: { addLink() { diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index 375b7195..da2711c7 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -3,7 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DateInput.extend({ useNativePicker: false, - layoutName: 'wizard/templates/components/wizard-date-input', + layoutName: "wizard/templates/components/wizard-date-input", @discourseComputed() placeholder() { diff --git a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 index 0fe1e68a..84a2b03e 100644 --- a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 @@ -2,7 +2,7 @@ import DateTimeInput from "discourse/components/date-time-input"; import discourseComputed from "discourse-common/utils/decorators"; export default DateTimeInput.extend({ - layoutName: 'wizard/templates/components/wizard-date-time-input', + layoutName: "wizard/templates/components/wizard-date-time-input", @discourseComputed("timeFirst", "tabindex") timeTabindex(timeFirst, tabindex) { diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index dc20538e..9f4b65ba 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -2,7 +2,7 @@ import { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; export default Ember.Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-category', + layoutName: "wizard/templates/components/wizard-field-category", didInsertElement() { const property = this.field.property || "id"; diff --git a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 index f9653cd2..6f9daba2 100644 --- a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-checkbox' + layoutName: "wizard/templates/components/wizard-field-checkbox", }); diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 index 0aee0d13..a2056a86 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 @@ -7,7 +7,7 @@ import { ajax } from "discourse/lib/ajax"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-composer-preview', + layoutName: "wizard/templates/components/wizard-field-composer-preview", @on("init") updatePreview() { diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 0ef5fe20..07829e8a 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -5,7 +5,7 @@ import { import EmberObject from "@ember/object"; export default Ember.Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-composer', + layoutName: "wizard/templates/components/wizard-field-composer", showPreview: false, classNameBindings: [ diff --git a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 index a916f18e..eee98892 100644 --- a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-date-time', + layoutName: "wizard/templates/components/wizard-field-date-time", @observes("dateTime") setValue() { diff --git a/assets/javascripts/wizard/components/wizard-field-date.js.es6 b/assets/javascripts/wizard/components/wizard-field-date.js.es6 index a06d582a..df35638c 100644 --- a/assets/javascripts/wizard/components/wizard-field-date.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-date', + layoutName: "wizard/templates/components/wizard-field-date", @observes("date") setValue() { diff --git a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 index 4b8b7e63..e6b08102 100644 --- a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-dropdown', + layoutName: "wizard/templates/components/wizard-field-dropdown", keyPress(e) { e.stopPropagation(); diff --git a/assets/javascripts/wizard/components/wizard-field-group.js.es6 b/assets/javascripts/wizard/components/wizard-field-group.js.es6 index 93538071..65a19719 100644 --- a/assets/javascripts/wizard/components/wizard-field-group.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-group.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-group' + layoutName: "wizard/templates/components/wizard-field-group", }); diff --git a/assets/javascripts/wizard/components/wizard-field-number.js.es6 b/assets/javascripts/wizard/components/wizard-field-number.js.es6 index e7c4d77f..14e1bfcd 100644 --- a/assets/javascripts/wizard/components/wizard-field-number.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-number.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-number' + layoutName: "wizard/templates/components/wizard-field-number", }); diff --git a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 index 45343522..473bba08 100644 --- a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-tag' + layoutName: "wizard/templates/components/wizard-field-tag", }); diff --git a/assets/javascripts/wizard/components/wizard-field-text.js.es6 b/assets/javascripts/wizard/components/wizard-field-text.js.es6 index f9d5b056..d9e7cca8 100644 --- a/assets/javascripts/wizard/components/wizard-field-text.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-text.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-text', + layoutName: "wizard/templates/components/wizard-field-text", keyPress(e) { e.stopPropagation(); diff --git a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 index 54865d3c..e59a1707 100644 --- a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-textarea', + layoutName: "wizard/templates/components/wizard-field-textarea", keyPress(e) { e.stopPropagation(); diff --git a/assets/javascripts/wizard/components/wizard-field-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-time.js.es6 index bf954ec4..a2f2a10d 100644 --- a/assets/javascripts/wizard/components/wizard-field-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-time.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-time', + layoutName: "wizard/templates/components/wizard-field-time", @observes("time") setValue() { diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 index edadb4f0..4774e942 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 @@ -3,7 +3,7 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend(UppyUploadMixin, { - layoutName: 'wizard/templates/components/wizard-field-upload', + layoutName: "wizard/templates/components/wizard-field-upload", classNames: ["wizard-field-upload"], classNameBindings: ["isImage"], uploading: false, diff --git a/assets/javascripts/wizard/components/wizard-field-url.js.es6 b/assets/javascripts/wizard/components/wizard-field-url.js.es6 index 411b5fe9..96c10cc2 100644 --- a/assets/javascripts/wizard/components/wizard-field-url.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-url.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-url' + layoutName: "wizard/templates/components/wizard-field-url", }); diff --git a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 index c2a32f44..7cf5b446 100644 --- a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-user-selector' + layoutName: "wizard/templates/components/wizard-field-user-selector", }); diff --git a/assets/javascripts/wizard/components/wizard-field.js.es6 b/assets/javascripts/wizard/components/wizard-field.js.es6 index 372ee182..493d7676 100644 --- a/assets/javascripts/wizard/components/wizard-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -4,8 +4,13 @@ import discourseComputed from "discourse-common/utils/decorators"; import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field', - classNameBindings: [":wizard-field", "typeClasses", "field.invalid", "field.id"], + layoutName: "wizard/templates/components/wizard-field", + classNameBindings: [ + ":wizard-field", + "typeClasses", + "field.invalid", + "field.id", + ], @discourseComputed("field.type", "field.id") typeClasses: (type, id) => diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 index b7523f9a..4ff56ec9 100644 --- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 @@ -3,7 +3,7 @@ import { computed } from "@ember/object"; import { makeArray } from "discourse-common/lib/helpers"; export default ComboBox.extend({ - layoutName: 'wizard/templates/components/wizard-group-selector', + layoutName: "wizard/templates/components/wizard-group-selector", content: computed("groups.[]", "field.content.[]", function () { const whitelist = makeArray(this.field.content); return this.groups diff --git a/assets/javascripts/wizard/components/wizard-no-access.js.es6 b/assets/javascripts/wizard/components/wizard-no-access.js.es6 index 78a23aa8..492a41dc 100644 --- a/assets/javascripts/wizard/components/wizard-no-access.js.es6 +++ b/assets/javascripts/wizard/components/wizard-no-access.js.es6 @@ -4,17 +4,17 @@ import Component from "@ember/component"; import { dasherize } from "@ember/string"; export default Component.extend({ - classNameBindings: [':wizard-no-access', 'reasonClass'], - layoutName: 'wizard/templates/components/wizard-no-access', + classNameBindings: [":wizard-no-access", "reasonClass"], + layoutName: "wizard/templates/components/wizard-no-access", - @discourseComputed('reason') + @discourseComputed("reason") reasonClass(reason) { return dasherize(reason); }, @discourseComputed siteName() { - return (this.siteSettings.title || ''); + return this.siteSettings.title || ""; }, actions: { diff --git a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 index c52bfeae..6a56873e 100644 --- a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 +++ b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 @@ -4,7 +4,7 @@ import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["wizard-similar-topics"], - layoutName: 'wizard/templates/components/wizard-similar-topics', + layoutName: "wizard/templates/components/wizard-similar-topics", showTopics: true, didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 index 4518afee..18b1a255 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -12,7 +12,7 @@ import CustomWizard from "../models/wizard"; const alreadyWarned = {}; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-step', + layoutName: "wizard/templates/components/wizard-step", classNameBindings: [":wizard-step", "step.id"], saving: null, @@ -27,7 +27,7 @@ export default Component.extend({ }, @discourseComputed("step.index", "wizard.required") - showQuitButton: (index, required) => (index === 0 && !required), + showQuitButton: (index, required) => index === 0 && !required, showNextButton: not("step.final"), showDoneButton: alias("step.final"), diff --git a/assets/javascripts/wizard/components/wizard-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-time-input.js.es6 index ec121002..14b08288 100644 --- a/assets/javascripts/wizard/components/wizard-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-time-input.js.es6 @@ -1,5 +1,5 @@ import TimeInput from "discourse/components/time-input"; export default TimeInput.extend({ - layoutName: 'wizard/templates/components/wizard-time-input' + layoutName: "wizard/templates/components/wizard-time-input", }); diff --git a/assets/javascripts/wizard/controllers/wizard-index.js.es6 b/assets/javascripts/wizard/controllers/wizard-index.js.es6 index 2dfdee40..f56db02d 100644 --- a/assets/javascripts/wizard/controllers/wizard-index.js.es6 +++ b/assets/javascripts/wizard/controllers/wizard-index.js.es6 @@ -6,19 +6,19 @@ const reasons = { noWizard: "none", requiresLogin: "requires_login", notPermitted: "not_permitted", - completed: "completed" -} + completed: "completed", +}; export default Controller.extend({ - noAccess: or('noWizard', 'requiresLogin', 'notPermitted', 'completed'), + noAccess: or("noWizard", "requiresLogin", "notPermitted", "completed"), - @discourseComputed('noAccessReason') + @discourseComputed("noAccessReason") noAccessI18nKey(reason) { - return reason ? `wizard.${reasons[reason]}` : 'wizard.none'; + return reason ? `wizard.${reasons[reason]}` : "wizard.none"; }, @discourseComputed noAccessReason() { - return Object.keys(reasons).find(reason => this.get(reason)); - } + return Object.keys(reasons).find((reason) => this.get(reason)); + }, }); diff --git a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 index 022ac48e..0e637f6c 100644 --- a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 @@ -3,10 +3,10 @@ export default { const { createHelperContext } = requirejs("discourse-common/lib/helpers"); createHelperContext({ - siteSettings: container.lookup('site-settings:main'), + siteSettings: container.lookup("site-settings:main"), site: container.lookup("site:main"), session: container.lookup("session:main"), capabilities: container.lookup("capabilities:main"), }); - } -} + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 index d31efb4d..fb11c9e7 100644 --- a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -1,5 +1,5 @@ export default { - run(app, container) { + run(app) { const Store = requirejs("discourse/services/store").default; const Site = requirejs( "discourse/plugins/discourse-custom-wizard/wizard/models/site" @@ -7,7 +7,9 @@ export default { const Session = requirejs("discourse/models/session").default; const RestAdapter = requirejs("discourse/adapters/rest").default; const messageBus = requirejs("message-bus-client").default; - const sniffCapabilites = requirejs("discourse/pre-initializers/sniff-capabilities").default; + const sniffCapabilites = requirejs( + "discourse/pre-initializers/sniff-capabilities" + ).default; const site = Site.current(); const session = Session.current(); @@ -17,26 +19,35 @@ export default { ["site:main", site, false], ["session:main", session, false], ["service:store", Store, true], - ["adapter:rest", RestAdapter, true] + ["adapter:rest", RestAdapter, true], ]; - registrations.forEach(registration => { + registrations.forEach((registration) => { if (!app.hasRegistration(registration[0])) { - app.register(registration[0], registration[1], { instantiate: registration[2] }); + app.register(registration[0], registration[1], { + instantiate: registration[2], + }); } }); - const targets = ["controller", "component", "route", "model", "adapter", "mixin"]; + const targets = [ + "controller", + "component", + "route", + "model", + "adapter", + "mixin", + ]; const injections = [ ["siteSettings", "site-settings:main"], ["messageBus", "message-bus:main"], ["site", "site:main"], ["session", "session:main"], ["store", "service:store"], - ["appEvents", "service:app-events"] + ["appEvents", "service:app-events"], ]; - injections.forEach(injection => { + injections.forEach((injection) => { targets.forEach((t) => app.inject(t, injection[0], injection[1])); }); @@ -45,5 +56,5 @@ export default { } site.set("can_create_tag", false); - } -} + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 65086e53..75cc63f1 100644 --- a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,11 +1,13 @@ export default { run(app, container) { const getToken = requirejs("wizard/lib/ajax").getToken; - const isTesting = requirejs("discourse-common/config/environment").isTesting; + const isTesting = requirejs("discourse-common/config/environment") + .isTesting; - if (!isTesting) { + if (!isTesting()) { // Add a CSRF token to all AJAX requests let token = getToken(); + const session = container.lookup("session:main"); session.set("csrfToken", token); let callbacks = $.Callbacks(); $.ajaxPrefilter(callbacks.fire); @@ -140,5 +142,5 @@ export default { return resArray; }; } - } + }, }; diff --git a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 index 8d4b850e..85079270 100644 --- a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 @@ -1,10 +1,14 @@ export default { run(app, container) { - const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default; + const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") + .default; const Handlebars = requirejs("handlebars").default; - const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers; + const registerRawHelpers = requirejs( + "discourse-common/lib/raw-handlebars-helpers" + ).registerRawHelpers; const { registerHelpers } = requirejs("discourse-common/lib/helpers"); - const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins").default; + const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins") + .default; Object.keys(Ember.TEMPLATES).forEach((k) => { if (k.indexOf("select-kit") === 0) { @@ -22,5 +26,5 @@ export default { registerRawHelpers(RawHandlebars, Handlebars); registerHelpers(app); jqueryPlugins.initialize(container, app); - } -} + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 index 134257a1..0020f7a8 100644 --- a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -1,7 +1,8 @@ export default { name: "custom-wizard", initialize(app) { - const isTesting = requirejs("discourse-common/config/environment").isTesting; + const isTesting = requirejs("discourse-common/config/environment") + .isTesting; const isWizard = window.location.pathname.indexOf("/w/") > -1; if (!isWizard && !isTesting()) { @@ -9,14 +10,17 @@ export default { } const container = app.__container__; - const setDefaultOwner = requirejs("discourse-common/lib/get-owner").setDefaultOwner; + const setDefaultOwner = requirejs("discourse-common/lib/get-owner") + .setDefaultOwner; setDefaultOwner(container); if (!isTesting()) { const PreloadStore = requirejs("discourse/lib/preload-store").default; let preloaded; - const preloadedDataElement = document.getElementById("data-preloaded-wizard"); + const preloadedDataElement = document.getElementById( + "data-preloaded-wizard" + ); if (preloadedDataElement) { preloaded = JSON.parse(preloadedDataElement.dataset.preloadedWizard); } @@ -28,7 +32,8 @@ export default { app.SiteSettings = PreloadStore.get("siteSettings"); } - const setEnvironment = requirejs("discourse-common/config/environment").setEnvironment; + const setEnvironment = requirejs("discourse-common/config/environment") + .setEnvironment; const setupData = document.getElementById("data-discourse-setup").dataset; setEnvironment(setupData.environment); @@ -37,13 +42,15 @@ export default { session.set("highlightJsPath", setupData.highlightJsPath); [ - 'register-files', - 'inject-objects', - 'create-contexts', - 'patch-components' - ].forEach(fileName => { - const initializer = requirejs(`discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}`).default; + "register-files", + "inject-objects", + "create-contexts", + "patch-components", + ].forEach((fileName) => { + const initializer = requirejs( + `discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}` + ).default; initializer.run(app, container); }); - } + }, }; diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/wizard/models/field.js.es6 index 5f76074e..2b88140e 100644 --- a/assets/javascripts/wizard/models/field.js.es6 +++ b/assets/javascripts/wizard/models/field.js.es6 @@ -75,5 +75,5 @@ export default EmberObject.extend(ValidState, { this.setValid(valid); return valid; - } + }, }); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 index e18657b5..2729e993 100644 --- a/assets/javascripts/wizard/models/step.js.es6 +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -71,11 +71,7 @@ export default EmberObject.extend(ValidState, { type: "PUT", data: { fields }, }).catch((response) => { - if ( - response && - response.responseJSON && - response.responseJSON.errors - ) { + if (response && response.responseJSON && response.responseJSON.errors) { let wizardErrors = []; response.responseJSON.errors.forEach((err) => { if (err.field === wizardId) { diff --git a/assets/javascripts/wizard/routes/step.js.es6 b/assets/javascripts/wizard/routes/step.js.es6 index 2454fc95..a076951f 100644 --- a/assets/javascripts/wizard/routes/step.js.es6 +++ b/assets/javascripts/wizard/routes/step.js.es6 @@ -26,7 +26,7 @@ export default Route.extend({ }, renderTemplate() { - this.render('wizard/templates/step'); + this.render("wizard/templates/step"); }, setupController(controller, model) { diff --git a/assets/javascripts/wizard/routes/wizard-index.js.es6 b/assets/javascripts/wizard/routes/wizard-index.js.es6 index 16b1140a..264cb0a2 100644 --- a/assets/javascripts/wizard/routes/wizard-index.js.es6 +++ b/assets/javascripts/wizard/routes/wizard-index.js.es6 @@ -4,7 +4,13 @@ import Route from "@ember/routing/route"; export default Route.extend({ beforeModel() { const wizard = getCachedWizard(); - if (wizard && wizard.user && wizard.permitted && !wizard.completed && wizard.start) { + if ( + wizard && + wizard.user && + wizard.permitted && + !wizard.completed && + wizard.start + ) { this.replaceWith("step", wizard.start); } }, @@ -14,7 +20,7 @@ export default Route.extend({ }, renderTemplate() { - this.render('wizard/templates/wizard-index'); + this.render("wizard/templates/wizard-index"); }, setupController(controller, model) { @@ -39,5 +45,5 @@ export default Route.extend({ } else { controller.set("noWizard", true); } - } + }, }); diff --git a/assets/javascripts/wizard/routes/wizard.js.es6 b/assets/javascripts/wizard/routes/wizard.js.es6 index 2910ee6d..a2c34f13 100644 --- a/assets/javascripts/wizard/routes/wizard.js.es6 +++ b/assets/javascripts/wizard/routes/wizard.js.es6 @@ -1,5 +1,4 @@ import { findCustomWizard, updateCachedWizard } from "../models/wizard"; -import { ajax } from "wizard/lib/ajax"; import WizardI18n from "../lib/wizard-i18n"; import Route from "@ember/routing/route"; import { scheduleOnce } from "@ember/runloop"; @@ -20,7 +19,7 @@ export default Route.extend({ const title = WizardI18n("wizard.incomplete_submission.title", { date: moment(wizardModel.submission_last_updated_at).format( "MMMM Do YYYY" - ) + ), }); const buttons = [ @@ -49,7 +48,7 @@ export default Route.extend({ }, renderTemplate() { - this.render('wizard/templates/wizard'); + this.render("wizard/templates/wizard"); }, setupController(controller, model) { diff --git a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 index f73d1ab7..9300fc09 100644 --- a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 +++ b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 @@ -1,135 +1,173 @@ -import { - visit, - click, - fillIn, - triggerKeyEvent -} from "@ember/test-helpers"; +import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers"; import { test } from "qunit"; import { exists } from "../helpers/test"; import acceptance, { - query, count, + query, + server, visible, - server } from "../helpers/acceptance"; -import { - allFieldsWizard, - getWizard -} from "../helpers/wizard"; +import { allFieldsWizard, getWizard } from "../helpers/wizard"; import tagsJson from "../fixtures/tags"; import usersJson from "../fixtures/users"; import { response } from "../pretender"; -acceptance("Field | Fields", [ getWizard(allFieldsWizard) ], - function(hooks) { - test("Text", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); - }); +acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { + test("Text", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); + }); - test("Textarea", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.textarea-field textarea.wizard-focusable")); - }); + test("Textarea", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.textarea-field textarea.wizard-focusable") + ); + }); - test("Composer", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.composer-field .wizard-field-composer textarea")); - assert.strictEqual(count(".wizard-field.composer-field .d-editor-button-bar button"), 8); - assert.ok(visible(".wizard-btn.toggle-preview")); + test("Composer", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.composer-field .wizard-field-composer textarea") + ); + assert.strictEqual( + count(".wizard-field.composer-field .d-editor-button-bar button"), + 8 + ); + assert.ok(visible(".wizard-btn.toggle-preview")); - await fillIn(".wizard-field.composer-field .wizard-field-composer textarea", "Input in composer"); - await click(".wizard-btn.toggle-preview"); - assert.strictEqual(query('.wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p').textContent.trim(), "Input in composer"); - }); + await fillIn( + ".wizard-field.composer-field .wizard-field-composer textarea", + "Input in composer" + ); + await click(".wizard-btn.toggle-preview"); + assert.strictEqual( + query( + ".wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p" + ).textContent.trim(), + "Input in composer" + ); + }); - test("Text Only", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.text-only-field label.field-label")); - }); + test("Text Only", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.text-only-field label.field-label")); + }); - test("Date", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.date-field input.date-picker")); - await click(".wizard-field.date-field input.date-picker"); - assert.ok(visible(".wizard-field.date-field .pika-single")); - }); + test("Date", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-field input.date-picker")); + await click(".wizard-field.date-field input.date-picker"); + assert.ok(visible(".wizard-field.date-field .pika-single")); + }); - test("Time", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); - await click(".wizard-field.time-field .d-time-input .select-kit .select-kit-header"); - assert.ok(visible(".wizard-field.time-field .select-kit-collection")); - }); + test("Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); + await click( + ".wizard-field.time-field .d-time-input .select-kit .select-kit-header" + ); + assert.ok(visible(".wizard-field.time-field .select-kit-collection")); + }); - test("Date Time", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.date-time-field .d-date-time-input .select-kit")); - await click(".wizard-field.date-time-field .d-date-input input.date-picker"); - assert.ok(visible(".wizard-field.date-time-field .d-date-input .pika-single")); - await click(".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header"); - assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); - }); + test("Date Time", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.date-time-field .d-date-time-input .select-kit") + ); + await click( + ".wizard-field.date-time-field .d-date-input input.date-picker" + ); + assert.ok( + visible(".wizard-field.date-time-field .d-date-input .pika-single") + ); + await click( + ".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header" + ); + assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); + }); - test("Number", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.number-field input[type='number']")); - }); + test("Number", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.number-field input[type='number']")); + }); - test("Checkbox", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); - }); + test("Checkbox", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); + }); - test("Url", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.url-field input[type='text']")); - }); + test("Url", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.url-field input[type='text']")); + }); - test("Upload", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.upload-field label.wizard-btn-upload-file")); - assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); - }); + test("Upload", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.upload-field label.wizard-btn-upload-file") + ); + assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); + }); - test("Dropdown", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); - await click(".wizard-field.dropdown-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.dropdown-field .select-kit-collection li"), 3); - }); + test("Dropdown", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); + await click(".wizard-field.dropdown-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.dropdown-field .select-kit-collection li"), + 3 + ); + }); - test("Tag", async function (assert) { - server.get("/tags/filter/search", () => (response(200, { results: tagsJson['tags']}))); - await visit("/wizard"); - assert.ok(visible(".wizard-field.tag-field .multi-select-header")); - await click(".wizard-field.tag-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.tag-field .select-kit-collection li"), 2); - }); + test("Tag", async function (assert) { + server.get("/tags/filter/search", () => + response(200, { results: tagsJson["tags"] }) + ); + await visit("/wizard"); + assert.ok(visible(".wizard-field.tag-field .multi-select-header")); + await click(".wizard-field.tag-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.tag-field .select-kit-collection li"), + 2 + ); + }); - test("Category", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.category-field .multi-select-header")); - await click(".wizard-field.category-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.category-field .select-kit-collection li"), 5); - }); + test("Category", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.category-field .multi-select-header")); + await click(".wizard-field.category-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.category-field .select-kit-collection li"), + 5 + ); + }); - test("Group", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.group-field .single-select-header")); - await click(".wizard-field.group-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.group-field .select-kit-collection li"), 10); - }); + test("Group", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.group-field .single-select-header")); + await click(".wizard-field.group-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.group-field .select-kit-collection li"), + 10 + ); + }); - test("User", async function (assert) { - server.get("/u/search/users", () => (response(200, usersJson))); + test("User", async function (assert) { + server.get("/u/search/users", () => response(200, usersJson)); - await visit("/wizard"); - await fillIn(".wizard-field.user-selector-field input.ember-text-field", "a"); - await triggerKeyEvent(".wizard-field.user-selector-field input.ember-text-field", "keyup", "a".charCodeAt(0)); + await visit("/wizard"); + await fillIn( + ".wizard-field.user-selector-field input.ember-text-field", + "a" + ); + await triggerKeyEvent( + ".wizard-field.user-selector-field input.ember-text-field", + "keyup", + "a".charCodeAt(0) + ); - assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); - // TODO: add assertion for ac results. autocomplete does not appear in time. - }); - } -); + assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); + // TODO: add assertion for ac results. autocomplete does not appear in time. + }); +}); diff --git a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 index 731c9b76..1537f1c7 100644 --- a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 +++ b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 @@ -1,47 +1,41 @@ -import { visit, click } from "@ember/test-helpers"; +import { click, visit } from "@ember/test-helpers"; import { test } from "qunit"; import { exists } from "../helpers/test"; -import acceptance, { - query, - count, - visible -} from "../helpers/acceptance"; -import { - stepNotPermitted, - wizard, - getWizard -} from "../helpers/wizard"; -import { - saveStep, - update -} from "../helpers/step"; +import acceptance, { count, query, visible } from "../helpers/acceptance"; +import { getWizard, stepNotPermitted, wizard } from "../helpers/wizard"; +import { saveStep, update } from "../helpers/step"; -acceptance("Step | Not permitted", [ getWizard(stepNotPermitted) ], - function(hooks) { - test("Shows not permitted message", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".step-message.not-permitted")); - }); - } -); +acceptance("Step | Not permitted", [getWizard(stepNotPermitted)], function () { + test("Shows not permitted message", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".step-message.not-permitted")); + }); +}); -acceptance("Step | Step", [ getWizard(wizard), saveStep(update) ], - function(hooks) { - test("Renders the step", async function (assert) { - await visit("/wizard"); - assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); - assert.ok(visible('.wizard-step-footer .wizard-progress'), true); - assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); - }); +acceptance("Step | Step", [getWizard(wizard), saveStep(update)], function () { + test("Renders the step", async function (assert) { + await visit("/wizard"); + assert.strictEqual( + query(".wizard-step-title p").textContent.trim(), + "Text" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual(count(".wizard-step-form .wizard-field"), 6); + assert.ok(visible(".wizard-step-footer .wizard-progress"), true); + assert.ok(visible(".wizard-step-footer .wizard-buttons"), true); + }); - test("Goes to the next step", async function (assert) { - await visit("/wizard"); - assert.ok(visible('.wizard-step.step_1'), true); - await click('.wizard-btn.next'); - assert.ok(visible('.wizard-step.step_2'), true); - }); - } -); + test("Goes to the next step", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-step.step_1"), true); + await click(".wizard-btn.next"); + assert.ok(visible(".wizard-step.step_2"), true); + }); +}); diff --git a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 index e68f59ae..8fd82d11 100644 --- a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 +++ b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 @@ -1,30 +1,26 @@ import { visit } from "@ember/test-helpers"; import { test } from "qunit"; import { exists } from "../helpers/test"; -import acceptance, { - query, - count, - visible -} from "../helpers/acceptance"; +import acceptance, { count, query, visible } from "../helpers/acceptance"; import { + getWizard, + wizard, + wizardCompleted, wizardNoUser, wizardNotPermitted, - wizardCompleted, - wizard, - getWizard } from "../helpers/wizard"; -acceptance("Wizard | Not logged in", [ getWizard(wizardNoUser) ], - function(hooks) { - test("Wizard no access requires login", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".wizard-no-access.requires-login")); - }); - } -); +acceptance("Wizard | Not logged in", [getWizard(wizardNoUser)], function () { + test("Wizard no access requires login", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.requires-login")); + }); +}); -acceptance("Wizard | Not permitted", [ getWizard(wizardNotPermitted) ], - function(hooks) { +acceptance( + "Wizard | Not permitted", + [getWizard(wizardNotPermitted)], + function () { test("Wizard no access not permitted", async function (assert) { await visit("/wizard"); assert.ok(exists(".wizard-no-access.not-permitted")); @@ -32,41 +28,46 @@ acceptance("Wizard | Not permitted", [ getWizard(wizardNotPermitted) ], } ); -acceptance("Wizard | Completed", [ getWizard(wizardCompleted) ], - function(hooks) { - test("Wizard no access completed", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".wizard-no-access.completed")); - }); - } -); +acceptance("Wizard | Completed", [getWizard(wizardCompleted)], function () { + test("Wizard no access completed", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.completed")); + }); +}); -acceptance("Wizard | Wizard", [ getWizard(wizard) ], - function(hooks) { - test("Starts", async function (assert) { - await visit("/wizard"); - assert.ok(query('.wizard-column'), true); - }); +acceptance("Wizard | Wizard", [getWizard(wizard)], function () { + test("Starts", async function (assert) { + await visit("/wizard"); + assert.ok(query(".wizard-column"), true); + }); - test("Applies the body background color", async function (assert) { - await visit("/wizard"); - assert.ok($("body")[0].style.background); - }); + test("Applies the body background color", async function (assert) { + await visit("/wizard"); + assert.ok($("body")[0].style.background); + }); - test("Renders the wizard form", async function (assert) { - await visit("/wizard"); - assert.ok(visible('.wizard-column-contents .wizard-step'), true); - assert.ok(visible('.wizard-footer img'), true); - }); + test("Renders the wizard form", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-column-contents .wizard-step"), true); + assert.ok(visible(".wizard-footer img"), true); + }); - test("Renders the first step", async function (assert) { - await visit("/wizard"); - assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); - assert.ok(visible('.wizard-step-footer .wizard-progress'), true); - assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); - }); - } -); + test("Renders the first step", async function (assert) { + await visit("/wizard"); + assert.strictEqual( + query(".wizard-step-title p").textContent.trim(), + "Text" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual(count(".wizard-step-form .wizard-field"), 6); + assert.ok(visible(".wizard-step-footer .wizard-progress"), true); + assert.ok(visible(".wizard-step-footer .wizard-buttons"), true); + }); +}); diff --git a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 index e862f54a..e553f860 100644 --- a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 @@ -1,209 +1,221 @@ export default { - "categories": [ + categories: [ { - "id": 1, - "name": "Uncategorized", - "color": "0088CC", - "text_color": "FFFFFF", - "slug": "uncategorized", - "topic_count": 1, - "post_count": 1, - "position": 0, - "description": "Topics that don't need a category, or don't fit into any other existing category.", - "description_text": "Topics that don't need a category, or don't fit into any other existing category.", - "description_excerpt": "Topics that don't need a category, or don't fit into any other existing category.", - "topic_url": "/t/", - "read_restricted": false, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 1, + name: "Uncategorized", + color: "0088CC", + text_color: "FFFFFF", + slug: "uncategorized", + topic_count: 1, + post_count: 1, + position: 0, + description: + "Topics that don't need a category, or don't fit into any other existing category.", + description_text: + "Topics that don't need a category, or don't fit into any other existing category.", + description_excerpt: + "Topics that don't need a category, or don't fit into any other existing category.", + topic_url: "/t/", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 2, - "name": "Site Feedback", - "color": "808281", - "text_color": "FFFFFF", - "slug": "site-feedback", - "topic_count": 20, - "post_count": 21, - "position": 1, - "description": "

Discussion about this site, its organization, how it works, and how we can improve it.

", - "description_text": "Discussion about this site, its organization, how it works, and how we can improve it.", - "description_excerpt": "Discussion about this site, its organization, how it works, and how we can improve it.", - "topic_url": "/t/about-the-site-feedback-category/1", - "read_restricted": false, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 2, + name: "Site Feedback", + color: "808281", + text_color: "FFFFFF", + slug: "site-feedback", + topic_count: 20, + post_count: 21, + position: 1, + description: + "

Discussion about this site, its organization, how it works, and how we can improve it.

", + description_text: + "Discussion about this site, its organization, how it works, and how we can improve it.", + description_excerpt: + "Discussion about this site, its organization, how it works, and how we can improve it.", + topic_url: "/t/about-the-site-feedback-category/1", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 3, - "name": "Staff", - "color": "E45735", - "text_color": "FFFFFF", - "slug": "staff", - "topic_count": 4, - "post_count": 7, - "position": 2, - "description": "

Private category for staff discussions. Topics are only visible to admins and moderators.

", - "description_text": "Private category for staff discussions. Topics are only visible to admins and moderators.", - "description_excerpt": "Private category for staff discussions. Topics are only visible to admins and moderators.", - "topic_url": "/t/about-the-staff-category/2", - "read_restricted": true, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 3, + name: "Staff", + color: "E45735", + text_color: "FFFFFF", + slug: "staff", + topic_count: 4, + post_count: 7, + position: 2, + description: + "

Private category for staff discussions. Topics are only visible to admins and moderators.

", + description_text: + "Private category for staff discussions. Topics are only visible to admins and moderators.", + description_excerpt: + "Private category for staff discussions. Topics are only visible to admins and moderators.", + topic_url: "/t/about-the-staff-category/2", + read_restricted: true, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 4, - "name": "Lounge", - "color": "A461EF", - "text_color": "652D90", - "slug": "lounge", - "topic_count": 1, - "post_count": 1, - "position": 3, - "description": "

A category exclusive to members with trust level 3 and higher.

", - "description_text": "A category exclusive to members with trust level 3 and higher.", - "description_excerpt": "A category exclusive to members with trust level 3 and higher.", - "topic_url": "/t/about-the-lounge-category/3", - "read_restricted": true, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 4, + name: "Lounge", + color: "A461EF", + text_color: "652D90", + slug: "lounge", + topic_count: 1, + post_count: 1, + position: 3, + description: + "

A category exclusive to members with trust level 3 and higher.

", + description_text: + "A category exclusive to members with trust level 3 and higher.", + description_excerpt: + "A category exclusive to members with trust level 3 and higher.", + topic_url: "/t/about-the-lounge-category/3", + read_restricted: true, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 5, - "name": "Custom Categories", - "color": "0088CC", - "text_color": "FFFFFF", - "slug": "custom-category", - "topic_count": 0, - "post_count": 0, - "position": 10, - "description": "Description of custom category", - "description_text": "Description of custom category", - "description_excerpt": "Description of custom category", - "topic_url": "/t/about-the-custom-category/5", - "read_restricted": false, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 5, + name: "Custom Categories", + color: "0088CC", + text_color: "FFFFFF", + slug: "custom-category", + topic_count: 0, + post_count: 0, + position: 10, + description: "Description of custom category", + description_text: "Description of custom category", + description_excerpt: "Description of custom category", + topic_url: "/t/about-the-custom-category/5", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true - } - ] -} + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 index 16f8150d..a770cf58 100644 --- a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 @@ -1,313 +1,313 @@ export default { - "groups": [ + groups: [ { - "id": 1, - "automatic": true, - "name": "admins", - "display_name": "admins", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 1, + automatic: true, + name: "admins", + display_name: "admins", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 0, - "automatic": true, - "name": "everyone", - "display_name": "everyone", - "user_count": 0, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 3, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 0, + automatic: true, + name: "everyone", + display_name: "everyone", + user_count: 0, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 3, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 15, - "automatic": false, - "name": "custom_group", - "user_count": 1, - "mentionable_level": 1, - "messageable_level": 2, - "visibility_level": 3, - "primary_group": false, - "title": "Custom Group", - "grant_trust_level": 3, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": "I am prefilled", - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 99, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 15, + automatic: false, + name: "custom_group", + user_count: 1, + mentionable_level: 1, + messageable_level: 2, + visibility_level: 3, + primary_group: false, + title: "Custom Group", + grant_trust_level: 3, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: "I am prefilled", + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 99, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 2, - "automatic": true, - "name": "moderators", - "display_name": "moderators", - "user_count": 0, - "mentionable_level": 0, - "messageable_level": 99, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 2, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 2, + automatic: true, + name: "moderators", + display_name: "moderators", + user_count: 0, + mentionable_level: 0, + messageable_level: 99, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 2, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 3, - "automatic": true, - "name": "staff", - "display_name": "staff", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 3, + automatic: true, + name: "staff", + display_name: "staff", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 10, - "automatic": true, - "name": "trust_level_0", - "display_name": "trust_level_0", - "user_count": 2, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 10, + automatic: true, + name: "trust_level_0", + display_name: "trust_level_0", + user_count: 2, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 11, - "automatic": true, - "name": "trust_level_1", - "display_name": "trust_level_1", - "user_count": 2, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 11, + automatic: true, + name: "trust_level_1", + display_name: "trust_level_1", + user_count: 2, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 12, - "automatic": true, - "name": "trust_level_2", - "display_name": "trust_level_2", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 12, + automatic: true, + name: "trust_level_2", + display_name: "trust_level_2", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 13, - "automatic": true, - "name": "trust_level_3", - "display_name": "trust_level_3", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 13, + automatic: true, + name: "trust_level_3", + display_name: "trust_level_3", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 14, - "automatic": true, - "name": "trust_level_4", - "display_name": "trust_level_4", - "user_count": 0, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false - } - ] -} + id: 14, + automatic: true, + name: "trust_level_4", + display_name: "trust_level_4", + user_count: 0, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 index f71ace8e..7568cd49 100644 --- a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 @@ -1,283 +1,294 @@ export default { - "default_locale": "en", - "title": "Discourse", - "short_site_description": "", - "exclude_rel_nofollow_domains": "", - "logo": "/images/discourse-logo-sketch.png", - "logo_small": "/images/discourse-logo-sketch-small.png", - "digest_logo": "", - "mobile_logo": "", - "logo_dark": "", - "logo_small_dark": "", - "mobile_logo_dark": "", - "large_icon": "", - "favicon": "", - "apple_touch_icon": "", - "display_local_time_in_user_card": false, - "allow_user_locale": false, - "set_locale_from_accept_language_header": false, - "support_mixed_text_direction": false, - "suggested_topics": 5, - "ga_universal_tracking_code": "", - "ga_universal_domain_name": "auto", - "gtm_container_id": "", - "top_menu": "categories|latest", - "post_menu": "read|like|share|flag|edit|bookmark|delete|admin|reply", - "post_menu_hidden_items": "flag|bookmark|edit|delete|admin", - "share_links": "twitter|facebook|email", - "share_quote_visibility": "anonymous", - "share_quote_buttons": "twitter|email", - "desktop_category_page_style": "categories_and_latest_topics", - "category_colors": "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", - "category_style": "bullet", - "max_category_nesting": 2, - "enable_mobile_theme": true, - "enable_direct_s3_uploads": false, - "enable_upload_debug_mode": false, - "default_dark_mode_color_scheme_id": 1, - "relative_date_duration": 30, - "fixed_category_positions": false, - "fixed_category_positions_on_create": false, - "enable_badges": true, - "enable_badge_sql": true, - "max_favorite_badges": 2, - "enable_whispers": false, - "enable_bookmarks_with_reminders": true, - "push_notifications_prompt": true, - "vapid_public_key_bytes": "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", - "invite_only": false, - "login_required": false, - "must_approve_users": false, - "enable_local_logins": true, - "enable_local_logins_via_email": true, - "allow_new_registrations": true, - "enable_signup_cta": true, - "facebook_app_id": "", - "auth_skip_create_confirm": false, - "auth_overrides_email": false, - "enable_discourse_connect": true, - "discourse_connect_overrides_avatar": false, - "hide_email_address_taken": false, - "min_username_length": 3, - "max_username_length": 20, - "unicode_usernames": false, - "min_password_length": 10, - "min_admin_password_length": 15, - "email_editable": true, - "logout_redirect": "", - "full_name_required": false, - "enable_names": true, - "invite_expiry_days": 90, - "invites_per_page": 40, - "delete_user_max_post_age": 60, - "delete_all_posts_max": 15, - "prioritize_username_in_ux": true, - "enable_user_directory": true, - "allow_anonymous_posting": false, - "anonymous_posting_min_trust_level": 1, - "allow_users_to_hide_profile": true, - "hide_user_profiles_from_public": false, - "allow_featured_topic_on_user_profiles": true, - "hide_suspension_reasons": false, - "ignored_users_count_message_threshold": 5, - "ignored_users_message_gap_days": 365, - "user_selected_primary_groups": false, - "gravatar_name": "Gravatar", - "gravatar_base_url": "www.gravatar.com", - "gravatar_login_url": "/emails", - "enable_group_directory": true, - "enable_category_group_moderation": false, - "min_post_length": 20, - "min_first_post_length": 20, - "min_personal_message_post_length": 10, - "max_post_length": 32000, - "topic_featured_link_enabled": true, - "min_topic_views_for_delete_confirm": 5000, - "min_topic_title_length": 15, - "max_topic_title_length": 255, - "enable_filtered_replies_view": false, - "min_personal_message_title_length": 2, - "allow_uncategorized_topics": true, - "min_title_similar_length": 10, - "enable_personal_messages": true, - "edit_history_visible_to_public": true, - "delete_removed_posts_after": 24, - "traditional_markdown_linebreaks": false, - "enable_markdown_typographer": true, - "enable_markdown_linkify": true, - "markdown_linkify_tlds": "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", - "markdown_typographer_quotation_marks": "“|”|‘|’", - "enable_rich_text_paste": true, - "suppress_reply_directly_below": true, - "suppress_reply_directly_above": true, - "max_reply_history": 1, - "enable_mentions": true, - "here_mention": "here", - "newuser_max_embedded_media": 1, - "newuser_max_attachments": 0, - "show_pinned_excerpt_mobile": true, - "show_pinned_excerpt_desktop": true, - "display_name_on_posts": false, - "show_time_gap_days": 7, - "short_progress_text_threshold": 10000, - "default_code_lang": "auto", - "autohighlight_all_code": false, - "highlighted_languages": "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", - "show_copy_button_on_codeblocks": false, - "enable_emoji": true, - "enable_emoji_shortcuts": true, - "emoji_set": "twitter", - "emoji_autocomplete_min_chars": 0, - "enable_inline_emoji_translation": false, - "code_formatting_style": "code-fences", - "allowed_href_schemes": "", - "watched_words_regular_expressions": false, - "enable_diffhtml_preview": false, - "enable_fast_edit": true, - "old_post_notice_days": 14, - "blur_tl0_flagged_posts_media": true, - "email_time_window_mins": 10, - "disable_digest_emails": false, - "email_in": false, - "enable_imap": false, - "enable_smtp": false, - "disable_emails": "no", - "bounce_score_threshold": 4, - "enable_secondary_emails": true, - "max_image_size_kb": 4096, - "max_attachment_size_kb": 4096, - "authorized_extensions": "jpg|jpeg|png|gif|heic|heif|webp", - "authorized_extensions_for_staff": "", - "max_image_width": 690, - "max_image_height": 500, - "prevent_anons_from_downloading_files": false, - "secure_media": false, - "enable_s3_uploads": false, - "allow_profile_backgrounds": true, - "allow_uploaded_avatars": "0", - "default_avatars": "", - "external_system_avatars_enabled": true, - "external_system_avatars_url": "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", - "external_emoji_url": "", - "selectable_avatars_mode": "disabled", - "selectable_avatars": "", - "allow_staff_to_upload_any_file_in_pm": true, - "simultaneous_uploads": 5, - "composer_media_optimization_image_enabled": true, - "composer_media_optimization_image_bytes_optimization_threshold": 524288, - "composer_media_optimization_image_resize_dimensions_threshold": 1920, - "composer_media_optimization_image_resize_width_target": 1920, - "composer_media_optimization_image_resize_pre_multiply": false, - "composer_media_optimization_image_resize_linear_rgb": false, - "composer_media_optimization_image_encode_quality": 75, - "composer_media_optimization_debug_mode": false, - "min_trust_level_to_allow_profile_background": 0, - "min_trust_level_to_allow_user_card_background": 0, - "min_trust_level_to_allow_ignore": 2, - "tl1_requires_read_posts": 30, - "tl3_links_no_follow": false, - "enforce_second_factor": "no", - "moderators_change_post_ownership": false, - "moderators_view_emails": false, - "use_admin_ip_allowlist": false, - "allowed_iframes": "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", - "can_permanently_delete": false, - "max_oneboxes_per_post": 50, - "reviewable_claiming": "disabled", - "reviewable_default_topics": false, - "reviewable_default_visibility": "low", - "alert_admins_if_errors_per_minute": 0, - "alert_admins_if_errors_per_hour": 0, - "max_prints_per_hour_per_user": 5, - "invite_link_max_redemptions_limit": 5000, - "invite_link_max_redemptions_limit_users": 10, - "enable_long_polling": true, - "enable_chunked_encoding": true, - "long_polling_base_url": "/", - "background_polling_interval": 60000, - "polling_interval": 3000, - "anon_polling_interval": 25000, - "flush_timings_secs": 60, - "verbose_localization": false, - "max_new_topics": 500, - "enable_safe_mode": true, - "tos_url": "", - "privacy_policy_url": "", - "faq_url": "", - "enable_backups": true, - "backup_location": "local", - "maximum_backups": 5, - "use_pg_headlines_for_excerpt": false, - "min_search_term_length": 3, - "log_search_queries": true, - "version_checks": true, - "suppress_uncategorized_badge": true, - "header_dropdown_category_count": 8, - "slug_generation_method": "ascii", - "summary_timeline_button": false, - "topic_views_heat_low": 1000, - "topic_views_heat_medium": 2000, - "topic_views_heat_high": 3500, - "topic_post_like_heat_low": 0.5, - "topic_post_like_heat_medium": 1, - "topic_post_like_heat_high": 2, - "history_hours_low": 12, - "history_hours_medium": 24, - "history_hours_high": 48, - "cold_age_days_low": 14, - "cold_age_days_medium": 90, - "cold_age_days_high": 180, - "global_notice": "", - "show_create_topics_notice": true, - "bootstrap_mode_min_users": 50, - "bootstrap_mode_enabled": true, - "automatically_unpin_topics": true, - "read_time_word_count": 500, - "topic_page_title_includes_category": true, - "svg_icon_subset": "", - "allow_bulk_invite": true, - "disable_mailing_list_mode": true, - "default_topics_automatic_unpin": true, - "mute_all_categories_by_default": false, - "tagging_enabled": true, - "tag_style": "simple", - "max_tags_per_topic": 5, - "max_tag_length": 20, - "min_trust_level_to_tag_topics": "0", - "max_tag_search_results": 5, - "max_tags_in_filter_list": 30, - "tags_sort_alphabetically": false, - "tags_listed_by_group": false, - "suppress_overlapping_tags_in_list": false, - "remove_muted_tags_from_latest": "always", - "force_lowercase_tags": true, - "dashboard_hidden_reports": "", - "dashboard_visible_tabs": "moderation|security|reports", - "dashboard_general_tab_activity_metrics": "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", - "discourse_narrative_bot_enabled": true, - "details_enabled": true, - "custom_wizard_enabled": true, - "wizard_redirect_exclude_paths": "admin", - "wizard_recognised_image_upload_formats": "jpg|jpeg|png|gif", - "wizard_important_notices_on_dashboard": true, - "discourse_local_dates_email_format": "YYYY-MM-DDTHH:mm:ss[Z]", - "discourse_local_dates_enabled": true, - "discourse_local_dates_default_formats": "LLL|LTS|LL|LLLL", - "discourse_local_dates_default_timezones": "Europe/Paris|America/Los_Angeles", - "poll_enabled": true, - "poll_maximum_options": 20, - "poll_minimum_trust_level_to_create": 1, - "poll_groupable_user_fields": "", - "poll_export_data_explorer_query_id": -16, - "presence_enabled": true, - "presence_max_users_shown": 5, - "available_locales": "[{\"name\":\"اللغة العربية\",\"value\":\"ar\"},{\"name\":\"беларуская мова\",\"value\":\"be\"},{\"name\":\"български език\",\"value\":\"bg\"},{\"name\":\"bosanski jezik\",\"value\":\"bs_BA\"},{\"name\":\"català\",\"value\":\"ca\"},{\"name\":\"čeština\",\"value\":\"cs\"},{\"name\":\"dansk\",\"value\":\"da\"},{\"name\":\"Deutsch\",\"value\":\"de\"},{\"name\":\"ελληνικά\",\"value\":\"el\"},{\"name\":\"English (US)\",\"value\":\"en\"},{\"name\":\"English (UK)\",\"value\":\"en_GB\"},{\"name\":\"Español\",\"value\":\"es\"},{\"name\":\"eesti\",\"value\":\"et\"},{\"name\":\"فارسی\",\"value\":\"fa_IR\"},{\"name\":\"suomi\",\"value\":\"fi\"},{\"name\":\"Français\",\"value\":\"fr\"},{\"name\":\"galego\",\"value\":\"gl\"},{\"name\":\"עברית\",\"value\":\"he\"},{\"name\":\"magyar\",\"value\":\"hu\"},{\"name\":\"Հայերեն\",\"value\":\"hy\"},{\"name\":\"Indonesian\",\"value\":\"id\"},{\"name\":\"Italiano\",\"value\":\"it\"},{\"name\":\"日本語\",\"value\":\"ja\"},{\"name\":\"한국어\",\"value\":\"ko\"},{\"name\":\"lietuvių kalba\",\"value\":\"lt\"},{\"name\":\"latviešu valoda\",\"value\":\"lv\"},{\"name\":\"Norsk bokmål\",\"value\":\"nb_NO\"},{\"name\":\"Nederlands\",\"value\":\"nl\"},{\"name\":\"polski\",\"value\":\"pl_PL\"},{\"name\":\"Português\",\"value\":\"pt\"},{\"name\":\"Português (BR)\",\"value\":\"pt_BR\"},{\"name\":\"limba română\",\"value\":\"ro\"},{\"name\":\"Русский\",\"value\":\"ru\"},{\"name\":\"slovenčina\",\"value\":\"sk\"},{\"name\":\"slovenščina\",\"value\":\"sl\"},{\"name\":\"Shqip\",\"value\":\"sq\"},{\"name\":\"српски језик\",\"value\":\"sr\"},{\"name\":\"svenska\",\"value\":\"sv\"},{\"name\":\"Kiswahili\",\"value\":\"sw\"},{\"name\":\"తెలుగు\",\"value\":\"te\"},{\"name\":\"ไทย\",\"value\":\"th\"},{\"name\":\"Türkçe\",\"value\":\"tr_TR\"},{\"name\":\"українська мова\",\"value\":\"uk\"},{\"name\":\"اردو\",\"value\":\"ur\"},{\"name\":\"Việt Nam\",\"value\":\"vi\"},{\"name\":\"简体中文\",\"value\":\"zh_CN\"},{\"name\":\"繁體中文\",\"value\":\"zh_TW\"}]", - "require_invite_code": false, - "site_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", - "site_logo_small_url": "http://localhost:3000/images/discourse-logo-sketch-small.png", - "site_mobile_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", - "site_favicon_url": "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", - "site_logo_dark_url": "", - "site_logo_small_dark_url": "", - "site_mobile_logo_dark_url": "" -} + default_locale: "en", + title: "Discourse", + short_site_description: "", + exclude_rel_nofollow_domains: "", + logo: "/images/discourse-logo-sketch.png", + logo_small: "/images/discourse-logo-sketch-small.png", + digest_logo: "", + mobile_logo: "", + logo_dark: "", + logo_small_dark: "", + mobile_logo_dark: "", + large_icon: "", + favicon: "", + apple_touch_icon: "", + display_local_time_in_user_card: false, + allow_user_locale: false, + set_locale_from_accept_language_header: false, + support_mixed_text_direction: false, + suggested_topics: 5, + ga_universal_tracking_code: "", + ga_universal_domain_name: "auto", + gtm_container_id: "", + top_menu: "categories|latest", + post_menu: "read|like|share|flag|edit|bookmark|delete|admin|reply", + post_menu_hidden_items: "flag|bookmark|edit|delete|admin", + share_links: "twitter|facebook|email", + share_quote_visibility: "anonymous", + share_quote_buttons: "twitter|email", + desktop_category_page_style: "categories_and_latest_topics", + category_colors: + "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", + category_style: "bullet", + max_category_nesting: 2, + enable_mobile_theme: true, + enable_direct_s3_uploads: false, + enable_upload_debug_mode: false, + default_dark_mode_color_scheme_id: 1, + relative_date_duration: 30, + fixed_category_positions: false, + fixed_category_positions_on_create: false, + enable_badges: true, + enable_badge_sql: true, + max_favorite_badges: 2, + enable_whispers: false, + enable_bookmarks_with_reminders: true, + push_notifications_prompt: true, + vapid_public_key_bytes: + "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", + invite_only: false, + login_required: false, + must_approve_users: false, + enable_local_logins: true, + enable_local_logins_via_email: true, + allow_new_registrations: true, + enable_signup_cta: true, + facebook_app_id: "", + auth_skip_create_confirm: false, + auth_overrides_email: false, + enable_discourse_connect: true, + discourse_connect_overrides_avatar: false, + hide_email_address_taken: false, + min_username_length: 3, + max_username_length: 20, + unicode_usernames: false, + min_password_length: 10, + min_admin_password_length: 15, + email_editable: true, + logout_redirect: "", + full_name_required: false, + enable_names: true, + invite_expiry_days: 90, + invites_per_page: 40, + delete_user_max_post_age: 60, + delete_all_posts_max: 15, + prioritize_username_in_ux: true, + enable_user_directory: true, + allow_anonymous_posting: false, + anonymous_posting_min_trust_level: 1, + allow_users_to_hide_profile: true, + hide_user_profiles_from_public: false, + allow_featured_topic_on_user_profiles: true, + hide_suspension_reasons: false, + ignored_users_count_message_threshold: 5, + ignored_users_message_gap_days: 365, + user_selected_primary_groups: false, + gravatar_name: "Gravatar", + gravatar_base_url: "www.gravatar.com", + gravatar_login_url: "/emails", + enable_group_directory: true, + enable_category_group_moderation: false, + min_post_length: 20, + min_first_post_length: 20, + min_personal_message_post_length: 10, + max_post_length: 32000, + topic_featured_link_enabled: true, + min_topic_views_for_delete_confirm: 5000, + min_topic_title_length: 15, + max_topic_title_length: 255, + enable_filtered_replies_view: false, + min_personal_message_title_length: 2, + allow_uncategorized_topics: true, + min_title_similar_length: 10, + enable_personal_messages: true, + edit_history_visible_to_public: true, + delete_removed_posts_after: 24, + traditional_markdown_linebreaks: false, + enable_markdown_typographer: true, + enable_markdown_linkify: true, + markdown_linkify_tlds: + "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", + markdown_typographer_quotation_marks: "“|”|‘|’", + enable_rich_text_paste: true, + suppress_reply_directly_below: true, + suppress_reply_directly_above: true, + max_reply_history: 1, + enable_mentions: true, + here_mention: "here", + newuser_max_embedded_media: 1, + newuser_max_attachments: 0, + show_pinned_excerpt_mobile: true, + show_pinned_excerpt_desktop: true, + display_name_on_posts: false, + show_time_gap_days: 7, + short_progress_text_threshold: 10000, + default_code_lang: "auto", + autohighlight_all_code: false, + highlighted_languages: + "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", + show_copy_button_on_codeblocks: false, + enable_emoji: true, + enable_emoji_shortcuts: true, + emoji_set: "twitter", + emoji_autocomplete_min_chars: 0, + enable_inline_emoji_translation: false, + code_formatting_style: "code-fences", + allowed_href_schemes: "", + watched_words_regular_expressions: false, + enable_diffhtml_preview: false, + enable_fast_edit: true, + old_post_notice_days: 14, + blur_tl0_flagged_posts_media: true, + email_time_window_mins: 10, + disable_digest_emails: false, + email_in: false, + enable_imap: false, + enable_smtp: false, + disable_emails: "no", + bounce_score_threshold: 4, + enable_secondary_emails: true, + max_image_size_kb: 4096, + max_attachment_size_kb: 4096, + authorized_extensions: "jpg|jpeg|png|gif|heic|heif|webp", + authorized_extensions_for_staff: "", + max_image_width: 690, + max_image_height: 500, + prevent_anons_from_downloading_files: false, + secure_media: false, + enable_s3_uploads: false, + allow_profile_backgrounds: true, + allow_uploaded_avatars: "0", + default_avatars: "", + external_system_avatars_enabled: true, + external_system_avatars_url: + "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", + external_emoji_url: "", + selectable_avatars_mode: "disabled", + selectable_avatars: "", + allow_staff_to_upload_any_file_in_pm: true, + simultaneous_uploads: 5, + composer_media_optimization_image_enabled: true, + composer_media_optimization_image_bytes_optimization_threshold: 524288, + composer_media_optimization_image_resize_dimensions_threshold: 1920, + composer_media_optimization_image_resize_width_target: 1920, + composer_media_optimization_image_resize_pre_multiply: false, + composer_media_optimization_image_resize_linear_rgb: false, + composer_media_optimization_image_encode_quality: 75, + composer_media_optimization_debug_mode: false, + min_trust_level_to_allow_profile_background: 0, + min_trust_level_to_allow_user_card_background: 0, + min_trust_level_to_allow_ignore: 2, + tl1_requires_read_posts: 30, + tl3_links_no_follow: false, + enforce_second_factor: "no", + moderators_change_post_ownership: false, + moderators_view_emails: false, + use_admin_ip_allowlist: false, + allowed_iframes: + "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", + can_permanently_delete: false, + max_oneboxes_per_post: 50, + reviewable_claiming: "disabled", + reviewable_default_topics: false, + reviewable_default_visibility: "low", + alert_admins_if_errors_per_minute: 0, + alert_admins_if_errors_per_hour: 0, + max_prints_per_hour_per_user: 5, + invite_link_max_redemptions_limit: 5000, + invite_link_max_redemptions_limit_users: 10, + enable_long_polling: true, + enable_chunked_encoding: true, + long_polling_base_url: "/", + background_polling_interval: 60000, + polling_interval: 3000, + anon_polling_interval: 25000, + flush_timings_secs: 60, + verbose_localization: false, + max_new_topics: 500, + enable_safe_mode: true, + tos_url: "", + privacy_policy_url: "", + faq_url: "", + enable_backups: true, + backup_location: "local", + maximum_backups: 5, + use_pg_headlines_for_excerpt: false, + min_search_term_length: 3, + log_search_queries: true, + version_checks: true, + suppress_uncategorized_badge: true, + header_dropdown_category_count: 8, + slug_generation_method: "ascii", + summary_timeline_button: false, + topic_views_heat_low: 1000, + topic_views_heat_medium: 2000, + topic_views_heat_high: 3500, + topic_post_like_heat_low: 0.5, + topic_post_like_heat_medium: 1, + topic_post_like_heat_high: 2, + history_hours_low: 12, + history_hours_medium: 24, + history_hours_high: 48, + cold_age_days_low: 14, + cold_age_days_medium: 90, + cold_age_days_high: 180, + global_notice: "", + show_create_topics_notice: true, + bootstrap_mode_min_users: 50, + bootstrap_mode_enabled: true, + automatically_unpin_topics: true, + read_time_word_count: 500, + topic_page_title_includes_category: true, + svg_icon_subset: "", + allow_bulk_invite: true, + disable_mailing_list_mode: true, + default_topics_automatic_unpin: true, + mute_all_categories_by_default: false, + tagging_enabled: true, + tag_style: "simple", + max_tags_per_topic: 5, + max_tag_length: 20, + min_trust_level_to_tag_topics: "0", + max_tag_search_results: 5, + max_tags_in_filter_list: 30, + tags_sort_alphabetically: false, + tags_listed_by_group: false, + suppress_overlapping_tags_in_list: false, + remove_muted_tags_from_latest: "always", + force_lowercase_tags: true, + dashboard_hidden_reports: "", + dashboard_visible_tabs: "moderation|security|reports", + dashboard_general_tab_activity_metrics: + "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", + discourse_narrative_bot_enabled: true, + details_enabled: true, + custom_wizard_enabled: true, + wizard_redirect_exclude_paths: "admin", + wizard_recognised_image_upload_formats: "jpg|jpeg|png|gif", + wizard_important_notices_on_dashboard: true, + discourse_local_dates_email_format: "YYYY-MM-DDTHH:mm:ss[Z]", + discourse_local_dates_enabled: true, + discourse_local_dates_default_formats: "LLL|LTS|LL|LLLL", + discourse_local_dates_default_timezones: "Europe/Paris|America/Los_Angeles", + poll_enabled: true, + poll_maximum_options: 20, + poll_minimum_trust_level_to_create: 1, + poll_groupable_user_fields: "", + poll_export_data_explorer_query_id: -16, + presence_enabled: true, + presence_max_users_shown: 5, + available_locales: + '[{"name":"اللغة العربية","value":"ar"},{"name":"беларуская мова","value":"be"},{"name":"български език","value":"bg"},{"name":"bosanski jezik","value":"bs_BA"},{"name":"català","value":"ca"},{"name":"čeština","value":"cs"},{"name":"dansk","value":"da"},{"name":"Deutsch","value":"de"},{"name":"ελληνικά","value":"el"},{"name":"English (US)","value":"en"},{"name":"English (UK)","value":"en_GB"},{"name":"Español","value":"es"},{"name":"eesti","value":"et"},{"name":"فارسی","value":"fa_IR"},{"name":"suomi","value":"fi"},{"name":"Français","value":"fr"},{"name":"galego","value":"gl"},{"name":"עברית","value":"he"},{"name":"magyar","value":"hu"},{"name":"Հայերեն","value":"hy"},{"name":"Indonesian","value":"id"},{"name":"Italiano","value":"it"},{"name":"日本語","value":"ja"},{"name":"한국어","value":"ko"},{"name":"lietuvių kalba","value":"lt"},{"name":"latviešu valoda","value":"lv"},{"name":"Norsk bokmål","value":"nb_NO"},{"name":"Nederlands","value":"nl"},{"name":"polski","value":"pl_PL"},{"name":"Português","value":"pt"},{"name":"Português (BR)","value":"pt_BR"},{"name":"limba română","value":"ro"},{"name":"Русский","value":"ru"},{"name":"slovenčina","value":"sk"},{"name":"slovenščina","value":"sl"},{"name":"Shqip","value":"sq"},{"name":"српски језик","value":"sr"},{"name":"svenska","value":"sv"},{"name":"Kiswahili","value":"sw"},{"name":"తెలుగు","value":"te"},{"name":"ไทย","value":"th"},{"name":"Türkçe","value":"tr_TR"},{"name":"українська мова","value":"uk"},{"name":"اردو","value":"ur"},{"name":"Việt Nam","value":"vi"},{"name":"简体中文","value":"zh_CN"},{"name":"繁體中文","value":"zh_TW"}]', + require_invite_code: false, + site_logo_url: "http://localhost:3000/images/discourse-logo-sketch.png", + site_logo_small_url: + "http://localhost:3000/images/discourse-logo-sketch-small.png", + site_mobile_logo_url: + "http://localhost:3000/images/discourse-logo-sketch.png", + site_favicon_url: + "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", + site_logo_dark_url: "", + site_logo_small_dark_url: "", + site_mobile_logo_dark_url: "", +}; diff --git a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 index a072772e..61e51994 100644 --- a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 @@ -1,22 +1,22 @@ export default { - "tags": [ + tags: [ { - "id": "tag1", - "text": "tag1", - "name": "tag1", - "description": null, - "count": 1, - "pm_count": 0, - "target_tag": null + id: "tag1", + text: "tag1", + name: "tag1", + description: null, + count: 1, + pm_count: 0, + target_tag: null, }, { - "id": "tag2", - "text": "tag2", - "name": "tag2", - "description": null, - "count": 1, - "pm_count": 0, - "target_tag": null - } - ] -} + id: "tag2", + text: "tag2", + name: "tag2", + description: null, + count: 1, + pm_count: 0, + target_tag: null, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/update.js.es6 b/assets/javascripts/wizard/tests/fixtures/update.js.es6 index 3908525c..5b20788c 100644 --- a/assets/javascripts/wizard/tests/fixtures/update.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/update.js.es6 @@ -1,5 +1,5 @@ export default { - "final": false, - "next_step_id": "step_2", - "wizard": {} -} + final: false, + next_step_id: "step_2", + wizard: {}, +}; diff --git a/assets/javascripts/wizard/tests/fixtures/user.js.es6 b/assets/javascripts/wizard/tests/fixtures/user.js.es6 index 8acd7392..d954905c 100644 --- a/assets/javascripts/wizard/tests/fixtures/user.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/user.js.es6 @@ -30,5 +30,5 @@ export default { title_count_mode: "notifications", timezone: "Australia/Perth", skip_new_user_tips: false, - can_review: true -} + can_review: true, +}; diff --git a/assets/javascripts/wizard/tests/fixtures/users.js.es6 b/assets/javascripts/wizard/tests/fixtures/users.js.es6 index 267d4909..437631a2 100644 --- a/assets/javascripts/wizard/tests/fixtures/users.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/users.js.es6 @@ -1,14 +1,14 @@ export default { - "users": [ + users: [ { - "username": "angus", - "name": "Angus", - "avatar_template": "/user_avatar/localhost/angus/{size}/12_2.png" + username: "angus", + name: "Angus", + avatar_template: "/user_avatar/localhost/angus/{size}/12_2.png", }, { - "username": "angus_2", - "name": "Angus 2", - "avatar_template": "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png" - } - ] -} + username: "angus_2", + name: "Angus 2", + avatar_template: "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png", + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 index be4fa8b2..73fe45c1 100644 --- a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 @@ -1,469 +1,471 @@ export default { - "id": "wizard", - "name": "Wizard", - "start": "step_1", - "background": "#333333", - "submission_last_updated_at": "2022-03-15T21:11:01+01:00", - "theme_id": 2, - "required": false, - "permitted": true, - "uncategorized_category_id": 1, - "categories": [], - "subscribed": false, - "resume_on_revisit": false, - "steps": [ + id: "wizard", + name: "Wizard", + start: "step_1", + background: "#333333", + submission_last_updated_at: "2022-03-15T21:11:01+01:00", + theme_id: 2, + required: false, + permitted: true, + uncategorized_category_id: 1, + categories: [], + subscribed: false, + resume_on_revisit: false, + steps: [ { - "id": "step_1", - "index": 0, - "next": "step_2", - "description": "

Text inputs!

", - "title": "Text", - "permitted": true, - "permitted_message": null, - "final": false, - "fields": [ + id: "step_1", + index: 0, + next: "step_2", + description: "

Text inputs!

", + title: "Text", + permitted: true, + permitted_message: null, + final: false, + fields: [ { - "id": "step_1_field_1", - "index": 0, - "type": "text", - "required": false, - "value": "I am prefilled", - "label": "

Text

", - "description": "Text field description.", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 1, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_1", + index: 0, + type: "text", + required: false, + value: "I am prefilled", + label: "

Text

", + description: "Text field description.", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_2", - "index": 0, - "type": "textarea", - "required": false, - "value": "", - "label": "

Textarea

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 2, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_2", + index: 0, + type: "textarea", + required: false, + value: "", + label: "

Textarea

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_3", - "index": 2, - "type": "composer", - "required": false, - "value": "", - "label": "

Composer

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 3, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_3", + index: 2, + type: "composer", + required: false, + value: "", + label: "

Composer

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_4", - "index": 3, - "type": "text_only", - "required": false, - "value": null, - "label": "

I’m only text

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 4, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_4", + index: 3, + type: "text_only", + required: false, + value: null, + label: "

I’m only text

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 4, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_5", - "index": 4, - "type": "composer_preview", - "required": false, - "value": "", - "label": "

I’m a preview

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": "

I am prefilled

", - "tabindex": 5, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_5", + index: 4, + type: "composer_preview", + required: false, + value: "", + label: "

I’m a preview

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: "

I am prefilled

", + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_6", - "index": 5, - "type": "composer_preview", - "required": false, - "value": "", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": "

This is the preview of the composer

", - "tabindex": 6, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 - } + id: "step_1_field_6", + index: 5, + type: "composer_preview", + required: false, + value: "", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: "

This is the preview of the composer

", + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, ], - "_validState": 0, - "wizardId": "super_mega_fun_wizard" + _validState: 0, + wizardId: "super_mega_fun_wizard", }, { - "id": "step_2", - "index": 1, - "next": "step_3", - "previous": "step_1", - "description": "

Because I couldn’t think of another name for this step \":slight_smile:\"

", - "title": "Values", - "permitted": true, - "permitted_message": null, - "final": false, - "fields": [ + id: "step_2", + index: 1, + next: "step_3", + previous: "step_1", + description: + '

Because I couldn’t think of another name for this step :slight_smile:

', + title: "Values", + permitted: true, + permitted_message: null, + final: false, + fields: [ { - "id": "step_2_field_1", - "index": 0, - "type": "date", - "required": false, - "value": "", - "label": "

Date

", - "file_types": null, - "format": "YYYY-MM-DD", - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 1, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_1", + index: 0, + type: "date", + required: false, + value: "", + label: "

Date

", + file_types: null, + format: "YYYY-MM-DD", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_2", - "index": 0, - "type": "time", - "required": false, - "value": "", - "label": "

Time

", - "file_types": null, - "format": "HH:mm", - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 2, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_2", + index: 0, + type: "time", + required: false, + value: "", + label: "

Time

", + file_types: null, + format: "HH:mm", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_3", - "index": 2, - "type": "date_time", - "required": false, - "value": "", - "label": "

Date & Time

", - "file_types": null, - "format": "", - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 3, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_3", + index: 2, + type: "date_time", + required: false, + value: "", + label: "

Date & Time

", + file_types: null, + format: "", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_4", - "index": 3, - "type": "number", - "required": false, - "value": "", - "label": "

Number

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 5, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_4", + index: 3, + type: "number", + required: false, + value: "", + label: "

Number

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_5", - "index": 4, - "type": "checkbox", - "required": false, - "value": false, - "label": "

Checkbox

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 6, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_5", + index: 4, + type: "checkbox", + required: false, + value: false, + label: "

Checkbox

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_6", - "index": 5, - "type": "url", - "required": false, - "value": "", - "label": "

Url

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 7, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_6", + index: 5, + type: "url", + required: false, + value: "", + label: "

Url

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 7, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_7", - "index": 6, - "type": "upload", - "required": false, - "value": "", - "label": "

Upload

", - "file_types": ".jpg,.jpeg,.png", - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 8, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 - } + id: "step_2_field_7", + index: 6, + type: "upload", + required: false, + value: "", + label: "

Upload

", + file_types: ".jpg,.jpeg,.png", + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 8, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, ], - "_validState": 0, - "wizardId": "super_mega_fun_wizard" + _validState: 0, + wizardId: "super_mega_fun_wizard", }, { - "id": "step_3", - "index": 2, - "previous": "step_2", - "description": "

Unfortunately not the edible type \":sushi:\"

", - "title": "Combo-boxes", - "permitted": true, - "permitted_message": null, - "final": true, - "fields": [ + id: "step_3", + index: 2, + previous: "step_2", + description: + '

Unfortunately not the edible type :sushi:

', + title: "Combo-boxes", + permitted: true, + permitted_message: null, + final: true, + fields: [ { - "id": "step_3_field_1", - "index": 0, - "type": "dropdown", - "required": false, - "value": "choice1", - "label": "

Custom Dropdown

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": [ + id: "step_3_field_1", + index: 0, + type: "dropdown", + required: false, + value: "choice1", + label: "

Custom Dropdown

", + file_types: null, + format: null, + limit: null, + property: null, + content: [ { - "id": "one", - "name": "One" + id: "one", + name: "One", }, { - "id": "two", - "name": "Two" - } + id: "two", + name: "Two", + }, ], - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 1, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_2", - "index": 0, - "type": "tag", - "required": false, - "value": null, - "label": "

Tag

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 2, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_2", + index: 0, + type: "tag", + required: false, + value: null, + label: "

Tag

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_3", - "index": 2, - "type": "category", - "required": false, - "value": null, - "label": "

Category

", - "file_types": null, - "format": null, - "limit": 1, - "property": "id", - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 3, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_3", + index: 2, + type: "category", + required: false, + value: null, + label: "

Category

", + file_types: null, + format: null, + limit: 1, + property: "id", + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_4", - "index": 3, - "type": "group", - "required": false, - "value": null, - "label": "

Group

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 4, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_4", + index: 3, + type: "group", + required: false, + value: null, + label: "

Group

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 4, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_5", - "index": 4, - "type": "user_selector", - "required": false, - "value": null, - "label": "

User Selector

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 5, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_5", + index: 4, + type: "user_selector", + required: false, + value: null, + label: "

User Selector

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_6", - "index": 5, - "type": "user_selector", - "required": false, - "value": null, - "label": "

Conditional User Selector

", - "description": "Shown when checkbox in step_2_field_5 is true", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 6, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 - } + id: "step_3_field_6", + index: 5, + type: "user_selector", + required: false, + value: null, + label: "

Conditional User Selector

", + description: "Shown when checkbox in step_2_field_5 is true", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, ], - "_validState": 0, - "wizardId": "super_mega_fun_wizard" - } + _validState: 0, + wizardId: "super_mega_fun_wizard", + }, ], - "groups": [] -} + groups: [], +}; diff --git a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 index f5c1175f..913e8c7a 100644 --- a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 @@ -6,18 +6,20 @@ let server; let app; function acceptance(name, requests, cb) { - module(`Acceptance: ${name}`, function(hooks) { - hooks.beforeEach(function() { - server = setupPretender(function(pretender) { - requests.forEach(req => { - pretender[req.verb](req.path, () => (response(req.status, req.response))); + module(`Acceptance: ${name}`, function (hooks) { + hooks.beforeEach(function () { + server = setupPretender(function (pretender) { + requests.forEach((req) => { + pretender[req.verb](req.path, () => + response(req.status, req.response) + ); }); return pretender; }); app = startApp(); }); - hooks.afterEach(function() { + hooks.afterEach(function () { app.destroy(); server.shutdown(); }); @@ -28,9 +30,7 @@ function acceptance(name, requests, cb) { export default acceptance; -export { - server -}; +export { server }; // The discourse/test/helpers/qunit-helpers file has many functions and imports // we don't need, so there will be some duplciation here. diff --git a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 index 6afe6eb9..821d8033 100644 --- a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 @@ -1,6 +1,12 @@ -const CustomWizard = requirejs("discourse/plugins/discourse-custom-wizard/wizard/application").default; -const initializer = requirejs("discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard").default; -const siteSettings = requirejs("discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings").default; +const CustomWizard = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/application" +).default; +const initializer = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard" +).default; +const siteSettings = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings" +).default; const { cloneJSON } = requirejs("discourse-common/lib/object").default; let app; diff --git a/assets/javascripts/wizard/tests/helpers/step.js.es6 b/assets/javascripts/wizard/tests/helpers/step.js.es6 index a24e04e1..41fe16cd 100644 --- a/assets/javascripts/wizard/tests/helpers/step.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/step.js.es6 @@ -5,16 +5,13 @@ import wizardJson from "../fixtures/wizard"; const update = cloneJSON(updateJson); update.wizard = cloneJSON(wizardJson); -const saveStep = function(response) { +const saveStep = function (response) { return { verb: "put", - path: '/w/wizard/steps/:step_id', + path: "/w/wizard/steps/:step_id", status: 200, - response - } -} + response, + }; +}; -export { - saveStep, - update -} +export { saveStep, update }; diff --git a/assets/javascripts/wizard/tests/helpers/test.js.es6 b/assets/javascripts/wizard/tests/helpers/test.js.es6 index c7401fd5..360c883a 100644 --- a/assets/javascripts/wizard/tests/helpers/test.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/test.js.es6 @@ -2,6 +2,4 @@ function exists(selector) { return document.querySelector(selector) !== null; } -export { - exists -} +export { exists }; diff --git a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 index 997f6c36..4cd2e003 100644 --- a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 @@ -26,20 +26,20 @@ const allFieldsWizard = cloneJSON(wizard); allFieldsWizard.steps[0].fields = [ ...allFieldsWizard.steps[0].fields, ...allFieldsWizard.steps[1].fields, - ...allFieldsWizard.steps[2].fields + ...allFieldsWizard.steps[2].fields, ]; allFieldsWizard.steps = [cloneJSON(allFieldsWizard.steps[0])]; -allFieldsWizard.categories = cloneJSON(categoriesJson['categories']); -allFieldsWizard.groups = cloneJSON(groupsJson['groups']); +allFieldsWizard.categories = cloneJSON(categoriesJson["categories"]); +allFieldsWizard.groups = cloneJSON(groupsJson["groups"]); -const getWizard = function(response) { +const getWizard = function (response) { return { verb: "get", path: "/w/wizard", status: 200, - response - } -} + response, + }; +}; export { getWizard, @@ -48,5 +48,5 @@ export { wizardCompleted, stepNotPermitted, allFieldsWizard, - wizard -} + wizard, +}; diff --git a/assets/javascripts/wizard/tests/pretender.js.es6 b/assets/javascripts/wizard/tests/pretender.js.es6 index 88eae666..1f1d4a7d 100644 --- a/assets/javascripts/wizard/tests/pretender.js.es6 +++ b/assets/javascripts/wizard/tests/pretender.js.es6 @@ -1,23 +1,5 @@ import Pretender from "pretender"; -function parsePostData(query) { - const result = {}; - query.split("&").forEach(function (part) { - const item = part.split("="); - const firstSeg = decodeURIComponent(item[0]); - const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg); - - const val = decodeURIComponent(item[1]).replace(/\+/g, " "); - if (m) { - result[m[1]] = result[m[1]] || {}; - result[m[1]][m[2]] = val; - } else { - result[firstSeg] = val; - } - }); - return result; -} - function response(code, obj) { if (typeof code === "object") { obj = code; @@ -42,7 +24,7 @@ export default function (cb) { return body; }; - server.unhandledRequest = function (verb, path, request) { + server.unhandledRequest = function (verb, path) { const error = "Unhandled request in test environment: " + path + " (" + verb + ")"; window.console.error(error); From e7755b106f3edf805f817fec901296f320de034b Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:09:23 +0100 Subject: [PATCH 186/556] Update workflow to add frontend tests && handle deprecations --- .github/workflows/plugin-tests.yml | 16 +------- .../components/custom-user-selector.js.es6 | 3 +- .../components/wizard-field-category.js.es6 | 3 +- .../components/wizard-field-composer.js.es6 | 3 +- .../wizard/components/wizard-step.js.es6 | 6 +-- .../components/wizard-text-field.js.es6 | 3 +- .../lib/initialize/inject-objects.js.es6 | 38 +++++++++---------- .../lib/initialize/patch-components.js.es6 | 16 ++++++++ .../wizard/lib/initialize/wizard.js.es6 | 1 + .../javascripts/wizard/lib/load-script.js.es6 | 16 ++++---- .../javascripts/wizard/lib/text-lite.js.es6 | 16 +++++--- .../javascripts/wizard/lib/user-search.js.es6 | 3 +- assets/javascripts/wizard/models/step.js.es6 | 3 +- .../javascripts/wizard/models/wizard.js.es6 | 3 +- .../components/wizard-composer-editor.hbs | 2 +- assets/javascripts/wizard/templates/step.hbs | 5 +-- 16 files changed, 76 insertions(+), 61 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 782ebc4f..baf5e381 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -73,18 +73,6 @@ jobs: ref: "${{ github.base_ref }}" fetch-depth: 1 - - name: Check spec existence - id: check_spec - uses: andstor/file-existence-action@v1 - with: - files: "plugins/${{ steps.repo-name.outputs.value }}/spec" - - - name: Check qunit existence - id: check_qunit - uses: andstor/file-existence-action@v1 - with: - files: "plugins/${{ steps.repo-name.outputs.value }}/test/javascripts" - - name: Setup Git run: | git config --global user.email "ci@ci.invalid" @@ -140,7 +128,7 @@ jobs: bin/rake db:migrate - name: Plugin RSpec with Coverage - if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' + if: matrix.build_type == 'backend' run: | if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ] then @@ -150,6 +138,6 @@ jobs: bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] - name: Plugin QUnit - if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' + if: matrix.build_type == 'frontend' run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000'] timeout-minutes: 30 diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index c53c3bf4..56eb8f57 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -7,6 +7,7 @@ import userSearch from "../lib/user-search"; import WizardI18n from "../lib/wizard-i18n"; import Handlebars from "handlebars"; import { isEmpty } from "@ember/utils"; +import TextField from "@ember/component/text-field"; const template = function (params) { const options = params.options; @@ -31,7 +32,7 @@ const template = function (params) { return new Handlebars.SafeString(html).string; }; -export default Ember.TextField.extend({ +export default TextField.extend({ attributeBindings: ["autofocus", "maxLength"], autocorrect: false, autocapitalize: false, diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index 9f4b65ba..441f83d3 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -1,7 +1,8 @@ import { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ layoutName: "wizard/templates/components/wizard-field-category", didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 07829e8a..255982ea 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -3,8 +3,9 @@ import { observes, } from "discourse-common/utils/decorators"; import EmberObject from "@ember/object"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ layoutName: "wizard/templates/components/wizard-field-composer", showPreview: false, diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 index 18b1a255..ea1a63c6 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -87,7 +87,7 @@ export default Component.extend({ @observes("step.message") _handleMessage: function () { const message = this.get("step.message"); - this.sendAction("showMessage", message); + this.showMessage(message); }, keyPress(event) { @@ -162,7 +162,7 @@ export default Component.extend({ if (response["final"]) { CustomWizard.finished(response); } else { - this.sendAction("goNext", response); + this.goNext(response); } }) .catch(() => this.animateInvalidFields()) @@ -181,7 +181,7 @@ export default Component.extend({ }, showMessage(message) { - this.sendAction("showMessage", message); + this.sendAction(message); }, stylingDropdownChanged(id, value) { diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/wizard/components/wizard-text-field.js.es6 index 3d87be09..5991eefc 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-text-field.js.es6 @@ -1,8 +1,9 @@ import computed from "discourse-common/utils/decorators"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import WizardI18n from "../lib/wizard-i18n"; +import TextField from "@ember/component/text-field"; -export default Ember.TextField.extend({ +export default TextField.extend({ attributeBindings: [ "autocorrect", "autocapitalize", diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 index fb11c9e7..51dff7e1 100644 --- a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -1,5 +1,11 @@ export default { run(app) { + // siteSettings must always be registered first + if (!app.hasRegistration("site-settings:main")) { + const siteSettings = app.SiteSettings; + app.register("site-settings:main", siteSettings, { instantiate: false }); + } + const Store = requirejs("discourse/services/store").default; const Site = requirejs( "discourse/plugins/discourse-custom-wizard/wizard/models/site" @@ -10,11 +16,10 @@ export default { const sniffCapabilites = requirejs( "discourse/pre-initializers/sniff-capabilities" ).default; + const site = Site.current(); const session = Session.current(); - const registrations = [ - ["site-settings:main", app.SiteSettings, false], ["message-bus:main", messageBus, false], ["site:main", site, false], ["session:main", session, false], @@ -30,25 +35,18 @@ export default { } }); - const targets = [ - "controller", - "component", - "route", - "model", - "adapter", - "mixin", - ]; - const injections = [ - ["siteSettings", "site-settings:main"], - ["messageBus", "message-bus:main"], - ["site", "site:main"], - ["session", "session:main"], - ["store", "service:store"], - ["appEvents", "service:app-events"], - ]; + const targets = ["controller", "component", "route", "model", "adapter"]; - injections.forEach((injection) => { - targets.forEach((t) => app.inject(t, injection[0], injection[1])); + targets.forEach((t) => { + app.inject(t, "appEvents", "service:app-events"); + app.inject(t, "store", "service:store"); + app.inject(t, "site", "site:main"); + }); + + targets.concat("service").forEach((t) => { + app.inject(t, "session", "session:main"); + app.inject(t, "messageBus", "message-bus:main"); + app.inject(t, "siteSettings", "site-settings:main"); }); if (!app.hasRegistration("capabilities:main")) { diff --git a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 75cc63f1..d5e7ea25 100644 --- a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -22,6 +22,11 @@ export default { const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const toMarkdown = requirejs("discourse/lib/to-markdown").default; + const discourseComputed = requirejs("discourse-common/utils/decorators") + .default; + const WizardI18n = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" + ).default; const isInside = (text, regex) => { const matches = text.match(regex); return matches && matches.length % 2; @@ -46,6 +51,17 @@ export default { } }, + @discourseComputed("placeholder", "placeholderOverride") + placeholderTranslated(placeholder, placeholderOverride) { + if (placeholderOverride) { + return placeholderOverride; + } + if (placeholder) { + return WizardI18n(placeholder); + } + return null; + }, + _wizardInsertText(args = {}) { if (args.fieldId === this.fieldId) { this._insertText(args.text, args.options); diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 index 0020f7a8..915e5f26 100644 --- a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -40,6 +40,7 @@ export default { const Session = requirejs("discourse/models/session").default; const session = Session.current(); session.set("highlightJsPath", setupData.highlightJsPath); + session.set("markdownItUrl", setupData.markdownItUrl); [ "register-files", diff --git a/assets/javascripts/wizard/lib/load-script.js.es6 b/assets/javascripts/wizard/lib/load-script.js.es6 index 43d97d0a..d0b07c26 100644 --- a/assets/javascripts/wizard/lib/load-script.js.es6 +++ b/assets/javascripts/wizard/lib/load-script.js.es6 @@ -1,5 +1,7 @@ import { ajax } from "wizard/lib/ajax"; -import getURL from "discourse-common/lib/get-url"; +import getURL, { getURLWithCDN } from "discourse-common/lib/get-url"; +import { run } from "@ember/runloop"; +import { Promise } from "rsvp"; const _loaded = {}; const _loading = {}; @@ -25,7 +27,7 @@ function loadWithTag(path, cb) { ) { s = s.onload = s.onreadystatechange = null; if (!abort) { - Ember.run(null, cb); + run(null, cb); } } }; @@ -38,7 +40,7 @@ export function loadCSS(url) { export default function loadScript(url, opts) { // TODO: Remove this once plugins have been updated not to use it: if (url === "defer/html-sanitizer-bundle") { - return Ember.RSVP.Promise.resolve(); + return Promise.resolve(); } opts = opts || {}; @@ -51,7 +53,7 @@ export default function loadScript(url, opts) { } }); - return new Ember.RSVP.Promise(function (resolve) { + return new Promise(function (resolve) { url = getURL(url); // If we already loaded this url @@ -63,7 +65,7 @@ export default function loadScript(url, opts) { } let done; - _loading[url] = new Ember.RSVP.Promise(function (_done) { + _loading[url] = new Promise(function (_done) { done = _done; }); @@ -84,8 +86,8 @@ export default function loadScript(url, opts) { // Scripts should always load from CDN // CSS is type text, to accept it from a CDN we would need to handle CORS - if (!opts.css && Discourse.CDN && url[0] === "/" && url[1] !== "/") { - cdnUrl = Discourse.CDN.replace(/\/$/, "") + url; + if (!opts.css) { + cdnUrl = getURLWithCDN(url); } // Some javascript depends on the path of where it is loaded (ace editor) diff --git a/assets/javascripts/wizard/lib/text-lite.js.es6 b/assets/javascripts/wizard/lib/text-lite.js.es6 index cc161426..26cfc27a 100644 --- a/assets/javascripts/wizard/lib/text-lite.js.es6 +++ b/assets/javascripts/wizard/lib/text-lite.js.es6 @@ -3,6 +3,8 @@ import { default as PrettyText, buildOptions } from "pretty-text/pretty-text"; import Handlebars from "handlebars"; import getURL from "discourse-common/lib/get-url"; import { getOwner } from "discourse-common/lib/get-owner"; +import { Promise } from "rsvp"; +import Session from "discourse/models/session"; export function cook(text, options) { if (!options) { @@ -18,11 +20,15 @@ export function cook(text, options) { // everything should eventually move to async API and this should be renamed // cook export function cookAsync(text, options) { - if (Discourse.MarkdownItURL) { - return loadScript(Discourse.MarkdownItURL) - .then(() => cook(text, options)) - .catch((e) => Ember.Logger.error(e)); + let markdownItURL = Session.currentProp("markdownItURL"); + if (markdownItURL) { + return ( + loadScript(markdownItURL) + .then(() => cook(text, options)) + // eslint-disable-next-line no-console + .catch((e) => console.error(e)) + ); } else { - return Ember.RSVP.Promise.resolve(cook(text)); + return Promise.resolve(cook(text)); } } diff --git a/assets/javascripts/wizard/lib/user-search.js.es6 b/assets/javascripts/wizard/lib/user-search.js.es6 index e7171f18..04b6f97c 100644 --- a/assets/javascripts/wizard/lib/user-search.js.es6 +++ b/assets/javascripts/wizard/lib/user-search.js.es6 @@ -1,6 +1,7 @@ import { CANCELLED_STATUS } from "discourse/lib/autocomplete"; import { debounce } from "@ember/runloop"; import getUrl from "discourse-common/lib/get-url"; +import { Promise } from "rsvp"; let cache = {}, cacheTopicId, @@ -120,7 +121,7 @@ export default function userSearch(options) { currentTerm = term; - return new Ember.RSVP.Promise(function (resolve) { + return new Promise(function (resolve) { // TODO site setting for allowed regex in username if (term.match(/[^\w_\-\.@\+]/)) { resolve([]); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 index 2729e993..36503276 100644 --- a/assets/javascripts/wizard/models/step.js.es6 +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -3,6 +3,7 @@ import ValidState from "wizard/mixins/valid-state"; import { ajax } from "wizard/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; +import { later } from "@ember/runloop"; export default EmberObject.extend(ValidState, { id: null, @@ -105,6 +106,6 @@ export default EmberObject.extend(ValidState, { state: "error", text: message, }); - Ember.run.later(() => this.set("message", null), 6000); + later(() => this.set("message", null), 6000); }, }); diff --git a/assets/javascripts/wizard/models/wizard.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 index b2e26b25..15bcbb8c 100644 --- a/assets/javascripts/wizard/models/wizard.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -113,8 +113,7 @@ CustomWizard.reopenClass({ } }); - Site.currentProp("categoriesList", categories); - Site.currentProp("sortedCategories", categories); + Site.currentProp("categories", categories); Site.currentProp("listByActivity", categories); Site.currentProp("categoriesById", categoriesById); Site.currentProp( diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs index be98db8e..7d453a0b 100644 --- a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs @@ -1,7 +1,7 @@ {{d-editor tabindex=field.tabindex value=composer.reply - placeholderTranslated=replyPlaceholder + placeholderOverride=replyPlaceholder previewUpdated=(action "previewUpdated") markdownOptions=markdownOptions extraButtons=(action "extraButtons") diff --git a/assets/javascripts/wizard/templates/step.hbs b/assets/javascripts/wizard/templates/step.hbs index 6456a59c..5ed14cdf 100644 --- a/assets/javascripts/wizard/templates/step.hbs +++ b/assets/javascripts/wizard/templates/step.hbs @@ -13,8 +13,7 @@ {{#if step.permitted}} {{wizard-step step=step wizard=wizard - goNext="goNext" + goNext=(action "goNext") goBack=(action "goBack") - finished="finished" - showMessage="showMessage"}} + showMessage=(action "showMessage")}} {{/if}} From ea52b2779bbe9c382f719e5f69b0dfffff94b0f0 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:10:54 +0100 Subject: [PATCH 187/556] Remove duplicated functions --- assets/javascripts/wizard/components/validator.js.es6 | 1 - assets/javascripts/wizard/components/wizard-step.js.es6 | 2 -- 2 files changed, 3 deletions(-) diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index dafbf8c0..aa68660c 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -11,7 +11,6 @@ export default Component.extend({ invalidMessageKey: null, isValid: null, isInvalid: equal("isValid", false), - layoutName: "wizard/templates/components/validator", init() { this._super(...arguments); diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 index ea1a63c6..cc23c5bf 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -169,8 +169,6 @@ export default Component.extend({ .finally(() => this.set("saving", false)); }, - keyPress() {}, - actions: { quit() { this.get("wizard").skip(); From 61928ce4360a3837002e07417935e06f376cf562 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:12:13 +0100 Subject: [PATCH 188/556] Apply rubocop --- app/serializers/custom_wizard/submission_serializer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/serializers/custom_wizard/submission_serializer.rb b/app/serializers/custom_wizard/submission_serializer.rb index 9c2f8133..732d6743 100644 --- a/app/serializers/custom_wizard/submission_serializer.rb +++ b/app/serializers/custom_wizard/submission_serializer.rb @@ -7,12 +7,12 @@ class CustomWizard::SubmissionSerializer < ApplicationSerializer has_one :user, serializer: ::BasicUserSerializer, embed: :objects def include_user? - object.user.present? + object.user.present? end def fields @fields ||= begin - result = {} + result = {} object.wizard.template['steps'].each do |step| step['fields'].each do |field| From 6cd6b111bd3d5bec16dbccc5d745560e3636e356 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:13:45 +0100 Subject: [PATCH 189/556] Bump minor version --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index e8dcf457..e745c3a8 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards for topic creation, onboarding, user surveys and much more. -# version: 1.18.4 +# version: 1.19.0 # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George # contact_emails: support@thepavilion.io # url: https://github.com/paviliondev/discourse-custom-wizard From 7e295dfb1419d51333241b0e11cb073b016e3db2 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:22:18 +0100 Subject: [PATCH 190/556] Update plugin-tests.yml --- .github/workflows/plugin-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index baf5e381..13f76074 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -139,5 +139,5 @@ jobs: - name: Plugin QUnit if: matrix.build_type == 'frontend' - run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000'] + run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake "qunit:test['600000','/w/qunit']" timeout-minutes: 30 From 01d972f6baa4b6b24a2558a0102025e114b9990a Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:39:39 +0100 Subject: [PATCH 191/556] Try using bundle exec --- .github/workflows/plugin-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 13f76074..34af2f04 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -139,5 +139,5 @@ jobs: - name: Plugin QUnit if: matrix.build_type == 'frontend' - run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake "qunit:test['600000','/w/qunit']" + run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bundle exec rake "qunit:test['600000','/w/qunit']" timeout-minutes: 30 From 591274d38fcf09e7c5816bfd184b0a095e59dc97 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 15:07:49 +0100 Subject: [PATCH 192/556] Skip core qunit --- .github/workflows/plugin-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 34af2f04..f32cdf5c 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -139,5 +139,5 @@ jobs: - name: Plugin QUnit if: matrix.build_type == 'frontend' - run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bundle exec rake "qunit:test['600000','/w/qunit']" + run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake "qunit:test['600000','/w/qunit']" timeout-minutes: 30 From 5edfb4c41ea5095bff6d965e8275f6b4984ec045 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 15:49:25 +0100 Subject: [PATCH 193/556] Remove subs and notices files --- app/controllers/custom_wizard/admin/admin.rb | 11 +- app/controllers/custom_wizard/admin/notice.rb | 68 ---- .../custom_wizard/admin/subscription.rb | 48 --- app/controllers/custom_wizard/wizard.rb | 5 - .../scheduled/custom_wizard/update_notices.rb | 9 - .../custom_wizard/update_subscription.rb | 9 - .../custom_wizard/notice_serializer.rb | 33 -- .../subscription/authentication_serializer.rb | 11 - .../subscription/subscription_serializer.rb | 10 - .../custom_wizard/subscription_serializer.rb | 6 - .../components/subscription-container.js.es6 | 25 -- .../components/wizard-notice-row.js.es6 | 19 - .../discourse/components/wizard-notice.js.es6 | 29 -- .../components/wizard-subscription.js.es6 | 55 --- .../custom-wizard-critical-notice.hbs | 5 - .../custom-wizard-critical-notice.js.es6 | 19 - .../controllers/admin-wizards-notices.js.es6 | 68 ---- .../admin-wizards-subscription.js.es6 | 62 --- .../discourse/helpers/notice-badge.js.es6 | 43 --- .../initializers/custom-wizard-edits.js.es6 | 45 --- .../discourse/mixins/notice-message.js.es6 | 68 ---- .../models/custom-wizard-notice.js.es6 | 74 ---- .../models/custom-wizard-subscription.js.es6 | 33 -- .../routes/admin-wizards-notices.js.es6 | 17 - .../routes/admin-wizards-subscription.js.es6 | 19 - .../templates/admin-wizards-notices.hbs | 49 --- .../templates/admin-wizards-subscription.hbs | 35 -- .../discourse/templates/admin-wizards.hbs | 7 - .../components/wizard-notice-row.hbs | 30 -- .../templates/components/wizard-notice.hbs | 39 -- .../components/wizard-subscription.hbs | 31 -- .../javascripts/wizard/tests/bootstrap.js.es6 | 30 +- config/locales/server.en.yml | 14 - config/routes.rb | 12 - lib/custom_wizard/notice.rb | 358 ------------------ lib/custom_wizard/notice/connection_error.rb | 77 ---- lib/custom_wizard/subscription.rb | 232 ------------ .../subscription/authentication.rb | 95 ----- .../subscription/subscription.rb | 22 -- plugin.rb | 21 - 40 files changed, 17 insertions(+), 1826 deletions(-) delete mode 100644 app/controllers/custom_wizard/admin/notice.rb delete mode 100644 app/controllers/custom_wizard/admin/subscription.rb delete mode 100644 app/jobs/scheduled/custom_wizard/update_notices.rb delete mode 100644 app/jobs/scheduled/custom_wizard/update_subscription.rb delete mode 100644 app/serializers/custom_wizard/notice_serializer.rb delete mode 100644 app/serializers/custom_wizard/subscription/authentication_serializer.rb delete mode 100644 app/serializers/custom_wizard/subscription/subscription_serializer.rb delete mode 100644 app/serializers/custom_wizard/subscription_serializer.rb delete mode 100644 assets/javascripts/discourse/components/subscription-container.js.es6 delete mode 100644 assets/javascripts/discourse/components/wizard-notice-row.js.es6 delete mode 100644 assets/javascripts/discourse/components/wizard-notice.js.es6 delete mode 100644 assets/javascripts/discourse/components/wizard-subscription.js.es6 delete mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs delete mode 100644 assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 delete mode 100644 assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 delete mode 100644 assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 delete mode 100644 assets/javascripts/discourse/helpers/notice-badge.js.es6 delete mode 100644 assets/javascripts/discourse/mixins/notice-message.js.es6 delete mode 100644 assets/javascripts/discourse/models/custom-wizard-notice.js.es6 delete mode 100644 assets/javascripts/discourse/models/custom-wizard-subscription.js.es6 delete mode 100644 assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 delete mode 100644 assets/javascripts/discourse/routes/admin-wizards-subscription.js.es6 delete mode 100644 assets/javascripts/discourse/templates/admin-wizards-notices.hbs delete mode 100644 assets/javascripts/discourse/templates/admin-wizards-subscription.hbs delete mode 100644 assets/javascripts/discourse/templates/components/wizard-notice-row.hbs delete mode 100644 assets/javascripts/discourse/templates/components/wizard-notice.hbs delete mode 100644 assets/javascripts/discourse/templates/components/wizard-subscription.hbs delete mode 100644 lib/custom_wizard/notice.rb delete mode 100644 lib/custom_wizard/notice/connection_error.rb delete mode 100644 lib/custom_wizard/subscription/authentication.rb delete mode 100644 lib/custom_wizard/subscription/subscription.rb diff --git a/app/controllers/custom_wizard/admin/admin.rb b/app/controllers/custom_wizard/admin/admin.rb index 63a89833..e011e094 100644 --- a/app/controllers/custom_wizard/admin/admin.rb +++ b/app/controllers/custom_wizard/admin/admin.rb @@ -4,16 +4,7 @@ class CustomWizard::AdminController < ::Admin::AdminController def index render_json_dump( - #TODO replace with appropriate static? - api_section: ["business"].include?(CustomWizard::Subscription.type), - active_notice_count: CustomWizard::Notice.active_count, - featured_notices: ActiveModel::ArraySerializer.new( - CustomWizard::Notice.list( - type: CustomWizard::Notice.types[:info], - archetype: CustomWizard::Notice.archetypes[:subscription_message] - ), - each_serializer: CustomWizard::NoticeSerializer - ) + api_section: ["business"].include?(CustomWizard::Subscription.type) ) end diff --git a/app/controllers/custom_wizard/admin/notice.rb b/app/controllers/custom_wizard/admin/notice.rb deleted file mode 100644 index 81ae00da..00000000 --- a/app/controllers/custom_wizard/admin/notice.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -class CustomWizard::AdminNoticeController < CustomWizard::AdminController - before_action :find_notice, only: [:dismiss, :hide] - - def index - type = params[:type] - archetype = params[:archtype] - page = params[:page].to_i - include_all = ActiveRecord::Type::Boolean.new.cast(params[:include_all]) - visible = ActiveRecord::Type::Boolean.new.cast(params[:visible]) - - if type - if type.is_a?(Array) - type = type.map { |t| CustomWizard::Notice.types[t.to_sym] } - else - type = CustomWizard::Notice.types[type.to_sym] - end - end - - if archetype - if archetype.is_a?(Array) - archetype = archetype.map { |t| CustomWizard::Notice.archetypes[archetype.to_sym] } - else - archetype = CustomWizard::Notice.archetypes[archetype.to_sym] - end - end - - notices = CustomWizard::Notice.list( - include_all: include_all, - page: page, - type: type, - archetype: archetype, - visible: visible - ) - - render_serialized(notices, CustomWizard::NoticeSerializer, root: :notices) - end - - def dismiss - if @notice.dismissable? && @notice.dismiss! - render json: success_json.merge(dismissed_at: @notice.dismissed_at) - else - render json: failed_json - end - end - - def hide - if @notice.can_hide? && @notice.hide! - render json: success_json.merge(hidden_at: @notice.hidden_at) - else - render json: failed_json - end - end - - def dismiss_all - if CustomWizard::Notice.dismiss_all - render json: success_json - else - render json: failed_json - end - end - - def find_notice - @notice = CustomWizard::Notice.find(params[:notice_id]) - raise Discourse::InvalidParameters.new(:notice_id) unless @notice - end -end diff --git a/app/controllers/custom_wizard/admin/subscription.rb b/app/controllers/custom_wizard/admin/subscription.rb deleted file mode 100644 index 15ff6396..00000000 --- a/app/controllers/custom_wizard/admin/subscription.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -class CustomWizard::AdminSubscriptionController < CustomWizard::AdminController - skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback] - - def index - render_serialized(subscription, CustomWizard::SubscriptionSerializer, root: false) - end - - def authorize - request_id = SecureRandom.hex(32) - cookies[:user_api_request_id] = request_id - redirect_to subscription.authentication_url(current_user.id, request_id).to_s - end - - def authorize_callback - payload = params[:payload] - request_id = cookies[:user_api_request_id] - - subscription.authentication_response(request_id, payload) - subscription.update - - redirect_to '/admin/wizards/subscription' - end - - def destroy_authentication - if subscription.destroy_authentication - render json: success_json - else - render json: failed_json - end - end - - def update_subscription - if subscription.update - serialized_subscription = CustomWizard::Subscription::SubscriptionSerializer.new(subscription.subscription, root: false) - render json: success_json.merge(subscription: serialized_subscription) - else - render json: failed_json - end - end - - protected - - def subscription - @subscription ||= CustomWizard::Subscription.new - end -end diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index a7988b0d..bc0a06af 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -11,7 +11,6 @@ class CustomWizard::WizardController < ::ActionController::Base before_action :preload_wizard_json before_action :ensure_plugin_enabled - before_action :update_subscription, only: [:index] before_action :ensure_logged_in, only: [:skip] helper_method :wizard_page_title @@ -123,8 +122,4 @@ class CustomWizard::WizardController < ::ActionController::Base redirect_to path("/") end end - - def update_subscription - CustomWizard::Subscription.update - end end diff --git a/app/jobs/scheduled/custom_wizard/update_notices.rb b/app/jobs/scheduled/custom_wizard/update_notices.rb deleted file mode 100644 index 25ebdf3b..00000000 --- a/app/jobs/scheduled/custom_wizard/update_notices.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class Jobs::CustomWizardUpdateNotices < ::Jobs::Scheduled - every 5.minutes - - def execute(args = {}) - CustomWizard::Notice.update - end -end diff --git a/app/jobs/scheduled/custom_wizard/update_subscription.rb b/app/jobs/scheduled/custom_wizard/update_subscription.rb deleted file mode 100644 index 72e34435..00000000 --- a/app/jobs/scheduled/custom_wizard/update_subscription.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class Jobs::CustomWizardUpdateSubscription < ::Jobs::Scheduled - every 1.hour - - def execute(args = {}) - CustomWizard::Subscription.update - end -end diff --git a/app/serializers/custom_wizard/notice_serializer.rb b/app/serializers/custom_wizard/notice_serializer.rb deleted file mode 100644 index 4354731d..00000000 --- a/app/serializers/custom_wizard/notice_serializer.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -class CustomWizard::NoticeSerializer < ApplicationSerializer - attributes :id, - :title, - :message, - :type, - :archetype, - :created_at, - :expired_at, - :updated_at, - :dismissed_at, - :retrieved_at, - :hidden_at, - :dismissable, - :can_hide - - def dismissable - object.dismissable? - end - - def can_hide - object.can_hide? - end - - def type - CustomWizard::Notice.types.key(object.type) - end - - def messsage - PrettyText.cook(object.message) - end -end diff --git a/app/serializers/custom_wizard/subscription/authentication_serializer.rb b/app/serializers/custom_wizard/subscription/authentication_serializer.rb deleted file mode 100644 index 3d54d0f2..00000000 --- a/app/serializers/custom_wizard/subscription/authentication_serializer.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true -class CustomWizard::Subscription::AuthenticationSerializer < ApplicationSerializer - attributes :active, - :client_id, - :auth_by, - :auth_at - - def active - object.active? - end -end diff --git a/app/serializers/custom_wizard/subscription/subscription_serializer.rb b/app/serializers/custom_wizard/subscription/subscription_serializer.rb deleted file mode 100644 index 95a2b323..00000000 --- a/app/serializers/custom_wizard/subscription/subscription_serializer.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true -class CustomWizard::Subscription::SubscriptionSerializer < ApplicationSerializer - attributes :type, - :active, - :updated_at - - def active - object.active? - end -end diff --git a/app/serializers/custom_wizard/subscription_serializer.rb b/app/serializers/custom_wizard/subscription_serializer.rb deleted file mode 100644 index 067cfbcd..00000000 --- a/app/serializers/custom_wizard/subscription_serializer.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -class CustomWizard::SubscriptionSerializer < ApplicationSerializer - attributes :server - has_one :authentication, serializer: CustomWizard::Subscription::AuthenticationSerializer, embed: :objects - has_one :subscription, serializer: CustomWizard::Subscription::SubscriptionSerializer, embed: :objects -end diff --git a/assets/javascripts/discourse/components/subscription-container.js.es6 b/assets/javascripts/discourse/components/subscription-container.js.es6 deleted file mode 100644 index a12b8949..00000000 --- a/assets/javascripts/discourse/components/subscription-container.js.es6 +++ /dev/null @@ -1,25 +0,0 @@ -import Component from "@ember/component"; -import discourseComputed from "discourse-common/utils/decorators"; - -export default Component.extend({ - classNameBindings: [":subscription-container", "subscribed"], - - @discourseComputed("subscribed") - subscribedIcon(subscribed) { - return subscribed ? "check" : "dash"; - }, - - @discourseComputed("subscribed") - subscribedLabel(subscribed) { - return `admin.wizard.subscription_container.${ - subscribed ? "subscribed" : "not_subscribed" - }.label`; - }, - - @discourseComputed("subscribed") - subscribedTitle(subscribed) { - return `admin.wizard.subscription_container.${ - subscribed ? "subscribed" : "not_subscribed" - }.title`; - }, -}); diff --git a/assets/javascripts/discourse/components/wizard-notice-row.js.es6 b/assets/javascripts/discourse/components/wizard-notice-row.js.es6 deleted file mode 100644 index ada4384d..00000000 --- a/assets/javascripts/discourse/components/wizard-notice-row.js.es6 +++ /dev/null @@ -1,19 +0,0 @@ -import Component from "@ember/component"; -import NoticeMessage from "../mixins/notice-message"; - -export default Component.extend(NoticeMessage, { - tagName: "tr", - attributeBindings: ["notice.id:data-notice-id"], - classNameBindings: [ - ":wizard-notice-row", - "notice.typeClass", - "notice.expired:expired", - "notice.dismissed:dismissed", - ], - - actions: { - dismiss() { - this.notice.dismiss(); - }, - }, -}); diff --git a/assets/javascripts/discourse/components/wizard-notice.js.es6 b/assets/javascripts/discourse/components/wizard-notice.js.es6 deleted file mode 100644 index ca6b7658..00000000 --- a/assets/javascripts/discourse/components/wizard-notice.js.es6 +++ /dev/null @@ -1,29 +0,0 @@ -import Component from "@ember/component"; -import NoticeMessage from "../mixins/notice-message"; - -export default Component.extend(NoticeMessage, { - attributeBindings: ["notice.id:data-notice-id"], - classNameBindings: [ - ":wizard-notice", - "notice.typeClass", - "notice.dismissed:dismissed", - "notice.expired:expired", - "notice.hidden:hidden", - ], - - actions: { - dismiss() { - this.set("dismissing", true); - this.notice.dismiss().then(() => { - this.set("dismissing", false); - }); - }, - - hide() { - this.set("hiding", true); - this.notice.hide().then(() => { - this.set("hiding", false); - }); - }, - }, -}); diff --git a/assets/javascripts/discourse/components/wizard-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-subscription.js.es6 deleted file mode 100644 index 29e47bc2..00000000 --- a/assets/javascripts/discourse/components/wizard-subscription.js.es6 +++ /dev/null @@ -1,55 +0,0 @@ -import Component from "@ember/component"; -import CustomWizardSubscription from "../models/custom-wizard-subscription"; -import { notEmpty } from "@ember/object/computed"; -import discourseComputed from "discourse-common/utils/decorators"; -import I18n from "I18n"; - -export default Component.extend({ - classNameBindings: [ - ":custom-wizard-subscription", - "subscription.active:active:inactive", - ], - subscribed: notEmpty("subscription"), - - @discourseComputed("subscription.type") - title(type) { - return type - ? I18n.t(`admin.wizard.subscription.subscription.title.${type}`) - : I18n.t("admin.wizard.subscription.not_subscribed"); - }, - - @discourseComputed("subscription.active") - stateClass(active) { - return active ? "active" : "inactive"; - }, - - @discourseComputed("stateClass") - stateLabel(stateClass) { - return I18n.t( - `admin.wizard.subscription.subscription.status.${stateClass}` - ); - }, - - actions: { - update() { - this.set("updating", true); - CustomWizardSubscription.update() - .then((result) => { - if (result.success) { - this.setProperties({ - updateIcon: "check", - subscription: result.subscription, - }); - } else { - this.set("updateIcon", "times"); - } - }) - .finally(() => { - this.set("updating", false); - setTimeout(() => { - this.set("updateIcon", null); - }, 7000); - }); - }, - }, -}); diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs deleted file mode 100644 index 9d96bed9..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{#if notices}} - {{#each notices as |notice|}} - {{wizard-notice notice=notice showPlugin=true}} - {{/each}} -{{/if}} diff --git a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 b/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 deleted file mode 100644 index 803e58a4..00000000 --- a/assets/javascripts/discourse/connectors/admin-dashboard-top/custom-wizard-critical-notice.js.es6 +++ /dev/null @@ -1,19 +0,0 @@ -import { getOwner } from "discourse-common/lib/get-owner"; - -export default { - shouldRender(attrs, ctx) { - return ctx.siteSettings.wizard_critical_notices_on_dashboard; - }, - - setupComponent(attrs, component) { - const controller = getOwner(this).lookup("controller:admin-dashboard"); - - component.set("notices", controller.get("customWizardCriticalNotices")); - controller.addObserver("customWizardCriticalNotices.[]", () => { - if (this._state === "destroying") { - return; - } - component.set("notices", controller.get("customWizardCriticalNotices")); - }); - }, -}; diff --git a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 deleted file mode 100644 index 1721e699..00000000 --- a/assets/javascripts/discourse/controllers/admin-wizards-notices.js.es6 +++ /dev/null @@ -1,68 +0,0 @@ -import Controller from "@ember/controller"; -import CustomWizardNotice from "../models/custom-wizard-notice"; -import discourseComputed from "discourse-common/utils/decorators"; -import { notEmpty } from "@ember/object/computed"; -import { A } from "@ember/array"; -import I18n from "I18n"; - -export default Controller.extend({ - messageUrl: "https://thepavilion.io/t/3652", - messageKey: "info", - messageIcon: "info-circle", - messageClass: "info", - hasNotices: notEmpty("notices"), - page: 0, - loadingMore: false, - canLoadMore: true, - - @discourseComputed("notices.[]", "notices.@each.dismissed") - allDismisssed(notices) { - return notices.every((n) => !n.canDismiss || n.dismissed); - }, - - loadMoreNotices() { - if (!this.canLoadMore) { - return; - } - const page = this.get("page"); - this.set("loadingMore", true); - - CustomWizardNotice.list({ page, include_all: true }) - .then((result) => { - if (result.notices.length === 0) { - this.set("canLoadMore", false); - return; - } - - this.get("notices").pushObjects( - A(result.notices.map((notice) => CustomWizardNotice.create(notice))) - ); - }) - .finally(() => this.set("loadingMore", false)); - }, - - actions: { - loadMore() { - if (this.canLoadMore) { - this.set("page", this.page + 1); - this.loadMoreNotices(); - } - }, - - dismissAll() { - bootbox.confirm( - I18n.t("admin.wizard.notice.dismiss_all.confirm"), - I18n.t("no_value"), - I18n.t("yes_value"), - (result) => { - if (result) { - this.set("loadingMore", true); - CustomWizardNotice.dismissAll().finally(() => - this.set("loadingMore", false) - ); - } - } - ); - }, - }, -}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 deleted file mode 100644 index 76f16119..00000000 --- a/assets/javascripts/discourse/controllers/admin-wizards-subscription.js.es6 +++ /dev/null @@ -1,62 +0,0 @@ -import Controller from "@ember/controller"; -import discourseComputed from "discourse-common/utils/decorators"; -import CustomWizardSubscription from "../models/custom-wizard-subscription"; -import { alias } from "@ember/object/computed"; - -export default Controller.extend({ - messageUrl: "https://thepavilion.io/t/3652", - messageType: "info", - messageKey: null, - showSubscription: alias("model.authentication.active"), - - setup() { - const authentication = this.get("model.authentication"); - const subscription = this.get("model.subscription"); - const subscribed = subscription && subscription.active; - const authenticated = authentication && authentication.active; - - if (!subscribed) { - this.set("messageKey", authenticated ? "not_subscribed" : "authorize"); - } else { - this.set( - "messageKey", - !authenticated - ? "subscription_expiring" - : subscribed - ? "subscription_active" - : "subscription_inactive" - ); - } - }, - - @discourseComputed("model.server") - messageOpts(server) { - return { server }; - }, - - actions: { - unauthorize() { - this.set("unauthorizing", true); - - CustomWizardSubscription.unauthorize() - .then((result) => { - if (result.success) { - this.setProperties({ - messageKey: "unauthorized", - messageType: "warn", - "model.authentication": null, - "model.subscription": null, - }); - } else { - this.setProperties({ - messageKey: "unauthorize_failed", - messageType: "error", - }); - } - }) - .finally(() => { - this.set("unauthorizing", false); - }); - }, - }, -}); diff --git a/assets/javascripts/discourse/helpers/notice-badge.js.es6 b/assets/javascripts/discourse/helpers/notice-badge.js.es6 deleted file mode 100644 index ea32b462..00000000 --- a/assets/javascripts/discourse/helpers/notice-badge.js.es6 +++ /dev/null @@ -1,43 +0,0 @@ -import { autoUpdatingRelativeAge } from "discourse/lib/formatter"; -import { iconHTML } from "discourse-common/lib/icon-library"; -import I18n from "I18n"; -import { registerUnbound } from "discourse-common/lib/helpers"; -import { htmlSafe } from "@ember/template"; - -registerUnbound("notice-badge", function (attrs) { - let tag = attrs.url ? "a" : "div"; - let attrStr = ""; - if (attrs.title) { - attrStr += `title='${I18n.t(attrs.title)}'`; - } - if (attrs.url) { - attrStr += `href='${attrs.url}'`; - } - let html = `<${tag} class="${ - attrs.class ? `${attrs.class} ` : "" - }notice-badge" ${attrStr}>`; - if (attrs.icon) { - html += iconHTML(attrs.icon); - } - if (attrs.label) { - if (attrs.icon) { - html += " "; - } - html += `${I18n.t(attrs.label)}`; - } - if (attrs.date) { - if (attrs.icon || attrs.label) { - html += " "; - } - let dateAttrs = {}; - if (attrs.leaveAgo) { - dateAttrs = { - format: "medium", - leaveAgo: true, - }; - } - html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs); - } - html += ``; - return htmlSafe(html); -}); diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 6a027dee..40d21b35 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -1,6 +1,5 @@ import DiscourseURL from "discourse/lib/url"; import { withPluginApi } from "discourse/lib/plugin-api"; -import CustomWizardNotice from "../models/custom-wizard-notice"; import { isPresent } from "@ember/utils"; import { A } from "@ember/array"; import getUrl from "discourse-common/lib/get-url"; @@ -23,50 +22,6 @@ export default { }; withPluginApi("0.8.36", (api) => { - api.modifyClass("route:admin-dashboard", { - pluginId: "custom-wizard", - - setupController(controller) { - this._super(...arguments); - - controller.loadCriticalNotices(); - controller.subscribe(); - }, - }); - - api.modifyClass("controller:admin-dashboard", { - pluginId: "custom-wizard", - criticalNotices: A(), - - unsubscribe() { - this.messageBus.unsubscribe("/custom-wizard/notices"); - }, - - subscribe() { - this.unsubscribe(); - this.messageBus.subscribe("/custom-wizard/notices", (data) => { - if (isPresent(data.active_notice_count)) { - this.loadCriticalNotices(); - } - }); - }, - - loadCriticalNotices() { - CustomWizardNotice.list({ - type: ["connection_error", "warning"], - archetype: "plugin_status", - visible: true, - }).then((result) => { - if (result.notices && result.notices.length) { - const criticalNotices = A( - result.notices.map((n) => CustomWizardNotice.create(n)) - ); - this.set("customWizardCriticalNotices", criticalNotices); - } - }); - }, - }); - api.modifyClass("component:d-navigation", { pluginId: "custom-wizard", actions: { diff --git a/assets/javascripts/discourse/mixins/notice-message.js.es6 b/assets/javascripts/discourse/mixins/notice-message.js.es6 deleted file mode 100644 index 76e311bb..00000000 --- a/assets/javascripts/discourse/mixins/notice-message.js.es6 +++ /dev/null @@ -1,68 +0,0 @@ -import Mixin from "@ember/object/mixin"; -import { bind, scheduleOnce } from "@ember/runloop"; -import { cookAsync } from "discourse/lib/text"; -import { createPopper } from "@popperjs/core"; - -export default Mixin.create({ - showCookedMessage: false, - - didReceiveAttrs() { - const message = this.notice.message; - cookAsync(message).then((cooked) => { - this.set("cookedMessage", cooked); - }); - }, - - createMessageModal() { - let container = this.element.querySelector(".notice-message"); - let modal = this.element.querySelector(".cooked-notice-message"); - - this._popper = createPopper(container, modal, { - strategy: "absolute", - placement: "bottom-start", - modifiers: [ - { - name: "preventOverflow", - }, - { - name: "offset", - options: { - offset: [0, 5], - }, - }, - ], - }); - }, - - didInsertElement() { - $(document).on("click", bind(this, this.documentClick)); - }, - - willDestroyElement() { - $(document).off("click", bind(this, this.documentClick)); - }, - - documentClick(event) { - if (this._state === "destroying") { - return; - } - - if ( - !event.target.closest( - `[data-notice-id="${this.notice.id}"] .notice-message` - ) - ) { - this.set("showCookedMessage", false); - } - }, - - actions: { - toggleCookedMessage() { - this.toggleProperty("showCookedMessage"); - - if (this.showCookedMessage) { - scheduleOnce("afterRender", this, this.createMessageModal); - } - }, - }, -}); diff --git a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 b/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 deleted file mode 100644 index 035e2ad5..00000000 --- a/assets/javascripts/discourse/models/custom-wizard-notice.js.es6 +++ /dev/null @@ -1,74 +0,0 @@ -import EmberObject from "@ember/object"; -import discourseComputed from "discourse-common/utils/decorators"; -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import { and, not, notEmpty } from "@ember/object/computed"; -import { dasherize } from "@ember/string"; -import I18n from "I18n"; - -const CustomWizardNotice = EmberObject.extend({ - expired: notEmpty("expired_at"), - dismissed: notEmpty("dismissed_at"), - hidden: notEmpty("hidden_at"), - notHidden: not("hidden"), - notDismissed: not("dismissed"), - canDismiss: and("dismissable", "notDismissed"), - canHide: and("can_hide", "notHidden"), - - @discourseComputed("type") - typeClass(type) { - return dasherize(type); - }, - - @discourseComputed("type") - typeLabel(type) { - return I18n.t(`admin.wizard.notice.type.${type}`); - }, - - dismiss() { - if (!this.get("canDismiss")) { - return; - } - - return ajax(`/admin/wizards/notice/${this.get("id")}/dismiss`, { - type: "PUT", - }) - .then((result) => { - if (result.success) { - this.set("dismissed_at", result.dismissed_at); - } - }) - .catch(popupAjaxError); - }, - - hide() { - if (!this.get("canHide")) { - return; - } - - return ajax(`/admin/wizards/notice/${this.get("id")}/hide`, { type: "PUT" }) - .then((result) => { - if (result.success) { - this.set("hidden_at", result.hidden_at); - } - }) - .catch(popupAjaxError); - }, -}); - -CustomWizardNotice.reopenClass({ - list(data = {}) { - return ajax("/admin/wizards/notice", { - type: "GET", - data, - }).catch(popupAjaxError); - }, - - dismissAll() { - return ajax("/admin/wizards/notice/dismiss", { - type: "PUT", - }).catch(popupAjaxError); - }, -}); - -export default CustomWizardNotice; diff --git a/assets/javascripts/discourse/models/custom-wizard-subscription.js.es6 b/assets/javascripts/discourse/models/custom-wizard-subscription.js.es6 deleted file mode 100644 index 469460d5..00000000 --- a/assets/javascripts/discourse/models/custom-wizard-subscription.js.es6 +++ /dev/null @@ -1,33 +0,0 @@ -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import EmberObject from "@ember/object"; - -const CustomWizardSubscription = EmberObject.extend(); - -const basePath = "/admin/wizards/subscription"; - -CustomWizardSubscription.reopenClass({ - status() { - return ajax(basePath, { - type: "GET", - }).catch(popupAjaxError); - }, - - authorize() { - window.location.href = `${basePath}/authorize`; - }, - - unauthorize() { - return ajax(`${basePath}/authorize`, { - type: "DELETE", - }).catch(popupAjaxError); - }, - - update() { - return ajax(basePath, { - type: "POST", - }).catch(popupAjaxError); - }, -}); - -export default CustomWizardSubscription; diff --git a/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 deleted file mode 100644 index 1d8b7cc8..00000000 --- a/assets/javascripts/discourse/routes/admin-wizards-notices.js.es6 +++ /dev/null @@ -1,17 +0,0 @@ -import CustomWizardNotice from "../models/custom-wizard-notice"; -import DiscourseRoute from "discourse/routes/discourse"; -import { A } from "@ember/array"; - -export default DiscourseRoute.extend({ - model() { - return CustomWizardNotice.list({ include_all: true }); - }, - - setupController(controller, model) { - controller.setProperties({ - notices: A( - model.notices.map((notice) => CustomWizardNotice.create(notice)) - ), - }); - }, -}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-subscription.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-subscription.js.es6 deleted file mode 100644 index 4824425a..00000000 --- a/assets/javascripts/discourse/routes/admin-wizards-subscription.js.es6 +++ /dev/null @@ -1,19 +0,0 @@ -import CustomWizardSubscription from "../models/custom-wizard-subscription"; -import DiscourseRoute from "discourse/routes/discourse"; - -export default DiscourseRoute.extend({ - model() { - return CustomWizardSubscription.status(); - }, - - setupController(controller, model) { - controller.set("model", model); - controller.setup(); - }, - - actions: { - authorize() { - CustomWizardSubscription.authorize(); - }, - }, -}); diff --git a/assets/javascripts/discourse/templates/admin-wizards-notices.hbs b/assets/javascripts/discourse/templates/admin-wizards-notices.hbs deleted file mode 100644 index 039afe49..00000000 --- a/assets/javascripts/discourse/templates/admin-wizards-notices.hbs +++ /dev/null @@ -1,49 +0,0 @@ -
-

{{i18n "admin.wizard.notices.title"}}

- -
- {{d-button - label="admin.wizard.notice.dismiss_all.label" - title="admin.wizard.notice.dismiss_all.title" - action=(action "dismissAll") - disabled=allDismisssed - icon="check"}} -
-
- -{{wizard-message - key=messageKey - url=messageUrl - type=messageType - opts=messageOpts - items=messageItems - loading=loading - component="notices"}} - -
- {{#load-more selector=".wizard-table tr" action=(action "loadMore")}} - {{#if hasNotices}} - - - - - - - - - - - {{#each notices as |notice|}} - {{wizard-notice-row notice=notice}} - {{/each}} - -
{{I18n "admin.wizard.notice.time"}}{{I18n "admin.wizard.notice.type.label"}}{{I18n "admin.wizard.notice.title"}}{{I18n "admin.wizard.notice.status"}}
- {{else}} - {{#unless loadingMore}} -

{{i18n "search.no_results"}}

- {{/unless}} - {{/if}} - - {{conditional-loading-spinner condition=loadingMore}} - {{/load-more}} -
diff --git a/assets/javascripts/discourse/templates/admin-wizards-subscription.hbs b/assets/javascripts/discourse/templates/admin-wizards-subscription.hbs deleted file mode 100644 index c75f3e1b..00000000 --- a/assets/javascripts/discourse/templates/admin-wizards-subscription.hbs +++ /dev/null @@ -1,35 +0,0 @@ -
-

{{i18n "admin.wizard.subscription.title"}}

- -
- {{#if model.authentication.active}} - {{conditional-loading-spinner size="small" condition=unauthorizing}} - - {{i18n "admin.wizard.subscription.unauthorize"}} - - - {{else}} - {{d-button - icon="id-card" - class="btn-primary" - label="admin.wizard.subscription.authorize" - title="admin.wizard.subscription.authorize" - action=(route-action "authorize")}} - {{/if}} -
-
- -{{wizard-message - key=messageKey - url=messageUrl - type=messageType - opts=messageOpts - component="subscription"}} - -
- {{#if showSubscription}} - {{wizard-subscription subscription=model.subscription}} - {{/if}} -
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index 4447380f..a50748cc 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -7,15 +7,8 @@ {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} - {{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}}
- {{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs b/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs deleted file mode 100644 index cc22a42e..00000000 --- a/assets/javascripts/discourse/templates/components/wizard-notice-row.hbs +++ /dev/null @@ -1,30 +0,0 @@ - - {{#if notice.updated_at}} - {{notice-badge class="notice-updated-at" date=notice.updated_at label="admin.wizard.notice.updated_at" leaveAgo=true}} - {{else}} - {{notice-badge class="notice-created-at" date=notice.created_at label="admin.wizard.notice.created_at" leaveAgo=true}} - {{/if}} - -{{notice.typeLabel}} - - {{notice.title}} - {{#if showCookedMessage}} - {{cookedMessage}} - {{/if}} - - - {{#if notice.canDismiss}} - {{d-button - action="dismiss" - label="admin.wizard.notice.dismiss.label" - title="admin.wizard.notice.dismiss.title" - class="btn-dismiss" - icon="check"}} - {{else if notice.dismissed}} - {{i18n "admin.wizard.notice.dismissed_at"}} {{format-date notice.dismissed_at leaveAgo="true"}} - {{else if notice.expired}} - {{i18n "admin.wizard.notice.expired_at"}} {{format-date notice.expired_at leaveAgo="true"}} - {{else}} - {{i18n "admin.wizard.notice.active"}} - {{/if}} - diff --git a/assets/javascripts/discourse/templates/components/wizard-notice.hbs b/assets/javascripts/discourse/templates/components/wizard-notice.hbs deleted file mode 100644 index 24a853d3..00000000 --- a/assets/javascripts/discourse/templates/components/wizard-notice.hbs +++ /dev/null @@ -1,39 +0,0 @@ -
-
- {{notice.title}} - {{#if showCookedMessage}} - {{cookedMessage}} - {{/if}} -
-
- {{#if notice.expired}} - {{notice-badge class="notice-expired-at" icon="check" label="admin.wizard.notice.expired_at" date=notice.expired_at}} - {{/if}} - {{#if showPlugin}} - {{notice-badge class="notice-plugin" icon="plug" title="admin.wizard.notice.plugin" label="admin.wizard.notice.plugin" url="/admin/wizards/notices"}} - {{/if}} - {{notice-badge class="notice-created-at" icon="far-clock" label="admin.wizard.notice.created_at" date=notice.created_at leaveAgo=true}} - {{#if notice.updated_at}} - {{notice-badge class="notice-updated-at" icon="far-clock" label="admin.wizard.notice.updated_at" date=notice.updated_at}} - {{/if}} - - {{#if notice.canDismiss}} -
- {{#if dismissing}} - {{loading-spinner size="small"}} - {{else}} - {{d-icon "times"}} - {{/if}} -
- {{/if}} - {{#if notice.canHide}} -
- {{#if hiding}} - {{loading-spinner size="small"}} - {{else}} - {{d-icon "far-eye-slash"}} - {{/if}} -
- {{/if}} -
-
diff --git a/assets/javascripts/discourse/templates/components/wizard-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription.hbs deleted file mode 100644 index 418225a3..00000000 --- a/assets/javascripts/discourse/templates/components/wizard-subscription.hbs +++ /dev/null @@ -1,31 +0,0 @@ -
-

{{title}}

- -
- - {{#if updating}} - {{loading-spinner size="small"}} - {{else if updateIcon}} - {{d-icon updateIcon}} - {{/if}} - - {{d-button - icon="sync" - action=(action "update") - disabled=updating - title="admin.wizard.subscription.subscription.update" - label="admin.wizard.subscription.subscription.update"}} -
-
- -{{#if subscribed}} -
-
{{stateLabel}}
- - {{#if subscription.updated_at}} -
- {{i18n "admin.wizard.subscription.subscription.last_updated"}} {{format-date subscription.updated_at leaveAgo="true"}} -
- {{/if}} -
-{{/if}} diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index d2c503ad..dec0c4b4 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,17 +1,19 @@ // discourse-skip-module -document.addEventListener("DOMContentLoaded", function () { - document.body.insertAdjacentHTML( - "afterbegin", - ` -
- - ` - ); -}); +if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { + document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); + }); -Object.keys(requirejs.entries).forEach(function (entry) { - if (/\-test/.test(entry)) { - requirejs(entry); - } -}); + Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); + } + }); +} diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index d0ca29a6..2483503b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -54,20 +54,6 @@ en: after_time: "After time setting is invalid." liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}" - notice: - connection_error: "Failed to connect to http://%{domain}" - compatibility_issue: - title: The Custom Wizard Plugin is incompatibile with the latest version of Discourse. - message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. - plugin_status: - connection_error: - title: Unable to connect to the Custom Wizard Plugin status server - message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. If this issue persists contact support@thepavilion.io for further assistance. - subscription_message: - connection_error: - title: Unable to connect to the Custom Wizard Plugin subscription server - message: If this issue persists contact support@thepavilion.io for further assistance. - site_settings: custom_wizard_enabled: "Enable custom wizards." wizard_redirect_exclude_paths: "Routes excluded from wizard redirects." diff --git a/config/routes.rb b/config/routes.rb index 514605de..2d9f52c6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -45,17 +45,5 @@ Discourse::Application.routes.append do get 'admin/wizards/manager/export' => 'admin_manager#export' post 'admin/wizards/manager/import' => 'admin_manager#import' delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy' - - get 'admin/wizards/subscription' => 'admin_subscription#index' - post 'admin/wizards/subscription' => 'admin_subscription#update_subscription' - get 'admin/wizards/subscription/authorize' => 'admin_subscription#authorize' - get 'admin/wizards/subscription/authorize/callback' => 'admin_subscription#authorize_callback' - delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication' - - get 'admin/wizards/notice' => 'admin_notice#index' - put 'admin/wizards/notice/:notice_id/dismiss' => 'admin_notice#dismiss' - put 'admin/wizards/notice/:notice_id/hide' => 'admin_notice#hide' - put 'admin/wizards/notice/dismiss' => 'admin_notice#dismiss_all' - get 'admin/wizards/notices' => 'admin_notice#index' end end diff --git a/lib/custom_wizard/notice.rb b/lib/custom_wizard/notice.rb deleted file mode 100644 index 9d5c1edf..00000000 --- a/lib/custom_wizard/notice.rb +++ /dev/null @@ -1,358 +0,0 @@ -# frozen_string_literal: true - -class CustomWizard::Notice - include ActiveModel::Serialization - - PLUGIN_STATUS_DOMAINS = { - "main" => "plugins.discourse.pavilion.tech", - "master" => "plugins.discourse.pavilion.tech", - "tests-passed" => "plugins.discourse.pavilion.tech", - "stable" => "stable.plugins.discourse.pavilion.tech" - } - SUBSCRIPTION_MESSAGE_DOMAIN = "test.thepavilion.io" - LOCALHOST_DOMAIN = "localhost:3000" - PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing) - CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable) - PAGE_LIMIT = 30 - - attr_reader :id, - :title, - :message, - :type, - :archetype, - :created_at - - attr_accessor :retrieved_at, - :updated_at, - :dismissed_at, - :expired_at, - :hidden_at - - def initialize(attrs) - @id = self.class.generate_notice_id(attrs[:title], attrs[:created_at]) - @title = attrs[:title] - @message = attrs[:message] - @type = attrs[:type].to_i - @archetype = attrs[:archetype].to_i - @created_at = attrs[:created_at] - @updated_at = attrs[:updated_at] - @retrieved_at = attrs[:retrieved_at] - @dismissed_at = attrs[:dismissed_at] - @expired_at = attrs[:expired_at] - @hidden_at = attrs[:hidden_at] - end - - def dismiss! - if dismissable? - self.dismissed_at = DateTime.now.iso8601(3) - self.save_and_publish - end - end - - def hide! - if can_hide? - self.hidden_at = DateTime.now.iso8601(3) - self.save_and_publish - end - end - - def expire! - if !expired? - self.expired_at = DateTime.now.iso8601(3) - self.save_and_publish - end - end - - def save_and_publish - if self.save - self.class.publish_notice_count - true - else - false - end - end - - def expired? - expired_at.present? - end - - def dismissed? - dismissed_at.present? - end - - def dismissable? - !expired? && !dismissed? && type === self.class.types[:info] - end - - def hidden? - hidden_at.present? - end - - def can_hide? - !hidden? && ( - type === self.class.types[:connection_error] || - type === self.class.types[:warning] - ) && ( - archetype === self.class.archetypes[:plugin_status] - ) - end - - def save - attrs = { - expired_at: expired_at, - updated_at: updated_at, - retrieved_at: retrieved_at, - created_at: created_at, - title: title, - message: message, - type: type, - archetype: archetype - } - - if current = self.class.find(self.id) - attrs[:dismissed_at] = current.dismissed_at || self.dismissed_at - attrs[:hidden_at] = current.hidden_at || self.hidden_at - end - - self.class.store(id, attrs) - end - - def self.types - @types ||= Enum.new( - info: 0, - warning: 1, - connection_error: 2 - ) - end - - def self.archetypes - @archetypes ||= Enum.new( - subscription_message: 0, - plugin_status: 1 - ) - end - - def self.update(skip_subscription: false, skip_plugin: false) - notices = [] - - if !skip_subscription - subscription_messages = request(:subscription_message) - - if subscription_messages.present? - subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages]) - notices.push(*subscription_notices) - end - end - - if !skip_plugin && request_plugin_status? - plugin_status = request(:plugin_status) - - if plugin_status.present? && plugin_status[:status].present? - plugin_notice = convert_plugin_status_to_notice(plugin_status) - notices.push(plugin_notice) if plugin_notice - end - end - - if notices.any? - notices.each do |notice_data| - notice = new(notice_data) - notice.retrieved_at = DateTime.now.iso8601(3) - notice.save - end - end - - publish_notice_count - end - - def self.publish_notice_count - payload = { - active_notice_count: CustomWizard::Notice.active_count - } - MessageBus.publish("/custom-wizard/notices", payload, group_ids: [Group::AUTO_GROUPS[:admins]]) - end - - def self.convert_subscription_messages_to_notices(messages) - messages.reduce([]) do |result, message| - id = generate_notice_id(message[:title], message[:created_at]) - result.push( - id: id, - title: message[:title], - message: message[:message], - type: types[message[:type].to_sym], - archetype: archetypes[:subscription_message], - created_at: message[:created_at], - expired_at: message[:expired_at] - ) - result - end - end - - def self.convert_plugin_status_to_notice(plugin_status) - notice = nil - - if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status]) - title = I18n.t('wizard.notice.compatibility_issue.title') - created_at = plugin_status[:status_changed_at] - id = generate_notice_id(title, created_at) - - unless exists?(id) - message = I18n.t('wizard.notice.compatibility_issue.message', domain: plugin_status_domain) - notice = { - id: id, - title: title, - message: message, - type: types[:warning], - archetype: archetypes[:plugin_status], - created_at: created_at - } - end - else - expire_all(types[:warning], archetypes[:plugin_status]) - end - - notice - end - - def self.notify_connection_errors(archetype) - domain = self.send("#{archetype.to_s}_domain") - title = I18n.t("wizard.notice.#{archetype.to_s}.connection_error.title") - notices = list(type: types[:connection_error], archetype: archetypes[archetype.to_sym], title: title) - - if notices.any? - notice = notices.first - notice.updated_at = DateTime.now.iso8601(3) - notice.save - else - notice = new( - title: title, - message: I18n.t("wizard.notice.#{archetype.to_s}.connection_error.message", domain: domain), - archetype: archetypes[archetype.to_sym], - type: types[:connection_error], - created_at: DateTime.now.iso8601(3), - updated_at: DateTime.now.iso8601(3) - ) - notice.save - end - end - - def self.request_plugin_status? - CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development? - end - - def self.subscription_message_domain - return LOCALHOST_DOMAIN if (Rails.env.test? || Rails.env.development?) - SUBSCRIPTION_MESSAGE_DOMAIN - end - - def self.subscription_message_url - "https://#{subscription_message_domain}/subscription-server/messages.json" - end - - def self.plugin_status_domain - return LOCALHOST_DOMAIN if (Rails.env.test? || Rails.env.development?) - PLUGIN_STATUS_DOMAINS[Discourse.git_branch] - end - - def self.plugin_status_url - "https://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard" - end - - def self.request(archetype) - url = self.send("#{archetype.to_s}_url") - - begin - response = Excon.get(url) - rescue Excon::Error::Socket, Excon::Error::Timeout => e - response = nil - end - connection_error = CustomWizard::Notice::ConnectionError.new(archetype) - - if response && response.status == 200 - connection_error.expire! - expire_all(types[:connection_error], archetypes[archetype.to_sym]) - - begin - data = JSON.parse(response.body).deep_symbolize_keys - rescue JSON::ParserError - return nil - end - - data - else - connection_error.create! - notify_connection_errors(archetype) if connection_error.reached_limit? - nil - end - end - - def self.namespace - "#{CustomWizard::PLUGIN_NAME}_notice" - end - - def self.find(id) - raw = PluginStore.get(namespace, id) - new(raw.symbolize_keys) if raw.present? - end - - def self.exists?(id) - PluginStoreRow.where(plugin_name: namespace, key: id).exists? - end - - def self.store(id, raw_notice) - PluginStore.set(namespace, id, raw_notice) - end - - def self.list_query(type: nil, archetype: nil, title: nil, include_all: false, page: nil, visible: false) - query = PluginStoreRow.where(plugin_name: namespace) - query = query.where("(value::json->>'hidden_at') IS NULL") if visible - query = query.where("(value::json->>'dismissed_at') IS NULL") unless include_all - query = query.where("(value::json->>'expired_at') IS NULL") unless include_all - query = query.where("(value::json->>'archetype')::integer = ?", archetype) if archetype - if type - type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?" - query = query.where(type_query_str, type) - end - query = query.where("(value::json->>'title')::text = ?", title) if title - query = query.limit(PAGE_LIMIT).offset(page.to_i * PAGE_LIMIT) if !page.nil? - query.order("value::json->>'expired_at' DESC, value::json->>'updated_at' DESC,value::json->>'dismissed_at' DESC, value::json->>'created_at' DESC") - end - - def self.list(type: nil, archetype: nil, title: nil, include_all: false, page: 0, visible: false) - list_query(type: type, archetype: archetype, title: title, include_all: include_all, page: page, visible: visible) - .map { |r| self.new(JSON.parse(r.value).symbolize_keys) } - end - - def self.active_count - list_query.count - end - - def self.dismiss_all - dismissed_count = PluginStoreRow.where(" - plugin_name = '#{namespace}' AND - (value::json->>'type')::integer = #{types[:info]} AND - (value::json->>'expired_at') IS NULL AND - (value::json->>'dismissed_at') IS NULL - ").update_all(" - value = jsonb_set(value::jsonb, '{dismissed_at}', (to_json(now())::text)::jsonb, true) - ") - publish_notice_count if dismissed_count.to_i > 0 - dismissed_count - end - - def self.expire_all(type, archetype) - expired_count = PluginStoreRow.where(" - plugin_name = '#{namespace}' AND - (value::json->>'type')::integer = #{type} AND - (value::json->>'archetype')::integer = #{archetype} AND - (value::json->>'expired_at') IS NULL - ").update_all(" - value = jsonb_set(value::jsonb, '{expired_at}', (to_json(now())::text)::jsonb, true) - ") - publish_notice_count if expired_count.to_i > 0 - expired_count - end - - def self.generate_notice_id(title, created_at) - Digest::SHA1.hexdigest("#{title}-#{created_at}") - end -end diff --git a/lib/custom_wizard/notice/connection_error.rb b/lib/custom_wizard/notice/connection_error.rb deleted file mode 100644 index a1d834c6..00000000 --- a/lib/custom_wizard/notice/connection_error.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -class CustomWizard::Notice::ConnectionError - - attr_reader :archetype - - def initialize(archetype) - @archetype = archetype - end - - def create! - if attrs = current_error - key = "#{archetype.to_s}_error_#{attrs[:id]}" - attrs[:updated_at] = Time.now - attrs[:count] = attrs[:count].to_i + 1 - else - domain = CustomWizard::Notice.send("#{archetype.to_s}_domain") - id = SecureRandom.hex(8) - attrs = { - id: id, - message: I18n.t("wizard.notice.connection_error", domain: domain), - archetype: CustomWizard::Notice.archetypes[archetype.to_sym], - created_at: Time.now, - count: 1 - } - key = "#{archetype.to_s}_error_#{id}" - end - - PluginStore.set(namespace, key, attrs) - - @current_error = nil - end - - def expire! - if query = current_error(query_only: true) - record = query.first - error = JSON.parse(record.value) - error['expired_at'] = Time.now - record.value = error.to_json - record.save - end - end - - def plugin_status_limit - 5 - end - - def subscription_message_limit - 10 - end - - def limit - self.send("#{archetype.to_s}_limit") - end - - def reached_limit? - return false unless current_error.present? - current_error[:count].to_i >= limit - end - - def namespace - "#{CustomWizard::PLUGIN_NAME}_notice_connection" - end - - def current_error(query_only: false) - @current_error ||= begin - query = PluginStoreRow.where(plugin_name: namespace) - query = query.where("(value::json->>'archetype')::integer = ?", CustomWizard::Notice.archetypes[archetype.to_sym]) - query = query.where("(value::json->>'expired_at') IS NULL") - - return nil if !query.exists? - return query if query_only - - JSON.parse(query.first.value).deep_symbolize_keys - end - end -end diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index 7bac7f59..39febc14 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -1,240 +1,8 @@ -# frozen_string_literal: true - class CustomWizard::Subscription - include ActiveModel::Serialization - - attr_accessor :authentication, - :subscription - - SUBSCRIPTION_LEVELS = { - standard: { - actions: ["send_message", "add_to_group", "watch_categories"], - custom_fields: { - klass: [], - type: ["json"], - }, - }, - business: { - actions: ["create_category", "create_group", "send_to_api"], - custom_fields: { - klass: ["group", "category"], - type: [], - }, - }, - } - - def initialize - @authentication = CustomWizard::Subscription::Authentication.new(get_authentication) - @subscription = CustomWizard::Subscription::Subscription.new(get_subscription) - end - - def authorized? - @authentication.active? - end - - def subscribed? - @subscription.active? - end - - def server - "test.thepavilion.io" - end - - def subscription_type - "stripe" - end - - def type - @subscription.type - end - - def client_name - "custom-wizard" - end - - def scope - "discourse-subscription-server:user_subscription" - end - - def requires_additional_subscription(kategory, sub_kategory) - case kategory - when "actions" - case self.type - when "business" - [] - when "standard" - SUBSCRIPTION_LEVELS[:business][kategory.to_sym] - else - SUBSCRIPTION_LEVELS[:standard][kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym] - end - when "custom_fields" - case self.type - when "business" - [] - when "standard" - SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym] - else - SUBSCRIPTION_LEVELS[:standard][kategory.to_sym][sub_kategory.to_sym] + SUBSCRIPTION_LEVELS[:business][kategory.to_sym][sub_kategory.to_sym] - end - else - [] - end - end - - def update - if @authentication.active? - response = Excon.get( - "https://#{server}/subscription-server/user-subscriptions/#{subscription_type}/#{client_name}", - headers: { - "User-Api-Key" => @authentication.api_key - } - ) - - if response.status == 200 - begin - data = JSON.parse(response.body).deep_symbolize_keys - rescue JSON::ParserError - return false - end - - return false unless data && data.is_a?(Hash) - subscriptions = data[:subscriptions] - - if subscriptions.present? && type = subscriptions.first[:price_nickname] - @subscription = set_subscription(type) - return true - end - end - end - - destroy_subscription - false - end - - def destroy_subscription - if remove_subscription - @subscription = CustomWizard::Subscription::Subscription.new(get_subscription) - !@subscription.active? - else - false - end - end - - def authentication_url(user_id, request_id) - keys = @authentication.generate_keys(user_id, request_id) - params = { - public_key: keys.public_key, - nonce: keys.nonce, - client_id: @authentication.client_id, - auth_redirect: "#{Discourse.base_url}/admin/wizards/subscription/authorize/callback", - application_name: SiteSetting.title, - scopes: scope - } - - uri = URI.parse("https://#{server}/user-api-key/new") - uri.query = URI.encode_www_form(params) - uri.to_s - end - - def authentication_response(request_id, payload) - data = @authentication.decrypt_payload(request_id, payload) - return false unless data.is_a?(Hash) && data[:key] && data[:user_id] - - api_key = data[:key] - user_id = data[:user_id] - user = User.find(user_id) - - if user&.admin - @authentication = set_authentication(api_key, user.id) - true - else - false - end - end - - def destroy_authentication - if remove_authentication - @authentication = CustomWizard::Subscription::Authentication.new(get_authentication) - !@authentication.active? - else - false - end - end - def self.subscribed? - self.new.subscribed? end def self.type - self.new.type - end - def self.requires_additional_subscription(kategory, sub_kategory) - self.new.requires_additional_subscription(kategory, sub_kategory) - end - - def self.authorized? - self.new.authorized? - end - - def self.update - self.new.update - end - - def self.namespace - "custom_wizard_subscription" - end - - private - - def subscription_db_key - "subscription" - end - - def authentication_db_key - "authentication" - end - - def get_subscription - raw = PluginStore.get(self.class.namespace, subscription_db_key) - - if raw.present? - OpenStruct.new( - type: raw['type'], - updated_at: raw['updated_at'] - ) - end - end - - def remove_subscription - PluginStore.remove(self.class.namespace, subscription_db_key) - end - - def set_subscription(type) - PluginStore.set(CustomWizard::Subscription.namespace, subscription_db_key, type: type, updated_at: Time.now) - CustomWizard::Subscription::Subscription.new(get_subscription) - end - - def get_authentication - raw = PluginStore.get(self.class.namespace, authentication_db_key) - OpenStruct.new( - key: raw && raw['key'], - auth_by: raw && raw['auth_by'], - auth_at: raw && raw['auth_at'] - ) - end - - def set_authentication(key, user_id) - PluginStore.set(self.class.namespace, authentication_db_key, - key: key, - auth_by: user_id, - auth_at: Time.now - ) - CustomWizard::Subscription::Authentication.new(get_authentication) - end - - def remove_authentication - PluginStore.remove(self.class.namespace, authentication_db_key) - get_authentication end end diff --git a/lib/custom_wizard/subscription/authentication.rb b/lib/custom_wizard/subscription/authentication.rb deleted file mode 100644 index 7ad02d2c..00000000 --- a/lib/custom_wizard/subscription/authentication.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true -class CustomWizard::Subscription::Authentication - include ActiveModel::Serialization - - attr_reader :client_id, - :auth_by, - :auth_at, - :api_key - - def initialize(auth) - if auth - @api_key = auth.key - @auth_at = auth.auth_at - @auth_by = auth.auth_by - end - - @client_id = get_client_id || set_client_id - end - - def active? - @api_key.present? - end - - def generate_keys(user_id, request_id) - rsa = OpenSSL::PKey::RSA.generate(2048) - nonce = SecureRandom.hex(32) - set_keys(request_id, user_id, rsa, nonce) - - OpenStruct.new(nonce: nonce, public_key: rsa.public_key) - end - - def decrypt_payload(request_id, payload) - keys = get_keys(request_id) - - return false unless keys.present? && keys.pem - delete_keys(request_id) - - rsa = OpenSSL::PKey::RSA.new(keys.pem) - decrypted_payload = rsa.private_decrypt(Base64.decode64(payload)) - - return false unless decrypted_payload.present? - - begin - data = JSON.parse(decrypted_payload).symbolize_keys - rescue JSON::ParserError - return false - end - - return false unless data[:nonce] == keys.nonce - data[:user_id] = keys.user_id - - data - end - - def get_keys(request_id) - raw = PluginStore.get(CustomWizard::Subscription.namespace, "#{keys_db_key}_#{request_id}") - OpenStruct.new( - user_id: raw && raw['user_id'], - pem: raw && raw['pem'], - nonce: raw && raw['nonce'] - ) - end - - private - - def keys_db_key - "keys" - end - - def client_id_db_key - "client_id" - end - - def set_keys(request_id, user_id, rsa, nonce) - PluginStore.set(CustomWizard::Subscription.namespace, "#{keys_db_key}_#{request_id}", - user_id: user_id, - pem: rsa.export, - nonce: nonce - ) - end - - def delete_keys(request_id) - PluginStore.remove(CustomWizard::Subscription.namespace, "#{keys_db_key}_#{request_id}") - end - - def get_client_id - PluginStore.get(CustomWizard::Subscription.namespace, client_id_db_key) - end - - def set_client_id - client_id = SecureRandom.hex(32) - PluginStore.set(CustomWizard::Subscription.namespace, client_id_db_key, client_id) - client_id - end -end diff --git a/lib/custom_wizard/subscription/subscription.rb b/lib/custom_wizard/subscription/subscription.rb deleted file mode 100644 index 0b3bb84d..00000000 --- a/lib/custom_wizard/subscription/subscription.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -class CustomWizard::Subscription::Subscription - include ActiveModel::Serialization - - attr_reader :type, - :updated_at - - def initialize(subscription) - if subscription - @type = subscription.type - @updated_at = subscription.updated_at - end - end - - def types - %w(none standard business) - end - - def active? - types.include?(type) && updated_at.to_datetime > (Time.zone.now - 2.hours).to_datetime - end -end diff --git a/plugin.rb b/plugin.rb index e745c3a8..03862103 100644 --- a/plugin.rb +++ b/plugin.rb @@ -70,15 +70,11 @@ after_initialize do ../app/controllers/custom_wizard/admin/logs.rb ../app/controllers/custom_wizard/admin/manager.rb ../app/controllers/custom_wizard/admin/custom_fields.rb - ../app/controllers/custom_wizard/admin/subscription.rb - ../app/controllers/custom_wizard/admin/notice.rb ../app/controllers/custom_wizard/wizard.rb ../app/controllers/custom_wizard/steps.rb ../app/controllers/custom_wizard/realtime_validations.rb ../app/jobs/regular/refresh_api_access_token.rb ../app/jobs/regular/set_after_time_wizard.rb - ../app/jobs/scheduled/custom_wizard/update_subscription.rb - ../app/jobs/scheduled/custom_wizard/update_notices.rb ../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/action_result.rb @@ -97,11 +93,6 @@ after_initialize do ../lib/custom_wizard/submission.rb ../lib/custom_wizard/template.rb ../lib/custom_wizard/wizard.rb - ../lib/custom_wizard/notice.rb - ../lib/custom_wizard/notice/connection_error.rb - ../lib/custom_wizard/subscription.rb - ../lib/custom_wizard/subscription/subscription.rb - ../lib/custom_wizard/subscription/authentication.rb ../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/endpoint.rb @@ -122,10 +113,6 @@ after_initialize do ../app/serializers/custom_wizard/log_serializer.rb ../app/serializers/custom_wizard/submission_serializer.rb ../app/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb - ../app/serializers/custom_wizard/subscription/authentication_serializer.rb - ../app/serializers/custom_wizard/subscription/subscription_serializer.rb - ../app/serializers/custom_wizard/subscription_serializer.rb - ../app/serializers/custom_wizard/notice_serializer.rb ../lib/custom_wizard/extensions/extra_locales_controller.rb ../lib/custom_wizard/extensions/invites_controller.rb ../lib/custom_wizard/extensions/users_controller.rb @@ -271,14 +258,6 @@ after_initialize do "#{serializer_klass}_serializer".classify.constantize.prepend CustomWizardCustomFieldSerializer end - AdminDashboardData.add_problem_check do - warning_notices = CustomWizard::Notice.list( - type: CustomWizard::Notice.types[:warning], - archetype: CustomWizard::Notice.archetypes[:plugin_status] - ) - warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil - end - reloadable_patch do |plugin| ::TagsController.prepend CustomWizardTagsController ::DiscourseTagging.singleton_class.prepend CustomWizardDiscourseTagging From f607863510f0cb58d961192409244151df119120 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 25 Mar 2022 12:18:54 +0100 Subject: [PATCH 194/556] Remove more subscription related things and integrate with subscription client --- app/controllers/custom_wizard/admin/admin.rb | 6 +- .../custom_wizard/admin/custom_fields.rb | 4 +- app/controllers/custom_wizard/admin/wizard.rb | 4 +- .../components/custom-field-input.js.es6 | 33 --- .../components/wizard-custom-action.js.es6 | 26 +- .../wizard-subscription-badge.js.es6 | 30 ++ .../wizard-subscription-container.js.es6 | 26 ++ .../wizard-subscription-selector.js.es6 | 49 ++- .../custom-wizard-category-settings.hbs | 12 +- .../admin-wizards-wizard-show.js.es6 | 1 + .../controllers/admin-wizards.js.es6 | 27 +- .../custom-wizard-admin-route-map.js.es6 | 10 - .../discourse/lib/wizard-schema.js.es6 | 90 +----- .../discourse/lib/wizard-subscription.js.es6 | 17 ++ .../discourse/mixins/subscription.js.es6 | 26 ++ .../routes/admin-wizards-custom-fields.js.es6 | 6 +- .../routes/admin-wizards-wizard-show.js.es6 | 4 +- .../discourse/routes/admin-wizards.js.es6 | 18 +- .../templates/admin-wizards-custom-fields.hbs | 4 +- .../templates/admin-wizards-wizard-show.hbs | 8 +- .../discourse/templates/admin-wizards.hbs | 3 +- .../components/custom-field-input.hbs | 24 +- .../components/wizard-custom-action.hbs | 3 +- .../components/wizard-custom-field.hbs | 9 +- .../components/wizard-custom-step.hbs | 8 +- .../components/wizard-subscription-badge.hbs | 6 + ....hbs => wizard-subscription-container.hbs} | 4 +- .../wizard-subscription-selector-header.hbs | 6 +- .../wizard-subscription-selector-row.hbs | 4 +- assets/stylesheets/admin/admin.scss | 279 +++++------------- .../stylesheets/admin/wizard/variables.scss | 5 + config/locales/client.en.yml | 89 ++---- config/settings.yml | 2 +- lib/custom_wizard/custom_field.rb | 4 +- lib/custom_wizard/mapper.rb | 3 +- lib/custom_wizard/subscription.rb | 164 +++++++++- lib/custom_wizard/validators/template.rb | 43 +-- plugin.rb | 25 +- spec/components/custom_wizard/notice_spec.rb | 159 ---------- .../custom_wizard/subscription_spec.rb | 125 -------- spec/jobs/update_notices_spec.rb | 29 -- spec/jobs/update_subscription_spec.rb | 11 - spec/plugin_helper.rb | 35 --- .../admin/notice_controller_spec.rb | 72 ----- .../admin/subscription_controller_spec.rb | 71 ----- .../custom_wizard/notice_serializer_spec.rb | 22 -- .../authentication_serializer_spec.rb | 17 -- .../subscription_serializer_spec.rb | 14 - .../subscription_serializer_spec.rb | 14 - 49 files changed, 522 insertions(+), 1129 deletions(-) create mode 100644 assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 create mode 100644 assets/javascripts/discourse/components/wizard-subscription-container.js.es6 create mode 100644 assets/javascripts/discourse/lib/wizard-subscription.js.es6 create mode 100644 assets/javascripts/discourse/mixins/subscription.js.es6 create mode 100644 assets/javascripts/discourse/templates/components/wizard-subscription-badge.hbs rename assets/javascripts/discourse/templates/components/{subscription-container.hbs => wizard-subscription-container.hbs} (54%) delete mode 100644 spec/components/custom_wizard/notice_spec.rb delete mode 100644 spec/components/custom_wizard/subscription_spec.rb delete mode 100644 spec/jobs/update_notices_spec.rb delete mode 100644 spec/jobs/update_subscription_spec.rb delete mode 100644 spec/requests/custom_wizard/admin/notice_controller_spec.rb delete mode 100644 spec/requests/custom_wizard/admin/subscription_controller_spec.rb delete mode 100644 spec/serializers/custom_wizard/notice_serializer_spec.rb delete mode 100644 spec/serializers/custom_wizard/subscription/authentication_serializer_spec.rb delete mode 100644 spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb delete mode 100644 spec/serializers/custom_wizard/subscription_serializer_spec.rb diff --git a/app/controllers/custom_wizard/admin/admin.rb b/app/controllers/custom_wizard/admin/admin.rb index e011e094..867be56c 100644 --- a/app/controllers/custom_wizard/admin/admin.rb +++ b/app/controllers/custom_wizard/admin/admin.rb @@ -3,8 +3,12 @@ class CustomWizard::AdminController < ::Admin::AdminController before_action :ensure_admin def index + subcription = CustomWizard::Subscription.new render_json_dump( - api_section: ["business"].include?(CustomWizard::Subscription.type) + subscribed: subcription.subscribed?, + subscription_type: subcription.type, + subscription_attributes: CustomWizard::Subscription.attributes, + subscription_client_installed: subcription.client_installed? ) end diff --git a/app/controllers/custom_wizard/admin/custom_fields.rb b/app/controllers/custom_wizard/admin/custom_fields.rb index 1cd20f5d..111e9faf 100644 --- a/app/controllers/custom_wizard/admin/custom_fields.rb +++ b/app/controllers/custom_wizard/admin/custom_fields.rb @@ -2,9 +2,7 @@ class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController def index render_json_dump( - custom_fields: custom_field_list, - subscribed: CustomWizard::Subscription.subscribed?, - subscription: CustomWizard::Subscription.type + custom_fields: custom_field_list ) end diff --git a/app/controllers/custom_wizard/admin/wizard.rb b/app/controllers/custom_wizard/admin/wizard.rb index ad63209a..0a59e02b 100644 --- a/app/controllers/custom_wizard/admin/wizard.rb +++ b/app/controllers/custom_wizard/admin/wizard.rb @@ -10,9 +10,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController ), field_types: CustomWizard::Field.types, realtime_validations: CustomWizard::RealtimeValidation.types, - custom_fields: custom_field_list, - subscribed: CustomWizard::Subscription.subscribed?, - subscription: CustomWizard::Subscription.type + custom_fields: custom_field_list ) end diff --git a/assets/javascripts/discourse/components/custom-field-input.js.es6 b/assets/javascripts/discourse/components/custom-field-input.js.es6 index 7ee70716..5d2d6c3b 100644 --- a/assets/javascripts/discourse/components/custom-field-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-field-input.js.es6 @@ -3,29 +3,6 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { alias, equal, or } from "@ember/object/computed"; import I18n from "I18n"; -import wizardSchema, { - requiringAdditionalSubscription, - subscriptionLevel, -} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; - -const generateContent = function (kategory, subscription) { - let unsubscribedCustomFields = requiringAdditionalSubscription( - subscription, - "custom_fields", - kategory - ); - return wizardSchema.custom_field[kategory].reduce((result, item) => { - let disabled = unsubscribedCustomFields.includes(item); - result.push({ - id: item, - name: I18n.t(`admin.wizard.custom_field.${kategory}.${item}`), - subscription: subscriptionLevel(item, "custom_fields", kategory), - disabled, - }); - return result; - }, []); -}; - export default Component.extend({ tagName: "tr", topicSerializers: ["topic_view", "topic_list_item"], @@ -58,16 +35,6 @@ export default Component.extend({ } }, - @discourseComputed("subscription") - customFieldTypes(subscription) { - return generateContent("type", subscription); - }, - - @discourseComputed("subscription") - customFieldKlasses(subscription) { - return generateContent("klass", subscription); - }, - @observes("field.klass") clearSerializersWhenClassChanges() { this.set("field.serializers", null); diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 6cf22942..a0a9cc72 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,8 +1,6 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; -import wizardSchema, { - requiringAdditionalSubscription, - subscriptionLevel, -} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; +import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; +import { subscriptionSelectKitContent } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription"; import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; import { computed } from "@ember/object"; @@ -97,22 +95,8 @@ export default Component.extend(UndoChanges, { return apis.find((a) => a.name === api).endpoints; }, - @discourseComputed("subscription") - actionTypes(subscription) { - let unsubscribedActions = requiringAdditionalSubscription( - subscription, - "actions", - "" - ); - return Object.keys(wizardSchema.action.types).reduce((result, type) => { - let disabled = unsubscribedActions.includes(type); - result.push({ - id: type, - name: I18n.t(`admin.wizard.action.${type}.label`), - subscription: subscriptionLevel(type, "actions", ""), - disabled, - }); - return result; - }, []); + @discourseComputed + actionTypes() { + return subscriptionSelectKitContent("action", "types"); }, }); diff --git a/assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 new file mode 100644 index 00000000..7a63f4d3 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 @@ -0,0 +1,30 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; +import Subscription from "../mixins/subscription"; +import DiscourseURL from "discourse/lib/url"; +import I18n from "I18n"; + +export default Component.extend(Subscription, { + tagName: 'a', + classNameBindings: [":wizard-subscription-badge", "subscriptionType"], + attributeBindings: ["title"], + + @discourseComputed("subscriptionType") + i18nKey(type) { + return `admin.wizard.subscription_container.type.${type ? type : "none"}`; + }, + + @discourseComputed("i18nKey") + title(i18nKey) { + return I18n.t(`${i18nKey}.title`); + }, + + @discourseComputed("i18nKey") + label(i18nKey) { + return I18n.t(`${i18nKey}.label`); + }, + + click() { + DiscourseURL.routeTo(this.subscriptionLink); + } +}); diff --git a/assets/javascripts/discourse/components/wizard-subscription-container.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-container.js.es6 new file mode 100644 index 00000000..3569276c --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-subscription-container.js.es6 @@ -0,0 +1,26 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; +import Subscription from "../mixins/subscription"; + +export default Component.extend(Subscription, { + classNameBindings: [":wizard-subscription-container", "subscribed"], + + @discourseComputed("subscribed") + subscribedIcon(subscribed) { + return subscribed ? "check" : "dash"; + }, + + @discourseComputed("subscribed") + subscribedLabel(subscribed) { + return `admin.wizard.subscription_container.${ + subscribed ? "subscribed" : "not_subscribed" + }.label`; + }, + + @discourseComputed("subscribed") + subscribedTitle(subscribed) { + return `admin.wizard.subscription_container.${ + subscribed ? "subscribed" : "not_subscribed" + }.title`; + } +}); diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 index ea1aa5d9..59714578 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 @@ -1,6 +1,21 @@ import SingleSelectComponent from "select-kit/components/single-select"; +import Subscription from "../mixins/subscription"; +import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; +import { + subscriptionTypeSufficient, + subscriptionTypes +} from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription"; +import discourseComputed from "discourse-common/utils/decorators"; -export default SingleSelectComponent.extend({ +const nameKey = function(feature, attribute, value) { + if (feature === 'action') { + return `admin.wizard.action.${value}.label`; + } else { + return `admin.wizard.${feature}.${attribute}.${value}`; + } +} + +export default SingleSelectComponent.extend(Subscription, { classNames: ["combo-box", "wizard-subscription-selector"], selectKitOptions: { @@ -13,6 +28,38 @@ export default SingleSelectComponent.extend({ caretDownIcon: "caret-down", }, + requiredSubscriptionType(feature, attribute, value) { + let attributes = this.subscriptionAttributes[feature]; + if (!attributes || !attributes[attribute]) return null; + + let requiredType = null; + Object.keys(attributes[attribute]).some(subscriptionType => { + let values = attributes[attribute][subscriptionType]; + if (values[0] === "*" || values.includes(value)) { + if (subscriptionTypes.includes(subscriptionType)) { + requiredType = subscriptionType; + } + return true; + } + return false; + }); + + return requiredType; + }, + + @discourseComputed('feature', 'attribute') + content(feature, attribute) { + return wizardSchema[feature][attribute].map((value) => { + let requiredSubscriptionType = this.requiredSubscriptionType(feature, attribute, value); + return { + id: value, + name: I18n.t(nameKey(feature, attribute, value)), + subscriptionType: requiredSubscriptionType, + disabled: !subscriptionTypeSufficient(this.subscriptionType, requiredSubscriptionType) + }; + }); + }, + modifyComponentForRow() { return "wizard-subscription-selector/wizard-subscription-selector-row"; }, diff --git a/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.hbs b/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.hbs index 4b5d673d..5ce96f2f 100644 --- a/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.hbs +++ b/assets/javascripts/discourse/connectors/category-custom-settings/custom-wizard-category-settings.hbs @@ -6,11 +6,11 @@
{{combo-box - value=wizardListVal - content=wizardList - onChange=(action "changeWizard") - options=(hash - none="admin.wizard.select" - )}} + value=wizardListVal + content=wizardList + onChange=(action "changeWizard") + options=(hash + none="admin.wizard.select" + )}}
diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 index e6b0ad04..2ecc42da 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 @@ -14,6 +14,7 @@ import I18n from "I18n"; export default Controller.extend({ hasName: notEmpty("wizard.name"), + @observes("currentStep") resetCurrentObjects() { const currentStep = this.currentStep; diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index 33841460..b69b878a 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -1,26 +1,7 @@ -import Controller, { inject as controller } from "@ember/controller"; -import { isPresent } from "@ember/utils"; -import { A } from "@ember/array"; +import Controller from "@ember/controller"; +import { equal } from "@ember/object/computed"; export default Controller.extend({ - adminWizardsNotices: controller(), - - unsubscribe() { - this.messageBus.unsubscribe("/custom-wizard/notices"); - }, - - subscribe() { - this.unsubscribe(); - this.messageBus.subscribe("/custom-wizard/notices", (data) => { - if (isPresent(data.active_notice_count)) { - this.set("activeNoticeCount", data.active_notice_count); - this.adminWizardsNotices.setProperties({ - notices: A(), - page: 0, - canLoadMore: true, - }); - this.adminWizardsNotices.loadMoreNotices(); - } - }); - }, + businessSubscription: equal('subscriptionType', 'business'), + standardSubscription: equal('subscriptionType', 'standard') }); diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index c3c95e48..272e276e 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -58,16 +58,6 @@ export default { path: "/manager", resetNamespace: true, }); - - this.route("adminWizardsSubscription", { - path: "/subscription", - resetNamespace: true, - }); - - this.route("adminWizardsNotices", { - path: "/notices", - resetNamespace: true, - }); } ); }, diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 26c21bd2..ca5c30ee 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -203,101 +203,17 @@ const custom_field = { type: ["string", "boolean", "integer", "json"], }; -const subscription_levels = { - standard: { - actions: ["send_message", "add_to_group", "watch_categories"], - custom_fields: { - klass: [], - type: ["json"], - }, - }, - - business: { - actions: ["create_category", "create_group", "send_to_api"], - custom_fields: { - klass: ["group", "category"], - type: [], - }, - }, -}; +field.type = Object.keys(field.types); +action.type = Object.keys(action.types); const wizardSchema = { wizard, step, field, custom_field, - action, - subscription_levels, + action }; -export function requiringAdditionalSubscription( - currentSubscription, - category, - subCategory -) { - switch (category) { - case "actions": - switch (currentSubscription) { - case "business": - return []; - case "standard": - return subscription_levels["business"][category]; - default: - return subscription_levels["standard"][category].concat( - subscription_levels["business"][category] - ); - } - case "custom_fields": - switch (currentSubscription) { - case "business": - return []; - case "standard": - return subscription_levels["business"][category][subCategory]; - default: - return subscription_levels["standard"][category][subCategory].concat( - subscription_levels["business"][category][subCategory] - ); - } - default: - return []; - } -} - -export function subscriptionLevel(type, category, subCategory) { - switch (category) { - case "actions": - if (subscription_levels["business"].actions.includes(type)) { - return "business"; - } else { - if (subscription_levels["standard"].actions.includes(type)) { - return "standard"; - } else { - return ""; - } - } - case "custom_fields": - if ( - subscription_levels["business"].custom_fields[subCategory].includes( - type - ) - ) { - return "business"; - } else { - if ( - subscription_levels["standard"].custom_fields[subCategory].includes( - type - ) - ) { - return "standard"; - } else { - return ""; - } - } - default: - return ""; - } -} - export function buildFieldTypes(types) { wizardSchema.field.types = types; } diff --git a/assets/javascripts/discourse/lib/wizard-subscription.js.es6 b/assets/javascripts/discourse/lib/wizard-subscription.js.es6 new file mode 100644 index 00000000..deff32ca --- /dev/null +++ b/assets/javascripts/discourse/lib/wizard-subscription.js.es6 @@ -0,0 +1,17 @@ +const subscriptionTypes = [ + 'standard', + 'business' +] + +function subscriptionTypeSufficient(subscriptionType, requiredType) { + if (requiredType && !subscriptionType) return false; + if (requiredType === 'none' || requiredType === null) return true; + if (requiredType === 'standard' && subscriptionTypes.includes(subscriptionType)) return true; + if (requiredType === 'business' && subscriptionType === 'business') return true; + return false; +} + +export { + subscriptionTypeSufficient, + subscriptionTypes +} diff --git a/assets/javascripts/discourse/mixins/subscription.js.es6 b/assets/javascripts/discourse/mixins/subscription.js.es6 new file mode 100644 index 00000000..00f77bb2 --- /dev/null +++ b/assets/javascripts/discourse/mixins/subscription.js.es6 @@ -0,0 +1,26 @@ +import Mixin from "@ember/object/mixin"; +import { getOwner } from "discourse-common/lib/get-owner"; +import { readOnly } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Mixin.create({ + subscriptionLandingUrl: "https://custom-wizard.pavilion.tech", + subscriptionClientUrl: "/admin/plugins/subscription-client", + + @discourseComputed + adminWizards() { + return getOwner(this).lookup('controller:admin-wizards'); + }, + + subscribed: readOnly('adminWizards.subscribed'), + subscriptionType: readOnly('adminWizards.subscriptionType'), + businessSubscription: readOnly('adminWizards.businessSubscription'), + standardSubscription: readOnly('adminWizards.standardSubscription'), + subscriptionAttributes: readOnly('adminWizards.subscriptionAttributes'), + subscriptionClientInstalled: readOnly('adminWizards.subscriptionClientInstalled'), + + @discourseComputed("subscriptionClientInstalled") + subscriptionLink(subscriptionClientInstalled) { + return subscriptionClientInstalled ? this.subscriptionClientUrl : this.subscriptionLandingUrl; + } +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index 4992f92d..4d9de2c4 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -9,13 +9,9 @@ export default DiscourseRoute.extend({ setupController(controller, model) { const customFields = A(model.custom_fields || []); - const subscribed = model.subscribed; - const subscription = model.subscription; controller.setProperties({ - customFields, - subscribed, - subscription, + customFields }); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index d6263471..63ff564e 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -38,9 +38,7 @@ export default DiscourseRoute.extend({ wizard, currentStep: wizard.steps[0], currentAction: wizard.actions[0], - creating: model.create, - subscribed: parentModel.subscribed, - subscription: parentModel.subscription, + creating: model.create }; controller.setProperties(props); diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index 5c39c0d6..0286926d 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -7,21 +7,17 @@ export default DiscourseRoute.extend({ }, setupController(controller, model) { - controller.set("api_section", model.api_section); - - if (model.active_notice_count) { - controller.set("activeNoticeCount", model.active_notice_count); - } - if (model.featured_notices) { - controller.set("featuredNotices", model.featured_notices); - } - - controller.subscribe(); + controller.setProperties({ + subscribed: model.subscribed, + subscriptionType: model.subscription_type, + subscriptionAttributes: model.subscription_attributes, + subscriptionClientInstalled: model.subscription_client_installed + }); }, afterModel(model, transition) { if (transition.targetName === "adminWizards.index") { this.transitionTo("adminWizardsWizard"); } - }, + } }); diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index dceed458..10501498 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -32,9 +32,7 @@ {{custom-field-input field=field removeField=(action "removeField") - saveField=(action "saveField") - subscribed=subscribed - subscription=subscription}} + saveField=(action "saveField")}} {{/each}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index abf06ba9..5d1c2166 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -126,7 +126,7 @@
- {{#subscription-container subscribed=subscribed}} + {{#wizard-subscription-container}}
@@ -146,7 +146,7 @@ {{i18n "admin.wizard.restart_on_revisit_label"}}
- {{/subscription-container}} + {{/wizard-subscription-container}}
{{wizard-links @@ -177,9 +177,7 @@ wizard=wizard apis=apis removeAction="removeAction" - wizardFields=wizardFields - subscribed=subscribed - subscription=subscription}} + wizardFields=wizardFields}} {{/each}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index a50748cc..26ba3e06 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -2,13 +2,14 @@ {{nav-item route="adminWizardsWizard" label="admin.wizard.nav_label"}} {{nav-item route="adminWizardsCustomFields" label="admin.wizard.custom_field.nav_label"}} {{nav-item route="adminWizardsSubmissions" label="admin.wizard.submissions.nav_label"}} - {{#if api_section}} + {{#if businessSubscription}} {{nav-item route="adminWizardsApi" label="admin.wizard.api.nav_label"}} {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}}
+ {{wizard-subscription-badge}} {{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}} diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index b4ddcc81..335a9b9d 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -2,16 +2,22 @@ {{wizard-subscription-selector value=field.klass - content=customFieldKlasses - none="admin.wizard.custom_field.klass.select" - onChange=(action (mut field.klass))}} + feature="custom_field" + attribute="klass" + onChange=(action (mut field.klass)) + options=(hash + none="admin.wizard.custom_field.klass.select" + )}} {{wizard-subscription-selector value=field.type - content=customFieldTypes - none="admin.wizard.custom_field.type.select" - onChange=(action (mut field.type))}} + feature="custom_field" + attribute="type" + onChange=(action (mut field.type)) + options=(hash + none="admin.wizard.custom_field.type.select" + )}} {{input @@ -22,8 +28,10 @@ {{multi-select value=field.serializers content=serializerContent - none="admin.wizard.custom_field.serializers.select" - onChange=(action (mut field.serializers))}} + onChange=(action (mut field.serializers)) + options=(hash + none="admin.wizard.custom_field.serializers.select" + )}} {{#if loading}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index cb4cf28d..818c525d 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -14,7 +14,8 @@
{{wizard-subscription-selector value=action.type - content=actionTypes + feature="action" + attribute="type" onChange=(action "changeType") options=(hash none="admin.wizard.select_type" diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index b4f1ac3a..00c20153 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -222,7 +222,7 @@
{{/if}} -{{#subscription-container subscribed=subscribed}} +{{#wizard-subscription-container}}
@@ -237,7 +237,7 @@
- > +
@@ -248,10 +248,9 @@
{{#if isCategory}} -
+
- {{i18n "admin.wizard.pro.label"}}
@@ -269,4 +268,4 @@ {{#if validations}} {{wizard-realtime-validations field=field validations=validations}} {{/if}} -{{/subscription-container}} +{{/wizard-subscription-container}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 0d1294f3..61812d04 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -34,7 +34,7 @@
-{{#subscription-container subscribed=subscribed}} +{{#wizard-subscription-container}}
@@ -56,11 +56,11 @@
-
+
- {{i18n "admin.wizard.pro.label"}}
+
{{wizard-mapper inputs=step.required_data @@ -99,7 +99,7 @@ )}}
-{{/subscription-container}} +{{/wizard-subscription-container}} {{wizard-links itemType="field" diff --git a/assets/javascripts/discourse/templates/components/wizard-subscription-badge.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-badge.hbs new file mode 100644 index 00000000..b2ce05bc5 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-badge.hbs @@ -0,0 +1,6 @@ + + + +{{label}} diff --git a/assets/javascripts/discourse/templates/components/subscription-container.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-container.hbs similarity index 54% rename from assets/javascripts/discourse/templates/components/subscription-container.hbs rename to assets/javascripts/discourse/templates/components/wizard-subscription-container.hbs index 8b012671..78be031f 100644 --- a/assets/javascripts/discourse/templates/components/subscription-container.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-container.hbs @@ -1,7 +1,7 @@
-

{{i18n "admin.wizard.subscription_container.title"}}

+

{{i18n "admin.wizard.subscription_container.title"}}

- + {{d-icon subscribedIcon}} {{i18n subscribedLabel}} diff --git a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-header.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-header.hbs index a7e3d2e6..708b0887 100644 --- a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-header.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-header.hbs @@ -7,8 +7,10 @@ shouldDisplayClearableButton=shouldDisplayClearableButton }} - {{#if selectedContent.subscription}} - {{i18n "admin.wizard.subscription.label"}} + {{#if selectedContent.subscriptionType}} + + {{selectedContent.subscriptionType}} + {{/if}} {{d-icon caretIcon class="caret-icon"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs index ecd77cb1..1db906e6 100644 --- a/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-subscription-selector/wizard-subscription-selector-row.hbs @@ -9,9 +9,9 @@
{{html-safe label}} - {{#if item.subscription}} + {{#if item.subscriptionType}} - {{item.subscription}} + {{item.subscriptionType}} {{/if}}
diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index c026263a..b8458163 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -25,6 +25,14 @@ $error: #ef1700; } } +.admin-wizards .admin-actions { + display: flex; + + .btn-pavilion-support { + margin-left: 10px; + } +} + .wizard-message { background-color: var(--primary-low); width: 100%; @@ -156,6 +164,16 @@ $error: #ef1700; @extend .wizard-settings-group; } +.admin-wizard-container.settings { + .wizard-settings { + .wizard-subscription-container { + [class~="setting"] { + margin-bottom: 0; + } + } + } +} + .wizard-custom-field { background: transparent; background-color: var(--primary-very-low); @@ -802,84 +820,6 @@ $error: #ef1700; vertical-align: middle; } -.admin-wizards-subscription { - .admin-wizard-controls { - h3, - label { - margin: 0; - } - - label { - padding: 0.4em 0.5em; - margin-left: 0.75em; - background-color: var(--success); - color: var(--secondary); - } - - .buttons { - display: flex; - align-items: center; - - .loading-container { - margin-right: 1em; - } - } - } - - .custom-wizard-subscription { - .title-container { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.5em; - - h3 { - margin: 0; - } - - .buttons > span { - margin-right: 0.5em; - } - } - - .detail-container { - display: flex; - align-items: center; - padding: 1em; - background-color: var(--primary-very-low); - - .subscription-state { - padding: 0.25em 0.5em; - margin-right: 0.75em; - - &.active { - background-color: var(--success); - color: var(--secondary); - } - } - } - } -} - -.wizard-subscription-selector.select-kit.single-select { - .select-kit-row { - .texts { - display: flex; - align-items: center; - } - &.disabled { - background: var(--primary-low); - } - } - - .subscription-label { - margin-left: 0.75em; - padding-top: 0.25em; - color: var(--tertiary); - font-size: 0.75em; - } -} - .btn.btn-pavilion-support { background: var(--pavilion-primary); color: var(--pavilion-secondary); @@ -899,7 +839,7 @@ $error: #ef1700; } } -.subscription-container { +.wizard-subscription-container { width: 100%; padding: 1em; background-color: rgba($pavilion_primary, 0.1); @@ -907,158 +847,79 @@ $error: #ef1700; .subscription-header { display: flex; justify-content: space-between; - margin-bottom: 1em; + margin-bottom: .25em; h3 { margin: 0; } a { - color: var(--pavilion-primary); + color: var(--primary); } } &:not(.subscribed) .subscription-settings { filter: blur(1px); + pointer-events: none; } } -.admin-wizards-notices { - .wizard-table { - overflow: unset; - } -} - -.wizard-notice { - padding: 0.75em; - margin-bottom: 1em; - border: 1px solid var(--primary-low); - - &.dismissed { - display: none; - } - - &.expired .notice-badge:not(.notice-expired-at), - &.expired a, - &.expired p { - color: var(--primary-medium) !important; - } - - .notice-badge { - padding: 0 0.5em; - } - - .notice-header { - display: flex; - - .notice-title { - padding: 0; - } - - .notice-header-right { - margin-left: auto; - display: flex; - align-items: center; - - .notice-badge { - margin-left: 0.5em; - } - } - } - - .dismiss-notice-container, - .hide-notice-container { - width: 40px; - display: flex; - justify-content: center; - align-items: center; - } - - .dismiss-notice, - .hide-notice { - display: flex; - align-items: center; - - .d-icon { - margin-right: 0; - color: var(--primary); - } - } -} - -.notice-badge { +.wizard-subscription-badge { display: inline-flex; align-items: center; - min-height: 25px; + max-height: 34px; box-sizing: border-box; - color: var(--primary) !important; -} - -.admin-actions { - display: flex; - align-items: center; -} - -.wizard-notices-link { position: relative; - margin-right: 10px; + cursor: pointer; + padding: .5em .65em; + background-color: rgba($primary-medium, 0.05); + border: 1.5px solid rgba($primary-medium, 0.5); + color: $primary-medium; - div > a { - @include btn; - color: var(--secondary) !important; - background-color: var(--primary-medium); + &:hover { + color: $primary-medium; + } - &.active { - background-color: var(--tertiary) !important; - color: var(--secondary) !important; + svg { + width: 15px; + height: 15px; + margin-right: 0.45em; + margin-bottom: 0.15em; + } + + &.standard { + background-color: rgba($subscription_standard, 0.05); + border: 1.5px solid rgba($subscription_standard, 0.5); + color: $subscription_standard; + } + + &.business { + background-color: $subscription_business; + border: 1.5px solid $subscription_business; + color: $secondary; + } + + .d-icon { + margin-right: 0.75em; + } +} + +.wizard-subscription-selector.select-kit.single-select { + .select-kit-row { + .texts { + display: flex; + align-items: center; + } + &.disabled { + background: var(--primary-low); + cursor: unset; } } -} -.active-notice-count { - background-color: $danger; - color: $secondary; - border-radius: 50%; - width: 18px; - height: 18px; - line-height: 18px; - text-align: center; - position: absolute; - top: -8px; - right: -8px; - font-size: 0.7em; -} - -a.show-notice-message { - padding: 0.25em 0.5em; - color: var(--primary); -} - -.wizard-notice, -.wizard-notice-row:not(.expired):not(.dismissed) { - &.info { - background-color: rgba($info, 0.1); - border: 1px solid rgba($info, 0.5); - } - &.warning, - &.connection-error { - background-color: rgba($warning, 0.1); - border: 1px solid rgba($warning, 0.5); - } -} - -.notice-message { - position: relative; - - .cooked-notice-message { - background-color: var(--secondary); - padding: 1em; - z-index: 1; - box-shadow: shadow("dropdown"); - border-top: 1px solid var(--primary-low); - - p { - margin: 0; - } + .subscription-label { + margin-left: 0.75em; + padding-top: 0.25em; + color: var(--pavilion-primary); + font-size: 0.75em; } } diff --git a/assets/stylesheets/admin/wizard/variables.scss b/assets/stylesheets/admin/wizard/variables.scss index 5912f961..9bb93798 100644 --- a/assets/stylesheets/admin/wizard/variables.scss +++ b/assets/stylesheets/admin/wizard/variables.scss @@ -2,8 +2,13 @@ $pavilion_primary: #3c1c8c; $pavilion_secondary: #ffffff; $pavilion_warning: rgb(243, 163, 61); +$subscription_standard: $pavilion_primary; +$subscription_business: #333; + :root { --pavilion-primary: #{$pavilion_primary}; --pavilion-secondary: #{$pavilion_secondary}; --pavilion-warning: #{$pavilion_warning}; + --subscription-standard: #{$subscription_standard}; + --subscription-business: #{$subscription_business}; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 34c7003b..45efabb8 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -47,7 +47,7 @@ en: value: "Value" profile: "profile" type: "Type" - none: "Make a selection" + none: "Make a selection" submission_key: 'submission key' param_key: 'param' group: "Group" @@ -126,9 +126,9 @@ en: hide: "Hide" preview: "{{action}} Preview" popover: "{{action}} Fields" - + input: - conditional: + conditional: name: 'if' output: 'then' assignment: @@ -137,7 +137,7 @@ en: name: 'map' validation: name: 'ensure' - + selector: label: text: "text" @@ -175,7 +175,7 @@ en: dependent: "{{property}} is dependent on {{dependent}}" conflict: "{{type}} with {{property}} '{{value}}' already exists" after_time: "After time invalid" - + step: header: "Steps" title: "Title" @@ -189,7 +189,7 @@ en: force_final: label: "Conditional Final Step" description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step." - + field: header: "Fields" label: "Label" @@ -212,7 +212,7 @@ en: prefill: "Prefill" content: "Content" tag_groups: "Tag Groups" - date_time_format: + date_time_format: label: "Format" instructions: "Moment.js format" validations: @@ -229,7 +229,7 @@ en: weeks: "Weeks" months: "Months" years: "Years" - + type: text: "Text" textarea: Textarea @@ -248,7 +248,7 @@ en: date: Date time: Time date_time: Date & Time - + connector: and: "and" or: "or" @@ -262,7 +262,7 @@ en: regex: '=~' association: '→' is: 'is' - + action: header: "Actions" include: "Include Fields" @@ -270,8 +270,8 @@ en: post: "Post" topic_attr: "Topic Attribute" interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}." - - run_after: + + run_after: label: "Run After" wizard_completion: "Wizard Completion" custom_fields: @@ -353,11 +353,11 @@ en: messageable_level: Messageable Level visibility_level: Visibility Level members_visibility_level: Members Visibility Level - + custom_field: nav_label: "Custom Fields" add: "Add" - external: + external: label: "from another plugin" title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here." name: @@ -386,7 +386,7 @@ en: basic_category: "Category" basic_group: "Group" post: "Post" - + submissions: nav_label: "Submissions" title: "{{name}} Submissions" @@ -468,59 +468,22 @@ en: subscription_container: title: Subscriber Features - subscribed: + subscribed: label: Subscribed title: You're subscribed and can use these features not_subscribed: label: Not Subscribed title: Subscribe to use these features - - subscription: - nav_label: Subscription - label: In subscription - additional_label: "Add subscription" - title: Custom Wizard Subscription - authorize: Authorize - authorized: Authorized - unauthorize: cancel - not_subscribed: You're not currently subscribed - subscription: - title: - standard: Standard Subscription - business: Business Subscription - status: - active: Active - inactive: Inactive - update: Update - last_updated: Last updated - - notice: - plugin: Custom Wizard Plugin - message: Message - time: Time - status: Status - title: Title - dismiss: - label: Dismiss - title: Dismiss notice - dismiss_all: - label: Dismiss All - title: Dismiss all informational Custom Wizard notices - confirm: Are you sure you want to dismiss all informational Custom Wizard notices? - active: active - created_at: issued - updated_at: updated - expired_at: expired - dismissed_at: dismissed type: - label: Type - info: Information - warning: Warning - connection_error: Connection Error - - notices: - nav_label: Notices - title: Plugin Notices + none: + label: Not Subscribed + title: There is no Custom Wizard subscription active on this forum. + business: + label: Business + title: There is a Custom Wizard Business subscription active on this forum. + standard: + label: Standard + title: There is a Custom Wizard Standard subscription active on this forum. wizard_js: group: @@ -636,7 +599,7 @@ en: yourself_confirm: title: "Did you forget to add recipients?" body: "Right now this message is only being sent to yourself!" - + realtime_validations: similar_topics: insufficient_characters: "Type a minimum 5 characters to start looking for similar topics" diff --git a/config/settings.yml b/config/settings.yml index c4337d92..514462d5 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -1,5 +1,5 @@ plugins: - custom_wizard_enabled: + custom_wizard_enabled: default: true client: true wizard_redirect_exclude_paths: diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index ed0c9594..f26c11fa 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -84,7 +84,7 @@ class ::CustomWizard::CustomField next end - if attr == 'klass' && @subscription.requires_additional_subscription("custom_fields", "klass").include?(value) + if attr == 'klass' && @subscription.includes?(:custom_field, :klass, value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end @@ -99,7 +99,7 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end - if attr == 'type' && @subscription.requires_additional_subscription("custom_fields", "type").include?(value) + if attr == 'type' && @subscription.includes?(:custom_field, :type, value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index c513307b..aa444de1 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -47,7 +47,6 @@ class CustomWizard::Mapper @data = params[:data] || {} @user = params[:user] @opts = params[:opts] || {} - @subscription = CustomWizard::Subscription.new end def perform @@ -252,7 +251,7 @@ class CustomWizard::Mapper end end - if opts[:template] && @subscription.subscribed? + if opts[:template] && CustomWizard::Subscription.subscribed? template = Liquid::Template.parse(string) string = template.render(data) end diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index 39febc14..b59791b3 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -1,8 +1,170 @@ class CustomWizard::Subscription + STANDARD_PRODUCT_ID = 'prod_LNAGVAaIqDsHmB' + BUSINESS_PRODUCT_ID = 'prod_LNABQ50maBQ1pY' + + def self.attributes + { + wizard: { + required: { + none: [], + standard: ['*'], + business: ['*'] + }, + permitted: { + none: [], + standard: ['*'], + business: ['*'] + } + }, + step: { + condition: { + none: [], + standard: ['*'], + business: ['*'] + }, + index: { + none: [], + standard: ['*'], + business: ['*'] + }, + required_data: { + none: [], + standard: ['*'], + business: ['*'] + }, + permitted_params: { + none: [], + standard: ['*'], + business: ['*'] + } + }, + field: { + condition: { + none: [], + standard: ['*'], + business: ['*'] + }, + index: { + none: [], + standard: ['*'], + business: ['*'] + }, + type: { + none: ['label', 'description', 'image', 'required', 'placeholder', 'file'], + standard: ['*'], + business: ['*'] + }, + prefill: { + standard: ['*'], + business: ['*'] + }, + content: { + none: [], + standard: ['*'], + business: ['*'] + }, + realtime_validations: { + none: [], + standard: ['*'], + business: ['*'] + } + }, + action: { + type: { + none: [], + standard: ['send_message', 'watch_categories', 'add_to_group'], + business: ['*'] + } + }, + custom_field: { + klass: { + none: ['topic', 'post'], + standard: ['topic', 'post'], + business: ['*'] + }, + type: { + none: ['string', 'boolean', 'integer'], + standard: ['string', 'boolean', 'integer'], + business: ['*'] + } + } + } + end + + def initialize + @subscription = find_subscription + end + + def includes?(feature, attribute, value) + attributes = self.class.attributes[feature] + + ## Attribute is not part of a subscription + return true unless attributes.present? && attributes.key?(attribute) + + values = attributes[attribute][type] + + ## Subscription type does not support the attribute. + return false if values.blank? + + ## Subscription type supports all values of the attribute. + return true if values === "*" + + ## Subscription type supports some values of the attributes. + values.include?(value) + end + + def type + return :none unless subscribed? + return :standard if standard? + return :business if business? + end + + def subscribed? + standard? || business? + end + + def standard? + @subscription.product_id === STANDARD_PRODUCT_ID + end + + def business? + @subscription.product_id === BUSINESS_PRODUCT_ID + end + + def client_installed? + defined?(SubscriptionClient) == 'constant' && SubscriptionClient.class == Module + end + + def find_subscription + subscription = nil + + if client_installed? + subscription = SubscriptionClientSubscription.active + .where(product_id: [STANDARD_PRODUCT_ID, BUSINESS_PRODUCT_ID]) + .order("product_id = '#{BUSINESS_PRODUCT_ID}' DESC") + .first + end + + unless subscription + subscription = OpenStruct.new(product_id: nil) + end + + subscription + end + def self.subscribed? + new.subscribed? + end + + def self.business? + new.business? + end + + def self.standard? + new.standard? end def self.type - + new.type end end diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index 9ae37170..0ffc2608 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -56,29 +56,10 @@ class CustomWizard::TemplateValidator def self.subscription { - wizard: { - save_submissions: 'false', - restart_on_revisit: 'true', - }, - step: { - condition: 'present', - index: 'conditional', - required_data: 'present', - permitted_params: 'present' - }, - field: { - condition: 'present', - index: 'conditional' - }, - action: { - type: %w[ - send_message - add_to_group - create_category - create_group - send_to_api - ] - } + wizard: ['save_submissions', 'restart_on_revisit'], + step: ['condition', 'index', 'required_data', 'permitted_params'], + field: ['condition', 'index'], + action: ['type'] } end @@ -93,16 +74,8 @@ class CustomWizard::TemplateValidator end def validate_subscription(object, type) - self.class.subscription[type].each do |property, subscription_type| - val = object[property.to_s] - is_subscription = (val != nil) && ( - subscription_type === 'present' && val.present? || - (['true', 'false'].include?(subscription_type) && cast_bool(val) == cast_bool(subscription_type)) || - (subscription_type === 'conditional' && val.is_a?(Hash)) || - (subscription_type.is_a?(Array) && subscription_type.include?(val)) - ) - - if is_subscription && !@subscription.subscribed? + self.class.subscription[type].each do |property| + if !@subscription.includes?(type, property, object[property]) errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property) end end @@ -148,10 +121,6 @@ class CustomWizard::TemplateValidator end end - def cast_bool(val) - ActiveRecord::Type::Boolean.new.cast(val) - end - def validate_liquid_template(object, type) %w[ description diff --git a/plugin.rb b/plugin.rb index 03862103..ebd5c6d5 100644 --- a/plugin.rb +++ b/plugin.rb @@ -5,17 +5,13 @@ # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George # contact_emails: support@thepavilion.io # url: https://github.com/paviliondev/discourse-custom-wizard +# subscription_url: https://coop.pavilion.tech gem 'liquid', '5.0.1', require: true register_asset 'stylesheets/admin/admin.scss', :desktop enabled_site_setting :custom_wizard_enabled -config = Rails.application.config -plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets" -config.assets.paths << "#{plugin_asset_path}/javascripts" -config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" - if Rails.env.production? config.assets.precompile += %w{ wizard-custom-guest.js @@ -59,6 +55,24 @@ class ::Sprockets::DirectiveProcessor end end +## Override necessary due to 'assets/javascripts/wizard', particularly its tests. +def each_globbed_asset + if @path + root_path = "#{File.dirname(@path)}/assets/javascripts/discourse" + + Dir.glob(["#{root_path}/**/*"]).sort.each do |f| + f_str = f.to_s + if File.directory?(f) + yield [f, true] + elsif f_str.end_with?(".js.es6") || f_str.end_with?(".hbs") || f_str.end_with?(".hbr") + yield [f, false] + elsif transpile_js && f_str.end_with?(".js") + yield [f, false] + end + end + end +end + after_initialize do %w[ ../lib/custom_wizard/engine.rb @@ -91,6 +105,7 @@ after_initialize do ../lib/custom_wizard/step_updater.rb ../lib/custom_wizard/step.rb ../lib/custom_wizard/submission.rb + ../lib/custom_wizard/subscription.rb ../lib/custom_wizard/template.rb ../lib/custom_wizard/wizard.rb ../lib/custom_wizard/api/api.rb diff --git a/spec/components/custom_wizard/notice_spec.rb b/spec/components/custom_wizard/notice_spec.rb deleted file mode 100644 index 0b34664d..00000000 --- a/spec/components/custom_wizard/notice_spec.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../plugin_helper' - -describe CustomWizard::Notice do - fab!(:user) { Fabricate(:user) } - let(:subscription_message) { - { - title: "Title of message about subscription", - message: "Message about subscription", - type: "info", - created_at: Time.now - 3.day, - expired_at: nil - } - } - let(:plugin_status) { - { - name: 'discourse-custom-wizard', - status: 'incompatible', - status_changed_at: Time.now - 3.day - } - } - - context "subscription message" do - before do - freeze_time - stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) - described_class.update(skip_plugin: true) - end - - it "converts subscription messages into notices" do - notice = described_class.list.first - expect(notice.type).to eq(described_class.types[:info]) - expect(notice.message).to eq(subscription_message[:message]) - expect(notice.created_at.to_datetime).to be_within(1.second).of (subscription_message[:created_at].to_datetime) - end - - it "expires notice if subscription message is expired" do - subscription_message[:expired_at] = Time.now - stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) - described_class.update(skip_plugin: true) - - notice = described_class.list(include_all: true).first - expect(notice.expired?).to eq(true) - end - - it "dismisses informational subscription notices" do - notice = described_class.list(include_all: true).first - expect(notice.dismissed?).to eq(false) - - notice.dismiss! - expect(notice.dismissed?).to eq(true) - end - - it "dismisses all informational subscription notices" do - 4.times do |index| - subscription_message[:title] += " #{index}" - stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) - described_class.update(skip_plugin: true) - end - expect(described_class.list.count).to eq(5) - described_class.dismiss_all - expect(described_class.list.count).to eq(0) - end - end - - context "plugin status" do - before do - freeze_time - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) - described_class.update(skip_subscription: true) - end - - it "converts warning into notice" do - notice = described_class.list.first - expect(notice.type).to eq(described_class.types[:warning]) - expect(notice.message).to eq(I18n.t("wizard.notice.compatibility_issue.message", domain: described_class.plugin_status_domain)) - expect(notice.created_at.to_datetime).to be_within(1.second).of (plugin_status[:status_changed_at].to_datetime) - end - - it "expires warning notices if status is recommended or compatible" do - plugin_status[:status] = 'compatible' - plugin_status[:status_changed_at] = Time.now - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) - described_class.update(skip_subscription: true) - - notice = described_class.list(type: described_class.types[:warning], include_all: true).first - expect(notice.expired?).to eq(true) - end - - it "hides plugin status warnings" do - notice = described_class.list.first - expect(notice.hidden?).to eq(false) - - notice.hide! - expect(notice.hidden?).to eq(true) - end - end - - it "lists notices not expired more than a day ago" do - subscription_message[:expired_at] = Time.now - 8.hours - stub_request(:get, described_class.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) - - described_class.update - expect(described_class.list(include_all: true).length).to eq(2) - end - - context "connection errors" do - before do - freeze_time - end - - it "creates an error if connection to notice server fails" do - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) - described_class.update(skip_subscription: true) - - error = CustomWizard::Notice::ConnectionError.new(:plugin_status) - expect(error.current_error.present?).to eq(true) - end - - it "only creates one connection error per type at a time" do - stub_request(:get, described_class.subscription_message_url).to_return(status: 400, body: { messages: [subscription_message] }.to_json) - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) - - 5.times { described_class.update } - - plugin_status_errors = CustomWizard::Notice::ConnectionError.new(:plugin_status) - subscription_message_errors = CustomWizard::Notice::ConnectionError.new(:subscription_message) - - expect(plugin_status_errors.current_error[:count]).to eq(5) - expect(subscription_message_errors.current_error[:count]).to eq(5) - end - - it "creates a connection error notice if connection errors reach limit" do - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) - - error = CustomWizard::Notice::ConnectionError.new(:plugin_status) - error.limit.times { described_class.update(skip_subscription: true) } - notice = described_class.list(type: described_class.types[:connection_error]).first - - expect(error.current_error[:count]).to eq(error.limit) - expect(notice.type).to eq(described_class.types[:connection_error]) - end - - it "expires a connection error notice if connection succeeds" do - stub_request(:get, described_class.plugin_status_url).to_return(status: 400, body: plugin_status.to_json) - error = CustomWizard::Notice::ConnectionError.new(:plugin_status) - error.limit.times { described_class.update(skip_subscription: true) } - - stub_request(:get, described_class.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) - described_class.update(skip_subscription: true) - notice = described_class.list(type: described_class.types[:connection_error], include_all: true).first - - expect(notice.type).to eq(described_class.types[:connection_error]) - expect(notice.expired_at.present?).to eq(true) - end - end -end diff --git a/spec/components/custom_wizard/subscription_spec.rb b/spec/components/custom_wizard/subscription_spec.rb deleted file mode 100644 index 47fb2d3f..00000000 --- a/spec/components/custom_wizard/subscription_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../plugin_helper' - -describe CustomWizard::Subscription do - fab!(:user) { Fabricate(:user) } - - it "initializes subscription authentication and subscription" do - subscription = described_class.new - expect(subscription.authentication.class).to eq(CustomWizard::Subscription::Authentication) - expect(subscription.subscription.class).to eq(CustomWizard::Subscription::Subscription) - end - - it "returns authorized and subscribed states" do - subscription = described_class.new - expect(subscription.authorized?).to eq(false) - expect(subscription.subscribed?).to eq(false) - end - - context "subscription" do - before do - @subscription = described_class.new - end - - it "updates valid subscriptions" do - stub_subscription_request(200, valid_subscription) - expect(@subscription.update).to eq(true) - expect(@subscription.subscribed?).to eq(true) - end - - it "handles invalid subscriptions" do - stub_subscription_request(200, invalid_subscription) - expect(@subscription.update).to eq(false) - expect(@subscription.subscribed?).to eq(false) - end - - it "handles subscription http errors" do - stub_subscription_request(404, {}) - expect(@subscription.update).to eq(false) - expect(@subscription.subscribed?).to eq(false) - end - - it "destroys subscriptions" do - stub_subscription_request(200, valid_subscription) - expect(@subscription.update).to eq(true) - expect(@subscription.destroy_subscription).to eq(true) - expect(@subscription.subscribed?).to eq(false) - end - - it "has class aliases" do - authenticate_subscription - stub_subscription_request(200, valid_subscription) - expect(described_class.update).to eq(true) - expect(described_class.subscribed?).to eq(true) - end - end - - context "authentication" do - before do - @subscription = described_class.new - user.update!(admin: true) - end - - it "generates a valid authentication request url" do - request_id = SecureRandom.hex(32) - uri = URI(@subscription.authentication_url(user.id, request_id)) - expect(uri.host).to eq(@subscription.server) - - parsed_query = Rack::Utils.parse_query uri.query - expect(parsed_query['public_key'].present?).to eq(true) - expect(parsed_query['nonce'].present?).to eq(true) - expect(parsed_query['client_id'].present?).to eq(true) - expect(parsed_query['auth_redirect'].present?).to eq(true) - expect(parsed_query['application_name']).to eq(SiteSetting.title) - expect(parsed_query['scopes']).to eq(@subscription.scope) - end - - def generate_payload(request_id, user_id) - uri = URI(@subscription.authentication_url(user_id, request_id)) - keys = @subscription.authentication.get_keys(request_id) - raw_payload = { - key: "12345", - nonce: keys.nonce, - push: false, - api: UserApiKeysController::AUTH_API_VERSION - }.to_json - public_key = OpenSSL::PKey::RSA.new(keys.pem) - Base64.encode64(public_key.public_encrypt(raw_payload)) - end - - it "handles authentication response if request and response is valid" do - request_id = SecureRandom.hex(32) - payload = generate_payload(request_id, user.id) - - expect(@subscription.authentication_response(request_id, payload)).to eq(true) - expect(@subscription.authorized?).to eq(true) - end - - it "discards authentication response if user who made request as not an admin" do - user.update!(admin: false) - - request_id = SecureRandom.hex(32) - payload = generate_payload(request_id, user.id) - - expect(@subscription.authentication_response(request_id, payload)).to eq(false) - expect(@subscription.authorized?).to eq(false) - end - - it "discards authentication response if request_id is invalid" do - payload = generate_payload(SecureRandom.hex(32), user.id) - - expect(@subscription.authentication_response(SecureRandom.hex(32), payload)).to eq(false) - expect(@subscription.authorized?).to eq(false) - end - - it "destroys authentication" do - request_id = SecureRandom.hex(32) - payload = generate_payload(request_id, user.id) - @subscription.authentication_response(request_id, payload) - - expect(@subscription.destroy_authentication).to eq(true) - expect(@subscription.authorized?).to eq(false) - end - end -end diff --git a/spec/jobs/update_notices_spec.rb b/spec/jobs/update_notices_spec.rb deleted file mode 100644 index df0697b8..00000000 --- a/spec/jobs/update_notices_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require_relative '../plugin_helper' - -describe Jobs::CustomWizardUpdateNotices do - let(:subscription_message) { - { - message: "Message about subscription", - type: "info", - created_at: Time.now - 3.day, - expired_at: nil - } - } - let(:plugin_status) { - { - name: 'discourse-custom-wizard', - status: 'incompatible', - status_changed_at: Time.now - 3.day - } - } - - it "updates the notices" do - stub_request(:get, CustomWizard::Notice.subscription_message_url).to_return(status: 200, body: { messages: [subscription_message] }.to_json) - stub_request(:get, CustomWizard::Notice.plugin_status_url).to_return(status: 200, body: plugin_status.to_json) - - described_class.new.execute - expect(CustomWizard::Notice.list.length).to eq(2) - end -end diff --git a/spec/jobs/update_subscription_spec.rb b/spec/jobs/update_subscription_spec.rb deleted file mode 100644 index c5adaf38..00000000 --- a/spec/jobs/update_subscription_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -require_relative '../plugin_helper' - -describe Jobs::CustomWizardUpdateSubscription do - it "updates the subscription" do - stub_subscription_request(200, valid_subscription) - described_class.new.execute - expect(CustomWizard::Subscription.subscribed?).to eq(true) - end -end diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index 6e340ccf..f7818296 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -7,38 +7,3 @@ def get_wizard_fixture(path) ).read ).with_indifferent_access end - -def authenticate_subscription - CustomWizard::Subscription::Authentication.any_instance.stubs(:active?).returns(true) -end - -def enable_subscription(type) - # CustomWizard::Subscription.new - CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(true) - CustomWizard::Subscription.any_instance.stubs(:type).returns(type) -end - -def disable_subscription - CustomWizard::Subscription.any_instance.stubs(:subscribed?).returns(false) -end - -def valid_subscription - { - product_id: "prod_CBTNpi3fqWWkq0", - price_id: "price_id", - price_nickname: "business" - } -end - -def invalid_subscription - { - product_id: "prod_CBTNpi3fqWWkq0", - price_id: "price_id" - } -end - -def stub_subscription_request(status, subscription) - authenticate_subscription - sub = CustomWizard::Subscription.new - stub_request(:get, "https://#{sub.server}/subscription-server/user-subscriptions/#{sub.subscription_type}/#{sub.client_name}").to_return(status: status, body: { subscriptions: [subscription] }.to_json) -end diff --git a/spec/requests/custom_wizard/admin/notice_controller_spec.rb b/spec/requests/custom_wizard/admin/notice_controller_spec.rb deleted file mode 100644 index 33d03432..00000000 --- a/spec/requests/custom_wizard/admin/notice_controller_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true -require_relative '../../../plugin_helper' - -describe CustomWizard::AdminNoticeController do - fab!(:admin_user) { Fabricate(:user, admin: true) } - let(:subscription_message_notice) { - { - title: "Title of message about subscription", - message: "Message about subscription", - type: 0, - created_at: Time.now.iso8601(3), - expired_at: nil - } - } - let(:plugin_status_notice) { - { - title: "The Custom Wizard Plugin is incompatibile with the latest version of Discourse.", - message: "Please check the Custom Wizard Plugin status on [localhost:3000](http://localhost:3000) before updating Discourse.", - type: 1, - archetype: 1, - created_at: Time.now.iso8601(3), - expired_at: nil - } - } - - before do - sign_in(admin_user) - end - - it "lists notices" do - @notice = CustomWizard::Notice.new(subscription_message_notice) - @notice.save - - get "/admin/wizards/notice.json" - expect(response.status).to eq(200) - expect(response.parsed_body.length).to eq(1) - end - - it "dismisses notices" do - @notice = CustomWizard::Notice.new(subscription_message_notice) - @notice.save - - put "/admin/wizards/notice/#{@notice.id}/dismiss.json" - expect(response.status).to eq(200) - - updated = CustomWizard::Notice.find(@notice.id) - expect(updated.dismissed?).to eq(true) - end - - it "dismisses all notices" do - 5.times do |index| - subscription_message_notice[:title] += " #{index}" - @notice = CustomWizard::Notice.new(subscription_message_notice) - @notice.save - end - - put "/admin/wizards/notice/dismiss.json" - expect(response.status).to eq(200) - expect(CustomWizard::Notice.list.size).to eq(0) - end - - it "hides notices" do - @notice = CustomWizard::Notice.new(plugin_status_notice) - @notice.save - - put "/admin/wizards/notice/#{@notice.id}/hide.json" - expect(response.status).to eq(200) - - updated = CustomWizard::Notice.find(@notice.id) - expect(updated.hidden?).to eq(true) - end -end diff --git a/spec/requests/custom_wizard/admin/subscription_controller_spec.rb b/spec/requests/custom_wizard/admin/subscription_controller_spec.rb deleted file mode 100644 index 2f8aad20..00000000 --- a/spec/requests/custom_wizard/admin/subscription_controller_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true -require_relative '../../../plugin_helper' - -describe CustomWizard::AdminSubscriptionController do - fab!(:admin_user) { Fabricate(:user, admin: true) } - - def generate_payload(request_id, user_id) - uri = URI(@subscription.authentication_url(user_id, request_id)) - keys = @subscription.authentication.get_keys(request_id) - raw_payload = { - key: "12345", - nonce: keys.nonce, - push: false, - api: UserApiKeysController::AUTH_API_VERSION - }.to_json - public_key = OpenSSL::PKey::RSA.new(keys.pem) - Base64.encode64(public_key.public_encrypt(raw_payload)) - end - - before do - @subscription = CustomWizard::Subscription.new - sign_in(admin_user) - end - - it "#index" do - get "/admin/wizards/subscription.json" - expect(response.parsed_body['server']).to eq(@subscription.server) - expect(response.parsed_body['authentication'].deep_symbolize_keys).to eq(CustomWizard::Subscription::AuthenticationSerializer.new(@subscription.authentication, root: false).as_json) - expect(response.parsed_body['subscription'].deep_symbolize_keys).to eq(CustomWizard::Subscription::SubscriptionSerializer.new(@subscription.subscription, root: false).as_json) - end - - it "#authorize" do - get "/admin/wizards/subscription/authorize" - expect(response.status).to eq(302) - expect(cookies[:user_api_request_id].present?).to eq(true) - end - - it "#destroy_authentication" do - request_id = SecureRandom.hex(32) - payload = generate_payload(request_id, admin_user.id) - @subscription.authentication_response(request_id, payload) - - delete "/admin/wizards/subscription/authorize.json" - - expect(response.status).to eq(200) - expect(CustomWizard::Subscription.authorized?).to eq(false) - end - - context "subscription" do - before do - stub_subscription_request(200, valid_subscription) - end - - it "handles authentication response and the updates subscription" do - request_id = cookies[:user_api_request_id] = SecureRandom.hex(32) - payload = generate_payload(request_id, admin_user.id) - get "/admin/wizards/subscription/authorize/callback", params: { payload: payload } - - expect(response).to redirect_to("/admin/wizards/subscription") - expect(CustomWizard::Subscription.subscribed?).to eq(true) - end - - it "updates the subscription" do - authenticate_subscription - post "/admin/wizards/subscription.json" - - expect(response.status).to eq(200) - expect(CustomWizard::Subscription.subscribed?).to eq(true) - end - end -end diff --git a/spec/serializers/custom_wizard/notice_serializer_spec.rb b/spec/serializers/custom_wizard/notice_serializer_spec.rb deleted file mode 100644 index 5184d890..00000000 --- a/spec/serializers/custom_wizard/notice_serializer_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../plugin_helper' - -describe CustomWizard::NoticeSerializer do - before do - @notice = CustomWizard::Notice.new( - message: "Message about subscription", - type: "info", - created_at: Time.now - 3.day, - expired_at: nil - ) - @notice.save - end - - it 'should return notice attributes' do - serialized_notice = described_class.new(@notice) - expect(serialized_notice.message).to eq(@notice.message) - expect(serialized_notice.type).to eq(CustomWizard::Notice.types.key(@notice.type)) - expect(serialized_notice.dismissable).to eq(true) - end -end diff --git a/spec/serializers/custom_wizard/subscription/authentication_serializer_spec.rb b/spec/serializers/custom_wizard/subscription/authentication_serializer_spec.rb deleted file mode 100644 index ac29f95a..00000000 --- a/spec/serializers/custom_wizard/subscription/authentication_serializer_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../plugin_helper' - -describe CustomWizard::Subscription::AuthenticationSerializer do - fab!(:user) { Fabricate(:user) } - - it 'should return subscription authentication attributes' do - auth = CustomWizard::Subscription::Authentication.new(OpenStruct.new(key: '1234', auth_at: Time.now, auth_by: user.id)) - serialized = described_class.new(auth, root: false).as_json - - expect(serialized[:active]).to eq(true) - expect(serialized[:client_id]).to eq(auth.client_id) - expect(serialized[:auth_by]).to eq(auth.auth_by) - expect(serialized[:auth_at]).to eq(auth.auth_at) - end -end diff --git a/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb b/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb deleted file mode 100644 index 91dc59b1..00000000 --- a/spec/serializers/custom_wizard/subscription/subscription_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../plugin_helper' - -describe CustomWizard::Subscription::SubscriptionSerializer do - it 'should return subscription attributes' do - sub = CustomWizard::Subscription::Subscription.new(OpenStruct.new(type: 'standard', updated_at: Time.now)) - serialized = described_class.new(sub, root: false).as_json - - expect(serialized[:active]).to eq(true) - expect(serialized[:type]).to eq('standard') - expect(serialized[:updated_at]).to eq(sub.updated_at) - end -end diff --git a/spec/serializers/custom_wizard/subscription_serializer_spec.rb b/spec/serializers/custom_wizard/subscription_serializer_spec.rb deleted file mode 100644 index c6ea0ef2..00000000 --- a/spec/serializers/custom_wizard/subscription_serializer_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../plugin_helper' - -describe CustomWizard::SubscriptionSerializer do - it 'should return subscription attributes' do - subscription = CustomWizard::Subscription.new - serialized = described_class.new(subscription, root: false) - - expect(serialized.server).to eq(subscription.server) - expect(serialized.authentication.class).to eq(CustomWizard::Subscription::Authentication) - expect(serialized.subscription.class).to eq(CustomWizard::Subscription::Subscription) - end -end From 92219ace2f4b09b8f22cae86d5844c401c66ef25 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 25 Mar 2022 12:22:27 +0100 Subject: [PATCH 195/556] Apply linting --- .../components/wizard-custom-action.js.es6 | 1 - .../wizard-subscription-badge.js.es6 | 4 +-- .../wizard-subscription-container.js.es6 | 2 +- .../wizard-subscription-selector.js.es6 | 28 ++++++++++++------ .../admin-wizards-wizard-show.js.es6 | 1 - .../controllers/admin-wizards.js.es6 | 4 +-- .../initializers/custom-wizard-edits.js.es6 | 2 -- .../discourse/lib/wizard-schema.js.es6 | 2 +- .../discourse/lib/wizard-subscription.js.es6 | 29 +++++++++++-------- .../discourse/mixins/subscription.js.es6 | 22 ++++++++------ .../routes/admin-wizards-custom-fields.js.es6 | 2 +- .../routes/admin-wizards-wizard-show.js.es6 | 2 +- .../discourse/routes/admin-wizards.js.es6 | 4 +-- assets/stylesheets/admin/admin.scss | 4 +-- lib/custom_wizard/subscription.rb | 1 + 15 files changed, 62 insertions(+), 46 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index a0a9cc72..75667c2f 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,5 +1,4 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"; -import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { subscriptionSelectKitContent } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription"; import { empty, equal, or } from "@ember/object/computed"; import { notificationLevels, selectKitContent } from "../lib/wizard"; diff --git a/assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 index 7a63f4d3..87c985a4 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-badge.js.es6 @@ -5,7 +5,7 @@ import DiscourseURL from "discourse/lib/url"; import I18n from "I18n"; export default Component.extend(Subscription, { - tagName: 'a', + tagName: "a", classNameBindings: [":wizard-subscription-badge", "subscriptionType"], attributeBindings: ["title"], @@ -26,5 +26,5 @@ export default Component.extend(Subscription, { click() { DiscourseURL.routeTo(this.subscriptionLink); - } + }, }); diff --git a/assets/javascripts/discourse/components/wizard-subscription-container.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-container.js.es6 index 3569276c..ccaf2f48 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-container.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-container.js.es6 @@ -22,5 +22,5 @@ export default Component.extend(Subscription, { return `admin.wizard.subscription_container.${ subscribed ? "subscribed" : "not_subscribed" }.title`; - } + }, }); diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 index 59714578..2202f746 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 @@ -3,17 +3,18 @@ import Subscription from "../mixins/subscription"; import wizardSchema from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema"; import { subscriptionTypeSufficient, - subscriptionTypes + subscriptionTypes, } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-subscription"; import discourseComputed from "discourse-common/utils/decorators"; +import I18n from "I18n"; -const nameKey = function(feature, attribute, value) { - if (feature === 'action') { +const nameKey = function (feature, attribute, value) { + if (feature === "action") { return `admin.wizard.action.${value}.label`; } else { return `admin.wizard.${feature}.${attribute}.${value}`; } -} +}; export default SingleSelectComponent.extend(Subscription, { classNames: ["combo-box", "wizard-subscription-selector"], @@ -30,10 +31,12 @@ export default SingleSelectComponent.extend(Subscription, { requiredSubscriptionType(feature, attribute, value) { let attributes = this.subscriptionAttributes[feature]; - if (!attributes || !attributes[attribute]) return null; + if (!attributes || !attributes[attribute]) { + return null; + } let requiredType = null; - Object.keys(attributes[attribute]).some(subscriptionType => { + Object.keys(attributes[attribute]).some((subscriptionType) => { let values = attributes[attribute][subscriptionType]; if (values[0] === "*" || values.includes(value)) { if (subscriptionTypes.includes(subscriptionType)) { @@ -47,15 +50,22 @@ export default SingleSelectComponent.extend(Subscription, { return requiredType; }, - @discourseComputed('feature', 'attribute') + @discourseComputed("feature", "attribute") content(feature, attribute) { return wizardSchema[feature][attribute].map((value) => { - let requiredSubscriptionType = this.requiredSubscriptionType(feature, attribute, value); + let requiredSubscriptionType = this.requiredSubscriptionType( + feature, + attribute, + value + ); return { id: value, name: I18n.t(nameKey(feature, attribute, value)), subscriptionType: requiredSubscriptionType, - disabled: !subscriptionTypeSufficient(this.subscriptionType, requiredSubscriptionType) + disabled: !subscriptionTypeSufficient( + this.subscriptionType, + requiredSubscriptionType + ), }; }); }, diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 index 2ecc42da..e6b0ad04 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 @@ -14,7 +14,6 @@ import I18n from "I18n"; export default Controller.extend({ hasName: notEmpty("wizard.name"), - @observes("currentStep") resetCurrentObjects() { const currentStep = this.currentStep; diff --git a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 index b69b878a..f99c06cc 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards.js.es6 @@ -2,6 +2,6 @@ import Controller from "@ember/controller"; import { equal } from "@ember/object/computed"; export default Controller.extend({ - businessSubscription: equal('subscriptionType', 'business'), - standardSubscription: equal('subscriptionType', 'standard') + businessSubscription: equal("subscriptionType", "business"), + standardSubscription: equal("subscriptionType", "standard"), }); diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 40d21b35..6d1410ad 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -1,7 +1,5 @@ import DiscourseURL from "discourse/lib/url"; import { withPluginApi } from "discourse/lib/plugin-api"; -import { isPresent } from "@ember/utils"; -import { A } from "@ember/array"; import getUrl from "discourse-common/lib/get-url"; export default { diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index ca5c30ee..97477fe8 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -211,7 +211,7 @@ const wizardSchema = { step, field, custom_field, - action + action, }; export function buildFieldTypes(types) { diff --git a/assets/javascripts/discourse/lib/wizard-subscription.js.es6 b/assets/javascripts/discourse/lib/wizard-subscription.js.es6 index deff32ca..adecf063 100644 --- a/assets/javascripts/discourse/lib/wizard-subscription.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-subscription.js.es6 @@ -1,17 +1,22 @@ -const subscriptionTypes = [ - 'standard', - 'business' -] +const subscriptionTypes = ["standard", "business"]; function subscriptionTypeSufficient(subscriptionType, requiredType) { - if (requiredType && !subscriptionType) return false; - if (requiredType === 'none' || requiredType === null) return true; - if (requiredType === 'standard' && subscriptionTypes.includes(subscriptionType)) return true; - if (requiredType === 'business' && subscriptionType === 'business') return true; + if (requiredType && !subscriptionType) { + return false; + } + if (requiredType === "none" || requiredType === null) { + return true; + } + if ( + requiredType === "standard" && + subscriptionTypes.includes(subscriptionType) + ) { + return true; + } + if (requiredType === "business" && subscriptionType === "business") { + return true; + } return false; } -export { - subscriptionTypeSufficient, - subscriptionTypes -} +export { subscriptionTypeSufficient, subscriptionTypes }; diff --git a/assets/javascripts/discourse/mixins/subscription.js.es6 b/assets/javascripts/discourse/mixins/subscription.js.es6 index 00f77bb2..574f8585 100644 --- a/assets/javascripts/discourse/mixins/subscription.js.es6 +++ b/assets/javascripts/discourse/mixins/subscription.js.es6 @@ -9,18 +9,22 @@ export default Mixin.create({ @discourseComputed adminWizards() { - return getOwner(this).lookup('controller:admin-wizards'); + return getOwner(this).lookup("controller:admin-wizards"); }, - subscribed: readOnly('adminWizards.subscribed'), - subscriptionType: readOnly('adminWizards.subscriptionType'), - businessSubscription: readOnly('adminWizards.businessSubscription'), - standardSubscription: readOnly('adminWizards.standardSubscription'), - subscriptionAttributes: readOnly('adminWizards.subscriptionAttributes'), - subscriptionClientInstalled: readOnly('adminWizards.subscriptionClientInstalled'), + subscribed: readOnly("adminWizards.subscribed"), + subscriptionType: readOnly("adminWizards.subscriptionType"), + businessSubscription: readOnly("adminWizards.businessSubscription"), + standardSubscription: readOnly("adminWizards.standardSubscription"), + subscriptionAttributes: readOnly("adminWizards.subscriptionAttributes"), + subscriptionClientInstalled: readOnly( + "adminWizards.subscriptionClientInstalled" + ), @discourseComputed("subscriptionClientInstalled") subscriptionLink(subscriptionClientInstalled) { - return subscriptionClientInstalled ? this.subscriptionClientUrl : this.subscriptionLandingUrl; - } + return subscriptionClientInstalled + ? this.subscriptionClientUrl + : this.subscriptionLandingUrl; + }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 index 4d9de2c4..a04d36f9 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-custom-fields.js.es6 @@ -11,7 +11,7 @@ export default DiscourseRoute.extend({ const customFields = A(model.custom_fields || []); controller.setProperties({ - customFields + customFields, }); }, }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 index 63ff564e..cb2d54c3 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -38,7 +38,7 @@ export default DiscourseRoute.extend({ wizard, currentStep: wizard.steps[0], currentAction: wizard.actions[0], - creating: model.create + creating: model.create, }; controller.setProperties(props); diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 index 0286926d..1fa786d3 100644 --- a/assets/javascripts/discourse/routes/admin-wizards.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -11,7 +11,7 @@ export default DiscourseRoute.extend({ subscribed: model.subscribed, subscriptionType: model.subscription_type, subscriptionAttributes: model.subscription_attributes, - subscriptionClientInstalled: model.subscription_client_installed + subscriptionClientInstalled: model.subscription_client_installed, }); }, @@ -19,5 +19,5 @@ export default DiscourseRoute.extend({ if (transition.targetName === "adminWizards.index") { this.transitionTo("adminWizardsWizard"); } - } + }, }); diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index b8458163..f1911f2c 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -847,7 +847,7 @@ $error: #ef1700; .subscription-header { display: flex; justify-content: space-between; - margin-bottom: .25em; + margin-bottom: 0.25em; h3 { margin: 0; @@ -871,7 +871,7 @@ $error: #ef1700; box-sizing: border-box; position: relative; cursor: pointer; - padding: .5em .65em; + padding: 0.5em 0.65em; background-color: rgba($primary-medium, 0.05); border: 1.5px solid rgba($primary-medium, 0.5); color: $primary-medium; diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index b59791b3..3d2d665e 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true class CustomWizard::Subscription STANDARD_PRODUCT_ID = 'prod_LNAGVAaIqDsHmB' BUSINESS_PRODUCT_ID = 'prod_LNABQ50maBQ1pY' From 04f0d34ef35ed2f45610989018905629a38a524d Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 25 Mar 2022 17:08:24 +0100 Subject: [PATCH 196/556] Fix specs --- app/controllers/custom_wizard/wizard.rb | 2 +- config/locales/server.en.yml | 1 + lib/custom_wizard/custom_field.rb | 4 +- lib/custom_wizard/subscription.rb | 25 ++-- lib/custom_wizard/validators/template.rb | 15 +- spec/components/custom_wizard/action_spec.rb | 35 +++-- spec/components/custom_wizard/builder_spec.rb | 5 +- .../custom_wizard/subscription_spec.rb | 104 +++++++++++++ .../components/custom_wizard/template_spec.rb | 2 + .../custom_wizard/template_validator_spec.rb | 12 +- .../custom_wizard/update_validator_spec.rb | 61 ++++---- spec/components/custom_wizard/wizard_spec.rb | 139 +++++++++--------- spec/fixtures/actions/watch_categories.json | 23 +++ spec/fixtures/field/advanced_types.json | 27 ++++ spec/fixtures/field/composer_preview.json | 7 + spec/fixtures/field/url.json | 5 + spec/fixtures/subscription_client.rb | 4 + spec/fixtures/wizard.json | 69 --------- spec/plugin_helper.rb | 6 + .../admin/wizard_controller_spec.rb | 1 + .../custom_wizard/steps_controller_spec.rb | 1 + .../custom_wizard/wizard_controller_spec.rb | 3 +- .../wizard_field_serializer_spec.rb | 4 +- .../custom_wizard/wizard_serializer_spec.rb | 75 +++++----- 24 files changed, 379 insertions(+), 251 deletions(-) create mode 100644 spec/components/custom_wizard/subscription_spec.rb create mode 100644 spec/fixtures/actions/watch_categories.json create mode 100644 spec/fixtures/field/advanced_types.json create mode 100644 spec/fixtures/field/composer_preview.json create mode 100644 spec/fixtures/field/url.json create mode 100644 spec/fixtures/subscription_client.rb diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index bc0a06af..bb53670b 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -44,7 +44,7 @@ class CustomWizard::WizardController < ::ActionController::Base return render json: { error: I18n.t('wizard.no_skip') } end - result = success_json + result = { success: 'OK' } if current_user && wizard.can_access? if redirect_to = wizard.current_submission&.redirect_to diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 2483503b..08cf5336 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -53,6 +53,7 @@ en: after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard." after_time: "After time setting is invalid." liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}" + subscription: "%{type} %{property} is subscription only" site_settings: custom_wizard_enabled: "Enable custom wizards." diff --git a/lib/custom_wizard/custom_field.rb b/lib/custom_wizard/custom_field.rb index f26c11fa..29423ec6 100644 --- a/lib/custom_wizard/custom_field.rb +++ b/lib/custom_wizard/custom_field.rb @@ -84,7 +84,7 @@ class ::CustomWizard::CustomField next end - if attr == 'klass' && @subscription.includes?(:custom_field, :klass, value) + if attr == 'klass' && !@subscription.includes?(:custom_field, :klass, value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end @@ -99,7 +99,7 @@ class ::CustomWizard::CustomField add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) end - if attr == 'type' && @subscription.includes?(:custom_field, :type, value) + if attr == 'type' && !@subscription.includes?(:custom_field, :type, value) add_error(I18n.t("wizard.custom_field.error.subscription_type", type: value)) end diff --git a/lib/custom_wizard/subscription.rb b/lib/custom_wizard/subscription.rb index 3d2d665e..114f6743 100644 --- a/lib/custom_wizard/subscription.rb +++ b/lib/custom_wizard/subscription.rb @@ -51,16 +51,7 @@ class CustomWizard::Subscription business: ['*'] }, type: { - none: ['label', 'description', 'image', 'required', 'placeholder', 'file'], - standard: ['*'], - business: ['*'] - }, - prefill: { - standard: ['*'], - business: ['*'] - }, - content: { - none: [], + none: ['text', 'textarea', 'text_only', 'date', 'time', 'date_time', 'number', 'checkbox', 'dropdown', 'upload'], standard: ['*'], business: ['*'] }, @@ -72,8 +63,8 @@ class CustomWizard::Subscription }, action: { type: { - none: [], - standard: ['send_message', 'watch_categories', 'add_to_group'], + none: ['create_topic', 'update_profile', 'open_composer', 'route_to'], + standard: ['create_topic', 'update_profile', 'open_composer', 'route_to', 'send_message', 'watch_categories', 'add_to_group'], business: ['*'] } }, @@ -108,7 +99,7 @@ class CustomWizard::Subscription return false if values.blank? ## Subscription type supports all values of the attribute. - return true if values === "*" + return true if values.first === "*" ## Subscription type supports some values of the attributes. values.include?(value) @@ -168,4 +159,12 @@ class CustomWizard::Subscription def self.type new.type end + + def self.client_installed? + new.client_installed? + end + + def self.includes?(feature, attribute, value) + new.includes?(feature, attribute, value) + end end diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index 0ffc2608..079f9884 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -54,15 +54,6 @@ class CustomWizard::TemplateValidator } end - def self.subscription - { - wizard: ['save_submissions', 'restart_on_revisit'], - step: ['condition', 'index', 'required_data', 'permitted_params'], - field: ['condition', 'index'], - action: ['type'] - } - end - private def check_required(object, type) @@ -74,8 +65,10 @@ class CustomWizard::TemplateValidator end def validate_subscription(object, type) - self.class.subscription[type].each do |property| - if !@subscription.includes?(type, property, object[property]) + object.keys.each do |property| + value = object[property] + + if !@subscription.includes?(type, property.to_sym, value) errors.add :base, I18n.t("wizard.validation.subscription", type: type.to_s, property: property) end end diff --git a/spec/components/custom_wizard/action_spec.rb b/spec/components/custom_wizard/action_spec.rb index 6149272e..5155a7f4 100644 --- a/spec/components/custom_wizard/action_spec.rb +++ b/spec/components/custom_wizard/action_spec.rb @@ -8,6 +8,7 @@ describe CustomWizard::Action do let(:wizard_template) { get_wizard_fixture("wizard") } let(:open_composer) { get_wizard_fixture("actions/open_composer") } let(:create_category) { get_wizard_fixture("actions/create_category") } + let(:watch_categories) { get_wizard_fixture("actions/watch_categories") } let(:create_group) { get_wizard_fixture("actions/create_group") } let(:add_to_group) { get_wizard_fixture("actions/add_to_group") } let(:send_message) { get_wizard_fixture("actions/send_message") } @@ -158,17 +159,6 @@ describe CustomWizard::Action do end end - it 'watches categories' do - wizard = CustomWizard::Builder.new(@template[:id], user).build - wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update - wizard.create_updater(wizard.steps[1].id, {}).update - - expect(CategoryUser.where( - category_id: category.id, - user_id: user.id - ).first.notification_level).to eq(0) - end - it 're-routes a user' do wizard = CustomWizard::Builder.new(@template[:id], user).build updater = wizard.create_updater(wizard.steps.last.id, {}) @@ -176,11 +166,25 @@ describe CustomWizard::Action do expect(updater.result[:redirect_on_next]).to eq("https://google.com") end - context "subscription actions" do + context "standard subscription actions" do before do enable_subscription("standard") end + it 'watches categories' do + watch_categories[:categories][0][:output] = category.id + wizard_template[:actions] << watch_categories + update_template(wizard_template) + + wizard = CustomWizard::Builder.new(@template[:id], user).build + wizard.create_updater(wizard.steps[0].id, step_1_field_1: "Text input").update + + expect(CategoryUser.where( + category_id: category.id, + user_id: user.id + ).first.notification_level).to eq(2) + end + it '#send_message' do wizard_template['actions'] << send_message update_template(wizard_template) @@ -233,9 +237,16 @@ describe CustomWizard::Action do expect(topic.first.allowed_groups.map(&:name)).to include('cool_group', 'cool_group_1') expect(post.exists?).to eq(true) end + end + + context "business subscription actions" do + before do + enable_subscription("business") + end it '#create_category' do wizard_template['actions'] << create_category + wizard_template['actions'] << create_group update_template(wizard_template) wizard = CustomWizard::Builder.new(@template[:id], user).build diff --git a/spec/components/custom_wizard/builder_spec.rb b/spec/components/custom_wizard/builder_spec.rb index 1d7b1213..ebcc355b 100644 --- a/spec/components/custom_wizard/builder_spec.rb +++ b/spec/components/custom_wizard/builder_spec.rb @@ -100,6 +100,7 @@ describe CustomWizard::Builder do context "with restricted permissions" do before do + enable_subscription("standard") @template[:permitted] = permitted_json["permitted"] CustomWizard::Template.save(@template.as_json) end @@ -304,7 +305,7 @@ describe CustomWizard::Builder do before do enable_subscription("standard") @template[:steps][0][:fields][0][:condition] = user_condition_json['condition'] - @template[:steps][2][:fields][5][:condition] = boolean_field_condition_json['condition'] + @template[:steps][2][:fields][0][:condition] = boolean_field_condition_json['condition'] CustomWizard::Template.save(@template.as_json) end @@ -325,7 +326,7 @@ describe CustomWizard::Builder do builder = CustomWizard::Builder.new(@template[:id], user) wizard = builder.build - expect(wizard.steps.last.fields.last.id).to eq(@template[:steps][2][:fields][5]['id']) + expect(wizard.steps.last.fields.last.id).to eq(@template[:steps][2][:fields][0]['id']) end end end diff --git a/spec/components/custom_wizard/subscription_spec.rb b/spec/components/custom_wizard/subscription_spec.rb new file mode 100644 index 00000000..984eff9f --- /dev/null +++ b/spec/components/custom_wizard/subscription_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +describe CustomWizard::Subscription do + def undefine_client_classes + Object.send(:remove_const, :SubscriptionClient) if Object.constants.include?(:SubscriptionClient) + Object.send(:remove_const, :SubscriptionClientSubscription) if Object.constants.include?(:SubscriptionClientSubscription) + end + + def define_client_classes + load File.expand_path("#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/subscription_client.rb", __FILE__) + end + + def stub_client_methods + [:active, :where, :order, :first].each do |method| + SubscriptionClientSubscription.stubs(method) + .returns(SubscriptionClientSubscription) + end + SubscriptionClientSubscription.stubs(:product_id).returns(SecureRandom.hex(8)) + end + + after do + undefine_client_classes + end + + it "detects the subscription client" do + expect(described_class.client_installed?).to eq(false) + end + + context "without a subscription client" do + it "is not subscribed" do + expect(described_class.subscribed?).to eq(false) + end + + it "has none type" do + subscription = described_class.new + expect(subscription.type).to eq(:none) + end + + it "non subscriber features are included" do + expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true) + end + + it "ubscriber features are not included" do + expect(described_class.includes?(:wizard, :permitted, {})).to eq(false) + end + end + + context "with subscription client" do + before do + define_client_classes + stub_client_methods + end + + it "detects the subscription client" do + expect(described_class.client_installed?).to eq(true) + end + + context "without a subscription" do + it "has none type" do + expect(described_class.type).to eq(:none) + end + + it "non subscriber features are included" do + expect(described_class.includes?(:wizard, :after_signup, true)).to eq(true) + end + + it "subscriber features are not included" do + expect(described_class.includes?(:wizard, :permitted, {})).to eq(false) + end + end + + context "with standard subscription" do + before do + SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::STANDARD_PRODUCT_ID) + end + + it "detects standard type" do + expect(described_class.type).to eq(:standard) + end + + it "standard features are included" do + expect(described_class.includes?(:wizard, :type, 'send_message')).to eq(true) + end + + it "business features are not included" do + expect(described_class.includes?(:action, :type, 'create_category')).to eq(false) + end + end + + context "with business subscription" do + before do + SubscriptionClientSubscription.stubs(:product_id).returns(CustomWizard::Subscription::BUSINESS_PRODUCT_ID) + end + + it "detects business type" do + expect(described_class.type).to eq(:business) + end + + it "business are included" do + expect(described_class.includes?(:action, :type, 'create_category')).to eq(true) + end + end + end +end diff --git a/spec/components/custom_wizard/template_spec.rb b/spec/components/custom_wizard/template_spec.rb index 14e1659b..63ea25c6 100644 --- a/spec/components/custom_wizard/template_spec.rb +++ b/spec/components/custom_wizard/template_spec.rb @@ -47,6 +47,8 @@ describe CustomWizard::Template do context "wizard template list" do before do + enable_subscription('standard') + template_json_2 = template_json.dup template_json_2["id"] = 'super_mega_fun_wizard_2' template_json_2["permitted"] = permitted_json['permitted'] diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb index 3561e272..b149706f 100644 --- a/spec/components/custom_wizard/template_validator_spec.rb +++ b/spec/components/custom_wizard/template_validator_spec.rb @@ -5,6 +5,8 @@ describe CustomWizard::TemplateValidator do let(:template) { get_wizard_fixture("wizard") } let(:create_category) { get_wizard_fixture("actions/create_category") } let(:user_condition) { get_wizard_fixture("condition/user_condition") } + let(:permitted_json) { get_wizard_fixture("wizard/permitted") } + let(:composer_preview) { get_wizard_fixture("field/composer_preview") } let(:valid_liquid_template) { <<-LIQUID.strip @@ -104,7 +106,7 @@ describe CustomWizard::TemplateValidator do context "without subscription" do it "invalidates subscription wizard attributes" do - template[:save_submissions] = false + template[:permitted] = permitted_json['permitted'] expect( CustomWizard::TemplateValidator.new(template).perform ).to eq(false) @@ -134,11 +136,11 @@ describe CustomWizard::TemplateValidator do context "with subscription" do before do - enable_subscription("standard") + enable_subscription("business") end it "validates wizard attributes" do - template[:save_submissions] = false + template[:permitted] = permitted_json['permitted'] expect( CustomWizard::TemplateValidator.new(template).perform ).to eq(true) @@ -227,7 +229,9 @@ describe CustomWizard::TemplateValidator do end it "validates preview templates" do - template[:steps][0][:fields][4][:preview_template] = invalid_liquid_template + enable_subscription("standard") + template[:steps][0][:fields] << composer_preview + template[:steps][0][:fields][3][:preview_template] = invalid_liquid_template expect_validation_failure("step_1_field_5.preview_template", liquid_syntax_error) end end diff --git a/spec/components/custom_wizard/update_validator_spec.rb b/spec/components/custom_wizard/update_validator_spec.rb index a01f70d7..7caa1784 100644 --- a/spec/components/custom_wizard/update_validator_spec.rb +++ b/spec/components/custom_wizard/update_validator_spec.rb @@ -3,6 +3,7 @@ describe CustomWizard::UpdateValidator do fab!(:user) { Fabricate(:user) } let(:template) { get_wizard_fixture("wizard") } + let(:url_field) { get_wizard_fixture("field/url") } before do CustomWizard::Template.save(template, skip_jobs: true) @@ -21,7 +22,6 @@ describe CustomWizard::UpdateValidator do @template[:steps][0][:fields][0][:min_length] = min_length @template[:steps][0][:fields][1][:min_length] = min_length - @template[:steps][0][:fields][2][:min_length] = min_length CustomWizard::Template.save(@template) @@ -34,11 +34,6 @@ describe CustomWizard::UpdateValidator do expect( updater.errors.messages[:step_1_field_2].first ).to eq(I18n.t('wizard.field.too_short', label: 'Textarea', min: min_length)) - - updater = perform_validation('step_1', step_1_field_3: 'Te') - expect( - updater.errors.messages[:step_1_field_3].first - ).to eq(I18n.t('wizard.field.too_short', label: 'Composer', min: min_length)) end it 'prevents submission if the length is over the max length' do @@ -46,7 +41,6 @@ describe CustomWizard::UpdateValidator do @template[:steps][0][:fields][0][:max_length] = max_length @template[:steps][0][:fields][1][:max_length] = max_length - @template[:steps][0][:fields][2][:max_length] = max_length CustomWizard::Template.save(@template) long_string = "Our Competitive Capability solution offers platforms a suite of wholesale offerings. In the future, will you be able to effectively revolutionize synergies in your business? In the emerging market space, industry is ethically investing its mission critical executive searches. Key players will take ownership of their capabilities by iteratively right-sizing world-class visibilities. " @@ -59,11 +53,6 @@ describe CustomWizard::UpdateValidator do expect( updater.errors.messages[:step_1_field_2].first ).to eq(I18n.t('wizard.field.too_long', label: 'Textarea', max: max_length)) - - updater = perform_validation('step_1', step_1_field_3: long_string) - expect( - updater.errors.messages[:step_1_field_3].first - ).to eq(I18n.t('wizard.field.too_long', label: 'Composer', max: max_length)) end it "allows submission if the length is under or equal to the max length" do @@ -71,7 +60,6 @@ describe CustomWizard::UpdateValidator do @template[:steps][0][:fields][0][:max_length] = max_length @template[:steps][0][:fields][1][:max_length] = max_length - @template[:steps][0][:fields][2][:max_length] = max_length CustomWizard::Template.save(@template) hundred_chars_string = "This is a line, exactly hundred characters long and not more even a single character more than that." @@ -84,11 +72,6 @@ describe CustomWizard::UpdateValidator do expect( updater.errors.messages[:step_1_field_2].first ).to eq(nil) - - updater = perform_validation('step_1', step_1_field_3: hundred_chars_string) - expect( - updater.errors.messages[:step_1_field_3].first - ).to eq(nil) end it "applies min length only if the input is non-empty" do @@ -131,25 +114,33 @@ describe CustomWizard::UpdateValidator do ).to eq(I18n.t('wizard.field.required', label: 'Textarea')) end - it 'validates url fields' do - updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com') - expect( - updater.errors.messages[:step_2_field_6].first - ).to eq(nil) - end + context "subscription fields" do + before do + enable_subscription("standard") + end - it 'does not validate url fields with non-url inputs' do - updater = perform_validation('step_2', step_2_field_6: 'discourse') - expect( - updater.errors.messages[:step_2_field_6].first - ).to eq(I18n.t('wizard.field.not_url', label: 'Url')) - end + it 'validates url fields' do + updater = perform_validation('step_2', step_2_field_6: 'https://discourse.com') + expect( + updater.errors.messages[:step_2_field_6].first + ).to eq(nil) + end - it 'validates empty url fields' do - updater = perform_validation('step_2', step_2_field_6: '') - expect( - updater.errors.messages[:step_2_field_6].first - ).to eq(nil) + it 'does not validate url fields with non-url inputs' do + template[:steps][0][:fields] << url_field + CustomWizard::Template.save(template) + updater = perform_validation('step_1', step_2_field_6: 'discourse') + expect( + updater.errors.messages[:step_2_field_6].first + ).to eq(I18n.t('wizard.field.not_url', label: 'Url')) + end + + it 'validates empty url fields' do + updater = perform_validation('step_2', step_2_field_6: '') + expect( + updater.errors.messages[:step_2_field_6].first + ).to eq(nil) + end end it 'validates date fields' do diff --git a/spec/components/custom_wizard/wizard_spec.rb b/spec/components/custom_wizard/wizard_spec.rb index 00354bf1..5dc1cb95 100644 --- a/spec/components/custom_wizard/wizard_spec.rb +++ b/spec/components/custom_wizard/wizard_spec.rb @@ -113,69 +113,91 @@ describe CustomWizard::Wizard do expect(wizard.completed?).to eq(false) end - it "permits admins" do - expect( - CustomWizard::Wizard.new(@permitted_template, admin_user).permitted? - ).to eq(true) - end + context "with subscription" do + before do + enable_subscription("standard") + end - it "permits permitted users" do - expect( - CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted? - ).to eq(true) - end + it "permits admins" do + expect( + CustomWizard::Wizard.new(@permitted_template, admin_user).permitted? + ).to eq(true) + end - it "permits everyone if everyone is permitted" do - @permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone] - expect( - CustomWizard::Wizard.new(@permitted_template, user).permitted? - ).to eq(true) - end + it "permits permitted users" do + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).permitted? + ).to eq(true) + end - it "does not permit unpermitted users" do - expect( - CustomWizard::Wizard.new(@permitted_template, user).permitted? - ).to eq(false) - end + it "permits everyone if everyone is permitted" do + @permitted_template['permitted'][0]['output'] = Group::AUTO_GROUPS[:everyone] + expect( + CustomWizard::Wizard.new(@permitted_template, user).permitted? + ).to eq(true) + end - it "does not let an unpermitted user access a wizard" do - expect( - CustomWizard::Wizard.new(@permitted_template, user).can_access? - ).to eq(false) - end + it "does not permit unpermitted users" do + expect( + CustomWizard::Wizard.new(@permitted_template, user).permitted? + ).to eq(false) + end - it "lets a permitted user access an incomplete wizard" do - expect( - CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? - ).to eq(true) - end + it "does not let an unpermitted user access a wizard" do + expect( + CustomWizard::Wizard.new(@permitted_template, user).can_access? + ).to eq(false) + end - it "lets a permitted user access a complete wizard with multiple submissions" do - append_steps + it "lets a permitted user access an incomplete wizard" do + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(true) + end - progress_step("step_1", acting_user: trusted_user) - progress_step("step_2", acting_user: trusted_user) - progress_step("step_3", acting_user: trusted_user) + it "lets a permitted user access a complete wizard with multiple submissions" do + append_steps - @permitted_template["multiple_submissions"] = true + progress_step("step_1", acting_user: trusted_user) + progress_step("step_2", acting_user: trusted_user) + progress_step("step_3", acting_user: trusted_user) - expect( - CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? - ).to eq(true) - end + @permitted_template["multiple_submissions"] = true - it "does not let an unpermitted user access a complete wizard without multiple submissions" do - append_steps + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(true) + end - progress_step("step_1", acting_user: trusted_user) - progress_step("step_2", acting_user: trusted_user) - progress_step("step_3", acting_user: trusted_user) + it "does not let an unpermitted user access a complete wizard without multiple submissions" do + append_steps - @permitted_template['multiple_submissions'] = false + progress_step("step_1", acting_user: trusted_user) + progress_step("step_2", acting_user: trusted_user) + progress_step("step_3", acting_user: trusted_user) - expect( - CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? - ).to eq(false) + @permitted_template['multiple_submissions'] = false + + expect( + CustomWizard::Wizard.new(@permitted_template, trusted_user).can_access? + ).to eq(false) + end + + it "sets wizard redirects if user is permitted" do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user) + expect( + trusted_user.custom_fields['redirect_to_wizard'] + ).to eq("super_mega_fun_wizard") + end + + it "does not set a wizard redirect if user is not permitted" do + CustomWizard::Template.save(@permitted_template, skip_jobs: true) + CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user) + expect( + trusted_user.custom_fields['redirect_to_wizard'] + ).to eq(nil) + end end it "lists the site groups" do @@ -203,6 +225,7 @@ describe CustomWizard::Wizard do context "class methods" do before do + enable_subscription("standard") CustomWizard::Template.save(@permitted_template, skip_jobs: true) template_json_2 = template_json.dup @@ -238,20 +261,4 @@ describe CustomWizard::Wizard do expect(CustomWizard::Wizard.prompt_completion(user).length).to eq(1) end end - - it "sets wizard redirects if user is permitted" do - CustomWizard::Template.save(@permitted_template, skip_jobs: true) - CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user) - expect( - trusted_user.custom_fields['redirect_to_wizard'] - ).to eq("super_mega_fun_wizard") - end - - it "does not set a wizard redirect if user is not permitted" do - CustomWizard::Template.save(@permitted_template, skip_jobs: true) - CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user) - expect( - trusted_user.custom_fields['redirect_to_wizard'] - ).to eq(nil) - end end diff --git a/spec/fixtures/actions/watch_categories.json b/spec/fixtures/actions/watch_categories.json new file mode 100644 index 00000000..20644f44 --- /dev/null +++ b/spec/fixtures/actions/watch_categories.json @@ -0,0 +1,23 @@ +{ + "id": "action_5", + "run_after": "step_1", + "type": "watch_categories", + "notification_level": "tracking", + "wizard_user": true, + "categories": [ + { + "type": "assignment", + "output": "", + "output_type": "text", + "output_connector": "set" + } + ], + "mute_remainder": [ + { + "type": "assignment", + "output": "true", + "output_type": "text", + "output_connector": "set" + } + ] +} diff --git a/spec/fixtures/field/advanced_types.json b/spec/fixtures/field/advanced_types.json new file mode 100644 index 00000000..63357cdb --- /dev/null +++ b/spec/fixtures/field/advanced_types.json @@ -0,0 +1,27 @@ +{ + "fields": [ + { + "id": "step_3_field_2", + "label": "Tag", + "type": "tag" + }, + { + "id": "step_3_field_3", + "label": "Category", + "type": "category", + "limit": 1, + "property": "id" + }, + { + "id": "step_3_field_4", + "label": "Group", + "type": "group" + }, + { + "id": "step_3_field_5", + "label": "User Selector", + "description": "", + "type": "user_selector" + } + ] +} diff --git a/spec/fixtures/field/composer_preview.json b/spec/fixtures/field/composer_preview.json new file mode 100644 index 00000000..f4bec04a --- /dev/null +++ b/spec/fixtures/field/composer_preview.json @@ -0,0 +1,7 @@ +{ + "id": "step_1_field_5", + "label": "I'm a preview", + "description": "", + "type": "composer_preview", + "preview_template": "w{step_1_field_1}" +} diff --git a/spec/fixtures/field/url.json b/spec/fixtures/field/url.json new file mode 100644 index 00000000..e3153fe9 --- /dev/null +++ b/spec/fixtures/field/url.json @@ -0,0 +1,5 @@ +{ + "id": "step_2_field_6", + "label": "Url", + "type": "url" +} diff --git a/spec/fixtures/subscription_client.rb b/spec/fixtures/subscription_client.rb new file mode 100644 index 00000000..a041b507 --- /dev/null +++ b/spec/fixtures/subscription_client.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +module SubscriptionClient; end +class SubscriptionClientSubscription; end diff --git a/spec/fixtures/wizard.json b/spec/fixtures/wizard.json index a05c1782..de5e636e 100644 --- a/spec/fixtures/wizard.json +++ b/spec/fixtures/wizard.json @@ -33,23 +33,11 @@ "type": "textarea", "min_length": "" }, - { - "id": "step_1_field_3", - "label": "Composer", - "type": "composer" - }, { "id": "step_1_field_4", "label": "I'm only text", "description": "", "type": "text_only" - }, - { - "id": "step_1_field_5", - "label": "I'm a preview", - "description": "", - "type": "composer_preview", - "preview_template": "w{step_1_field_1}" } ], "description": "Text inputs!" @@ -87,11 +75,6 @@ "label": "Checkbox", "type": "checkbox" }, - { - "id": "step_2_field_6", - "label": "Url", - "type": "url" - }, { "id": "step_2_field_7", "label": "Upload", @@ -141,35 +124,6 @@ ] } ] - }, - { - "id": "step_3_field_2", - "label": "Tag", - "type": "tag" - }, - { - "id": "step_3_field_3", - "label": "Category", - "type": "category", - "limit": 1, - "property": "id" - }, - { - "id": "step_3_field_4", - "label": "Group", - "type": "group" - }, - { - "id": "step_3_field_5", - "label": "User Selector", - "description": "", - "type": "user_selector" - }, - { - "id": "step_3_field_6", - "label": "Conditional User Selector", - "description": "Shown when checkbox in step_2_field_5 is true", - "type": "user_selector" } ], "description": "Unfortunately not the edible type :sushi: " @@ -264,29 +218,6 @@ } ] }, - { - "id": "action_5", - "run_after": "step_1", - "type": "watch_categories", - "notification_level": "tracking", - "wizard_user": true, - "categories": [ - { - "type": "assignment", - "output": "action_8", - "output_type": "wizard_action", - "output_connector": "set" - } - ], - "mute_remainder": [ - { - "type": "assignment", - "output": "true", - "output_type": "text", - "output_connector": "set" - } - ] - }, { "id": "action_4", "run_after": "step_2", diff --git a/spec/plugin_helper.rb b/spec/plugin_helper.rb index f7818296..a0189de1 100644 --- a/spec/plugin_helper.rb +++ b/spec/plugin_helper.rb @@ -7,3 +7,9 @@ def get_wizard_fixture(path) ).read ).with_indifferent_access end + +def enable_subscription(type) + CustomWizard::Subscription.stubs(:client_installed?).returns(true) + CustomWizard::Subscription.stubs("#{type}?".to_sym).returns(true) + CustomWizard::Subscription.any_instance.stubs("#{type}?".to_sym).returns(true) +end diff --git a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb index fd2fa006..9f63cb6b 100644 --- a/spec/requests/custom_wizard/admin/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/wizard_controller_spec.rb @@ -8,6 +8,7 @@ describe CustomWizard::AdminWizardController do before do CustomWizard::Template.save(template, skip_jobs: true) + enable_subscription("standard") template_2 = template.dup template_2["id"] = 'super_mega_fun_wizard_2' diff --git a/spec/requests/custom_wizard/steps_controller_spec.rb b/spec/requests/custom_wizard/steps_controller_spec.rb index 26ba817a..e05ba917 100644 --- a/spec/requests/custom_wizard/steps_controller_spec.rb +++ b/spec/requests/custom_wizard/steps_controller_spec.rb @@ -34,6 +34,7 @@ describe CustomWizard::StepsController do end it "when the user cant access the wizard" do + enable_subscription("standard") new_template = wizard_template.dup new_template["permitted"] = permitted_json["permitted"] CustomWizard::Template.save(new_template, skip_jobs: true) diff --git a/spec/requests/custom_wizard/wizard_controller_spec.rb b/spec/requests/custom_wizard/wizard_controller_spec.rb index 3d081b8a..aa1f479b 100644 --- a/spec/requests/custom_wizard/wizard_controller_spec.rb +++ b/spec/requests/custom_wizard/wizard_controller_spec.rb @@ -40,14 +40,15 @@ describe CustomWizard::WizardController do end it 'lets user skip if user cant access wizard' do + enable_subscription("standard") @template["permitted"] = permitted_json["permitted"] CustomWizard::Template.save(@template, skip_jobs: true) - put '/w/super-mega-fun-wizard/skip.json' expect(response.status).to eq(200) end it 'returns a no skip message if user is not allowed to skip' do + enable_subscription("standard") @template['required'] = 'true' CustomWizard::Template.save(@template) put '/w/super-mega-fun-wizard/skip.json' diff --git a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb index c30279b7..1ac2579e 100644 --- a/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_field_serializer_spec.rb @@ -19,7 +19,7 @@ describe CustomWizard::FieldSerializer do expect(json_array.size).to eq(@wizard.steps.first.fields.size) expect(json_array[0][:label]).to eq("

Text

") expect(json_array[0][:description]).to eq("Text field description.") - expect(json_array[3][:index]).to eq(3) + expect(json_array[2][:index]).to eq(2) end it "should return optional field attributes" do @@ -29,6 +29,6 @@ describe CustomWizard::FieldSerializer do scope: Guardian.new(user) ).as_json expect(json_array[0][:format]).to eq("YYYY-MM-DD") - expect(json_array[6][:file_types]).to eq(".jpg,.jpeg,.png") + expect(json_array[5][:file_types]).to eq(".jpg,.jpeg,.png") end end diff --git a/spec/serializers/custom_wizard/wizard_serializer_spec.rb b/spec/serializers/custom_wizard/wizard_serializer_spec.rb index 030f109e..526f5259 100644 --- a/spec/serializers/custom_wizard/wizard_serializer_spec.rb +++ b/spec/serializers/custom_wizard/wizard_serializer_spec.rb @@ -5,6 +5,7 @@ describe CustomWizard::WizardSerializer do fab!(:category) { Fabricate(:category) } let(:template) { get_wizard_fixture("wizard") } let(:similar_topics_validation) { get_wizard_fixture("field/validation/similar_topics") } + let(:advanced_fields) { get_wizard_fixture("field/advanced_types") } before do CustomWizard::Template.save(template, skip_jobs: true) @@ -52,43 +53,51 @@ describe CustomWizard::WizardSerializer do expect(json[:wizard][:uncategorized_category_id].present?).to eq(false) end - it "should return categories if there is a category selector field" do - json = CustomWizard::WizardSerializer.new( - CustomWizard::Builder.new(@template[:id], user).build, - scope: Guardian.new(user) - ).as_json - expect(json[:wizard][:categories].present?).to eq(true) - expect(json[:wizard][:uncategorized_category_id].present?).to eq(true) - end + context "advanced fields" do + before do + enable_subscription("standard") + @template[:steps][0][:fields].push(*advanced_fields['fields']) + CustomWizard::Template.save(@template, skip_jobs: true) + end - it "should return categories if there is a similar topics validation scoped to category(s)" do - @template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations] - CustomWizard::Template.save(@template) + it "should return categories if there is a category selector field" do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:categories].present?).to eq(true) + expect(json[:wizard][:uncategorized_category_id].present?).to eq(true) + end - json = CustomWizard::WizardSerializer.new( - CustomWizard::Builder.new(@template[:id], user).build, - scope: Guardian.new(user) - ).as_json - expect(json[:wizard][:categories].present?).to eq(true) - expect(json[:wizard][:uncategorized_category_id].present?).to eq(true) - end + it "should return categories if there is a similar topics validation scoped to category(s)" do + @template[:steps][0][:fields][0][:validations] = similar_topics_validation[:validations] + CustomWizard::Template.save(@template) - it 'should return groups if there is a group selector field' do - json = CustomWizard::WizardSerializer.new( - CustomWizard::Builder.new(@template[:id], user).build, - scope: Guardian.new(user) - ).as_json - expect(json[:wizard][:groups].length).to eq(8) - end + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:categories].present?).to eq(true) + expect(json[:wizard][:uncategorized_category_id].present?).to eq(true) + end - it 'should not return groups if there is not a group selector field' do - @template[:steps][2][:fields].delete_at(3) - CustomWizard::Template.save(@template) + it 'should return groups if there is a group selector field' do + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:groups].length).to eq(8) + end - json = CustomWizard::WizardSerializer.new( - CustomWizard::Builder.new(@template[:id], user).build, - scope: Guardian.new(user) - ).as_json - expect(json[:wizard][:groups].present?).to eq(false) + it 'should not return groups if there is not a group selector field' do + @template[:steps][0][:fields].reject! { |f| f["type"] === "group" } + CustomWizard::Template.save(@template) + + json = CustomWizard::WizardSerializer.new( + CustomWizard::Builder.new(@template[:id], user).build, + scope: Guardian.new(user) + ).as_json + expect(json[:wizard][:groups].present?).to eq(false) + end end end From a0534404663c7f3d2b3b4d575a2bcb6d716e0cb4 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 29 Mar 2022 18:09:12 +0200 Subject: [PATCH 197/556] Update plugin.rb --- plugin.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index ebd5c6d5..cb79f052 100644 --- a/plugin.rb +++ b/plugin.rb @@ -13,10 +13,11 @@ register_asset 'stylesheets/admin/admin.scss', :desktop enabled_site_setting :custom_wizard_enabled if Rails.env.production? - config.assets.precompile += %w{ + Rails.application.config.assets.precompile += %w{ wizard-custom-guest.js wizard-custom-start.js wizard-custom.js + wizard-qunit.js wizard-plugin.js.erb wizard-raw-templates.js.erb } From 466c7a7a496963ad00236ea7393fe1f000a15c81 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 29 Mar 2022 20:17:59 +0200 Subject: [PATCH 198/556] Explicitly load asset paths for tests --- plugin.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugin.rb b/plugin.rb index cb79f052..4c941d48 100644 --- a/plugin.rb +++ b/plugin.rb @@ -23,6 +23,13 @@ if Rails.env.production? } end +if Rails.env.test? + config = Rails.application.config + plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets" + config.assets.paths << "#{plugin_asset_path}/javascripts" + config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" +end + if respond_to?(:register_svg_icon) register_svg_icon "far-calendar" register_svg_icon "chevron-right" From 1296d3bff8dbed2ea7dc1b8f099f415c1136ed5c Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 29 Mar 2022 21:15:09 +0200 Subject: [PATCH 199/556] Update action usage --- .../discourse/templates/admin-wizards-api.hbs | 2 +- .../templates/admin-wizards-custom-fields.hbs | 2 +- .../discourse/templates/admin-wizards-logs-show.hbs | 2 +- .../discourse/templates/admin-wizards-wizard-show.hbs | 4 ++-- .../discourse/templates/admin-wizards-wizard.hbs | 2 +- .../templates/components/custom-field-input.hbs | 8 ++++---- .../templates/components/wizard-custom-action.hbs | 2 +- .../templates/components/wizard-custom-field.hbs | 2 +- .../discourse/templates/components/wizard-links.hbs | 11 +++++------ .../discourse/templates/components/wizard-mapper.hbs | 2 +- .../templates/components/wizard-text-editor.hbs | 4 ++-- .../templates/modal/next-session-scheduled.hbs | 2 +- 12 files changed, 21 insertions(+), 22 deletions(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards-api.hbs b/assets/javascripts/discourse/templates/admin-wizards-api.hbs index 00d8ad60..f5e3214d 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-api.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-api.hbs @@ -8,7 +8,7 @@ )}} {{d-button - action="createApi" + action=(action "createApi") label="admin.wizard.api.create" icon="plus"}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index 10501498..a90f6299 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -5,7 +5,7 @@ {{d-button label="admin.wizard.custom_field.add" icon="plus" - action="addField"}} + action=(action "addField")}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs index 898198d1..270d5c21 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-logs-show.hbs @@ -8,7 +8,7 @@ {{d-button label="refresh" icon="sync" - action="refresh" + action=(action "refresh") class="refresh"}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index 5d1c2166..8dfb4aac 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -101,7 +101,7 @@ {{input type="checkbox" checked=wizard.after_time}} {{i18n "admin.wizard.after_time_label"}} {{d-button - action="setNextSessionScheduled" + action=(action "setNextSessionScheduled") translatedLabel=nextSessionScheduledLabel class="btn-after-time" icon="far-calendar"}} @@ -176,7 +176,7 @@ currentActionId=currentAction.id wizard=wizard apis=apis - removeAction="removeAction" + removeAction=(action "removeAction") wizardFields=wizardFields}} {{/each}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs index 081cd5f3..11b0cd11 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs @@ -8,7 +8,7 @@ )}} {{d-button - action="createWizard" + action=(action "createWizard") label="admin.wizard.create" icon="plus"}}
diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index 335a9b9d..c0bdaaff 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -42,17 +42,17 @@ {{/if}} {{/if}} {{d-button - action="destroy" + action=(action "destroy") icon="trash-alt" class="destroy" disabled=destroyDisabled}} {{d-button icon="save" - action="save" + action=(action "save") disabled=saveDisabled class="save"}} {{d-button - action="close" + action=(action "close") icon="times" disabled=closeDisabled}} @@ -77,7 +77,7 @@ {{else}} - {{d-button action="edit" icon="pencil-alt"}} + {{d-button action=(action "edit") icon="pencil-alt"}} {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 818c525d..be9c14c5 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -1,6 +1,6 @@ {{#if showUndo}} {{d-button - action="undoChanges" + action=(action "undoChanges") icon=undoIcon label=undoKey class="undo-changes"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index 00c20153..3bb32c05 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -1,6 +1,6 @@ {{#if showUndo}} {{d-button - action="undoChanges" + action=(action "undoChanges") icon=undoIcon label=undoKey class="undo-changes"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-links.hbs b/assets/javascripts/discourse/templates/components/wizard-links.hbs index a7a7662e..368acd35 100644 --- a/assets/javascripts/discourse/templates/components/wizard-links.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-links.hbs @@ -4,17 +4,16 @@ {{#if anyLinks}} {{#each links as |link|}}
- {{d-button action="change" actionParam=link.id translatedLabel=link.label class=link.classes}} + {{d-button action=(action "change") actionParam=link.id translatedLabel=link.label class=link.classes}} {{#unless link.first}} - {{d-button action="back" actionParam=link icon="arrow-left" class="back"}} + {{d-button action=(action "back") actionParam=link icon="arrow-left" class="back"}} {{/unless}} {{#unless link.last}} - {{d-button action="forward" actionParam=link icon="arrow-right" class="forward"}} + {{d-button action=(action "forward") actionParam=link icon="arrow-right" class="forward"}} {{/unless}} - {{d-button action="remove" actionParam=link.id icon="times" class="remove"}} + {{d-button action=(action "remove") actionParam=link.id icon="times" class="remove"}}
{{/each}} {{/if}} - {{d-button action="add" label="admin.wizard.add" icon="plus"}} + {{d-button action=(action "add") label="admin.wizard.add" icon="plus"}}
- diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs index 2de35e0d..c0cc6818 100644 --- a/assets/javascripts/discourse/templates/components/wizard-mapper.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs @@ -15,6 +15,6 @@ {{#if canAdd}} - {{d-button action="add" label="admin.wizard.add" icon="plus"}} + {{d-button action=(action "add") label="admin.wizard.add" icon="plus"}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs index c657049d..37a3e549 100644 --- a/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs @@ -6,13 +6,13 @@
{{#if previewEnabled}} {{d-button - action="togglePreview" + action=(action "togglePreview") translatedLabel=previewLabel}} {{/if}} {{#if fieldsEnabled}} {{d-button - action="togglePopover" + action=(action "togglePopover") translatedLabel=popoverLabel}} {{#if showPopover}} diff --git a/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs b/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs index 1b138360..cbc9d610 100644 --- a/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs +++ b/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs @@ -9,7 +9,7 @@ diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index 10501498..a90f6299 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -5,7 +5,7 @@ {{d-button label="admin.wizard.custom_field.add" icon="plus" - action="addField"}} + action=(action "addField")}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index c5ed70a7..6d4fa553 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -101,7 +101,7 @@ {{input type="checkbox" checked=wizard.after_time}} {{i18n "admin.wizard.after_time_label"}} {{d-button - action="setNextSessionScheduled" + action=(action "setNextSessionScheduled") translatedLabel=nextSessionScheduledLabel class="btn-after-time" icon="far-calendar"}} @@ -191,7 +191,7 @@ currentActionId=currentAction.id wizard=wizard apis=apis - removeAction="removeAction" + removeAction=(action "removeAction") wizardFields=wizardFields}} {{/each}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs index 081cd5f3..11b0cd11 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs @@ -8,7 +8,7 @@ )}} {{d-button - action="createWizard" + action=(action "createWizard") label="admin.wizard.create" icon="plus"}}
diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index 43a97be8..5f4d1ba1 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -34,17 +34,17 @@ {{/if}} {{/if}} {{d-button - action="destroy" + action=(action "destroy") icon="trash-alt" class="destroy" disabled=destroyDisabled}} {{d-button icon="save" - action="save" + action=(action "save") disabled=saveDisabled class="save"}} {{d-button - action="close" + action=(action "close") icon="times" disabled=closeDisabled}} @@ -69,7 +69,7 @@ {{else}} - {{d-button action="edit" icon="pencil-alt"}} + {{d-button action=(action "edit") icon="pencil-alt"}} {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 4c645cf7..c39623d7 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -1,6 +1,6 @@ {{#if showUndo}} {{d-button - action="undoChanges" + action=(action "undoChanges") icon=undoIcon label=undoKey class="undo-changes"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index f51b9fbb..b9c14999 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -1,6 +1,6 @@ {{#if showUndo}} {{d-button - action="undoChanges" + action=(action "undoChanges") icon=undoIcon label=undoKey class="undo-changes"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-links.hbs b/assets/javascripts/discourse/templates/components/wizard-links.hbs index a7a7662e..368acd35 100644 --- a/assets/javascripts/discourse/templates/components/wizard-links.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-links.hbs @@ -4,17 +4,16 @@ {{#if anyLinks}} {{#each links as |link|}}
- {{d-button action="change" actionParam=link.id translatedLabel=link.label class=link.classes}} + {{d-button action=(action "change") actionParam=link.id translatedLabel=link.label class=link.classes}} {{#unless link.first}} - {{d-button action="back" actionParam=link icon="arrow-left" class="back"}} + {{d-button action=(action "back") actionParam=link icon="arrow-left" class="back"}} {{/unless}} {{#unless link.last}} - {{d-button action="forward" actionParam=link icon="arrow-right" class="forward"}} + {{d-button action=(action "forward") actionParam=link icon="arrow-right" class="forward"}} {{/unless}} - {{d-button action="remove" actionParam=link.id icon="times" class="remove"}} + {{d-button action=(action "remove") actionParam=link.id icon="times" class="remove"}}
{{/each}} {{/if}} - {{d-button action="add" label="admin.wizard.add" icon="plus"}} + {{d-button action=(action "add") label="admin.wizard.add" icon="plus"}}
- diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs index 2de35e0d..c0cc6818 100644 --- a/assets/javascripts/discourse/templates/components/wizard-mapper.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs @@ -15,6 +15,6 @@ {{#if canAdd}} - {{d-button action="add" label="admin.wizard.add" icon="plus"}} + {{d-button action=(action "add") label="admin.wizard.add" icon="plus"}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs index c657049d..37a3e549 100644 --- a/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs @@ -6,13 +6,13 @@
{{#if previewEnabled}} {{d-button - action="togglePreview" + action=(action "togglePreview") translatedLabel=previewLabel}} {{/if}} {{#if fieldsEnabled}} {{d-button - action="togglePopover" + action=(action "togglePopover") translatedLabel=popoverLabel}} {{#if showPopover}} diff --git a/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs b/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs index 1b138360..cbc9d610 100644 --- a/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs +++ b/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs @@ -9,7 +9,7 @@ diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs index a90f6299..10501498 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-custom-fields.hbs @@ -5,7 +5,7 @@ {{d-button label="admin.wizard.custom_field.add" icon="plus" - action=(action "addField")}} + action="addField"}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index d36ed871..7fdeb20c 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -101,7 +101,7 @@ {{input type="checkbox" checked=wizard.after_time}} {{i18n "admin.wizard.after_time_label"}} {{d-button - action=(action "setNextSessionScheduled") + action="setNextSessionScheduled" translatedLabel=nextSessionScheduledLabel class="btn-after-time" icon="far-calendar"}} @@ -191,7 +191,7 @@ currentActionId=currentAction.id wizard=wizard apis=apis - removeAction=(action "removeAction") + removeAction="removeAction" wizardFields=wizardFields}} {{/each}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs index 11b0cd11..081cd5f3 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs @@ -8,7 +8,7 @@ )}} {{d-button - action=(action "createWizard") + action="createWizard" label="admin.wizard.create" icon="plus"}}
diff --git a/assets/javascripts/discourse/templates/components/custom-field-input.hbs b/assets/javascripts/discourse/templates/components/custom-field-input.hbs index 5f4d1ba1..43a97be8 100644 --- a/assets/javascripts/discourse/templates/components/custom-field-input.hbs +++ b/assets/javascripts/discourse/templates/components/custom-field-input.hbs @@ -34,17 +34,17 @@ {{/if}} {{/if}} {{d-button - action=(action "destroy") + action="destroy" icon="trash-alt" class="destroy" disabled=destroyDisabled}} {{d-button icon="save" - action=(action "save") + action="save" disabled=saveDisabled class="save"}} {{d-button - action=(action "close") + action="close" icon="times" disabled=closeDisabled}} @@ -69,7 +69,7 @@ {{else}} - {{d-button action=(action "edit") icon="pencil-alt"}} + {{d-button action="edit" icon="pencil-alt"}} {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index c39623d7..4c645cf7 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -1,6 +1,6 @@ {{#if showUndo}} {{d-button - action=(action "undoChanges") + action="undoChanges" icon=undoIcon label=undoKey class="undo-changes"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index b9c14999..f51b9fbb 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -1,6 +1,6 @@ {{#if showUndo}} {{d-button - action=(action "undoChanges") + action="undoChanges" icon=undoIcon label=undoKey class="undo-changes"}} diff --git a/assets/javascripts/discourse/templates/components/wizard-links.hbs b/assets/javascripts/discourse/templates/components/wizard-links.hbs index 368acd35..a7a7662e 100644 --- a/assets/javascripts/discourse/templates/components/wizard-links.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-links.hbs @@ -4,16 +4,17 @@ {{#if anyLinks}} {{#each links as |link|}}
- {{d-button action=(action "change") actionParam=link.id translatedLabel=link.label class=link.classes}} + {{d-button action="change" actionParam=link.id translatedLabel=link.label class=link.classes}} {{#unless link.first}} - {{d-button action=(action "back") actionParam=link icon="arrow-left" class="back"}} + {{d-button action="back" actionParam=link icon="arrow-left" class="back"}} {{/unless}} {{#unless link.last}} - {{d-button action=(action "forward") actionParam=link icon="arrow-right" class="forward"}} + {{d-button action="forward" actionParam=link icon="arrow-right" class="forward"}} {{/unless}} - {{d-button action=(action "remove") actionParam=link.id icon="times" class="remove"}} + {{d-button action="remove" actionParam=link.id icon="times" class="remove"}}
{{/each}} {{/if}} - {{d-button action=(action "add") label="admin.wizard.add" icon="plus"}} + {{d-button action="add" label="admin.wizard.add" icon="plus"}}
+ diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs index c0cc6818..2de35e0d 100644 --- a/assets/javascripts/discourse/templates/components/wizard-mapper.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs @@ -15,6 +15,6 @@ {{#if canAdd}} - {{d-button action=(action "add") label="admin.wizard.add" icon="plus"}} + {{d-button action="add" label="admin.wizard.add" icon="plus"}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs index 37a3e549..c657049d 100644 --- a/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs @@ -6,13 +6,13 @@
{{#if previewEnabled}} {{d-button - action=(action "togglePreview") + action="togglePreview" translatedLabel=previewLabel}} {{/if}} {{#if fieldsEnabled}} {{d-button - action=(action "togglePopover") + action="togglePopover" translatedLabel=popoverLabel}} {{#if showPopover}} diff --git a/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs b/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs index cbc9d610..1b138360 100644 --- a/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs +++ b/assets/javascripts/discourse/templates/modal/next-session-scheduled.hbs @@ -9,7 +9,7 @@ From ef22c1d33dfd328e6c190a7b234d702334c25b0a Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 8 Apr 2022 16:35:40 +0200 Subject: [PATCH 208/556] Increase qunit timeout --- .github/workflows/plugin-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 19524973..68c482ee 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -139,5 +139,5 @@ jobs: - name: Plugin QUnit if: matrix.build_type == 'frontend' - run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bundle exec rake "qunit:test['600000','/w/qunit']" + run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bundle exec rake "qunit:test['1200000','/w/qunit']" timeout-minutes: 30 From 9e8878e17457d298bd54674adf185dfcdbf3c203 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 8 Apr 2022 16:50:49 +0200 Subject: [PATCH 209/556] Move to new workflow approach (based on updated Discourse approach) --- .github/workflows/plugin-tests.yml | 104 ++++++++++++++++------------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 68c482ee..b0fc7aec 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -4,23 +4,26 @@ on: push: branches: - stable - - master - main pull_request: schedule: - cron: '0 */12 * * *' +concurrency: + group: plugin-tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }} + cancel-in-progress: true + jobs: build: name: ${{ matrix.build_type }} runs-on: ubuntu-latest - timeout-minutes: 60 + container: discourse/discourse_test:slim${{ startsWith(matrix.build_type, 'frontend') && '-browsers' || '' }} + timeout-minutes: 30 env: DISCOURSE_HOSTNAME: www.example.com RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072 RAILS_ENV: test - PGHOST: localhost PGUSER: discourse PGPASSWORD: discourse @@ -29,24 +32,6 @@ jobs: matrix: build_type: ["backend", "frontend"] - ruby: ["2.7"] - postgres: ["12"] - redis: ["6.x"] - - services: - postgres: - image: postgres:${{ matrix.postgres }} - ports: - - 5432:5432 - env: - POSTGRES_USER: discourse - POSTGRES_PASSWORD: discourse - options: >- - --mount type=tmpfs,destination=/var/lib/postgresql/data - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 steps: - uses: haya14busa/action-cond@v1 @@ -78,29 +63,32 @@ jobs: git config --global user.email "ci@ci.invalid" git config --global user.name "Discourse CI" - - name: Setup packages + - name: Start redis run: | - sudo apt-get update - sudo apt-get -yqq install postgresql-client libpq-dev gifsicle jpegoptim optipng jhead - wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh + redis-server /etc/redis/redis.conf & - - name: Update imagemagick - if: matrix.build_type == 'backend' + - name: Start Postgres run: | - wget https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-imagemagick - chmod +x install-imagemagick - sudo ./install-imagemagick + chown -R postgres /var/run/postgresql + sudo -E -u postgres script/start_test_db.rb + sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';" - - name: Setup redis - uses: shogo82148/actions-setup-redis@v1 + - name: Bundler cache + uses: actions/cache@v3 with: - redis-version: ${{ matrix.redis }} + path: vendor/bundle + key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gem- - - name: Setup ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true + - name: Setup gems + run: | + gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock) + bundle config --local path vendor/bundle + bundle config --local deployment true + bundle config --local without development + bundle install --jobs 4 + bundle clean - name: Lint English locale if: matrix.build_type == 'backend' @@ -111,23 +99,49 @@ jobs: run: echo "::set-output name=dir::$(yarn cache dir)" - name: Yarn cache - uses: actions/cache@v2 + uses: actions/cache@v3 id: yarn-cache with: path: ${{ steps.yarn-cache-dir.outputs.dir }} - key: ${{ runner.os }}-${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.os }}-${{ matrix.os }}-yarn- + ${{ runner.os }}-yarn- - name: Yarn install - run: yarn install --dev + run: yarn install - - name: Migrate database + - name: Fetch app state cache + uses: actions/cache@v3 + id: app-cache + with: + path: tmp/app-cache + key: >- + ${{ hashFiles('.github/workflows/tests.yml') }}- + ${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}- + + - name: Restore database from cache + if: steps.app-cache.outputs.cache-hit == 'true' + run: psql -f tmp/app-cache/cache.sql postgres + + - name: Restore uploads from cache + if: steps.app-cache.outputs.cache-hit == 'true' + run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads + + - name: Create and migrate database + if: steps.app-cache.outputs.cache-hit != 'true' run: | bin/rake db:create bin/rake db:migrate - - name: Plugin RSpec with Coverage + - name: Dump database for cache + if: steps.app-cache.outputs.cache-hit != 'true' + run: mkdir -p tmp/app-cache && pg_dumpall > tmp/app-cache/cache.sql + + - name: Dump uploads for cache + if: steps.app-cache.outputs.cache-hit != 'true' + run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads + + - name: RSpec with Coverage if: matrix.build_type == 'backend' run: | if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ] @@ -137,7 +151,7 @@ jobs: fi bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] - - name: Plugin QUnit + - name: Wizard QUnit if: matrix.build_type == 'frontend' run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bundle exec rake "qunit:test['1200000','/w/qunit']" timeout-minutes: 30 From b57bf8e0fe012f3bc06c1fe6230f03f09ff3af76 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 09:51:01 +0200 Subject: [PATCH 210/556] tweak qunit format --- .github/workflows/plugin-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index b0fc7aec..24cb326d 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -153,5 +153,5 @@ jobs: - name: Wizard QUnit if: matrix.build_type == 'frontend' - run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bundle exec rake "qunit:test['1200000','/w/qunit']" + run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000','/w/qunit'] timeout-minutes: 30 From 84d21f78e5df723de8d842f47608e1c562c09db8 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 10:01:28 +0200 Subject: [PATCH 211/556] Udpate version --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 4c941d48..176bb019 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards for topic creation, onboarding, user surveys and much more. -# version: 1.19.0 +# version: 2.0.0.beta # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George # contact_emails: support@thepavilion.io # url: https://github.com/paviliondev/discourse-custom-wizard From 305f94e9d9e30d7cb8cbccfd55986188dac080bf Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:02:21 +0200 Subject: [PATCH 212/556] Minor config updates --- .github/workflows/plugin-tests.yml | 10 +- .../javascripts/wizard/tests/bootstrap.js.es6 | 7 + yarn.lock | 1639 +++++++++++++++++ 3 files changed, 1649 insertions(+), 7 deletions(-) create mode 100644 yarn.lock diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 24cb326d..94357cb7 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -41,20 +41,16 @@ jobs: if_true: "stable" if_false: "tests-passed" - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: repository: discourse/discourse ref: ${{ steps.discourse_branch.outputs.value }} fetch-depth: 1 - - name: Fetch Repo Name - id: repo-name - run: echo "::set-output name=value::$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" - - name: Install plugin uses: actions/checkout@v2 with: - path: plugins/${{ steps.repo-name.outputs.value }} + path: plugins/${{ github.event.repository.name }} ref: "${{ github.base_ref }}" fetch-depth: 1 @@ -154,4 +150,4 @@ jobs: - name: Wizard QUnit if: matrix.build_type == 'frontend' run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000','/w/qunit'] - timeout-minutes: 30 + timeout-minutes: 10 diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index dec0c4b4..29cdfe80 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,4 +1,5 @@ // discourse-skip-module +/*global document, Logster, QUnit */ if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { document.addEventListener("DOMContentLoaded", function () { @@ -16,4 +17,10 @@ if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { requirejs(entry); } }); + + if (window.Logster) { + Logster.enabled = false; + } else { + window.Logster = { enabled: false }; + } } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..b9d7fc0e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1639 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/generator@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" + integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/highlight@^7.16.7": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" + integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.16.7", "@babel/parser@^7.17.9", "@babel/parser@^7.7.0": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" + integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== + +"@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.7.0": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" + integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.9" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.7.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@ember-data/rfc395-data@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843" + integrity sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ== + +"@glimmer/env@0.1.7", "@glimmer/env@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@glimmer/env/-/env-0.1.7.tgz#fd2d2b55a9029c6b37a6c935e8c8871ae70dfa07" + integrity sha1-/S0rVakCnGs3psk16MiHGucN+gc= + +"@glimmer/global-context@0.65.4": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/global-context/-/global-context-0.65.4.tgz#1da1d59dd4260ce912c40e474cd39c2e82de51b8" + integrity sha512-RSYCPG/uVR5XCDcPREBclncU7R0zkjACbADP+n3FWAH1TfWbXRMDIkvO/ZlwHkjHoCZf6tIM6p5S/MoFzfJEJA== + dependencies: + "@glimmer/env" "^0.1.7" + +"@glimmer/interfaces@0.65.4": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.65.4.tgz#d298cc2b12b8ebcf269f39246ca7ab92816f6680" + integrity sha512-R0kby79tGNKZOojVJa/7y0JH9Eq4SV+L1s6GcZy30QUZ1g1AAGS5XwCIXc9Sc09coGcv//q+6NLeSw7nlx1y4A== + dependencies: + "@simple-dom/interface" "^1.4.0" + +"@glimmer/reference@^0.65.0": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.65.4.tgz#bbc8becd6a1bf01fc189b6489e27446437194711" + integrity sha512-yuRVE4qyqrlCndDMrHKDWUbDmGDCjPzsFtlTmxxnhDMJAdQsnr2cRLITHvQRDm1tXfigVvyKnomeuYhRRbBqYQ== + dependencies: + "@glimmer/env" "^0.1.7" + "@glimmer/global-context" "0.65.4" + "@glimmer/interfaces" "0.65.4" + "@glimmer/util" "0.65.4" + "@glimmer/validator" "0.65.4" + +"@glimmer/syntax@^0.65.0": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.65.4.tgz#49164de5dc9e8b67084ec009bdd865e379d8a971" + integrity sha512-y+/C3e8w96efk3a/Z5If9o4ztKJwrr8RtDpbhV2J8X+DUsn5ic2N3IIdlThbt/Zn6tkP1K3dY6uaFUx3pGTvVQ== + dependencies: + "@glimmer/interfaces" "0.65.4" + "@glimmer/util" "0.65.4" + "@handlebars/parser" "^1.1.0" + simple-html-tokenizer "^0.5.10" + +"@glimmer/util@0.65.4": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.65.4.tgz#e464145078f3f40da9013ff2590a6016515455d2" + integrity sha512-aofe+rdBhkREKP2GZta6jy1UcbRRMfWx7M18zxGxspPoeD08NscD04Kx+WiOKXmC1TcrfITr8jvqMfrKrMzYWQ== + dependencies: + "@glimmer/env" "0.1.7" + "@glimmer/interfaces" "0.65.4" + "@simple-dom/interface" "^1.4.0" + +"@glimmer/validator@0.65.4", "@glimmer/validator@^0.65.0": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.65.4.tgz#12c27a9a63706c60e6499fd687940e9d1affb32c" + integrity sha512-0YUjAyo45DF5JkQxdv5kHn96nMNhvZiEwsAD4Jme0kk5Q9MQcPOUtN76pQAS4f+C6GdF9DeUr2yGXZLFMmb+LA== + dependencies: + "@glimmer/env" "^0.1.7" + "@glimmer/global-context" "0.65.4" + +"@handlebars/parser@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-1.1.0.tgz#d6dbc7574774b238114582410e8fee0dc3532bdf" + integrity sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@simple-dom/interface@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@simple-dom/interface/-/interface-1.4.0.tgz#e8feea579232017f89b0138e2726facda6fbb71f" + integrity sha512-l5qumKFWU0S+4ZzMaLXFU8tQZsicHEMEyAxI5kDFGhJsRqDwe0a7/iPA/GdxlGyDKseQQAgIz5kzU7eXTrlSpA== + +acorn-jsx@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-promise-queue@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/async-promise-queue/-/async-promise-queue-1.0.5.tgz#cb23bce9fce903a133946a700cc85f27f09ea49d" + integrity sha512-xi0aQ1rrjPWYmqbwr18rrSKbSaXIeIwSd1J4KAgVfkq8utNbdZoht7GfvfY6swFUAMJ9obkc4WPJmtGwl+B8dw== + dependencies: + async "^2.4.1" + debug "^2.6.8" + +async@^2.4.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0, chalk@^2.1.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.0.1, debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +ember-rfc176-data@^0.3.11: + version "0.3.17" + resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.17.tgz#d4fc6c33abd6ef7b3440c107a28e04417b49860a" + integrity sha512-EVzTTKqxv9FZbEh6Ktw56YyWRAA0MijKvl7H8C06wVF+8f/cRRz3dXxa4nkwjzyVwx4rzKGuIGq77hxJAQhWWw== + +ember-template-lint-plugin-discourse@latest: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ember-template-lint-plugin-discourse/-/ember-template-lint-plugin-discourse-2.0.0.tgz#9805dff60763fae68b5df82b92fb431eb739c13e" + integrity sha512-2bPz/47OfuYGj4w2RNyDcXCYA/4JtRAXRIsaA6PTm4Uc44exK/GBd4RfT2ywmq0CImvj2kGkqpuUgkAtVf6aZQ== + +ember-template-lint@^2.11.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/ember-template-lint/-/ember-template-lint-2.21.0.tgz#7e120abf309a8810eeed26c52377943faf15a95b" + integrity sha512-19QbEqJQdMfcRS7PsQsubflRowEtnkbD0tpYR4q/xq4lodmhU7hhOFvlTQgbxD/jwW5Ur+tkOwH4KFy9JwOyXA== + dependencies: + chalk "^4.0.0" + ember-template-recast "^5.0.1" + find-up "^5.0.0" + fuse.js "^6.4.6" + get-stdin "^8.0.0" + globby "^11.0.2" + is-glob "^4.0.1" + micromatch "^4.0.2" + resolve "^1.20.0" + v8-compile-cache "^2.2.0" + yargs "^16.2.0" + +ember-template-recast@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/ember-template-recast/-/ember-template-recast-5.0.3.tgz#79df27a70bdce7be17f14db13886afde1e9d02d6" + integrity sha512-qsJYQhf29Dk6QMfviXhUPE+byMOs6iRQxUDHgkj8yqjeppvjHaFG96hZi/NAXJTm/M7o3PpfF5YlmeaKtI9UeQ== + dependencies: + "@glimmer/reference" "^0.65.0" + "@glimmer/syntax" "^0.65.0" + "@glimmer/validator" "^0.65.0" + async-promise-queue "^1.0.5" + colors "^1.4.0" + commander "^6.2.1" + globby "^11.0.3" + ora "^5.4.0" + slash "^3.0.0" + tmp "^0.2.1" + workerpool "^6.1.4" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-discourse@^1.1.8: + version "1.1.9" + resolved "https://registry.yarnpkg.com/eslint-config-discourse/-/eslint-config-discourse-1.1.9.tgz#9a5ee6b3a4b986e5243f517e7945d1709c4e22df" + integrity sha512-a4KG+/9/7ZhYVV0URGK70K7QtxlydYKjie0ZssHEGxl2auOUTDcdRMBfZQBtIxQr9X8TF0+eeUUsScBXNU6xZw== + dependencies: + babel-eslint "^10.1.0" + ember-template-lint "^2.11.0" + ember-template-lint-plugin-discourse latest + eslint "^6.8.0" + eslint-plugin-discourse-ember latest + eslint-plugin-ember "^6.10.0" + eslint-plugin-lodash "^7.1.0" + eslint-plugin-node "^8.0.0" + prettier "2.2.1" + +eslint-plugin-discourse-ember@latest: + version "0.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-discourse-ember/-/eslint-plugin-discourse-ember-0.0.3.tgz#69e5876c2ece38ab3d6c4a05f0a20a7dc4c21e37" + integrity sha512-EFwWU4FlOSLBa4RolKZL8QD1eGOnvSkACLt4Big+o1ZUIpW7gGvfnJPtxkbaQ4XmhtZ5HetYt6862vVqhUMv9A== + dependencies: + requireindex "~1.1.0" + +eslint-plugin-ember@^6.10.0: + version "6.10.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-6.10.1.tgz#ca7a5cc28b91a247c31b1686421a66281467f238" + integrity sha512-RZI0+UoR4xeD6UE3KQCUwbN2nZOIIPaFCCXqBIRXDr0rFuwvknAHqYtDPJVZicvTzNHa4TEZvAKqfbE8t7SztQ== + dependencies: + "@ember-data/rfc395-data" "^0.0.4" + ember-rfc176-data "^0.3.11" + snake-case "^2.1.0" + +eslint-plugin-es@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998" + integrity sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA== + dependencies: + eslint-utils "^1.4.2" + regexpp "^2.0.1" + +eslint-plugin-lodash@^7.1.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-lodash/-/eslint-plugin-lodash-7.4.0.tgz#14a761547f126c92ff56789662a20a44f8bb6290" + integrity sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A== + dependencies: + lodash "^4.17.21" + +eslint-plugin-node@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz#55ae3560022863d141fa7a11799532340a685964" + integrity sha512-ZjOjbjEi6jd82rIpFSgagv4CHWzG9xsQAVp1ZPlhRnnYxcTgENUVBvhYmkQ7GvT1QFijUSo69RaiOJKhMu6i8w== + dependencies: + eslint-plugin-es "^1.3.1" + eslint-utils "^1.3.1" + ignore "^5.0.2" + minimatch "^3.0.4" + resolve "^1.8.1" + semver "^5.5.0" + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.3.1, eslint-utils@^1.4.2, eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +fuse.js@^6.4.6: + version "6.5.3" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.5.3.tgz#7446c0acbc4ab0ab36fa602e97499bdb69452b93" + integrity sha512-sA5etGE7yD/pOqivZRBvUBd/NaL2sjAu6QuSaFoe1H2BrJSkH/T/UXAJ8CdXdw7DvY3Hs8CXKYkDWX7RiP5KOg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +glob-parent@^5.0.0, glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@^11.0.2, globby@^11.0.3: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.0.2, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + +lru-cache@^7.4.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.1.tgz#68ee3f4807a57d2ba185b7fd90827d5c21ce82bb" + integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ora@^5.4.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +requireindex@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162" + integrity sha1-5UBLgVV+91225JxacgBIk/4D4WI= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.12.0, resolve@^1.20.0, resolve@^1.8.1: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.5: + version "7.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" + integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== + dependencies: + lru-cache "^7.4.0" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-html-tokenizer@^0.5.10: + version "0.5.11" + resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.5.11.tgz#4c5186083c164ba22a7b477b7687ac056ad6b1d9" + integrity sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +snake-case@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" + integrity sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8= + dependencies: + no-case "^2.2.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +workerpool@^6.1.4: + version "6.2.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" + integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 8b5b9624d74f5726ea604969d29d1599a9c911b2 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:08:07 +0200 Subject: [PATCH 213/556] Update wizard-qunit.js --- assets/javascripts/wizard-qunit.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js index 2866cb5d..8aaf23ab 100644 --- a/assets/javascripts/wizard-qunit.js +++ b/assets/javascripts/wizard-qunit.js @@ -1,11 +1,12 @@ +//= require env +//= require jquery.debug +//= require ember.debug //= require route-recognizer //= require fake_xml_http_request //= require pretender //= require qunit //= require ember-qunit //= require test-shims -//= require jquery.debug -//= require ember.debug //= require ember-template-compiler //= require_tree ./wizard/tests/fixtures From 7d941eaf5e98f18b85a83e5390a33e19c9e168a5 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:10:00 +0200 Subject: [PATCH 214/556] Update bootstrap.js.es6 --- assets/javascripts/wizard/tests/bootstrap.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index 29cdfe80..a3343b9a 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,5 +1,5 @@ // discourse-skip-module -/*global document, Logster, QUnit */ +/*global document, Logster */ if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { document.addEventListener("DOMContentLoaded", function () { From 1f899935a00087410942324e0eb77cf17196169e Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:28:51 +0200 Subject: [PATCH 215/556] Update plugin-tests.yml --- .github/workflows/plugin-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 94357cb7..de1133e4 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -48,7 +48,7 @@ jobs: fetch-depth: 1 - name: Install plugin - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: plugins/${{ github.event.repository.name }} ref: "${{ github.base_ref }}" @@ -140,12 +140,12 @@ jobs: - name: RSpec with Coverage if: matrix.build_type == 'backend' run: | - if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ] + if [ -e plugins/${{ github.event.repository.name }}/.simplecov ] then - cp plugins/${{ steps.repo-name.outputs.value }}/.simplecov .simplecov + cp plugins/${{ github.event.repository.name }}/.simplecov .simplecov export COVERAGE=1 fi - bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] + bin/rake plugin:spec[${{ github.event.repository.name }}] - name: Wizard QUnit if: matrix.build_type == 'frontend' From b0ea1081a276138ef938bbe6c5a04a0518cdcd86 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:31:11 +0200 Subject: [PATCH 216/556] Update plugin-tests.yml --- .github/workflows/plugin-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index de1133e4..c76eaff6 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -88,7 +88,7 @@ jobs: - name: Lint English locale if: matrix.build_type == 'backend' - run: bundle exec ruby script/i18n_lint.rb "plugins/${{ steps.repo-name.outputs.value }}/locales/{client,server}.en.yml" + run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml" - name: Get yarn cache directory id: yarn-cache-dir From 3c0d256e4e2db1d2cee33e35d7c12f941a33888e Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:35:00 +0200 Subject: [PATCH 217/556] Update plugin.rb --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 176bb019..3ebde0d0 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards for topic creation, onboarding, user surveys and much more. -# version: 2.0.0.beta +# version: 2.0.0-beta.1 # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George # contact_emails: support@thepavilion.io # url: https://github.com/paviliondev/discourse-custom-wizard From cebc63b6d87a0024d761b7d32f2a25fdd3e0b4bf Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:47:33 +0200 Subject: [PATCH 218/556] Try removing conditional from bootstrap --- .../javascripts/wizard/tests/bootstrap.js.es6 | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index a3343b9a..3083fb76 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,26 +1,24 @@ // discourse-skip-module /*global document, Logster */ -if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { - document.addEventListener("DOMContentLoaded", function () { - document.body.insertAdjacentHTML( - "afterbegin", - ` -
- - ` - ); - }); +document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); +}); - Object.keys(requirejs.entries).forEach(function (entry) { - if (/\-test/.test(entry)) { - requirejs(entry); - } - }); - - if (window.Logster) { - Logster.enabled = false; - } else { - window.Logster = { enabled: false }; +Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); } +}); + +if (window.Logster) { + Logster.enabled = false; +} else { + window.Logster = { enabled: false }; } From 9b96c02d0f8871a25a4cefc9eabbe3046fa65313 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 12:17:11 +0200 Subject: [PATCH 219/556] re-add qunit bootstrap conditional --- .../javascripts/wizard/tests/bootstrap.js.es6 | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index 3083fb76..a3343b9a 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,24 +1,26 @@ // discourse-skip-module /*global document, Logster */ -document.addEventListener("DOMContentLoaded", function () { - document.body.insertAdjacentHTML( - "afterbegin", - ` -
- - ` - ); -}); +if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { + document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); + }); -Object.keys(requirejs.entries).forEach(function (entry) { - if (/\-test/.test(entry)) { - requirejs(entry); + Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); + } + }); + + if (window.Logster) { + Logster.enabled = false; + } else { + window.Logster = { enabled: false }; } -}); - -if (window.Logster) { - Logster.enabled = false; -} else { - window.Logster = { enabled: false }; } From a87dc19eb3a874afadbdfa792732ec478789bcd8 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 12:21:38 +0200 Subject: [PATCH 220/556] Add qunit CSP extension --- .../extensions/content_security_policy_extension.rb | 12 ++++++++++++ plugin.rb | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 lib/custom_wizard/extensions/content_security_policy_extension.rb diff --git a/lib/custom_wizard/extensions/content_security_policy_extension.rb b/lib/custom_wizard/extensions/content_security_policy_extension.rb new file mode 100644 index 00000000..fa7a166c --- /dev/null +++ b/lib/custom_wizard/extensions/content_security_policy_extension.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module CustomWizardContentSecurityPolicyExtension + def path_specific_extension(path_info) + super.tap do |obj| + for_wizard_qunit_route = !Rails.env.production? && ["/w/qunit"].include?(path_info) + puts "PATH INFO: #{path_info}" + puts "FOR WIZARD QUNIT ROUTE: #{for_wizard_qunit_route}" + obj[:script_src] = :unsafe_eval if for_wizard_qunit_route + end + end +end diff --git a/plugin.rb b/plugin.rb index 3ebde0d0..a0ec79e5 100644 --- a/plugin.rb +++ b/plugin.rb @@ -17,7 +17,6 @@ if Rails.env.production? wizard-custom-guest.js wizard-custom-start.js wizard-custom.js - wizard-qunit.js wizard-plugin.js.erb wizard-raw-templates.js.erb } @@ -145,6 +144,7 @@ after_initialize do ../lib/custom_wizard/extensions/custom_field/serializer.rb ../lib/custom_wizard/extensions/custom_field/extension.rb ../lib/custom_wizard/extensions/discourse_tagging.rb + ../lib/custom_wizard/extensions/content_security_policy_extension.rb ].each do |path| load File.expand_path(path, __FILE__) end @@ -250,6 +250,7 @@ after_initialize do ::InvitesController.prepend InvitesControllerCustomWizard ::UsersController.prepend CustomWizardUsersController ::Guardian.prepend CustomWizardGuardian + ::ContentSecurityPolicy::Extension.singleton_class.prepend CustomWizardContentSecurityPolicyExtension full_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets/stylesheets/wizard/wizard_custom.scss" if Stylesheet::Importer.respond_to?(:plugin_assets) From 0783cac9e01e8bd83323e69973463e9f3dda46a8 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 12:42:26 +0200 Subject: [PATCH 221/556] Add LOAD_PLUGINS to env in action workflow --- .github/workflows/plugin-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index c76eaff6..f33f7bdd 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -26,6 +26,7 @@ jobs: RAILS_ENV: test PGUSER: discourse PGPASSWORD: discourse + LOAD_PLUGINS: 1 strategy: fail-fast: false From 86b0df89d18739e1b4e276d49b00b5a9a737ed13 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 13:42:32 +0200 Subject: [PATCH 222/556] Test config --- .github/workflows/plugin-tests.yml | 4 ++-- plugin.rb | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index f33f7bdd..00eaf7a0 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -26,7 +26,7 @@ jobs: RAILS_ENV: test PGUSER: discourse PGPASSWORD: discourse - LOAD_PLUGINS: 1 + LOAD_PLUGINS: "1" strategy: fail-fast: false @@ -150,5 +150,5 @@ jobs: - name: Wizard QUnit if: matrix.build_type == 'frontend' - run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000','/w/qunit'] + run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/w/qunit'] timeout-minutes: 10 diff --git a/plugin.rb b/plugin.rb index a0ec79e5..e20c056d 100644 --- a/plugin.rb +++ b/plugin.rb @@ -80,6 +80,8 @@ def each_globbed_asset end end +puts "LOADING CUSTOM WIZARD PLUGIN" + after_initialize do %w[ ../lib/custom_wizard/engine.rb From 15c0245edf07defc5984a888b7c26bcc33d227c6 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 14:00:12 +0200 Subject: [PATCH 223/556] Test config --- .github/workflows/plugin-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 00eaf7a0..51d26303 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -26,7 +26,6 @@ jobs: RAILS_ENV: test PGUSER: discourse PGPASSWORD: discourse - LOAD_PLUGINS: "1" strategy: fail-fast: false @@ -150,5 +149,5 @@ jobs: - name: Wizard QUnit if: matrix.build_type == 'frontend' - run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/w/qunit'] + run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/w/qunit'] timeout-minutes: 10 From 4348a4ee68cf1d8076de6d903d910a629e0f9bb8 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 14:36:04 +0200 Subject: [PATCH 224/556] Add more logs --- .github/workflows/plugin-tests.yml | 2 +- plugin.rb | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 51d26303..970a3f6f 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -112,7 +112,7 @@ jobs: with: path: tmp/app-cache key: >- - ${{ hashFiles('.github/workflows/tests.yml') }}- + ${{ hashFiles('.github/workflows/plugin-tests.yml') }}- ${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}- - name: Restore database from cache diff --git a/plugin.rb b/plugin.rb index e20c056d..3c0a840a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -22,6 +22,8 @@ if Rails.env.production? } end +puts "BEFORE ADD ASSET PATHS" + if Rails.env.test? config = Rails.application.config plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets" @@ -252,6 +254,7 @@ after_initialize do ::InvitesController.prepend InvitesControllerCustomWizard ::UsersController.prepend CustomWizardUsersController ::Guardian.prepend CustomWizardGuardian + puts "PREPENDING CSP EXTENSION" ::ContentSecurityPolicy::Extension.singleton_class.prepend CustomWizardContentSecurityPolicyExtension full_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets/stylesheets/wizard/wizard_custom.scss" From 7904bb5b3a3d83a9fe1e01180a1f716e796d83e9 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 14:37:04 +0200 Subject: [PATCH 225/556] Add more env variables --- .github/workflows/plugin-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 970a3f6f..8a45bf62 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -149,5 +149,5 @@ jobs: - name: Wizard QUnit if: matrix.build_type == 'frontend' - run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/w/qunit'] + run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 RAILS_ENABLE_TEST_LOG=1 QUNIT_SKIP_CORE=1 bin/rake qunit:test['600000','/w/qunit'] timeout-minutes: 10 From 0313e1be959557a1fa75e04a1beb22636d52a03e Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 14:56:19 +0200 Subject: [PATCH 226/556] Add backslash to qunit path --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 2d9f52c6..5f124001 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true CustomWizard::Engine.routes.draw do - get 'qunit' => 'wizard#qunit' + get '/qunit' => 'wizard#qunit' get ':wizard_id' => 'wizard#index' put ':wizard_id/skip' => 'wizard#skip' get ':wizard_id/steps' => 'wizard#index' From 1f320aac937f6ada2e20dedc41236cd2cff8704a Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 15:16:29 +0200 Subject: [PATCH 227/556] Fix tests --- .github/workflows/plugin-tests.yml | 4 ++-- config/routes.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 8a45bf62..6049931d 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -51,7 +51,7 @@ jobs: uses: actions/checkout@v3 with: path: plugins/${{ github.event.repository.name }} - ref: "${{ github.base_ref }}" + ref: "${{ github.head_ref }}" fetch-depth: 1 - name: Setup Git @@ -149,5 +149,5 @@ jobs: - name: Wizard QUnit if: matrix.build_type == 'frontend' - run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 RAILS_ENABLE_TEST_LOG=1 QUNIT_SKIP_CORE=1 bin/rake qunit:test['600000','/w/qunit'] + run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 QUNIT_SKIP_CORE=1 bin/rake qunit:test['600000','/w/qunit'] timeout-minutes: 10 diff --git a/config/routes.rb b/config/routes.rb index 5f124001..2d9f52c6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true CustomWizard::Engine.routes.draw do - get '/qunit' => 'wizard#qunit' + get 'qunit' => 'wizard#qunit' get ':wizard_id' => 'wizard#index' put ':wizard_id/skip' => 'wizard#skip' get ':wizard_id/steps' => 'wizard#index' From 0970b49db8de1f0953e73224a19f74195c45ea1c Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 15:24:10 +0200 Subject: [PATCH 228/556] Remove unnecessary extensions --- .../extensions/content_security_policy_extension.rb | 12 ------------ plugin.rb | 7 ------- 2 files changed, 19 deletions(-) delete mode 100644 lib/custom_wizard/extensions/content_security_policy_extension.rb diff --git a/lib/custom_wizard/extensions/content_security_policy_extension.rb b/lib/custom_wizard/extensions/content_security_policy_extension.rb deleted file mode 100644 index fa7a166c..00000000 --- a/lib/custom_wizard/extensions/content_security_policy_extension.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module CustomWizardContentSecurityPolicyExtension - def path_specific_extension(path_info) - super.tap do |obj| - for_wizard_qunit_route = !Rails.env.production? && ["/w/qunit"].include?(path_info) - puts "PATH INFO: #{path_info}" - puts "FOR WIZARD QUNIT ROUTE: #{for_wizard_qunit_route}" - obj[:script_src] = :unsafe_eval if for_wizard_qunit_route - end - end -end diff --git a/plugin.rb b/plugin.rb index 3c0a840a..a397e3a7 100644 --- a/plugin.rb +++ b/plugin.rb @@ -22,8 +22,6 @@ if Rails.env.production? } end -puts "BEFORE ADD ASSET PATHS" - if Rails.env.test? config = Rails.application.config plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets" @@ -82,8 +80,6 @@ def each_globbed_asset end end -puts "LOADING CUSTOM WIZARD PLUGIN" - after_initialize do %w[ ../lib/custom_wizard/engine.rb @@ -148,7 +144,6 @@ after_initialize do ../lib/custom_wizard/extensions/custom_field/serializer.rb ../lib/custom_wizard/extensions/custom_field/extension.rb ../lib/custom_wizard/extensions/discourse_tagging.rb - ../lib/custom_wizard/extensions/content_security_policy_extension.rb ].each do |path| load File.expand_path(path, __FILE__) end @@ -254,8 +249,6 @@ after_initialize do ::InvitesController.prepend InvitesControllerCustomWizard ::UsersController.prepend CustomWizardUsersController ::Guardian.prepend CustomWizardGuardian - puts "PREPENDING CSP EXTENSION" - ::ContentSecurityPolicy::Extension.singleton_class.prepend CustomWizardContentSecurityPolicyExtension full_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets/stylesheets/wizard/wizard_custom.scss" if Stylesheet::Importer.respond_to?(:plugin_assets) From 5325be03084ded8d6a5d6b82767b02b83cde8f15 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Tue, 19 Apr 2022 23:46:04 -0700 Subject: [PATCH 229/556] New translations client.en.yml (German) --- config/locales/client.de.yml | 78 ++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index bfec2666..c84fbc79 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -14,7 +14,7 @@ de: background: "Background" background_placeholder: "#hex" save_submissions: "Save" - save_submissions_label: "Save wizard submissions." + save_submissions_label: "Wizard Einreichungen speichern." multiple_submissions: "Multiple" multiple_submissions_label: "Users can submit multiple times." after_signup: "Signup" @@ -34,7 +34,7 @@ de: prompt_completion_label: "Prompt user to complete wizard." restart_on_revisit: "Restart" restart_on_revisit_label: "Clear submissions on each visit." - resume_on_revisit: "Resume" + resume_on_revisit: "Fortsetzen" resume_on_revisit_label: "Ask the user if they want to resume on each visit." theme_id: "Theme" no_theme: "Select a Theme (optional)" @@ -461,37 +461,37 @@ de: geo_location: "Search and select a result." select_kit: default_header_text: Select... - no_content: No matches found - filter_placeholder: Search... - filter_placeholder_with_any: Search or create... - create: "Create: '{{content}}'" + no_content: Keine Treffer gefunden + filter_placeholder: Suchen... + filter_placeholder_with_any: Suche oder Erstelle... + create: "Erstelle: '{{content}}'" max_content_reached: - one: "You can only select {{count}} item." - other: "You can only select {{count}} items." + one: "Du kannst nur {{count}} Element auswählen." + other: "Du kannst nur {{count}} Elemente auswählen." min_content_not_reached: - one: "Select at least {{count}} item." - other: "Select at least {{count}} items." + one: "Wähle mindestens {{count}} Element." + other: "Wähle mindestens {{count}} Elemente." wizard: - completed: "You have completed this wizard." - not_permitted: "You are not permitted to access this wizard." - none: "There is no wizard here." - return_to_site: "Return to {{siteName}}" - requires_login: "You need to be logged in to access this wizard." - reset: "Reset this wizard." - step_not_permitted: "You're not permitted to view this step." + completed: "Du hast diesen Wizard abgeschlossen." + not_permitted: "Du bist nicht berechtigt, auf diesen Wizard zuzugreifen." + none: "Es gibt hier keinen Wizard." + return_to_site: "Zurück zu {{siteName}}" + requires_login: "Du musst eingeloggt sein, um auf diesen Wizard zuzugreifen." + reset: "Diesen Wizard zurücksetzen." + step_not_permitted: "Du bist nicht berechtigt, diesen Schritt anzusehen." incomplete_submission: title: "Continue editing your draft submission from %{date}?" - resume: "Continue" - restart: "Start over" + resume: "Weiter" + restart: "Neu beginnen" x_characters: - one: "%{count} Character" - other: "%{count} Characters" + one: "%{count} Zeichen" + other: "%{count} Zeichen" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" - quote_post_title: "Quote whole post" - bold_label: "B" - bold_title: "Strong" + show_preview: "Beitrags-Vorschau" + hide_preview: "Beitrag bearbeiten" + quote_post_title: "Gesamten Beitrag zitieren" + bold_label: "F" + bold_title: "Fett" bold_text: "strong text" italic_label: "I" italic_title: "Emphasis" @@ -513,20 +513,20 @@ de: ulist_title: "Bulleted List" list_item: "List item" toggle_direction: "Toggle Direction" - help: "Markdown Editing Help" - collapse: "minimize the composer panel" - abandon: "close composer and discard draft" + help: "Markdown Hilfe" + collapse: "den Editor minimieren" + abandon: "Editor schließen und Entwurf verwerfen" modal_ok: "OK" - modal_cancel: "Cancel" - cant_send_pm: "Sorry, you can't send a message to %{username}." + modal_cancel: "Abbrechen" + cant_send_pm: "Sorry, Du kannst keine Nachricht an %{username} senden." yourself_confirm: - title: "Did you forget to add recipients?" - body: "Right now this message is only being sent to yourself!" + title: "Hast du vergessen, Empfänger hinzuzufügen?" + body: "Aktuell wird diese Nachricht nur an dich selber versendet!" realtime_validations: similar_topics: - insufficient_characters: "Type a minimum 5 characters to start looking for similar topics" - insufficient_characters_categories: "Type a minimum 5 characters to start looking for similar topics in %{catLinks}" - results: "Your topic is similar to..." - no_results: "No similar topics." - loading: "Looking for similar topics..." - show: "show" + insufficient_characters: "Gib mindestens 5 Zeichen ein, um nach ähnlichen Themen zu suchen" + insufficient_characters_categories: "Gib mindestens 5 Zeichen ein, um in %{catLinks} nach ähnlichen Themen zu suchen" + results: "Dein Thema hat Ähnlichkeit mit..." + no_results: "Keine ähnlichen Themen." + loading: "Suche nach ähnlichen Themen..." + show: "zeigen" From 04e78c102f22ca8af41b7549fbc312c24857efb3 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 20 Apr 2022 23:43:05 -0700 Subject: [PATCH 230/556] New translations client.en.yml (German) --- config/locales/client.de.yml | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index c84fbc79..7ffe0fef 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -200,8 +200,8 @@ de: type: text: "Text" textarea: Textarea - composer: Composer - composer_preview: Composer Preview + composer: Editor + composer_preview: Editor-Vorschau text_only: Text Only number: Number checkbox: Checkbox @@ -256,7 +256,7 @@ de: tags: "Tags" visible: "Visible" open_composer: - label: "Open Composer" + label: "Editor öffnen" update_profile: label: "Update Profile" setting: "Fields" @@ -480,7 +480,7 @@ de: reset: "Diesen Wizard zurücksetzen." step_not_permitted: "Du bist nicht berechtigt, diesen Schritt anzusehen." incomplete_submission: - title: "Continue editing your draft submission from %{date}?" + title: "Mit dem gespeicherten Entwurf vom %{date} fortsetzen?" resume: "Weiter" restart: "Neu beginnen" x_characters: @@ -492,27 +492,27 @@ de: quote_post_title: "Gesamten Beitrag zitieren" bold_label: "F" bold_title: "Fett" - bold_text: "strong text" - italic_label: "I" - italic_title: "Emphasis" - italic_text: "emphasized text" - link_title: "Hyperlink" - link_description: "enter link description here" - link_dialog_title: "Insert Hyperlink" - link_optional_text: "optional title" - link_url_placeholder: "http://example.com" - quote_title: "Blockquote" - quote_text: "Blockquote" - blockquote_text: "Blockquote" - code_title: "Preformatted text" - code_text: "indent preformatted text by 4 spaces" - paste_code_text: "type or paste code here" - upload_title: "Upload" - upload_description: "enter upload description here" - olist_title: "Numbered List" - ulist_title: "Bulleted List" - list_item: "List item" - toggle_direction: "Toggle Direction" + bold_text: "fett gedruckter Text" + italic_label: "K" + italic_title: "Betonung" + italic_text: "betonter Text" + link_title: "Link" + link_description: "hier Linkbeschreibung eingeben" + link_dialog_title: "Link einfügen" + link_optional_text: "optionaler Titel" + link_url_placeholder: "http://beispiel.com" + quote_title: "Blockzitat" + quote_text: "Blockzitat" + blockquote_text: "Blockzitat" + code_title: "Vorformatierter Text" + code_text: "vorformatierten Text mit 4 Leerzeichen einrücken" + paste_code_text: "tippe oder füge den Code hier ein" + upload_title: "Hochladen" + upload_description: "gib hier eine Beschreibung des Uploads ein" + olist_title: "Nummerierte Liste" + ulist_title: "Aufzählung" + list_item: "Listenelement" + toggle_direction: "Schreibrichtung wechseln" help: "Markdown Hilfe" collapse: "den Editor minimieren" abandon: "Editor schließen und Entwurf verwerfen" From 8dcdc9c19651e08e9e006a5c04e088222199eb84 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 20 Apr 2022 23:43:06 -0700 Subject: [PATCH 231/556] New translations server.en.yml (German) --- config/locales/server.de.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml index 798420d4..62fec811 100644 --- a/config/locales/server.de.yml +++ b/config/locales/server.de.yml @@ -2,7 +2,7 @@ de: admin: wizard: submission: - no_user: "deleted (user_id: %{user_id})" + no_user: "gelöscht (user_id: %{user_id})" wizard: custom_title: "Wizard" custom_field: @@ -24,23 +24,23 @@ de: invalid_date: "Invalid date" invalid_time: "Invalid time" none: "We couldn't find a wizard at that address." - no_skip: "Wizard can't be skipped" + no_skip: "Wizard kann nicht übersprungen werden" export: error: - select_one: "Please select at least one valid wizard" - invalid_wizards: "No valid wizards selected" + select_one: "Bitte wähle mindestens einen gültigen Wizard" + invalid_wizards: "Keine gültigen Wizards ausgewählt" import: error: - no_file: "No file selected" - file_large: "File too large" - invalid_json: "File is not a valid json file" + no_file: "Keine Datei ausgewählt" + file_large: "Datei ist zu groß" + invalid_json: "Datei ist keine gültige json-Datei" destroy: error: - no_template: No template found - default: Error destroying wizard + no_template: Kein Template gefunden + default: Fehler beim Löschen des Wizards validation: - required: "%{property} is required" - conflict: "Wizard with id '%{wizard_id}' already exists" + required: "%{property} ist erforderlich" + conflict: "Wizard mit id '%{wizard_id}' existiert bereits" after_signup: "You can only have one 'after signup' wizard at a time. %{wizard_id} has 'after signup' enabled." after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard." after_time: "After time setting is invalid." From 4ff50b191b19431b925ff5866cfb6be9044ef984 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 21 Apr 2022 23:47:20 -0700 Subject: [PATCH 232/556] New translations client.en.yml (Portuguese, Brazilian) --- config/locales/client.pt.yml | 674 +++++++++++++++++------------------ 1 file changed, 337 insertions(+), 337 deletions(-) diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index d7975b3b..5f2342e4 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -1,225 +1,225 @@ pt: js: wizard: - complete_custom: "Complete the {{name}}" + complete_custom: "Completar o {{name}}" admin_js: admin: wizard: - label: "Wizard" - nav_label: "Wizards" - select: "Select a wizard" - create: "Create Wizard" - name: "Name" - name_placeholder: "wizard name" + label: "Assistente" + nav_label: "Assistentes" + select: "Selecionar um assistente" + create: "Assistente de criação" + name: "Nome" + name_placeholder: "nome do assistente" background: "Background" background_placeholder: "#hex" - save_submissions: "Save" - save_submissions_label: "Save wizard submissions." - multiple_submissions: "Multiple" - multiple_submissions_label: "Users can submit multiple times." - after_signup: "Signup" - after_signup_label: "Users directed to wizard after signup." - after_time: "Time" - after_time_label: "Users directed to wizard after start time:" - after_time_time_label: "Start Time" + save_submissions: "Salvar" + save_submissions_label: "Salvar submissões do assistente." + multiple_submissions: "Múltiplos" + multiple_submissions_label: "Os usuários podem enviar várias vezes." + after_signup: "Registrar-se" + after_signup_label: "Usuários direcionados ao assistente após o horário de início." + after_time: "Horário" + after_time_label: "Usuários direcionados ao assistente após o horário de início:" + after_time_time_label: "Horário de Início" after_time_modal: - title: "Wizard Start Time" - date: "Date" - time: "Time" - done: "Set Time" - clear: "Clear" - required: "Required" - required_label: "Users cannot skip the wizard." + title: "Hora de Início do Assistente" + date: "Data" + time: "Horário" + done: "Definir horário" + clear: "Limpar" + required: "Obrigatório" + required_label: "Usuários não podem pular o assistente." prompt_completion: "Prompt" - prompt_completion_label: "Prompt user to complete wizard." - restart_on_revisit: "Restart" - restart_on_revisit_label: "Clear submissions on each visit." - resume_on_revisit: "Resume" - resume_on_revisit_label: "Ask the user if they want to resume on each visit." - theme_id: "Theme" - no_theme: "Select a Theme (optional)" - save: "Save Changes" - remove: "Delete Wizard" - add: "Add" + prompt_completion_label: "Solicitar usuário para completar o assistente." + restart_on_revisit: "Reiniciar" + restart_on_revisit_label: "Limpar submissões em cada visita." + resume_on_revisit: "Retomar" + resume_on_revisit_label: "Pergunte ao usuário se eles querem retomar cada visita." + theme_id: "Tema" + no_theme: "Selecione um tema (opcional)" + save: "Salvar as alterações" + remove: "Apagar Assistente" + add: "Adicionar" url: "Url" - key: "Key" - value: "Value" - profile: "profile" - translation: "Translation" - translation_placeholder: "key" - type: "Type" - none: "Make a selection" - submission_key: 'submission key' + key: "Chave" + value: "Valor" + profile: "perfil" + translation: "Tradução" + translation_placeholder: "chave" + type: "Tipo" + none: "Fazer uma seleção" + submission_key: 'submeta a chave' param_key: 'param' - group: "Group" - permitted: "Permitted" - advanced: "Advanced" - undo: "Undo" - clear: "Clear" - select_type: "Select a type" - condition: "Condition" - index: "Index" + group: "Grupo" + permitted: "Permitido" + advanced: "Avançado" + undo: "Desfazer" + clear: "Limpar" + select_type: "Selecione um tipo" + condition: "Condição" + index: "Indexação" category_settings: custom_wizard: - title: "Custom Wizard" - create_topic_wizard: "Select a wizard to replace the new topic composer in this category." + title: "Assistente personalizado" + create_topic_wizard: "Selecionar um assistente para substituir o novo compositor de tópicos nesta categoria." message: wizard: - select: "Select a wizard, or create a new one" - edit: "You're editing a wizard" - create: "You're creating a new wizard" - documentation: "Check out the wizard documentation" - contact: "Contact the developer" + select: "Selecione um assistente, ou crie um novo" + edit: "Você está editando um assistente" + create: "Você está criando um novo assistente" + documentation: "Confira a documentação do assistente" + contact: "Entre em contato com o desenvolvedor" field: - type: "Select a field type" - edit: "You're editing a field" - documentation: "Check out the field documentation" + type: "Selecione um tipo de campo" + edit: "Você está editando um assistente" + documentation: "Confira a documentação do assistente" action: - type: "Select an action type" - edit: "You're editing an action" - documentation: "Check out the action documentation" + type: "Selecionar um tipo de ação" + edit: "Você está editando um assistente" + documentation: "Confira a documentação do assistente" custom_fields: - create: "View, create, edit and destroy custom fields" - saved: "Saved custom field" - error: "Failed to save: {{messages}}" - documentation: Check out the custom field documentation + create: "Visualizar, criar, editar e destruir campos personalizados" + saved: "Campo personalizado salvo" + error: "Falha ao salvar: {{messages}}" + documentation: Confira a documentação do assistente manager: - info: "Export, import or destroy wizards" - documentation: Check out the manager documentation - none_selected: Please select atleast one wizard - no_file: Please choose a file to import - file_size_error: The file size must be 512kb or less - file_format_error: The file must be a .json file - server_error: "Error: {{message}}" - importing: Importing wizards... - destroying: Destroying wizards... - import_complete: Import complete - destroy_complete: Destruction complete + info: "Exportar, importar ou destruir assistentes" + documentation: Confira a documentação do assistente + none_selected: Por favor, selecione pelo menos um assistente válido + no_file: Por favor, escolha um arquivo para importar + file_size_error: O tamanho do arquivo deve ser 512kb ou menos + file_format_error: O arquivo deve ser um arquivo .json + server_error: "Erro: {{message}}" + importing: Importando assistentes... + destroying: Destruindo assistentes... + import_complete: Importação concluída + destroy_complete: Destruição completa editor: - show: "Show" - hide: "Hide" - preview: "{{action}} Preview" - popover: "{{action}} Fields" + show: "Apresentar" + hide: "Esconder" + preview: "Pré-visualização {{action}}" + popover: "{{action}} Campos" input: conditional: - name: 'if' - output: 'then' + name: 'se' + output: 'então' assignment: - name: 'set' + name: 'definir' association: - name: 'map' + name: 'mapa' validation: - name: 'ensure' + name: 'garantir' selector: label: - text: "text" - wizard_field: "wizard field" - wizard_action: "wizard action" - user_field: "user field" - user_field_options: "user field options" - user: "user" - category: "category" + text: "texto" + wizard_field: "campo assistente" + wizard_action: "ação do assistente" + user_field: "campo de usuário" + user_field_options: "opções do campo usuário" + user: "usuário" + category: "categoria" tag: "tag" - group: "group" - list: "list" - custom_field: "custom field" - value: "value" + group: "grupo" + list: "lista" + custom_field: "campo personalizado" + value: "valor" placeholder: - text: "Enter text" - property: "Select property" - wizard_field: "Select field" - wizard_action: "Select action" - user_field: "Select field" - user_field_options: "Select field" - user: "Select user" - category: "Select category" - tag: "Select tag" - group: "Select group" - list: "Enter item" - custom_field: "Select field" - value: "Select value" + text: "Inserir item" + property: "Selecione a propriedade" + wizard_field: "Selecionar campo" + wizard_action: "Selecionar ação" + user_field: "Selecionar campo" + user_field_options: "Selecionar campo" + user: "Selecionar usuário" + category: "Selecione a categoria" + tag: "Selecionar tag" + group: "Selecionar grupo" + list: "Inserir item" + custom_field: "Selecionar campo" + value: "Selecione o valor" error: - failed: "failed to save wizard" - required: "{{type}} requires {{property}}" - invalid: "{{property}} is invalid" - dependent: "{{property}} is dependent on {{dependent}}" - conflict: "{{type}} with {{property}} '{{value}}' already exists" - after_time: "After time invalid" + failed: "falhou em salvar o assistente" + required: "{{type}} requer {{property}}" + invalid: "{{property}} é inválido" + dependent: "{{property}} depende de {{dependent}}" + conflict: "{{type}} com {{property}} '{{value}}' já existe" + after_time: "Após tempo inválido" step: - header: "Steps" - title: "Title" - banner: "Banner" - description: "Description" + header: "Passos" + title: "Título" + banner: "Estandarte" + description: "Descrição" required_data: - label: "Required" - not_permitted_message: "Message shown when required data not present" + label: "Obrigatório" + not_permitted_message: "Mensagem exibida quando os dados necessários não estão presentes" permitted_params: label: "Params" force_final: - label: "Conditional Final Step" - description: "Display this step as the final step if conditions on later steps have not passed when the user reaches this step." + label: "Etapa final condicional" + description: "Exibir esta etapa como a última etapa se as condições em etapas posteriores não forem passadas quando o usuário chegar a esta etapa." field: - header: "Fields" - label: "Label" - description: "Description" - image: "Image" - image_placeholder: "Image url" - required: "Required" - required_label: "Field is Required" - min_length: "Min Length" - min_length_placeholder: "Minimum length in characters" - max_length: "Max Length" - max_length_placeholder: "Maximum length in characters" - char_counter: "Character Counter" - char_counter_placeholder: "Display Character Counter" - field_placeholder: "Field Placeholder" - file_types: "File Types" - preview_template: "Template" - limit: "Limit" - property: "Property" + header: "Campos" + label: "Etiqueta" + description: "Descrição" + image: "Imagem" + image_placeholder: "Url da imagem" + required: "Obrigatório" + required_label: "O campo é obrigatório" + min_length: "Comprimento Mínimo" + min_length_placeholder: "Comprimento mínimo em caracteres" + max_length: "Comprimento máximo" + max_length_placeholder: "Comprimento mínimo em caracteres" + char_counter: "Contador de Personagens" + char_counter_placeholder: "Exibir contador de caracteres" + field_placeholder: "Espaço reservado" + file_types: "Tipos de arquivo" + preview_template: "Modelo" + limit: "Limite" + property: "Propriedade" prefill: "Prefill" - content: "Content" - tag_groups: "Tag Groups" + content: "Conteúdo" + tag_groups: "Grupos de Tags" date_time_format: - label: "Format" - instructions: "Moment.js format" + label: "Formato" + instructions: "Formato moment.js" validations: - header: "Realtime Validations" - enabled: "Enabled" - similar_topics: "Similar Topics" - position: "Position" - above: "Above" - below: "Below" - categories: "Categories" - max_topic_age: "Max Topic Age" + header: "Validações em tempo real" + enabled: "Ativado" + similar_topics: "Tópicos similares" + position: "Posição" + above: "Abaixo" + below: "Abaixo" + categories: "Categorias" + max_topic_age: "Idade máxima do tópico" time_units: - days: "Days" - weeks: "Weeks" - months: "Months" - years: "Years" + days: "Dias" + weeks: "Semanas" + months: "Meses" + years: "Anos" type: - text: "Text" + text: "Texto" textarea: Textarea - composer: Composer - composer_preview: Composer Preview - text_only: Text Only - number: Number + composer: Compositor + composer_preview: Visualização do compositor + text_only: Somente Texto + number: Numero checkbox: Checkbox url: Url - upload: Upload + upload: Transferir dropdown: Dropdown tag: Tag - category: Category - group: Group - user_selector: User Selector - date: Date - time: Time - date_time: Date & Time + category: Categoria + group: Grupo + user_selector: Seletor de Usuário + date: Data + time: Horário + date_time: Data e Hora connector: - and: "and" - or: "or" - then: "then" - set: "set" + and: "e" + or: "ou" + then: "então" + set: "definir" equal: '=' greater: '>' less: '<' @@ -229,209 +229,209 @@ pt: association: '→' is: 'is' action: - header: "Actions" - include: "Include Fields" - title: "Title" - post: "Post" - topic_attr: "Topic Attribute" - interpolate_fields: "Insert wizard fields using the field_id in w{}. Insert user fields using field key in u{}." + header: "Ações" + include: "Incluir campos" + title: "Título" + post: "Postagem" + topic_attr: "Atributo do tópico" + interpolate_fields: "Inserir campos de assistente usando o field_id no w{}. Insira campos de usuário usando chave de campo em u{}." run_after: - label: "Run After" - wizard_completion: "Wizard Completion" + label: "Executar depois" + wizard_completion: "Conclusão do Assistente" custom_fields: - label: "Custom" - key: "field" + label: "Personalizado" + key: "campo" skip_redirect: - label: "Redirect" - description: "Don't redirect the user to this {{type}} after the wizard completes" + label: "Redirecionar" + description: "Não redirecionar o usuário para este {{type}} após a conclusão do assistente" suppress_notifications: - label: "Suppress Notifications" - description: "Suppress normal notifications triggered by post creation" + label: "Ocultar notificações" + description: "Ocultar notificações normais desencadeadas pela criação de publicações" send_message: - label: "Send Message" - recipient: "Recipient" + label: "Enviar mensagem" + recipient: "Destinatário" create_topic: - label: "Create Topic" - category: "Category" + label: "Criar tópico" + category: "Categoria" tags: "Tags" - visible: "Visible" + visible: "Visível" open_composer: - label: "Open Composer" + label: "Abrir compositor" update_profile: - label: "Update Profile" - setting: "Fields" - key: "field" + label: "Atualizar o perfil" + setting: "Campos" + key: "campo" watch_categories: - label: "Watch Categories" - categories: "Categories" - mute_remainder: "Mute Remainder" + label: "Ver categorias" + categories: "Categorias" + mute_remainder: "Silenciar lembretes" notification_level: - label: "Notification Level" + label: "Nível de notificação" regular: "Normal" - watching: "Watching" - tracking: "Tracking" - watching_first_post: "Watching First Post" - muted: "Muted" - select_a_notification_level: "Select level" - wizard_user: "Wizard User" - usernames: "Users" + watching: "Acompanhando" + tracking: "Rastreamento" + watching_first_post: "Assistindo primeira publicação" + muted: "Silenciado" + select_a_notification_level: "Selecionar nível" + wizard_user: "Usuário do Assistente" + usernames: "Usuários" post_builder: - checkbox: "Post Builder" - label: "Builder" - user_properties: "User Properties" - wizard_fields: "Wizard Fields" - wizard_actions: "Wizard Actions" - placeholder: "Insert wizard fields using the field_id in w{}. Insert user properties using property in u{}." + checkbox: "Construtor de Postagens" + label: "Construir" + user_properties: "Propriedades do usuário" + wizard_fields: "Campo assistente" + wizard_actions: "Ação do assistente" + placeholder: "Inserir campos de assistente usando o field_id no w{}. Insira campos de usuário usando chave de campo em u{}." add_to_group: - label: "Add to Group" + label: "Adicionar ao Grupo" route_to: - label: "Route To" + label: "Rota para" url: "Url" - code: "Code" + code: "Código" send_to_api: - label: "Send to API" + label: "Enviar para API" api: "API" endpoint: "Endpoint" - select_an_api: "Select an API" - select_an_endpoint: "Select an endpoint" - body: "Body" + select_an_api: "Selecione uma API" + select_an_endpoint: "Selecionar um endpoint" + body: "Conteúdo" body_placeholder: "JSON" create_category: - label: "Create Category" - name: Name - slug: Slug - color: Color - text_color: Text color - parent_category: Parent Category - permissions: Permissions + label: "Criar Categoria" + name: Nome + slug: Pedaço de texto + color: Cor + text_color: Cor do texto + parent_category: Categoria Pai + permissions: Permissões create_group: - label: Create Group - name: Name - full_name: Full Name - title: Title - bio_raw: About - owner_usernames: Owners - usernames: Members - grant_trust_level: Automatic Trust Level - mentionable_level: Mentionable Level - messageable_level: Messageable Level - visibility_level: Visibility Level - members_visibility_level: Members Visibility Level + label: Criar grupo + name: Nome + full_name: Nome Completo + title: Título + bio_raw: Sobre + owner_usernames: Proprietários + usernames: Membros + grant_trust_level: Nível de confiança automático + mentionable_level: Nível Mencionável + messageable_level: Nível Mensageável + visibility_level: Nível de visibilidade + members_visibility_level: Nível de Visibilidade dos Membros custom_field: - nav_label: "Custom Fields" - add: "Add" + nav_label: "Campos personalizados" + add: "Adicionar" external: - label: "from another plugin" - title: "This custom field has been added by another plugin. You can use it in your wizards but you can't edit the field here." + label: "de outro plugin" + title: "Este campo personalizado foi adicionado por outro plugin. Você pode usá-lo em seus assistentes, mas você não pode editar o campo aqui." name: - label: "Name" - select: "underscored_name" + label: "Nome" + select: "nome_sublinhado" type: - label: "Type" - select: "Select a type" + label: "Tipo" + select: "Selecione um tipo" string: "String" - integer: "Integer" - boolean: "Boolean" + integer: "Inteiro" + boolean: "Boleano" json: "JSON" klass: - label: "Class" - select: "Select a class" - post: "Post" - category: "Category" - topic: "Topic" - group: "Group" - user: "User" + label: "Classe" + select: "Selecione uma classe" + post: "Postagem" + category: "Categoria" + topic: "Tópico" + group: "Grupo" + user: "Usuário" serializers: - label: "Serializers" - select: "Select serializers" - topic_view: "Topic View" - topic_list_item: "Topic List Item" - basic_category: "Category" - basic_group: "Group" - post: "Post" + label: "Serializadores" + select: "Selecionar serializadores" + topic_view: "Exibição de tópico" + topic_list_item: "Item da Lista de Tópicos" + basic_category: "Categoria" + basic_group: "Grupo" + post: "Postagem" submissions: - nav_label: "Submissions" - title: "{{name}} Submissions" + nav_label: "Submissões" + title: "{{name}} Submissões" download: "Download" api: label: "API" nav_label: 'APIs' - select: "Select API" - create: "Create API" - new: 'New API' - name: "Name (can't be changed)" - name_placeholder: 'Underscored' - title: 'Title' - title_placeholder: 'Display name' - remove: 'Delete' - save: "Save" + select: "Selecione uma API" + create: "Criar API" + new: 'Nova API' + name: "Nome (não pode ser alterado)" + name_placeholder: 'Sublinhado' + title: 'Título' + title_placeholder: 'Nome para exibição' + remove: 'Excluir' + save: "Salvar" auth: - label: "Authorization" - btn: 'Authorize' - settings: "Settings" - status: "Status" - redirect_uri: "Redirect url" - type: 'Type' - type_none: 'Select a type' - url: "Authorization url" - token_url: "Token url" - client_id: 'Client id' - client_secret: 'Client secret' - username: 'username' - password: 'password' + label: "Autorização" + btn: 'Autorizar' + settings: "Confirgurações" + status: "Estado" + redirect_uri: "Url de redirecionamento" + type: 'Tipo' + type_none: 'Selecione um tipo' + url: "Autorização de url" + token_url: "Url do token" + client_id: 'Id do Cliente' + client_secret: 'Segredo do cliente' + username: 'nome de usuário' + password: 'senha' params: label: 'Params' new: 'New param' status: - label: "Status" - authorized: 'Authorized' - not_authorized: "Not authorized" - code: "Code" - access_token: "Access token" - refresh_token: "Refresh token" - expires_at: "Expires at" - refresh_at: "Refresh at" + label: "Estado" + authorized: 'Autorizar' + not_authorized: "Não autorizado" + code: "Código" + access_token: "Token de acesso" + refresh_token: "Atualizar token" + expires_at: "Expira em" + refresh_at: "Atualizar em" endpoint: label: "Endpoints" - add: "Add endpoint" - name: "Endpoint name" - method: "Select a method" - url: "Enter a url" - content_type: "Select a content type" - success_codes: "Select success codes" + add: "Adicionar endpoint" + name: "Nome do endpoint" + method: "Selecione um método" + url: "Digite uma url" + content_type: "Selecione um tipo de campo" + success_codes: "Selecionar códigos de sucesso" log: - label: "Logs" + label: "Registros" log: - nav_label: "Logs" + nav_label: "Registros" manager: - nav_label: Manager - title: Manage Wizards - export: Export - import: Import - imported: imported - upload: Select wizards.json - destroy: Destroy - destroyed: destroyed + nav_label: Administrador + title: Gerenciar Assistentes + export: Exportação + import: Importação + imported: importado + upload: Selecione wizards.json + destroy: Destruir + destroyed: destruído wizard_js: group: - select: "Select a group" + select: "Selecionar grupo" location: name: - title: "Name (optional)" - desc: "e.g. P. Sherman Dentist" + title: "Nome (opcional)" + desc: "por exemplo, P. Sherman Dentist" street: - title: "Number and Street" + title: "Número e rua" desc: "e.g. 42 Wallaby Way" postalcode: - title: "Postal Code (Zip)" + title: "Código Postal (Zip)" desc: "e.g. 2090" neighbourhood: - title: "Neighbourhood" + title: "Vizinhança" desc: "e.g. Cremorne Point" city: title: "City, Town or Village" desc: "e.g. Sydney" - coordinates: "Coordinates" + coordinates: "Coordenadas" lat: title: "Latitude" desc: "e.g. -31.9456702" @@ -439,10 +439,10 @@ pt: title: "Longitude" desc: "e.g. 115.8626477" country_code: - title: "Country" - placeholder: "Select a Country" + title: "País" + placeholder: "Selecione um país" query: - title: "Address" + title: "Endereço" desc: "e.g. 42 Wallaby Way, Sydney." geo: desc: "Locations provided by {{provider}}" @@ -487,29 +487,29 @@ pt: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" - quote_post_title: "Quote whole post" + show_preview: "Pré-visualizar postagem" + hide_preview: "Editar Publicação" + quote_post_title: "Citar publicação inteira" bold_label: "B" - bold_title: "Strong" - bold_text: "strong text" + bold_title: "Negrito" + bold_text: "texto em negrito" italic_label: "I" italic_title: "Emphasis" - italic_text: "emphasized text" - link_title: "Hyperlink" - link_description: "enter link description here" - link_dialog_title: "Insert Hyperlink" - link_optional_text: "optional title" - link_url_placeholder: "http://example.com" + italic_text: "texto enfatizado" + link_title: "Hiperlink" + link_description: "digite a descrição do upload aqui" + link_dialog_title: "Inserir Hyperlink" + link_optional_text: "título opcional" + link_url_placeholder: "http://exemplo.com" quote_title: "Blockquote" quote_text: "Blockquote" blockquote_text: "Blockquote" - code_title: "Preformatted text" + code_title: "Texto pré-formatado" code_text: "indent preformatted text by 4 spaces" paste_code_text: "type or paste code here" - upload_title: "Upload" - upload_description: "enter upload description here" - olist_title: "Numbered List" + upload_title: "Transferir" + upload_description: "digite a descrição do upload aqui" + olist_title: "Lista Numerada" ulist_title: "Bulleted List" list_item: "List item" toggle_direction: "Toggle Direction" From 883dacf34740f197e6c95e97475b92dfec11d7e2 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 21 Apr 2022 23:47:21 -0700 Subject: [PATCH 233/556] New translations server.en.yml (Portuguese, Brazilian) --- config/locales/server.pt.yml | 72 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml index 1606e062..6f7eca42 100644 --- a/config/locales/server.pt.yml +++ b/config/locales/server.pt.yml @@ -2,51 +2,51 @@ pt: admin: wizard: submission: - no_user: "deleted (user_id: %{user_id})" + no_user: "excluído (user_id: %{user_id})" wizard: - custom_title: "Wizard" + custom_title: "Assistente" custom_field: error: - required_attribute: "'%{attr}' is a required attribute" - unsupported_class: "'%{class}' is not a supported class" - unsupported_serializers: "'%{serializers}' are not supported serializers for '%{class}'" - unsupported_type: "%{type} is not a supported custom field type" - name_invalid: "'%{name}' is not a valid custom field name" - name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})" - name_already_taken: "'%{name}' is already taken as a custom field name" - save_default: "Failed to save custom field '%{name}'" + required_attribute: "'%{attr}' é um atributo obrigatório" + unsupported_class: "'%{class}' não é uma classe suportada" + unsupported_serializers: "'%{serializers}' não é suportado serializadores para '%{class}'" + unsupported_type: "%{type} não é um tipo de campo personalizado suportado" + name_invalid: "'%{name}' não é um nome de campo personalizado válido" + name_too_short: "'%{name}' é muito curto para um nome de campo personalizado (comprimento mínimo é #{min_length})" + name_already_taken: "'%{name}' já está tomado como um nome de campo personalizado" + save_default: "Falha ao salvar o campo personalizado '%{name}'" field: - too_short: "%{label} must be at least %{min} characters" - too_long: "%{label} must not be more than %{max} characters" - required: "%{label} is required." - not_url: "%{label} must be a valid url" - invalid_file: "%{label} must be a %{types}" - invalid_date: "Invalid date" - invalid_time: "Invalid time" - none: "We couldn't find a wizard at that address." - no_skip: "Wizard can't be skipped" + too_short: "%{label} deve ter pelo menos %{min} caracteres" + too_long: "%{label} deve ter pelo menos %{max} caracteres" + required: "%{label} é obrigatório." + not_url: "%{label} deve ser uma url válida" + invalid_file: "%{label} deve ser um %{types}" + invalid_date: "Data inválida" + invalid_time: "Tempo inválido" + none: "Nós não conseguimos encontrar um assistente nesse endereço." + no_skip: "Assistente não pode ser ignorado" export: error: - select_one: "Please select at least one valid wizard" - invalid_wizards: "No valid wizards selected" + select_one: "Por favor, selecione pelo menos um assistente válido" + invalid_wizards: "Nenhum assistente válido selecionado" import: error: - no_file: "No file selected" - file_large: "File too large" - invalid_json: "File is not a valid json file" + no_file: "Nenhum arquivo selecionado" + file_large: "Arquivo muito grande" + invalid_json: "O arquivo não é um arquivo json válido" destroy: error: - no_template: No template found - default: Error destroying wizard + no_template: Nenhum modelo encontrado + default: Erro ao destruir assistente validation: - required: "%{property} is required" - conflict: "Wizard with id '%{wizard_id}' already exists" - after_signup: "You can only have one 'after signup' wizard at a time. %{wizard_id} has 'after signup' enabled." - after_signup_after_time: "You can't use 'after time' and 'after signup' on the same wizard." - after_time: "After time setting is invalid." - liquid_syntax_error: "Liquid syntax error in %{attribute}: %{message}" + required: "%{property} é obrigatório" + conflict: "Assistente com id '%{wizard_id}' já existe" + after_signup: "Você só pode ter um assistente 'após a inscrição' por vez. %{wizard_id} tem 'após a inscrição' habilitado." + after_signup_after_time: "Você não pode usar 'após tempo' e 'após a inscrição' no mesmo assistente." + after_time: "Após a configuração do tempo é inválido." + liquid_syntax_error: "Erro de sintaxe Liquid em %{attribute}: %{message}" site_settings: - custom_wizard_enabled: "Enable custom wizards." - wizard_redirect_exclude_paths: "Routes excluded from wizard redirects." - wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview" - wizard_apis_enabled: "Enable API features (experimental)." + custom_wizard_enabled: "Ativar assistentes personalizados." + wizard_redirect_exclude_paths: "Rotas excluídas dos redirecionamentos do assistente." + wizard_recognised_image_upload_formats: "Tipos de arquivo que resultarão no upload exibindo uma visualização da imagem" + wizard_apis_enabled: "Habilitar recursos API (experimental)." From 449b81a93eff2b640ee057ad49c4d3ddc792969b Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 22 Apr 2022 15:46:32 +0300 Subject: [PATCH 234/556] COMPATIBILITY: new editor helper fn signatures See https://github.com/discourse/discourse/commit/94207e27d16f120fe8ce01e9542ded41118bccbc --- .../wizard/initializers/custom-wizard-field.js.es6 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 index f5deb927..e397af5f 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 +++ b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 @@ -114,13 +114,13 @@ export default { _wizardInsertText(args = {}) { if (args.fieldId === this.fieldId) { - this._insertText(args.text, args.options); + this.insertText(args.text, args.options); } }, _wizardReplaceText(args = {}) { if (args.fieldId === this.fieldId) { - this._replaceText(args.oldVal, args.newVal, (args.opts = {})); + this.replaceText(args.oldVal, args.newVal, (args.opts = {})); } }, @@ -139,7 +139,7 @@ export default { let html = clipboard.getData("text/html"); let handled = false; - const { pre, lineVal } = this._getSelected(null, { lineVal: true }); + const { pre, lineVal } = this.getSelected(null, { lineVal: true }); const isInlinePasting = pre.match(/[^\n]$/); const isCodeBlock = isInside(pre, /(^|\n)```/g); @@ -150,7 +150,7 @@ export default { !isCodeBlock ) { plainText = plainText.trim().replace(/\r/g, ""); - const table = this._extractTable(plainText); + const table = this.extractTable(plainText); if (table) { this.appEvents.trigger("wizard-editor:insert-text", { fieldId: this.fieldId, From 2c7addb9ae12645fe460ed28b4fe5cd8cd9c329e Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 12 May 2022 18:14:38 +0200 Subject: [PATCH 235/556] FIX: css grid causes issues for admin editor in safari --- assets/stylesheets/common/wizard-admin.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index acc6f112..607f9037 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -430,7 +430,6 @@ } .d-editor-textarea-wrapper { - display: grid; margin-bottom: 10px; textarea { From 294c35cf9b4e5f4fee5ccf55d308417b74ff83fe Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 13 May 2022 11:40:08 +0200 Subject: [PATCH 236/556] COMPATIBILITY: three custom fields were dropped in core https://github.com/discourse/discourse/commit/991b62b6f123e9df63f443bba411eee4f3c3066a --- spec/components/custom_wizard/custom_field_spec.rb | 4 ++-- .../custom_wizard/admin/custom_fields_controller_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index 155a6526..cd29f081 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -220,11 +220,11 @@ describe CustomWizard::CustomField do end it "lists custom field records added by other plugins " do - expect(CustomWizard::CustomField.external_list.length).to eq(11) + expect(CustomWizard::CustomField.external_list.length).to eq(8) end it "lists all custom field records" do - expect(CustomWizard::CustomField.full_list.length).to eq(15) + expect(CustomWizard::CustomField.full_list.length).to eq(12) end end diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb index 6f1aea12..1690660f 100644 --- a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb @@ -18,7 +18,7 @@ describe CustomWizard::AdminCustomFieldsController do it "returns the full list of custom fields" do get "/admin/wizards/custom-fields.json" - expect(response.parsed_body.length).to eq(15) + expect(response.parsed_body.length).to eq(12) end it "saves custom fields" do From 45ab9b1b8053aaab1182821e67f546bc6f3ca298 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 13 May 2022 11:52:11 +0200 Subject: [PATCH 237/556] Remove unconnected action --- .../discourse/templates/admin-wizards-wizard-show.hbs | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs index e10fd91d..e7fd0865 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -176,7 +176,6 @@ currentActionId=currentAction.id wizard=wizard apis=apis - removeAction=(action "removeAction") wizardFields=wizardFields}} {{/each}} From 45f52d56c0cb01c452d16e047c3d482320908f85 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 13 May 2022 12:04:53 +0200 Subject: [PATCH 238/556] Sort actions by subscription type --- .../components/wizard-subscription-selector.js.es6 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 index 2202f746..d6db8875 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 @@ -67,6 +67,18 @@ export default SingleSelectComponent.extend(Subscription, { requiredSubscriptionType ), }; + }).sort(function(a, b) { + if (a.subscriptionType && !b.subscriptionType) { + return 1; + } + if (!a.subscriptionType && b.subscriptionType) { + return -1; + } + if (a.subscriptionType == b.subscriptionType) { + return a.subscriptionType ? a.subscriptionType.localeCompare(b.subscriptionType) : 0; + } else { + return a.subscriptionType === 'standard' ? -1 : 0; + } }); }, From 1c9d52bcfc672fe988e6a55fd747234b58544a67 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 13 May 2022 12:06:38 +0200 Subject: [PATCH 239/556] Fix linting --- .../wizard-subscription-selector.js.es6 | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 index d6db8875..8ef6896a 100644 --- a/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 +++ b/assets/javascripts/discourse/components/wizard-subscription-selector.js.es6 @@ -52,34 +52,38 @@ export default SingleSelectComponent.extend(Subscription, { @discourseComputed("feature", "attribute") content(feature, attribute) { - return wizardSchema[feature][attribute].map((value) => { - let requiredSubscriptionType = this.requiredSubscriptionType( - feature, - attribute, - value - ); - return { - id: value, - name: I18n.t(nameKey(feature, attribute, value)), - subscriptionType: requiredSubscriptionType, - disabled: !subscriptionTypeSufficient( - this.subscriptionType, - requiredSubscriptionType - ), - }; - }).sort(function(a, b) { - if (a.subscriptionType && !b.subscriptionType) { - return 1; - } - if (!a.subscriptionType && b.subscriptionType) { - return -1; - } - if (a.subscriptionType == b.subscriptionType) { - return a.subscriptionType ? a.subscriptionType.localeCompare(b.subscriptionType) : 0; - } else { - return a.subscriptionType === 'standard' ? -1 : 0; - } - }); + return wizardSchema[feature][attribute] + .map((value) => { + let requiredSubscriptionType = this.requiredSubscriptionType( + feature, + attribute, + value + ); + return { + id: value, + name: I18n.t(nameKey(feature, attribute, value)), + subscriptionType: requiredSubscriptionType, + disabled: !subscriptionTypeSufficient( + this.subscriptionType, + requiredSubscriptionType + ), + }; + }) + .sort(function (a, b) { + if (a.subscriptionType && !b.subscriptionType) { + return 1; + } + if (!a.subscriptionType && b.subscriptionType) { + return -1; + } + if (a.subscriptionType === b.subscriptionType) { + return a.subscriptionType + ? a.subscriptionType.localeCompare(b.subscriptionType) + : 0; + } else { + return a.subscriptionType === "standard" ? -1 : 0; + } + }); }, modifyComponentForRow() { From 8fd5ae71695f5314a71a93c327cdd663424e87f7 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 31 May 2022 13:04:35 +0200 Subject: [PATCH 240/556] Ensure wizard asset paths are included --- plugin.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/plugin.rb b/plugin.rb index a397e3a7..e764c146 100644 --- a/plugin.rb +++ b/plugin.rb @@ -22,12 +22,10 @@ if Rails.env.production? } end -if Rails.env.test? - config = Rails.application.config - plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets" - config.assets.paths << "#{plugin_asset_path}/javascripts" - config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" -end +config = Rails.application.config +plugin_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/assets" +config.assets.paths << "#{plugin_asset_path}/javascripts" +config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" if respond_to?(:register_svg_icon) register_svg_icon "far-calendar" From 1d7d9d111942b3956199155e3aabfe7338cde09a Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 31 May 2022 13:16:28 +0200 Subject: [PATCH 241/556] Fix failing specs --- spec/components/custom_wizard/custom_field_spec.rb | 4 ++-- .../custom_wizard/admin/custom_fields_controller_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/components/custom_wizard/custom_field_spec.rb b/spec/components/custom_wizard/custom_field_spec.rb index ff47585b..4b8d43e2 100644 --- a/spec/components/custom_wizard/custom_field_spec.rb +++ b/spec/components/custom_wizard/custom_field_spec.rb @@ -260,11 +260,11 @@ describe CustomWizard::CustomField do end it "lists custom field records added by other plugins " do - expect(CustomWizard::CustomField.external_list.length).to eq(8) + expect(CustomWizard::CustomField.external_list.length).to be > 2 end it "lists all custom field records" do - expect(CustomWizard::CustomField.full_list.length).to eq(12) + expect(CustomWizard::CustomField.full_list.length).to be > 2 end end diff --git a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb index bc2dbb9d..7aef791c 100644 --- a/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb +++ b/spec/requests/custom_wizard/admin/custom_fields_controller_spec.rb @@ -13,7 +13,7 @@ describe CustomWizard::AdminCustomFieldsController do it "returns the full list of custom fields" do get "/admin/wizards/custom-fields.json" - expect(response.parsed_body.length).to eq(12) + expect(response.parsed_body["custom_fields"].length).to be > 2 end it "saves custom fields" do From eb8b289b502c57fc161910ab39d9ba3237e40a9e Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 31 May 2022 13:37:34 +0200 Subject: [PATCH 242/556] Fix create api action --- assets/javascripts/discourse/templates/admin-wizards-api.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/javascripts/discourse/templates/admin-wizards-api.hbs b/assets/javascripts/discourse/templates/admin-wizards-api.hbs index f5e3214d..af91d0fb 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-api.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-api.hbs @@ -8,7 +8,7 @@ )}} {{d-button - action=(action "createApi") + action=(route-action "createApi") label="admin.wizard.api.create" icon="plus"}}
From cf33fb97920c10a13811c988fe40b5c188b50a71 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 2 Jun 2022 16:45:20 +0200 Subject: [PATCH 243/556] Style and other fixes for API features --- .../controllers/admin-wizards-api-show.js.es6 | 5 ++++ .../templates/admin-wizards-api-show.hbs | 30 +++++++++++-------- assets/stylesheets/admin/admin.scss | 10 +++++-- assets/stylesheets/admin/wizard/api.scss | 19 +++++++----- config/locales/client.en.yml | 1 + lib/custom_wizard/api/log_entry.rb | 2 +- 6 files changed, 43 insertions(+), 24 deletions(-) diff --git a/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 index 7d3c1084..f41c3ada 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 @@ -88,6 +88,11 @@ export default Controller.extend({ twoLeggedOauth: equal("api.authType", "oauth_2"), threeLeggedOauth: equal("api.authType", "oauth_3"), + @discourseComputed("api.isNew") + nameClass(isNew) { + return isNew ? "new" : "saved"; + }, + actions: { addParam() { this.get("api.authParams").pushObject({}); diff --git a/assets/javascripts/discourse/templates/admin-wizards-api-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-api-show.hbs index 4d3def3d..303b3f6d 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-api-show.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-api-show.hbs @@ -21,11 +21,11 @@ {{/if}}
-
+
{{#if api.isNew}} {{i18n "admin.wizard.api.new"}} {{else}} - {{api.title}} + {{api.title}} {{/if}}
@@ -35,12 +35,12 @@ {{input value=api.title placeholder=(i18n "admin.wizard.api.title_placeholder")}}
-
+
{{#if api.isNew}} {{input value=api.name placeholder=(i18n "admin.wizard.api.name_placeholder")}} {{else}} - {{api.name}} + {{api.name}} {{/if}}
@@ -63,7 +63,7 @@ {{/if}}
-
+
{{i18n "admin.wizard.api.auth.label"}}
@@ -71,7 +71,7 @@
-
+
{{i18n "admin.wizard.api.auth.settings"}}
@@ -174,7 +174,7 @@ {{/if}}
-
+
{{i18n "admin.wizard.api.status.label"}}
@@ -220,7 +220,7 @@ {{/if}}
-
+
{{i18n "admin.wizard.api.endpoint.label"}}
@@ -277,11 +277,15 @@ {{/if}}
-
+
{{i18n "admin.wizard.api.log.label"}} - {{d-button action=(action "clearLogs") - icon="trash-alt" - class="clear-logs"}} + +
+ {{d-button + action=(action "clearLogs") + class="clear-logs" + label="admin.wizard.api.log.clear"}} +
@@ -300,7 +304,7 @@ {{logentry.time}} {{logentry.status}} diff --git a/assets/stylesheets/admin/admin.scss b/assets/stylesheets/admin/admin.scss index 70d5d539..d0106337 100644 --- a/assets/stylesheets/admin/admin.scss +++ b/assets/stylesheets/admin/admin.scss @@ -79,7 +79,8 @@ $error: #ef1700; } } - & + div { + & + div, + & + div + div { margin-top: 30px; } } @@ -212,6 +213,7 @@ $error: #ef1700; .wizard-header { margin-bottom: 20px; + display: flex; &.large { font-size: 1.5em; @@ -253,11 +255,12 @@ $error: #ef1700; } &.medium { - font-size: 1.3em; + font-size: 1.2em; } &.small { font-size: 1em; + font-weight: 700; margin-bottom: 5px; } @@ -266,8 +269,9 @@ $error: #ef1700; } .controls { + font-size: 1rem; + display: flex; margin-left: auto; - margin-right: 0.5rem; } } diff --git a/assets/stylesheets/admin/wizard/api.scss b/assets/stylesheets/admin/wizard/api.scss index 9d0ad261..29aff22d 100644 --- a/assets/stylesheets/admin/wizard/api.scss +++ b/assets/stylesheets/admin/wizard/api.scss @@ -40,9 +40,18 @@ float: right; } - .wizard-header { - overflow: hidden; - font-size: 1.3em; + .metadata { + display: flex; + + .title { + margin-right: 1em; + } + + .name.saved span { + display: inline-block; + padding: 6px 12px; + background-color: var(--primary-low); + } } } @@ -74,10 +83,6 @@ width: 50%; max-width: 50%; - .wizard-header { - overflow: hidden; - } - .authorization { float: right; } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 45efabb8..6fe9e5ee 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -447,6 +447,7 @@ en: log: label: "Logs" + clear: clear log: nav_label: "Logs" diff --git a/lib/custom_wizard/api/log_entry.rb b/lib/custom_wizard/api/log_entry.rb index 3fe2969e..31af514e 100644 --- a/lib/custom_wizard/api/log_entry.rb +++ b/lib/custom_wizard/api/log_entry.rb @@ -27,7 +27,7 @@ class CustomWizard::Api::LogEntry log_id = new_data['log_id'] else data = {} - log_id = SecureRandom.hex(3) + log_id = SecureRandom.hex(8) end new_data.each do |k, v| From dec670ac4358064a96b7f589cadd18edcc342065 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 2 Jun 2022 17:49:16 +0200 Subject: [PATCH 244/556] FIX: ensure we have the right upload ID when navigating between steps --- .../discourse/initializers/custom-wizard-edits.js.es6 | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 20787b4e..cecf2a03 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -1,6 +1,7 @@ import DiscourseURL from "discourse/lib/url"; import { withPluginApi } from "discourse/lib/plugin-api"; import getUrl from "discourse-common/lib/get-url"; +import { observes } from "discourse-common/utils/decorators"; export default { name: "custom-wizard-edits", @@ -35,6 +36,16 @@ export default { }, }, }); + + api.modifyClass("component:uppy-image-uploader", { + // Needed to ensure appEvents get registered when navigating between steps + @observes("id") + initOnStepChange() { + if (/wizard-field|wizard-step/.test(this.id)) { + this._initialize(); + } + }, + }); }); }, }; From 717aedb6075ee683cee6a720bec06d72282aada2 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 2 Jun 2022 18:00:01 +0200 Subject: [PATCH 245/556] Tweak checkbox field description css --- assets/stylesheets/wizard/custom/field.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/stylesheets/wizard/custom/field.scss b/assets/stylesheets/wizard/custom/field.scss index cb6f0635..3ef3010d 100644 --- a/assets/stylesheets/wizard/custom/field.scss +++ b/assets/stylesheets/wizard/custom/field.scss @@ -59,13 +59,16 @@ & > label.field-label { order: 1; - font-weight: 400; } & > .field-description { margin-top: 0; order: 2; } + + & > .field-label + .field-description { + margin-left: .5em; + } } .url-field input { From e88fff7e25f0d595a81bf0aac2d0df8cc7015682 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Thu, 2 Jun 2022 18:05:17 +0200 Subject: [PATCH 246/556] Tweak preview toggle text --- config/locales/client.af.yml | 4 ++-- config/locales/client.ar.yml | 4 ++-- config/locales/client.az.yml | 4 ++-- config/locales/client.be.yml | 4 ++-- config/locales/client.bg.yml | 4 ++-- config/locales/client.bn.yml | 4 ++-- config/locales/client.bo.yml | 4 ++-- config/locales/client.bs.yml | 4 ++-- config/locales/client.ca.yml | 4 ++-- config/locales/client.cs.yml | 4 ++-- config/locales/client.cy.yml | 4 ++-- config/locales/client.da.yml | 4 ++-- config/locales/client.de.yml | 4 ++-- config/locales/client.el.yml | 4 ++-- config/locales/client.en.yml | 4 ++-- config/locales/client.eo.yml | 4 ++-- config/locales/client.es.yml | 4 ++-- config/locales/client.et.yml | 4 ++-- config/locales/client.eu.yml | 4 ++-- config/locales/client.fa.yml | 4 ++-- config/locales/client.fi.yml | 4 ++-- config/locales/client.gl.yml | 4 ++-- config/locales/client.he.yml | 4 ++-- config/locales/client.hi.yml | 4 ++-- config/locales/client.hr.yml | 4 ++-- config/locales/client.hu.yml | 4 ++-- config/locales/client.hy.yml | 4 ++-- config/locales/client.id.yml | 4 ++-- config/locales/client.is.yml | 4 ++-- config/locales/client.it.yml | 4 ++-- config/locales/client.ja.yml | 4 ++-- config/locales/client.ka.yml | 4 ++-- config/locales/client.kk.yml | 4 ++-- config/locales/client.km.yml | 4 ++-- config/locales/client.kn.yml | 4 ++-- config/locales/client.ko.yml | 4 ++-- config/locales/client.ku.yml | 4 ++-- config/locales/client.lo.yml | 4 ++-- config/locales/client.lt.yml | 4 ++-- config/locales/client.lv.yml | 4 ++-- config/locales/client.mk.yml | 4 ++-- config/locales/client.ml.yml | 4 ++-- config/locales/client.mn.yml | 4 ++-- config/locales/client.ms.yml | 4 ++-- config/locales/client.ne.yml | 4 ++-- config/locales/client.nl.yml | 4 ++-- config/locales/client.no.yml | 4 ++-- config/locales/client.om.yml | 4 ++-- config/locales/client.pa.yml | 4 ++-- config/locales/client.pl.yml | 4 ++-- config/locales/client.pt.yml | 4 ++-- config/locales/client.ro.yml | 4 ++-- config/locales/client.ru.yml | 4 ++-- config/locales/client.sd.yml | 4 ++-- config/locales/client.sk.yml | 4 ++-- config/locales/client.sl.yml | 4 ++-- config/locales/client.sq.yml | 4 ++-- config/locales/client.sr.yml | 4 ++-- config/locales/client.sv.yml | 4 ++-- config/locales/client.sw.yml | 4 ++-- config/locales/client.ta.yml | 4 ++-- config/locales/client.te.yml | 4 ++-- config/locales/client.th.yml | 4 ++-- config/locales/client.tl.yml | 4 ++-- config/locales/client.tr.yml | 4 ++-- config/locales/client.tt.yml | 4 ++-- config/locales/client.uk.yml | 4 ++-- config/locales/client.ur.yml | 4 ++-- config/locales/client.vi.yml | 4 ++-- config/locales/client.yi.yml | 4 ++-- config/locales/client.zh.yml | 4 ++-- config/locales/client.zu.yml | 4 ++-- 72 files changed, 144 insertions(+), 144 deletions(-) diff --git a/config/locales/client.af.yml b/config/locales/client.af.yml index 2f399b99..6bacad0a 100644 --- a/config/locales/client.af.yml +++ b/config/locales/client.af.yml @@ -486,8 +486,8 @@ af: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml index b7253bba..39ca7df6 100644 --- a/config/locales/client.ar.yml +++ b/config/locales/client.ar.yml @@ -498,8 +498,8 @@ ar: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.az.yml b/config/locales/client.az.yml index 990f6a37..02686c04 100644 --- a/config/locales/client.az.yml +++ b/config/locales/client.az.yml @@ -486,8 +486,8 @@ az: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.be.yml b/config/locales/client.be.yml index f6fd6764..392dd4c8 100644 --- a/config/locales/client.be.yml +++ b/config/locales/client.be.yml @@ -492,8 +492,8 @@ be: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml index 058baf4d..61303c04 100644 --- a/config/locales/client.bg.yml +++ b/config/locales/client.bg.yml @@ -486,8 +486,8 @@ bg: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.bn.yml b/config/locales/client.bn.yml index ca21e418..5e1e799e 100644 --- a/config/locales/client.bn.yml +++ b/config/locales/client.bn.yml @@ -486,8 +486,8 @@ bn: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.bo.yml b/config/locales/client.bo.yml index cd8c7924..8fc80249 100644 --- a/config/locales/client.bo.yml +++ b/config/locales/client.bo.yml @@ -483,8 +483,8 @@ bo: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.bs.yml b/config/locales/client.bs.yml index e188aece..4cd291b9 100644 --- a/config/locales/client.bs.yml +++ b/config/locales/client.bs.yml @@ -489,8 +489,8 @@ bs: few: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml index 7d80c17b..54ee95ae 100644 --- a/config/locales/client.ca.yml +++ b/config/locales/client.ca.yml @@ -486,8 +486,8 @@ ca: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 018e4236..45e0980e 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -492,8 +492,8 @@ cs: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.cy.yml b/config/locales/client.cy.yml index 4510470b..6049360e 100644 --- a/config/locales/client.cy.yml +++ b/config/locales/client.cy.yml @@ -498,8 +498,8 @@ cy: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml index 4b254517..281bd56d 100644 --- a/config/locales/client.da.yml +++ b/config/locales/client.da.yml @@ -486,8 +486,8 @@ da: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index a69bf37f..7921b8f8 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -486,8 +486,8 @@ de: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml index 5a49f0c8..2bf699d1 100644 --- a/config/locales/client.el.yml +++ b/config/locales/client.el.yml @@ -486,8 +486,8 @@ el: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 6fe9e5ee..897049ed 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -565,8 +565,8 @@ en: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.eo.yml b/config/locales/client.eo.yml index ee59949b..685c9f98 100644 --- a/config/locales/client.eo.yml +++ b/config/locales/client.eo.yml @@ -486,8 +486,8 @@ eo: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml index b9d08e0b..2e7a106a 100644 --- a/config/locales/client.es.yml +++ b/config/locales/client.es.yml @@ -486,8 +486,8 @@ es: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml index 490c3afc..173700e3 100644 --- a/config/locales/client.et.yml +++ b/config/locales/client.et.yml @@ -486,8 +486,8 @@ et: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.eu.yml b/config/locales/client.eu.yml index a12a029c..f1099dfa 100644 --- a/config/locales/client.eu.yml +++ b/config/locales/client.eu.yml @@ -486,8 +486,8 @@ eu: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.fa.yml b/config/locales/client.fa.yml index 68f2a0c8..29db8d16 100644 --- a/config/locales/client.fa.yml +++ b/config/locales/client.fa.yml @@ -486,8 +486,8 @@ fa: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml index 95a82ae9..ebbd70c1 100644 --- a/config/locales/client.fi.yml +++ b/config/locales/client.fi.yml @@ -486,8 +486,8 @@ fi: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml index 0767376d..7617e006 100644 --- a/config/locales/client.gl.yml +++ b/config/locales/client.gl.yml @@ -486,8 +486,8 @@ gl: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml index 4fc73f73..1e7b5efb 100644 --- a/config/locales/client.he.yml +++ b/config/locales/client.he.yml @@ -492,8 +492,8 @@ he: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.hi.yml b/config/locales/client.hi.yml index 0bdd2a8f..b2e1bfc2 100644 --- a/config/locales/client.hi.yml +++ b/config/locales/client.hi.yml @@ -486,8 +486,8 @@ hi: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.hr.yml b/config/locales/client.hr.yml index a9b9e8ff..5cc2ae85 100644 --- a/config/locales/client.hr.yml +++ b/config/locales/client.hr.yml @@ -489,8 +489,8 @@ hr: few: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml index a70682f0..c8d0df17 100644 --- a/config/locales/client.hu.yml +++ b/config/locales/client.hu.yml @@ -486,8 +486,8 @@ hu: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.hy.yml b/config/locales/client.hy.yml index 028b9f42..59a3cf36 100644 --- a/config/locales/client.hy.yml +++ b/config/locales/client.hy.yml @@ -486,8 +486,8 @@ hy: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml index 5a24a279..29a0df74 100644 --- a/config/locales/client.id.yml +++ b/config/locales/client.id.yml @@ -483,8 +483,8 @@ id: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.is.yml b/config/locales/client.is.yml index 383c3f0a..444abda0 100644 --- a/config/locales/client.is.yml +++ b/config/locales/client.is.yml @@ -486,8 +486,8 @@ is: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index 6a4afdec..5496dc3a 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -486,8 +486,8 @@ it: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ja.yml b/config/locales/client.ja.yml index 3b18dfe7..2b0a2744 100644 --- a/config/locales/client.ja.yml +++ b/config/locales/client.ja.yml @@ -483,8 +483,8 @@ ja: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ka.yml b/config/locales/client.ka.yml index ee95af36..a8afb778 100644 --- a/config/locales/client.ka.yml +++ b/config/locales/client.ka.yml @@ -486,8 +486,8 @@ ka: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.kk.yml b/config/locales/client.kk.yml index d268cbfd..551b779b 100644 --- a/config/locales/client.kk.yml +++ b/config/locales/client.kk.yml @@ -486,8 +486,8 @@ kk: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.km.yml b/config/locales/client.km.yml index d64b2d1c..d6aed018 100644 --- a/config/locales/client.km.yml +++ b/config/locales/client.km.yml @@ -483,8 +483,8 @@ km: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.kn.yml b/config/locales/client.kn.yml index 8950065b..842155bf 100644 --- a/config/locales/client.kn.yml +++ b/config/locales/client.kn.yml @@ -486,8 +486,8 @@ kn: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml index 4d3edcc8..3e036dc9 100644 --- a/config/locales/client.ko.yml +++ b/config/locales/client.ko.yml @@ -483,8 +483,8 @@ ko: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ku.yml b/config/locales/client.ku.yml index 69694c34..547bce19 100644 --- a/config/locales/client.ku.yml +++ b/config/locales/client.ku.yml @@ -486,8 +486,8 @@ ku: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.lo.yml b/config/locales/client.lo.yml index 56206ebb..f347a26b 100644 --- a/config/locales/client.lo.yml +++ b/config/locales/client.lo.yml @@ -483,8 +483,8 @@ lo: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml index 01de5326..ad5153e5 100644 --- a/config/locales/client.lt.yml +++ b/config/locales/client.lt.yml @@ -492,8 +492,8 @@ lt: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml index eaaf4e35..c6742f92 100644 --- a/config/locales/client.lv.yml +++ b/config/locales/client.lv.yml @@ -489,8 +489,8 @@ lv: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.mk.yml b/config/locales/client.mk.yml index aacd434f..152505a5 100644 --- a/config/locales/client.mk.yml +++ b/config/locales/client.mk.yml @@ -486,8 +486,8 @@ mk: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ml.yml b/config/locales/client.ml.yml index f417dd95..c929f592 100644 --- a/config/locales/client.ml.yml +++ b/config/locales/client.ml.yml @@ -486,8 +486,8 @@ ml: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.mn.yml b/config/locales/client.mn.yml index f6493025..736ebbab 100644 --- a/config/locales/client.mn.yml +++ b/config/locales/client.mn.yml @@ -486,8 +486,8 @@ mn: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ms.yml b/config/locales/client.ms.yml index 4f8c9cad..25da928f 100644 --- a/config/locales/client.ms.yml +++ b/config/locales/client.ms.yml @@ -483,8 +483,8 @@ ms: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ne.yml b/config/locales/client.ne.yml index 33f52ead..004ca84f 100644 --- a/config/locales/client.ne.yml +++ b/config/locales/client.ne.yml @@ -486,8 +486,8 @@ ne: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 9e8cfabb..1a4dce06 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -486,8 +486,8 @@ nl: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.no.yml b/config/locales/client.no.yml index 4e0d5cab..4aec9787 100644 --- a/config/locales/client.no.yml +++ b/config/locales/client.no.yml @@ -486,8 +486,8 @@ one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.om.yml b/config/locales/client.om.yml index 3c7a7e46..714725e0 100644 --- a/config/locales/client.om.yml +++ b/config/locales/client.om.yml @@ -486,8 +486,8 @@ om: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.pa.yml b/config/locales/client.pa.yml index 00236d10..e04954ef 100644 --- a/config/locales/client.pa.yml +++ b/config/locales/client.pa.yml @@ -486,8 +486,8 @@ pa: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.pl.yml b/config/locales/client.pl.yml index b665aaf5..4574439c 100644 --- a/config/locales/client.pl.yml +++ b/config/locales/client.pl.yml @@ -492,8 +492,8 @@ pl: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml index eba3a5ac..48ea6400 100644 --- a/config/locales/client.pt.yml +++ b/config/locales/client.pt.yml @@ -486,8 +486,8 @@ pt: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml index a5136d01..4a910409 100644 --- a/config/locales/client.ro.yml +++ b/config/locales/client.ro.yml @@ -489,8 +489,8 @@ ro: few: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index 85e8ff50..28042ba4 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -492,8 +492,8 @@ ru: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.sd.yml b/config/locales/client.sd.yml index add951c7..e41466d1 100644 --- a/config/locales/client.sd.yml +++ b/config/locales/client.sd.yml @@ -486,8 +486,8 @@ sd: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml index c762d3bb..e933063f 100644 --- a/config/locales/client.sk.yml +++ b/config/locales/client.sk.yml @@ -492,8 +492,8 @@ sk: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml index 80912cb1..cabe86b2 100644 --- a/config/locales/client.sl.yml +++ b/config/locales/client.sl.yml @@ -492,8 +492,8 @@ sl: few: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml index 089e4fce..9beb19df 100644 --- a/config/locales/client.sq.yml +++ b/config/locales/client.sq.yml @@ -486,8 +486,8 @@ sq: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml index 5db98108..ea16bad9 100644 --- a/config/locales/client.sr.yml +++ b/config/locales/client.sr.yml @@ -489,8 +489,8 @@ sr-Cyrl: few: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index e19dbb9c..253df9eb 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -486,8 +486,8 @@ sv: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml index 5ebb0624..fcd71a16 100644 --- a/config/locales/client.sw.yml +++ b/config/locales/client.sw.yml @@ -486,8 +486,8 @@ sw: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ta.yml b/config/locales/client.ta.yml index 4cfc9a90..4549f503 100644 --- a/config/locales/client.ta.yml +++ b/config/locales/client.ta.yml @@ -486,8 +486,8 @@ ta: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml index a3b7c377..6c57db33 100644 --- a/config/locales/client.te.yml +++ b/config/locales/client.te.yml @@ -486,8 +486,8 @@ te: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml index 68b139ee..77363ef5 100644 --- a/config/locales/client.th.yml +++ b/config/locales/client.th.yml @@ -483,8 +483,8 @@ th: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.tl.yml b/config/locales/client.tl.yml index bc3a2557..d4ffd940 100644 --- a/config/locales/client.tl.yml +++ b/config/locales/client.tl.yml @@ -486,8 +486,8 @@ tl: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.tr.yml b/config/locales/client.tr.yml index b33e9e0b..8f598a24 100644 --- a/config/locales/client.tr.yml +++ b/config/locales/client.tr.yml @@ -486,8 +486,8 @@ tr: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.tt.yml b/config/locales/client.tt.yml index a48bac61..efb89a93 100644 --- a/config/locales/client.tt.yml +++ b/config/locales/client.tt.yml @@ -483,8 +483,8 @@ tt: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml index 7c1bf2b1..bf7b3b1f 100644 --- a/config/locales/client.uk.yml +++ b/config/locales/client.uk.yml @@ -492,8 +492,8 @@ uk: many: "%{count} Characters" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml index 2469e86c..0b2a10ec 100644 --- a/config/locales/client.ur.yml +++ b/config/locales/client.ur.yml @@ -486,8 +486,8 @@ ur: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml index 5dd7ce98..c1f76e2f 100644 --- a/config/locales/client.vi.yml +++ b/config/locales/client.vi.yml @@ -483,8 +483,8 @@ vi: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.yi.yml b/config/locales/client.yi.yml index 94cd0b5d..5d923728 100644 --- a/config/locales/client.yi.yml +++ b/config/locales/client.yi.yml @@ -486,8 +486,8 @@ yi: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.zh.yml b/config/locales/client.zh.yml index a8950f7b..cb09de6b 100644 --- a/config/locales/client.zh.yml +++ b/config/locales/client.zh.yml @@ -483,8 +483,8 @@ zh-TW: x_characters: other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" diff --git a/config/locales/client.zu.yml b/config/locales/client.zu.yml index b147ac1f..4223f5bd 100644 --- a/config/locales/client.zu.yml +++ b/config/locales/client.zu.yml @@ -486,8 +486,8 @@ zu: one: "%{count} Character" other: "%{count} Characters" wizard_composer: - show_preview: "Preview Post" - hide_preview: "Edit Post" + show_preview: "Preview" + hide_preview: "Edit" quote_post_title: "Quote whole post" bold_label: "B" bold_title: "Strong" From 969fff1a3c9418b8d61c2a958047411e96f05957 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 13 Jun 2022 09:25:34 +0200 Subject: [PATCH 247/556] COMPATIBILITY: Support tag-group-chooser interface && add base CSS to wizard app --- .../templates/components/wizard-custom-field.hbs | 8 +++++--- assets/stylesheets/wizard/wizard_custom.scss | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index f51b9fbb..6866f3ef 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -19,7 +19,7 @@
- +
{{i18n "admin.wizard.field.required_label"}} {{input type="checkbox" checked=field.required}} @@ -54,7 +54,7 @@
- +
{{combo-box value=field.type @@ -216,7 +216,9 @@
{{tag-group-chooser + id=(concat field.id "-tag-groups") tagGroups=field.tag_groups + onChange=(action (mut field.tag_groups)) }}
@@ -257,7 +259,7 @@
- +
{{combo-box value=field.property diff --git a/assets/stylesheets/wizard/wizard_custom.scss b/assets/stylesheets/wizard/wizard_custom.scss index 659c97f5..6c91385f 100644 --- a/assets/stylesheets/wizard/wizard_custom.scss +++ b/assets/stylesheets/wizard/wizard_custom.scss @@ -1,5 +1,6 @@ @import "common/foundation/colors"; @import "common/foundation/variables"; +@import "common/foundation/base"; @import "common/base/code_highlighting"; @import "common/base/modal"; @import "common/base/onebox"; From a19a1fa3b1627b7847ff7fa07abdf710003c80d4 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 12:33:34 +0100 Subject: [PATCH 248/556] Refactor wizard client and add tests --- app/controllers/custom_wizard/wizard.rb | 115 +++-- app/views/layouts/qunit.html.erb | 28 ++ app/views/layouts/wizard.html.erb | 6 +- assets/javascripts/wizard-custom-globals.js | 6 - assets/javascripts/wizard-custom-start.js | 2 +- assets/javascripts/wizard-custom.js | 14 +- assets/javascripts/wizard-qunit.js | 16 + assets/javascripts/wizard/application.js.es6 | 19 + .../components/custom-user-selector.js.es6 | 9 +- .../wizard/components/field-validators.js.es6 | 3 + .../similar-topics-validator.js.es6 | 1 + .../wizard/components/validator.js.es6 | 3 +- .../components/wizard-composer-editor.js.es6 | 1 + .../wizard-composer-hyperlink.js.es6 | 1 + .../components/wizard-date-input.js.es6 | 1 + .../components/wizard-date-time-input.js.es6 | 2 + .../components/wizard-field-category.js.es6 | 2 + .../components/wizard-field-checkbox.js.es6 | 5 + .../wizard-field-composer-preview.js.es6 | 2 + .../components/wizard-field-composer.js.es6 | 2 + .../components/wizard-field-date-time.js.es6 | 2 + .../components/wizard-field-date.js.es6 | 2 + .../components/wizard-field-dropdown.js.es6 | 15 + .../components/wizard-field-group.js.es6 | 5 + .../components/wizard-field-number.js.es6 | 5 + .../wizard/components/wizard-field-tag.js.es6 | 5 + .../components/wizard-field-text.js.es6 | 9 + .../components/wizard-field-textarea.js.es6 | 9 + .../components/wizard-field-time.js.es6 | 2 + .../components/wizard-field-upload.js.es6 | 4 + .../wizard/components/wizard-field-url.js.es6 | 5 + .../wizard-field-user-selector.js.es6 | 5 + .../wizard/components/wizard-field.js.es6 | 34 ++ .../components/wizard-group-selector.js.es6 | 1 + .../wizard/components/wizard-no-access.js.es6 | 23 +- .../components/wizard-similar-topics.js.es6 | 1 + .../wizard/components/wizard-step-form.js.es6 | 9 + .../wizard/components/wizard-step.js.es6 | 247 +++++++++ .../components/wizard-text-field.js.es6 | 6 +- .../components/wizard-time-input.js.es6 | 4 +- .../wizard/controllers/custom.js.es6 | 3 - .../{custom-step.js.es6 => step.js.es6} | 11 +- .../wizard/controllers/wizard-index.js.es6 | 24 + .../wizard/controllers/wizard.js.es6 | 5 + .../javascripts/wizard/custom-wizard.js.es6 | 39 -- .../initializers/custom-wizard-step.js.es6 | 199 -------- .../wizard/initializers/custom-wizard.js.es6 | 126 ----- .../lib/initialize/create-contexts.js.es6 | 12 + .../lib/initialize/inject-objects.js.es6 | 49 ++ .../initialize/patch-components.js.es6} | 116 ++--- .../lib/initialize/register-files.js.es6 | 26 + .../wizard/lib/initialize/wizard.js.es6 | 49 ++ .../wizard/lib/utilities-lite.js.es6 | 71 --- assets/javascripts/wizard/models/field.js.es6 | 79 +++ assets/javascripts/wizard/models/site.js.es6 | 3 +- assets/javascripts/wizard/models/step.js.es6 | 114 +++++ .../models/{custom.js.es6 => wizard.js.es6} | 10 +- assets/javascripts/wizard/router.js.es6 | 17 + .../wizard/routes/application.js.es6 | 3 + .../wizard/routes/custom-steps.js.es6 | 5 - assets/javascripts/wizard/routes/index.js.es6 | 9 + .../{custom-step.js.es6 => step.js.es6} | 11 +- assets/javascripts/wizard/routes/steps.js.es6 | 7 + ...ustom-index.js.es6 => wizard-index.js.es6} | 26 +- .../routes/{custom.js.es6 => wizard.js.es6} | 59 ++- .../components/wizard-field-dropdown.hbs | 1 + .../wizard/templates/custom.index.hbs | 15 - assets/javascripts/wizard/templates/index.hbs | 1 + .../templates/{custom.step.hbs => step.hbs} | 0 .../wizard/templates/wizard-index.hbs | 3 + .../templates/{custom.hbs => wizard.hbs} | 4 - .../wizard/tests/acceptance/field-test.js.es6 | 135 +++++ .../wizard/tests/acceptance/step-test.js.es6 | 47 ++ .../tests/acceptance/wizard-test.js.es6 | 72 +++ .../javascripts/wizard/tests/bootstrap.js.es6 | 17 + .../wizard/tests/fixtures/categories.js.es6 | 209 ++++++++ .../wizard/tests/fixtures/groups.js.es6 | 313 ++++++++++++ .../tests/fixtures/site-settings.js.es6 | 283 +++++++++++ .../wizard/tests/fixtures/tags.js.es6 | 22 + .../wizard/tests/fixtures/update.js.es6 | 5 + .../wizard/tests/fixtures/user.js.es6 | 34 ++ .../wizard/tests/fixtures/users.js.es6 | 14 + .../wizard/tests/fixtures/wizard.js.es6 | 469 ++++++++++++++++++ .../wizard/tests/helpers/acceptance.js.es6 | 53 ++ .../wizard/tests/helpers/start-app.js.es6 | 19 + .../wizard/tests/helpers/step.js.es6 | 20 + .../wizard/tests/helpers/test.js.es6 | 7 + .../wizard/tests/helpers/wizard.js.es6 | 52 ++ .../javascripts/wizard/tests/pretender.js.es6 | 53 ++ .../stylesheets/wizard/custom/composer.scss | 2 +- config/routes.rb | 1 + .../extensions/extra_locales_controller.rb | 1 + lib/custom_wizard/validators/update.rb | 2 +- plugin.rb | 3 +- 94 files changed, 2887 insertions(+), 674 deletions(-) create mode 100644 app/views/layouts/qunit.html.erb delete mode 100644 assets/javascripts/wizard-custom-globals.js create mode 100644 assets/javascripts/wizard-qunit.js create mode 100644 assets/javascripts/wizard/application.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-group.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-number.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-tag.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-text.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-textarea.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-url.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-field.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-step-form.js.es6 create mode 100644 assets/javascripts/wizard/components/wizard-step.js.es6 delete mode 100644 assets/javascripts/wizard/controllers/custom.js.es6 rename assets/javascripts/wizard/controllers/{custom-step.js.es6 => step.js.es6} (76%) create mode 100644 assets/javascripts/wizard/controllers/wizard-index.js.es6 create mode 100644 assets/javascripts/wizard/controllers/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/custom-wizard.js.es6 delete mode 100644 assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 delete mode 100644 assets/javascripts/wizard/initializers/custom-wizard.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 rename assets/javascripts/wizard/{initializers/custom-wizard-field.js.es6 => lib/initialize/patch-components.js.es6} (60%) create mode 100644 assets/javascripts/wizard/lib/initialize/register-files.js.es6 create mode 100644 assets/javascripts/wizard/lib/initialize/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/lib/utilities-lite.js.es6 create mode 100644 assets/javascripts/wizard/models/field.js.es6 create mode 100644 assets/javascripts/wizard/models/step.js.es6 rename assets/javascripts/wizard/models/{custom.js.es6 => wizard.js.es6} (94%) create mode 100644 assets/javascripts/wizard/router.js.es6 create mode 100644 assets/javascripts/wizard/routes/application.js.es6 delete mode 100644 assets/javascripts/wizard/routes/custom-steps.js.es6 create mode 100644 assets/javascripts/wizard/routes/index.js.es6 rename assets/javascripts/wizard/routes/{custom-step.js.es6 => step.js.es6} (78%) create mode 100644 assets/javascripts/wizard/routes/steps.js.es6 rename assets/javascripts/wizard/routes/{custom-index.js.es6 => wizard-index.js.es6} (50%) rename assets/javascripts/wizard/routes/{custom.js.es6 => wizard.js.es6} (59%) delete mode 100644 assets/javascripts/wizard/templates/custom.index.hbs create mode 100644 assets/javascripts/wizard/templates/index.hbs rename assets/javascripts/wizard/templates/{custom.step.hbs => step.hbs} (100%) create mode 100644 assets/javascripts/wizard/templates/wizard-index.hbs rename assets/javascripts/wizard/templates/{custom.hbs => wizard.hbs} (86%) create mode 100644 assets/javascripts/wizard/tests/acceptance/field-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/acceptance/step-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 create mode 100644 assets/javascripts/wizard/tests/bootstrap.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/categories.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/groups.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/tags.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/update.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/user.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/users.js.es6 create mode 100644 assets/javascripts/wizard/tests/fixtures/wizard.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/acceptance.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/start-app.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/step.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/test.js.es6 create mode 100644 assets/javascripts/wizard/tests/helpers/wizard.js.es6 create mode 100644 assets/javascripts/wizard/tests/pretender.js.es6 diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index 12e6bdff..43db5df8 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -1,54 +1,39 @@ # frozen_string_literal: true -class CustomWizard::WizardController < ::ApplicationController - include ApplicationHelper - prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views')) - layout 'wizard' +class CustomWizard::WizardController < ::ActionController::Base + helper ApplicationHelper + include CurrentUser + include CanonicalURL::ControllerExtensions + include GlobalPath + + prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) + layout :set_wizard_layout + + before_action :preload_wizard_json before_action :ensure_plugin_enabled before_action :ensure_logged_in, only: [:skip] + helper_method :wizard_page_title helper_method :wizard_theme_id helper_method :wizard_theme_lookup helper_method :wizard_theme_translations_lookup - def wizard - @builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - @wizard ||= @builder.build - @wizard - end - - def wizard_page_title - wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') - end - - def wizard_theme_id - wizard ? wizard.theme_id : nil - end - - def wizard_theme_lookup(name) - Theme.lookup_field(wizard_theme_id, mobile_view? ? :mobile : :desktop, name) - end - - def wizard_theme_translations_lookup - Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + def set_wizard_layout + action_name === 'qunit' ? 'qunit' : 'wizard' end def index respond_to do |format| format.json do - builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - - if builder.wizard.present? - builder_opts = {} - builder_opts[:reset] = params[:reset] - built_wizard = builder.build(builder_opts, params) - - render_serialized(built_wizard, ::CustomWizard::WizardSerializer, root: false) + if wizard.present? + render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200 else render json: { error: I18n.t('wizard.none') } end end - format.html {} + format.html do + render "default/empty" + end end end @@ -62,8 +47,10 @@ class CustomWizard::WizardController < ::ApplicationController result = success_json if current_user && wizard.can_access? - if redirect_to = wizard.current_submission&.redirect_to - result.merge!(redirect_to: redirect_to) + submission = wizard.current_submission + + if submission.present? && submission.redirect_to + result.merge!(redirect_to: submission.redirect_to) end wizard.cleanup_on_skip! @@ -72,6 +59,64 @@ class CustomWizard::WizardController < ::ApplicationController render json: result end + def qunit + raise Discourse::InvalidAccess.new if Rails.env.production? + + respond_to do |format| + format.html do + render "default/empty" + end + end + end + + protected + + def ensure_logged_in + raise Discourse::NotLoggedIn.new unless current_user.present? + end + + def guardian + @guardian ||= Guardian.new(current_user, request) + end + + def wizard + @wizard ||= begin + builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) + return nil unless builder.present? + opts = {} + opts[:reset] = params[:reset] + builder.build(opts, params) + end + end + + def wizard_page_title + wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') + end + + def wizard_theme_id + wizard ? wizard.theme_id : nil + end + + def wizard_theme_lookup(name) + Theme.lookup_field(wizard_theme_id, view_context.mobile_view? ? :mobile : :desktop, name) + end + + def wizard_theme_translations_lookup + Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + end + + def preload_wizard_json + return if request.xhr? || request.format.json? + return if request.method != "GET" + + store_preloaded("siteSettings", SiteSetting.client_settings_json) + end + + def store_preloaded(key, json) + @preloaded ||= {} + @preloaded[key] = json.gsub(" + + + Custom Wizard QUnit Test Runner + <%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %> + <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> + <%= discourse_stylesheet_link_tag :wizard_custom %> + <%= preload_script "locales/en" %> + <%= preload_script "ember_jquery" %> + <%= preload_script "wizard-vendor" %> + <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> + <%= preload_script "wizard-plugin" %> + <%= preload_script "pretty-text-bundle" %> + <%= preload_script "wizard-qunit" %> + <%= csrf_meta_tags %> + + + + <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> + + + + +
+
+ + diff --git a/app/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb index 16d119b7..f909ae84 100644 --- a/app/views/layouts/wizard.html.erb +++ b/app/views/layouts/wizard.html.erb @@ -11,10 +11,8 @@ <%= preload_script "locales/#{I18n.locale}" %> <%= preload_script "ember_jquery" %> <%= preload_script "wizard-vendor" %> - <%= preload_script "wizard-application" %> - <%= preload_script "wizard-custom-globals" %> - <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-plugin" %> <%= preload_script "pretty-text-bundle" %> @@ -58,5 +56,7 @@ <%= raw SvgSprite.bundle %>
+ + diff --git a/assets/javascripts/wizard-custom-globals.js b/assets/javascripts/wizard-custom-globals.js deleted file mode 100644 index 83923cba..00000000 --- a/assets/javascripts/wizard-custom-globals.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint no-undef: 0*/ -window.Discourse = {}; -window.Wizard = {}; -Wizard.SiteSettings = {}; -Discourse.__widget_helpers = {}; -Discourse.SiteSettings = Wizard.SiteSettings; diff --git a/assets/javascripts/wizard-custom-start.js b/assets/javascripts/wizard-custom-start.js index 3ffa9c5a..b92d2871 100644 --- a/assets/javascripts/wizard-custom-start.js +++ b/assets/javascripts/wizard-custom-start.js @@ -1,4 +1,4 @@ (function () { - let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/custom-wizard").default.create(); + let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/application").default.create(); wizard.start(); })(); diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 99a96a00..667d23fd 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,10 +1,13 @@ +//= require_tree_discourse truth-helpers/addon +//= require_tree_discourse discourse-common/addon +//= require_tree_discourse select-kit/addon +//= require_tree_discourse wizard/lib +//= require_tree_discourse wizard/mixins //= require_tree_discourse discourse/app/lib //= require_tree_discourse discourse/app/mixins //= require discourse/app/adapters/rest - //= require message-bus - //= require_tree_discourse discourse/app/models //= require discourse/app/helpers/category-link @@ -12,9 +15,6 @@ //= require discourse/app/helpers/format-username //= require discourse/app/helpers/share-url //= require discourse/app/helpers/decorate-username-selector -//= require discourse-common/addon/helpers/component-for-collection -//= require discourse-common/addon/helpers/component-for-row -//= require discourse-common/addon/lib/raw-templates //= require discourse/app/helpers/discourse-tag //= require discourse/app/services/app-events @@ -70,11 +70,11 @@ //= require bootbox.js //= require discourse-shims -//= require ./wizard/custom-wizard +//= require ./wizard/application +//= require ./wizard/router //= require_tree ./wizard/components //= require_tree ./wizard/controllers //= require_tree ./wizard/helpers -//= require_tree ./wizard/initializers //= require_tree ./wizard/lib //= require_tree ./wizard/models //= require_tree ./wizard/routes diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js new file mode 100644 index 00000000..2866cb5d --- /dev/null +++ b/assets/javascripts/wizard-qunit.js @@ -0,0 +1,16 @@ +//= require route-recognizer +//= require fake_xml_http_request +//= require pretender +//= require qunit +//= require ember-qunit +//= require test-shims +//= require jquery.debug +//= require ember.debug +//= require ember-template-compiler + +//= require_tree ./wizard/tests/fixtures +//= require ./wizard/tests/pretender +//= require_tree ./wizard/tests/helpers +//= require_tree ./wizard/tests/acceptance + +//= require ./wizard/tests/bootstrap diff --git a/assets/javascripts/wizard/application.js.es6 b/assets/javascripts/wizard/application.js.es6 new file mode 100644 index 00000000..012949b3 --- /dev/null +++ b/assets/javascripts/wizard/application.js.es6 @@ -0,0 +1,19 @@ +import { buildResolver } from "discourse-common/resolver"; +import Application from "@ember/application"; +import WizardInitializer from "./lib/initialize/wizard"; +import { isTesting } from "discourse-common/config/environment"; + +export default Application.extend({ + rootElement: "#custom-wizard-main", + Resolver: buildResolver("discourse/plugins/discourse-custom-wizard/wizard"), + + customEvents: { + paste: "paste", + }, + + start() { + if (!isTesting()) { + this.initializer(WizardInitializer); + } + }, +}); diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index b2f08ede..1698a008 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -1,6 +1,6 @@ import { default as computed, - observes, + observes } from "discourse-common/utils/decorators"; import { renderAvatar } from "discourse/helpers/user-avatar"; import userSearch from "../lib/user-search"; @@ -55,7 +55,6 @@ export default Ember.TextField.extend({ let self = this, selected = [], groups = [], - currentUser = this.currentUser, includeMentionableGroups = this.get("includeMentionableGroups") === "true", includeMessageableGroups = @@ -66,13 +65,8 @@ export default Ember.TextField.extend({ function excludedUsernames() { // hack works around some issues with allowAny eventing const usernames = self.get("single") ? [] : selected; - - if (currentUser && self.get("excludeCurrentUser")) { - return usernames.concat([currentUser.get("username")]); - } return usernames; } - $(this.element) .val(this.get("usernames")) .autocomplete({ @@ -84,7 +78,6 @@ export default Ember.TextField.extend({ dataSource(term) { const termRegex = /[^a-zA-Z0-9_\-\.@\+]/; - let results = userSearch({ term: term.replace(termRegex, ""), topicId: self.get("topicId"), diff --git a/assets/javascripts/wizard/components/field-validators.js.es6 b/assets/javascripts/wizard/components/field-validators.js.es6 index a315020d..15cfc181 100644 --- a/assets/javascripts/wizard/components/field-validators.js.es6 +++ b/assets/javascripts/wizard/components/field-validators.js.es6 @@ -1,5 +1,8 @@ import Component from "@ember/component"; + export default Component.extend({ + layoutName: 'wizard/templates/components/field-validators', + actions: { perform() { this.appEvents.trigger("custom-wizard:validate"); diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 index 98ea9270..e5133d4f 100644 --- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 +++ b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 @@ -10,6 +10,7 @@ import { dasherize } from "@ember/string"; export default WizardFieldValidator.extend({ classNames: ["similar-topics-validator"], + layoutName: 'wizard/templates/components/similar-topics-validator', similarTopics: null, hasInput: notEmpty("field.value"), hasSimilarTopics: notEmpty("similarTopics"), diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index 6227cc64..ab442d97 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -6,11 +6,12 @@ import { getToken } from "wizard/lib/ajax"; export default Component.extend({ classNames: ["validator"], classNameBindings: ["isValid", "isInvalid"], + layoutName: 'wizard/templates/components/validator', validMessageKey: null, invalidMessageKey: null, isValid: null, isInvalid: equal("isValid", false), - layoutName: "components/validator", // useful for sharing the template with extending components + layoutName: "wizard/templates/components/validator", init() { this._super(...arguments); diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index faada1f4..e4ce3ec0 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -13,6 +13,7 @@ import { uploadIcon } from "discourse/lib/uploads"; import { dasherize } from "@ember/string"; export default ComposerEditor.extend({ + layoutName: 'wizard/templates/components/wizard-composer-editor', classNameBindings: ["fieldClass"], allowUpload: true, showLink: false, diff --git a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 index a56b7aff..c700d3ce 100644 --- a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 @@ -2,6 +2,7 @@ import Component from "@ember/component"; export default Component.extend({ classNames: ["wizard-composer-hyperlink"], + layoutName: 'wizard/templates/components/wizard-composer-hyperlink', actions: { addLink() { diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index 9c8e4bff..375b7195 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -3,6 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DateInput.extend({ useNativePicker: false, + layoutName: 'wizard/templates/components/wizard-date-input', @discourseComputed() placeholder() { diff --git a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 index 44b675b0..0fe1e68a 100644 --- a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 @@ -2,6 +2,8 @@ import DateTimeInput from "discourse/components/date-time-input"; import discourseComputed from "discourse-common/utils/decorators"; export default DateTimeInput.extend({ + layoutName: 'wizard/templates/components/wizard-date-time-input', + @discourseComputed("timeFirst", "tabindex") timeTabindex(timeFirst, tabindex) { return timeFirst ? tabindex : tabindex + 1; diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index a7452214..dc20538e 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -2,6 +2,8 @@ import { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; export default Ember.Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-category', + didInsertElement() { const property = this.field.property || "id"; const value = this.field.value; diff --git a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 new file mode 100644 index 00000000..f9653cd2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-checkbox' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 index b49233f2..0aee0d13 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 @@ -7,6 +7,8 @@ import { ajax } from "discourse/lib/ajax"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-composer-preview', + @on("init") updatePreview() { if (this.isDestroyed) { diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 8b9ecb82..0ef5fe20 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -5,6 +5,8 @@ import { import EmberObject from "@ember/object"; export default Ember.Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-composer', + showPreview: false, classNameBindings: [ ":wizard-field-composer", diff --git a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 index 2d918636..a916f18e 100644 --- a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-date-time', + @observes("dateTime") setValue() { this.set("field.value", this.dateTime.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-date.js.es6 b/assets/javascripts/wizard/components/wizard-field-date.js.es6 index d5d0a830..a06d582a 100644 --- a/assets/javascripts/wizard/components/wizard-field-date.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-date', + @observes("date") setValue() { this.set("field.value", this.date.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 new file mode 100644 index 00000000..4b8b7e63 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 @@ -0,0 +1,15 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-dropdown', + + keyPress(e) { + e.stopPropagation(); + }, + + actions: { + onChangeValue(value) { + this.set("field.value", value); + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-group.js.es6 b/assets/javascripts/wizard/components/wizard-field-group.js.es6 new file mode 100644 index 00000000..93538071 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-group.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-group' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-number.js.es6 b/assets/javascripts/wizard/components/wizard-field-number.js.es6 new file mode 100644 index 00000000..e7c4d77f --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-number.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-number' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 new file mode 100644 index 00000000..45343522 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-tag' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-text.js.es6 b/assets/javascripts/wizard/components/wizard-field-text.js.es6 new file mode 100644 index 00000000..f9d5b056 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-text.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-text', + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 new file mode 100644 index 00000000..54865d3c --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-textarea', + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-time.js.es6 index 82f9c68b..bf954ec4 100644 --- a/assets/javascripts/wizard/components/wizard-field-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-time', + @observes("time") setValue() { this.set("field.value", this.time.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 index 876cdf0e..edadb4f0 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 @@ -3,12 +3,16 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend(UppyUploadMixin, { + layoutName: 'wizard/templates/components/wizard-field-upload', classNames: ["wizard-field-upload"], classNameBindings: ["isImage"], uploading: false, type: computed(function () { return `wizard_${this.field.id}`; }), + id: computed(function () { + return `wizard_field_upload_${this.field.id}`; + }), isImage: computed("field.value.extension", function () { return ( this.field.value && diff --git a/assets/javascripts/wizard/components/wizard-field-url.js.es6 b/assets/javascripts/wizard/components/wizard-field-url.js.es6 new file mode 100644 index 00000000..411b5fe9 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-url.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-url' +}); diff --git a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 new file mode 100644 index 00000000..c2a32f44 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field-user-selector' +}); diff --git a/assets/javascripts/wizard/components/wizard-field.js.es6 b/assets/javascripts/wizard/components/wizard-field.js.es6 new file mode 100644 index 00000000..372ee182 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -0,0 +1,34 @@ +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; +import discourseComputed from "discourse-common/utils/decorators"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-field', + classNameBindings: [":wizard-field", "typeClasses", "field.invalid", "field.id"], + + @discourseComputed("field.type", "field.id") + typeClasses: (type, id) => + `${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`, + + @discourseComputed("field.id") + fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`, + + @discourseComputed("field.type", "field.id") + inputComponentName(type, id) { + if (["text_only"].includes(type)) { + return false; + } + return dasherize(type === "component" ? id : `wizard-field-${type}`); + }, + + @discourseComputed("field.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed("field.type") + textType(fieldType) { + return ["text", "textarea"].includes(fieldType); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 index cb613107..b7523f9a 100644 --- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 @@ -3,6 +3,7 @@ import { computed } from "@ember/object"; import { makeArray } from "discourse-common/lib/helpers"; export default ComboBox.extend({ + layoutName: 'wizard/templates/components/wizard-group-selector', content: computed("groups.[]", "field.content.[]", function () { const whitelist = makeArray(this.field.content); return this.groups diff --git a/assets/javascripts/wizard/components/wizard-no-access.js.es6 b/assets/javascripts/wizard/components/wizard-no-access.js.es6 index 57fe9111..78a23aa8 100644 --- a/assets/javascripts/wizard/components/wizard-no-access.js.es6 +++ b/assets/javascripts/wizard/components/wizard-no-access.js.es6 @@ -1,10 +1,21 @@ -import CustomWizard from "../models/custom"; +import CustomWizard from "../models/wizard"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; -export default Ember.Component.extend({ - siteName: function () { - /*eslint no-undef:0*/ - return Wizard.SiteSettings.title; - }.property(), +export default Component.extend({ + classNameBindings: [':wizard-no-access', 'reasonClass'], + layoutName: 'wizard/templates/components/wizard-no-access', + + @discourseComputed('reason') + reasonClass(reason) { + return dasherize(reason); + }, + + @discourseComputed + siteName() { + return (this.siteSettings.title || ''); + }, actions: { skip() { diff --git a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 index 687cfa86..c52bfeae 100644 --- a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 +++ b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 @@ -4,6 +4,7 @@ import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["wizard-similar-topics"], + layoutName: 'wizard/templates/components/wizard-similar-topics', showTopics: true, didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-step-form.js.es6 b/assets/javascripts/wizard/components/wizard-step-form.js.es6 new file mode 100644 index 00000000..73406b4f --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step-form.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [":wizard-step-form", "customStepClass"], + + @discourseComputed("step.id") + customStepClass: (stepId) => `wizard-step-${stepId}`, +}); diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 new file mode 100644 index 00000000..4518afee --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -0,0 +1,247 @@ +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import I18n from "I18n"; +import getUrl from "discourse-common/lib/get-url"; +import { htmlSafe } from "@ember/template"; +import { schedule } from "@ember/runloop"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; +import { updateCachedWizard } from "discourse/plugins/discourse-custom-wizard/wizard/models/wizard"; +import { alias, not } from "@ember/object/computed"; +import CustomWizard from "../models/wizard"; + +const alreadyWarned = {}; + +export default Component.extend({ + layoutName: 'wizard/templates/components/wizard-step', + classNameBindings: [":wizard-step", "step.id"], + saving: null, + + init() { + this._super(...arguments); + this.set("stylingDropdown", {}); + }, + + didInsertElement() { + this._super(...arguments); + this.autoFocus(); + }, + + @discourseComputed("step.index", "wizard.required") + showQuitButton: (index, required) => (index === 0 && !required), + + showNextButton: not("step.final"), + showDoneButton: alias("step.final"), + + @discourseComputed("step.translatedTitle") + cookedTitle(title) { + return cook(title); + }, + + @discourseComputed("step.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed( + "step.index", + "step.displayIndex", + "wizard.totalSteps", + "wizard.completed" + ) + showFinishButton: (index, displayIndex, total, completed) => { + return index !== 0 && displayIndex !== total && completed; + }, + + @discourseComputed("step.index") + showBackButton: (index) => index > 0, + + @discourseComputed("step.banner") + bannerImage(src) { + if (!src) { + return; + } + return getUrl(src); + }, + + @discourseComputed("step.id") + bannerAndDescriptionClass(id) { + return `wizard-banner-and-description wizard-banner-and-description-${id}`; + }, + + @discourseComputed("step.fields.[]") + primaryButtonIndex(fields) { + return fields.length + 1; + }, + + @discourseComputed("step.fields.[]") + secondaryButtonIndex(fields) { + return fields.length + 2; + }, + + @observes("step.id") + _stepChanged() { + this.set("saving", false); + this.autoFocus(); + }, + + @observes("step.message") + _handleMessage: function () { + const message = this.get("step.message"); + this.sendAction("showMessage", message); + }, + + keyPress(event) { + if (event.key === "Enter") { + if (this.showDoneButton) { + this.send("quit"); + } else { + this.send("nextStep"); + } + } + }, + + @discourseComputed("step.index", "wizard.totalSteps") + barStyle(displayIndex, totalSteps) { + let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1); + if (ratio < 0) { + ratio = 0; + } + if (ratio > 1) { + ratio = 1; + } + + return htmlSafe(`width: ${ratio * 200}px`); + }, + + @discourseComputed("step.fields") + includeSidebar(fields) { + return !!fields.findBy("show_in_sidebar"); + }, + + autoFocus() { + schedule("afterRender", () => { + const $invalid = $( + ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" + ); + + if ($invalid.length) { + return $invalid.focus(); + } + + $(".wizard-focusable:first").focus(); + }); + }, + + animateInvalidFields() { + schedule("afterRender", () => { + let $element = $( + ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" + ); + + if ($element.length) { + $([document.documentElement, document.body]).animate( + { + scrollTop: $element.offset().top - 200, + }, + 400, + function () { + $element.wiggle(2, 100); + } + ); + } + }); + }, + + advance() { + this.set("saving", true); + this.get("step") + .save() + .then((response) => { + updateCachedWizard(CustomWizard.build(response["wizard"])); + + if (response["final"]) { + CustomWizard.finished(response); + } else { + this.sendAction("goNext", response); + } + }) + .catch(() => this.animateInvalidFields()) + .finally(() => this.set("saving", false)); + }, + + keyPress() {}, + + actions: { + quit() { + this.get("wizard").skip(); + }, + + done() { + this.send("nextStep"); + }, + + showMessage(message) { + this.sendAction("showMessage", message); + }, + + stylingDropdownChanged(id, value) { + this.set("stylingDropdown", { id, value }); + }, + + exitEarly() { + const step = this.step; + step.validate(); + + if (step.get("valid")) { + this.set("saving", true); + + step + .save() + .then(() => this.send("quit")) + .finally(() => this.set("saving", false)); + } else { + this.autoFocus(); + } + }, + + backStep() { + if (this.saving) { + return; + } + + this.goBack(); + }, + + nextStep() { + if (this.saving) { + return; + } + + const step = this.step; + const result = step.validate(); + + if (result.warnings.length) { + const unwarned = result.warnings.filter((w) => !alreadyWarned[w]); + if (unwarned.length) { + unwarned.forEach((w) => (alreadyWarned[w] = true)); + return window.bootbox.confirm( + unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"), + I18n.t("no_value"), + I18n.t("yes_value"), + (confirmed) => { + if (confirmed) { + this.advance(); + } + } + ); + } + } + + if (step.get("valid")) { + this.advance(); + } else { + this.autoFocus(); + } + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/wizard/components/wizard-text-field.js.es6 index c522eb2c..3d87be09 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-text-field.js.es6 @@ -1,5 +1,3 @@ -/* eslint no-undef: 0*/ - import computed from "discourse-common/utils/decorators"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import WizardI18n from "../lib/wizard-i18n"; @@ -15,7 +13,7 @@ export default Ember.TextField.extend({ @computed dir() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (val) { return isRTL(val) ? "rtl" : "ltr"; @@ -26,7 +24,7 @@ export default Ember.TextField.extend({ }, keyUp() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (isRTL(val)) { this.set("dir", "rtl"); diff --git a/assets/javascripts/wizard/components/wizard-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-time-input.js.es6 index 0bca244d..ec121002 100644 --- a/assets/javascripts/wizard/components/wizard-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-time-input.js.es6 @@ -1,3 +1,5 @@ import TimeInput from "discourse/components/time-input"; -export default TimeInput.extend(); +export default TimeInput.extend({ + layoutName: 'wizard/templates/components/wizard-time-input' +}); diff --git a/assets/javascripts/wizard/controllers/custom.js.es6 b/assets/javascripts/wizard/controllers/custom.js.es6 deleted file mode 100644 index 5f168f3a..00000000 --- a/assets/javascripts/wizard/controllers/custom.js.es6 +++ /dev/null @@ -1,3 +0,0 @@ -export default Ember.Controller.extend({ - queryParams: ["reset"], -}); diff --git a/assets/javascripts/wizard/controllers/custom-step.js.es6 b/assets/javascripts/wizard/controllers/step.js.es6 similarity index 76% rename from assets/javascripts/wizard/controllers/custom-step.js.es6 rename to assets/javascripts/wizard/controllers/step.js.es6 index b44c0fca..4b321173 100644 --- a/assets/javascripts/wizard/controllers/custom-step.js.es6 +++ b/assets/javascripts/wizard/controllers/step.js.es6 @@ -1,7 +1,10 @@ -import StepController from "wizard/controllers/step"; +import Controller from "@ember/controller"; import getUrl from "discourse-common/lib/get-url"; -export default StepController.extend({ +export default Controller.extend({ + wizard: null, + step: null, + actions: { goNext(response) { let nextStepId = response["next_step_id"]; @@ -12,12 +15,12 @@ export default StepController.extend({ const wizardId = this.get("wizard.id"); window.location.href = getUrl(`/w/${wizardId}/steps/${nextStepId}`); } else { - this.transitionToRoute("custom.step", nextStepId); + this.transitionToRoute("step", nextStepId); } }, goBack() { - this.transitionToRoute("custom.step", this.get("step.previous")); + this.transitionToRoute("step", this.get("step.previous")); }, showMessage(message) { diff --git a/assets/javascripts/wizard/controllers/wizard-index.js.es6 b/assets/javascripts/wizard/controllers/wizard-index.js.es6 new file mode 100644 index 00000000..2dfdee40 --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard-index.js.es6 @@ -0,0 +1,24 @@ +import Controller from "@ember/controller"; +import { or } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; + +const reasons = { + noWizard: "none", + requiresLogin: "requires_login", + notPermitted: "not_permitted", + completed: "completed" +} + +export default Controller.extend({ + noAccess: or('noWizard', 'requiresLogin', 'notPermitted', 'completed'), + + @discourseComputed('noAccessReason') + noAccessI18nKey(reason) { + return reason ? `wizard.${reasons[reason]}` : 'wizard.none'; + }, + + @discourseComputed + noAccessReason() { + return Object.keys(reasons).find(reason => this.get(reason)); + } +}); diff --git a/assets/javascripts/wizard/controllers/wizard.js.es6 b/assets/javascripts/wizard/controllers/wizard.js.es6 new file mode 100644 index 00000000..35f964e2 --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard.js.es6 @@ -0,0 +1,5 @@ +import Controller from "@ember/controller"; + +export default Controller.extend({ + queryParams: ["reset"], +}); diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6 deleted file mode 100644 index 8c0a473c..00000000 --- a/assets/javascripts/wizard/custom-wizard.js.es6 +++ /dev/null @@ -1,39 +0,0 @@ -import { buildResolver } from "discourse-common/resolver"; - -export default Ember.Application.extend({ - rootElement: "#custom-wizard-main", - Resolver: buildResolver("wizard"), - - customEvents: { - paste: "paste", - }, - - start() { - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/pre\-initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - - const init = module.default; - const oldInitialize = init.initialize; - init.initialize = () => { - oldInitialize.call(this, this.__container__, this); - }; - - this.initializer(init); - } - }); - - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - this.initializer(module.default); - } - }); - }, -}); diff --git a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 deleted file mode 100644 index fbbe7d8b..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 +++ /dev/null @@ -1,199 +0,0 @@ -export default { - name: "custom-wizard-step", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const CustomWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).default; - const updateCachedWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).updateCachedWizard; - const StepModel = requirejs("wizard/models/step").default; - const StepComponent = requirejs("wizard/components/wizard-step").default; - const ajax = requirejs("wizard/lib/ajax").ajax; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const discourseComputed = requirejs("discourse-common/utils/decorators") - .default; - const cook = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ).cook; - const { schedule } = requirejs("@ember/runloop"); - const { alias, not } = requirejs("@ember/object/computed"); - - StepModel.reopen({ - save() { - const wizardId = this.get("wizardId"); - const fields = {}; - - this.get("fields").forEach((f) => { - if (f.type !== "text_only") { - fields[f.id] = f.value; - } - }); - - return ajax({ - url: `/w/${wizardId}/steps/${this.get("id")}`, - type: "PUT", - data: { fields }, - }).catch((response) => { - if ( - response && - response.responseJSON && - response.responseJSON.errors - ) { - let wizardErrors = []; - response.responseJSON.errors.forEach((err) => { - if (err.field === wizardId) { - wizardErrors.push(err.description); - } else if (err.field) { - this.fieldError(err.field, err.description); - } else if (err) { - wizardErrors.push(err); - } - }); - if (wizardErrors.length) { - this.handleWizardError(wizardErrors.join("\n")); - } - this.animateInvalidFields(); - throw response; - } - - if (response && response.responseText) { - const responseText = response.responseText; - const start = responseText.indexOf(">") + 1; - const end = responseText.indexOf("plugins"); - const message = responseText.substring(start, end); - this.handleWizardError(message); - throw message; - } - }); - }, - - handleWizardError(message) { - this.set("message", { - state: "error", - text: message, - }); - Ember.run.later(() => this.set("message", null), 6000); - }, - }); - - StepComponent.reopen({ - classNameBindings: ["step.id"], - - autoFocus() { - schedule("afterRender", () => { - const $invalid = $( - ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" - ); - - if ($invalid.length) { - return $invalid.focus(); - } - - $(".wizard-focusable:first").focus(); - }); - }, - - animateInvalidFields() { - schedule("afterRender", () => { - let $element = $( - ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" - ); - - if ($element.length) { - $([document.documentElement, document.body]).animate( - { - scrollTop: $element.offset().top - 200, - }, - 400, - function () { - $element.wiggle(2, 100); - } - ); - } - }); - }, - - ensureStartsAtTop: function () { - window.scrollTo(0, 0); - }.observes("step.id"), - - showQuitButton: function () { - const index = this.get("step.index"); - const required = this.get("wizard.required"); - return index === 0 && !required; - }.property("step.index", "wizard.required"), - - cookedTitle: function () { - return cook(this.get("step.title")); - }.property("step.title"), - - cookedDescription: function () { - return cook(this.get("step.description")); - }.property("step.description"), - - bannerImage: function () { - const src = this.get("step.banner"); - if (!src) { - return; - } - return getUrl(src); - }.property("step.banner"), - - @discourseComputed("step.fields.[]") - primaryButtonIndex(fields) { - return fields.length + 1; - }, - - @discourseComputed("step.fields.[]") - secondaryButtonIndex(fields) { - return fields.length + 2; - }, - - handleMessage: function () { - const message = this.get("step.message"); - this.sendAction("showMessage", message); - }.observes("step.message"), - - showNextButton: not("step.final"), - showDoneButton: alias("step.final"), - - advance() { - this.set("saving", true); - this.get("step") - .save() - .then((response) => { - updateCachedWizard(CustomWizard.build(response["wizard"])); - - if (response["final"]) { - CustomWizard.finished(response); - } else { - this.sendAction("goNext", response); - } - }) - .catch(() => this.animateInvalidFields()) - .finally(() => this.set("saving", false)); - }, - - keyPress() {}, - - actions: { - quit() { - this.get("wizard").skip(); - }, - - done() { - this.send("nextStep"); - }, - - showMessage(message) { - this.sendAction("showMessage", message); - }, - }, - }); - }, -}; diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 deleted file mode 100644 index f2095827..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 +++ /dev/null @@ -1,126 +0,0 @@ -export default { - name: "custom-routes", - initialize(app) { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const EmberObject = requirejs("@ember/object").default; - const Router = requirejs("wizard/router").default; - const ApplicationRoute = requirejs("wizard/routes/application").default; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const Store = requirejs("discourse/services/store").default; - const registerRawHelpers = requirejs( - "discourse-common/lib/raw-handlebars-helpers" - ).registerRawHelpers; - const createHelperContext = requirejs("discourse-common/lib/helpers") - .createHelperContext; - const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") - .default; - const Handlebars = requirejs("handlebars").default; - const Site = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/site" - ).default; - const RestAdapter = requirejs("discourse/adapters/rest").default; - const Session = requirejs("discourse/models/session").default; - const setDefaultOwner = requirejs("discourse-common/lib/get-owner") - .setDefaultOwner; - const messageBus = requirejs("message-bus-client").default; - const getToken = requirejs("wizard/lib/ajax").getToken; - const setEnvironment = requirejs("discourse-common/config/environment") - .setEnvironment; - const container = app.__container__; - Discourse.Model = EmberObject.extend(); - Discourse.__container__ = container; - setDefaultOwner(container); - registerRawHelpers(RawHandlebars, Handlebars); - - // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill - if (!Object.entries) { - Object.entries = function (obj) { - let ownProps = Object.keys(obj), - i = ownProps.length, - resArray = new Array(i); // preallocate the Array - while (i--) { - resArray[i] = [ownProps[i], obj[ownProps[i]]]; - } - - return resArray; - }; - } - - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - const targets = ["controller", "component", "route", "model", "adapter"]; - /*eslint no-undef: 0*/ - const siteSettings = Wizard.SiteSettings; - app.register("site-settings:main", siteSettings, { instantiate: false }); - createHelperContext({ siteSettings }); - targets.forEach((t) => app.inject(t, "siteSettings", "site-settings:main")); - - app.register("message-bus:main", messageBus, { instantiate: false }); - targets.forEach((t) => app.inject(t, "messageBus", "message-bus:main")); - - app.register("service:store", Store); - targets.forEach((t) => app.inject(t, "store", "service:store")); - targets.forEach((t) => app.inject(t, "appEvents", "service:app-events")); - - app.register("adapter:rest", RestAdapter); - - const site = Site.current(); - app.register("site:main", site, { instantiate: false }); - targets.forEach((t) => app.inject(t, "site", "site:main")); - - site.set("can_create_tag", false); - app.register("session:main", Session.current(), { instantiate: false }); - targets.forEach((t) => app.inject(t, "session", "session:main")); - - createHelperContext({ - siteSettings: container.lookup("site-settings:main"), - currentUser: container.lookup("current-user:main"), - site: container.lookup("site:main"), - session: container.lookup("session:main"), - capabilities: container.lookup("capabilities:main"), - }); - - const session = container.lookup("session:main"); - const setupData = document.getElementById("data-discourse-setup").dataset; - session.set("highlightJsPath", setupData.highlightJsPath); - setEnvironment(setupData.environment); - - Router.reopen({ - rootURL: getUrl("/w/"), - }); - - Router.map(function () { - this.route("custom", { path: "/:wizard_id" }, function () { - this.route("steps"); - this.route("step", { path: "/steps/:step_id" }); - }); - }); - - ApplicationRoute.reopen({ - redirect() { - this.transitionTo("custom"); - }, - model() {}, - }); - - // Add a CSRF token to all AJAX requests - let token = getToken(); - session.set("csrfToken", token); - let callbacks = $.Callbacks(); - $.ajaxPrefilter(callbacks.fire); - - callbacks.add(function (options, originalOptions, xhr) { - if (!options.crossDomain) { - xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); - } - }); - }, -}; diff --git a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 new file mode 100644 index 00000000..022ac48e --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 @@ -0,0 +1,12 @@ +export default { + run(app, container) { + const { createHelperContext } = requirejs("discourse-common/lib/helpers"); + + createHelperContext({ + siteSettings: container.lookup('site-settings:main'), + site: container.lookup("site:main"), + session: container.lookup("session:main"), + capabilities: container.lookup("capabilities:main"), + }); + } +} diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 new file mode 100644 index 00000000..d31efb4d --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -0,0 +1,49 @@ +export default { + run(app, container) { + const Store = requirejs("discourse/services/store").default; + const Site = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/models/site" + ).default; + const Session = requirejs("discourse/models/session").default; + const RestAdapter = requirejs("discourse/adapters/rest").default; + const messageBus = requirejs("message-bus-client").default; + const sniffCapabilites = requirejs("discourse/pre-initializers/sniff-capabilities").default; + const site = Site.current(); + const session = Session.current(); + + const registrations = [ + ["site-settings:main", app.SiteSettings, false], + ["message-bus:main", messageBus, false], + ["site:main", site, false], + ["session:main", session, false], + ["service:store", Store, true], + ["adapter:rest", RestAdapter, true] + ]; + + registrations.forEach(registration => { + if (!app.hasRegistration(registration[0])) { + app.register(registration[0], registration[1], { instantiate: registration[2] }); + } + }); + + const targets = ["controller", "component", "route", "model", "adapter", "mixin"]; + const injections = [ + ["siteSettings", "site-settings:main"], + ["messageBus", "message-bus:main"], + ["site", "site:main"], + ["session", "session:main"], + ["store", "service:store"], + ["appEvents", "service:app-events"] + ]; + + injections.forEach(injection => { + targets.forEach((t) => app.inject(t, injection[0], injection[1])); + }); + + if (!app.hasRegistration("capabilities:main")) { + sniffCapabilites.initialize(null, app); + } + + site.set("can_create_tag", false); + } +} diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 similarity index 60% rename from assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 rename to assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index e397af5f..529a4cd9 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,93 +1,25 @@ -import { dasherize } from "@ember/string"; -import discourseComputed from "discourse-common/utils/decorators"; - export default { - name: "custom-wizard-field", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; + run(app, container) { + const getToken = requirejs("wizard/lib/ajax").getToken; + const isTesting = requirejs("discourse-common/config/environment").isTesting; + + if (!isTesting) { + // Add a CSRF token to all AJAX requests + let token = getToken(); + session.set("csrfToken", token); + let callbacks = $.Callbacks(); + $.ajaxPrefilter(callbacks.fire); + + callbacks.add(function (options, originalOptions, xhr) { + if (!options.crossDomain) { + xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); + } + }); } - const FieldComponent = requirejs("wizard/components/wizard-field").default; - const FieldModel = requirejs("wizard/models/wizard-field").default; - const { cook } = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ); const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const toMarkdown = requirejs("discourse/lib/to-markdown").default; - - FieldComponent.reopen({ - classNameBindings: ["field.id"], - - @discourseComputed("field.type") - textType(fieldType) { - return ["text", "textarea"].includes(fieldType); - }, - - cookedDescription: function () { - return cook(this.get("field.description")); - }.property("field.description"), - - inputComponentName: function () { - const type = this.get("field.type"); - const id = this.get("field.id"); - if (["text_only"].includes(type)) { - return false; - } - return dasherize(type === "component" ? id : `wizard-field-${type}`); - }.property("field.type", "field.id"), - }); - - const StandardFieldValidation = [ - "text", - "number", - "textarea", - "dropdown", - "tag", - "image", - "user_selector", - "text_only", - "composer", - "category", - "group", - "date", - "time", - "date_time", - ]; - - FieldModel.reopen({ - check() { - if (this.customCheck) { - return this.customCheck(); - } - - let valid = this.valid; - - if (!this.required) { - this.setValid(true); - return true; - } - - const val = this.get("value"); - const type = this.get("type"); - - if (type === "checkbox") { - valid = val; - } else if (type === "upload") { - valid = val && val.id > 0; - } else if (StandardFieldValidation.indexOf(type) > -1) { - valid = val && val.toString().length > 0; - } else if (type === "url") { - valid = true; - } - - this.setValid(valid); - - return valid; - }, - }); - const isInside = (text, regex) => { const matches = text.match(regex); return matches && matches.length % 2; @@ -194,5 +126,19 @@ export default { } }, }); - }, + + // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill + if (!Object.entries) { + Object.entries = function (obj) { + let ownProps = Object.keys(obj), + i = ownProps.length, + resArray = new Array(i); // preallocate the Array + while (i--) { + resArray[i] = [ownProps[i], obj[ownProps[i]]]; + } + + return resArray; + }; + } + } }; diff --git a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 new file mode 100644 index 00000000..8d4b850e --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 @@ -0,0 +1,26 @@ +export default { + run(app, container) { + const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default; + const Handlebars = requirejs("handlebars").default; + const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers; + const { registerHelpers } = requirejs("discourse-common/lib/helpers"); + const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins").default; + + Object.keys(Ember.TEMPLATES).forEach((k) => { + if (k.indexOf("select-kit") === 0) { + let template = Ember.TEMPLATES[k]; + define(k, () => template); + } + }); + + Object.keys(requirejs.entries).forEach((entry) => { + if (/\/helpers\//.test(entry)) { + requirejs(entry, null, null, true); + } + }); + + registerRawHelpers(RawHandlebars, Handlebars); + registerHelpers(app); + jqueryPlugins.initialize(container, app); + } +} diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 new file mode 100644 index 00000000..134257a1 --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -0,0 +1,49 @@ +export default { + name: "custom-wizard", + initialize(app) { + const isTesting = requirejs("discourse-common/config/environment").isTesting; + const isWizard = window.location.pathname.indexOf("/w/") > -1; + + if (!isWizard && !isTesting()) { + return; + } + + const container = app.__container__; + const setDefaultOwner = requirejs("discourse-common/lib/get-owner").setDefaultOwner; + setDefaultOwner(container); + + if (!isTesting()) { + const PreloadStore = requirejs("discourse/lib/preload-store").default; + + let preloaded; + const preloadedDataElement = document.getElementById("data-preloaded-wizard"); + if (preloadedDataElement) { + preloaded = JSON.parse(preloadedDataElement.dataset.preloadedWizard); + } + + Object.keys(preloaded).forEach(function (key) { + PreloadStore.store(key, JSON.parse(preloaded[key])); + }); + + app.SiteSettings = PreloadStore.get("siteSettings"); + } + + const setEnvironment = requirejs("discourse-common/config/environment").setEnvironment; + const setupData = document.getElementById("data-discourse-setup").dataset; + setEnvironment(setupData.environment); + + const Session = requirejs("discourse/models/session").default; + const session = Session.current(); + session.set("highlightJsPath", setupData.highlightJsPath); + + [ + 'register-files', + 'inject-objects', + 'create-contexts', + 'patch-components' + ].forEach(fileName => { + const initializer = requirejs(`discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}`).default; + initializer.run(app, container); + }); + } +}; diff --git a/assets/javascripts/wizard/lib/utilities-lite.js.es6 b/assets/javascripts/wizard/lib/utilities-lite.js.es6 deleted file mode 100644 index 19517b2b..00000000 --- a/assets/javascripts/wizard/lib/utilities-lite.js.es6 +++ /dev/null @@ -1,71 +0,0 @@ -// lite version of discourse/lib/utilities - -export function determinePostReplaceSelection({ - selection, - needle, - replacement, -}) { - const diff = - replacement.end - replacement.start - (needle.end - needle.start); - - if (selection.end <= needle.start) { - // Selection ends (and starts) before needle. - return { start: selection.start, end: selection.end }; - } else if (selection.start <= needle.start) { - // Selection starts before needle... - if (selection.end < needle.end) { - // ... and ends inside needle. - return { start: selection.start, end: needle.start }; - } else { - // ... and spans needle completely. - return { start: selection.start, end: selection.end + diff }; - } - } else if (selection.start < needle.end) { - // Selection starts inside needle... - if (selection.end <= needle.end) { - // ... and ends inside needle. - return { start: replacement.end, end: replacement.end }; - } else { - // ... and spans end of needle. - return { start: replacement.end, end: selection.end + diff }; - } - } else { - // Selection starts (and ends) behind needle. - return { start: selection.start + diff, end: selection.end + diff }; - } -} - -const toArray = (items) => { - items = items || []; - - if (!Array.isArray(items)) { - return Array.from(items); - } - - return items; -}; - -export function clipboardData(e, canUpload) { - const clipboard = - e.clipboardData || - e.originalEvent.clipboardData || - e.delegatedEvent.originalEvent.clipboardData; - - const types = toArray(clipboard.types); - let files = toArray(clipboard.files); - - if (types.includes("Files") && files.length === 0) { - // for IE - files = toArray(clipboard.items).filter((i) => i.kind === "file"); - } - - canUpload = files && canUpload && !types.includes("text/plain"); - const canUploadImage = - canUpload && files.filter((f) => f.type.match("^image/"))[0]; - const canPasteHtml = - Discourse.SiteSettings.enable_rich_text_paste && - types.includes("text/html") && - !canUploadImage; - - return { clipboard, types, canUpload, canPasteHtml }; -} diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/wizard/models/field.js.es6 new file mode 100644 index 00000000..5f76074e --- /dev/null +++ b/assets/javascripts/wizard/models/field.js.es6 @@ -0,0 +1,79 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; + +const StandardFieldValidation = [ + "text", + "number", + "textarea", + "dropdown", + "tag", + "image", + "user_selector", + "text_only", + "composer", + "category", + "group", + "date", + "time", + "date_time", +]; + +export default EmberObject.extend(ValidState, { + id: null, + type: null, + value: null, + required: null, + warning: null, + + @discourseComputed("wizardId", "stepId", "id") + i18nKey(wizardId, stepId, id) { + return `${wizardId}.${stepId}.${id}`; + }, + + @discourseComputed("i18nKey", "label") + translatedLabel(i18nKey, label) { + return translatedText(`${i18nKey}.label`, label); + }, + + @discourseComputed("i18nKey", "placeholder") + translatedPlaceholder(i18nKey, placeholder) { + return translatedText(`${i18nKey}.placeholder`, placeholder); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + check() { + if (this.customCheck) { + return this.customCheck(); + } + + let valid = this.valid; + + if (!this.required) { + this.setValid(true); + return true; + } + + const val = this.get("value"); + const type = this.get("type"); + + if (type === "checkbox") { + valid = val; + } else if (type === "upload") { + valid = val && val.id > 0; + } else if (StandardFieldValidation.indexOf(type) > -1) { + valid = val && val.toString().length > 0; + } else if (type === "url") { + valid = true; + } + + this.setValid(valid); + + return valid; + } +}); diff --git a/assets/javascripts/wizard/models/site.js.es6 b/assets/javascripts/wizard/models/site.js.es6 index c15ce98f..96837ff2 100644 --- a/assets/javascripts/wizard/models/site.js.es6 +++ b/assets/javascripts/wizard/models/site.js.es6 @@ -1,10 +1,11 @@ import Site from "discourse/models/site"; +import { getOwner } from "discourse-common/lib/get-owner"; export default Site.reopenClass({ // There is no site data actually loaded by the CW yet. This placeholder is // needed by imported classes createCurrent() { - const store = Discourse.__container__.lookup("service:store"); + const store = getOwner(this).lookup("service:store"); return store.createRecord("site", {}); }, }); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 new file mode 100644 index 00000000..e18657b5 --- /dev/null +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -0,0 +1,114 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import { ajax } from "wizard/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; + +export default EmberObject.extend(ValidState, { + id: null, + + @discourseComputed("wizardId", "id") + i18nKey(wizardId, stepId) { + return `${wizardId}.${stepId}`; + }, + + @discourseComputed("i18nKey", "title") + translatedTitle(i18nKey, title) { + return translatedText(`${i18nKey}.title`, title); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + @discourseComputed("index") + displayIndex: (index) => index + 1, + + @discourseComputed("fields.[]") + fieldsById(fields) { + const lookup = {}; + fields.forEach((field) => (lookup[field.get("id")] = field)); + return lookup; + }, + + validate() { + let allValid = true; + const result = { warnings: [] }; + + this.fields.forEach((field) => { + allValid = allValid && field.check(); + const warning = field.get("warning"); + if (warning) { + result.warnings.push(warning); + } + }); + + this.setValid(allValid); + + return result; + }, + + fieldError(id, description) { + const field = this.fields.findBy("id", id); + if (field) { + field.setValid(false, description); + } + }, + + save() { + const wizardId = this.get("wizardId"); + const fields = {}; + + this.get("fields").forEach((f) => { + if (f.type !== "text_only") { + fields[f.id] = f.value; + } + }); + + return ajax({ + url: `/w/${wizardId}/steps/${this.get("id")}`, + type: "PUT", + data: { fields }, + }).catch((response) => { + if ( + response && + response.responseJSON && + response.responseJSON.errors + ) { + let wizardErrors = []; + response.responseJSON.errors.forEach((err) => { + if (err.field === wizardId) { + wizardErrors.push(err.description); + } else if (err.field) { + this.fieldError(err.field, err.description); + } else if (err) { + wizardErrors.push(err); + } + }); + if (wizardErrors.length) { + this.handleWizardError(wizardErrors.join("\n")); + } + this.animateInvalidFields(); + throw response; + } + + if (response && response.responseText) { + const responseText = response.responseText; + const start = responseText.indexOf(">") + 1; + const end = responseText.indexOf("plugins"); + const message = responseText.substring(start, end); + this.handleWizardError(message); + throw message; + } + }); + }, + + handleWizardError(message) { + this.set("message", { + state: "error", + text: message, + }); + Ember.run.later(() => this.set("message", null), 6000); + }, +}); diff --git a/assets/javascripts/wizard/models/custom.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 similarity index 94% rename from assets/javascripts/wizard/models/custom.js.es6 rename to assets/javascripts/wizard/models/wizard.js.es6 index 0e7c557f..7fbe2c10 100644 --- a/assets/javascripts/wizard/models/custom.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -1,9 +1,9 @@ import { default as computed } from "discourse-common/utils/decorators"; import getUrl from "discourse-common/lib/get-url"; -import WizardField from "wizard/models/wizard-field"; +import Field from "./field"; import { ajax } from "wizard/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import Step from "wizard/models/step"; +import Step from "./step"; import EmberObject from "@ember/object"; import Site from "./site"; @@ -73,7 +73,11 @@ CustomWizard.reopenClass({ } }); - stepObj.fields = stepObj.fields.map((f) => WizardField.create(f)); + stepObj.fields = stepObj.fields.map((f) => { + f.wizardId = wizardJson.id; + f.stepId = stepObj.id; + return Field.create(f); + }); return stepObj; }) diff --git a/assets/javascripts/wizard/router.js.es6 b/assets/javascripts/wizard/router.js.es6 new file mode 100644 index 00000000..5178c91f --- /dev/null +++ b/assets/javascripts/wizard/router.js.es6 @@ -0,0 +1,17 @@ +import EmberRouter from "@ember/routing/router"; +import getUrl from "discourse-common/lib/get-url"; +import { isTesting } from "discourse-common/config/environment"; + +const Router = EmberRouter.extend({ + rootURL: isTesting() ? getUrl("/") : getUrl("/w/"), + location: isTesting() ? "none" : "history", +}); + +Router.map(function () { + this.route("wizard", { path: "/:wizard_id" }, function () { + this.route("steps", { path: "/steps", resetNamespace: true }); + this.route("step", { path: "/steps/:step_id", resetNamespace: true }); + }); +}); + +export default Router; diff --git a/assets/javascripts/wizard/routes/application.js.es6 b/assets/javascripts/wizard/routes/application.js.es6 new file mode 100644 index 00000000..0051f5ce --- /dev/null +++ b/assets/javascripts/wizard/routes/application.js.es6 @@ -0,0 +1,3 @@ +import Route from "@ember/routing/route"; + +export default Route.extend(); diff --git a/assets/javascripts/wizard/routes/custom-steps.js.es6 b/assets/javascripts/wizard/routes/custom-steps.js.es6 deleted file mode 100644 index c58d1251..00000000 --- a/assets/javascripts/wizard/routes/custom-steps.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -export default Ember.Route.extend({ - redirect() { - this.transitionTo("custom.index"); - }, -}); diff --git a/assets/javascripts/wizard/routes/index.js.es6 b/assets/javascripts/wizard/routes/index.js.es6 new file mode 100644 index 00000000..b24b3631 --- /dev/null +++ b/assets/javascripts/wizard/routes/index.js.es6 @@ -0,0 +1,9 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + beforeModel(transition) { + if (transition.intent.params) { + this.transitionTo("wizard"); + } + }, +}); diff --git a/assets/javascripts/wizard/routes/custom-step.js.es6 b/assets/javascripts/wizard/routes/step.js.es6 similarity index 78% rename from assets/javascripts/wizard/routes/custom-step.js.es6 rename to assets/javascripts/wizard/routes/step.js.es6 index 8088727a..2454fc95 100644 --- a/assets/javascripts/wizard/routes/custom-step.js.es6 +++ b/assets/javascripts/wizard/routes/step.js.es6 @@ -1,7 +1,8 @@ import WizardI18n from "../lib/wizard-i18n"; -import { getCachedWizard } from "../models/custom"; +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel() { this.set("wizard", getCachedWizard()); }, @@ -19,11 +20,15 @@ export default Ember.Route.extend({ afterModel(model) { if (model.completed) { - return this.transitionTo("index"); + return this.transitionTo("wizard.index"); } return model.set("wizardId", this.wizard.id); }, + renderTemplate() { + this.render('wizard/templates/step'); + }, + setupController(controller, model) { let props = { step: model, diff --git a/assets/javascripts/wizard/routes/steps.js.es6 b/assets/javascripts/wizard/routes/steps.js.es6 new file mode 100644 index 00000000..6f35d152 --- /dev/null +++ b/assets/javascripts/wizard/routes/steps.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + redirect() { + this.transitionTo("wizard.index"); + }, +}); diff --git a/assets/javascripts/wizard/routes/custom-index.js.es6 b/assets/javascripts/wizard/routes/wizard-index.js.es6 similarity index 50% rename from assets/javascripts/wizard/routes/custom-index.js.es6 rename to assets/javascripts/wizard/routes/wizard-index.js.es6 index a8abc152..16b1140a 100644 --- a/assets/javascripts/wizard/routes/custom-index.js.es6 +++ b/assets/javascripts/wizard/routes/wizard-index.js.es6 @@ -1,10 +1,11 @@ -import { getCachedWizard } from "../models/custom"; +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel() { const wizard = getCachedWizard(); - if (wizard && wizard.permitted && !wizard.completed && wizard.start) { - this.replaceWith("custom.step", wizard.start); + if (wizard && wizard.user && wizard.permitted && !wizard.completed && wizard.start) { + this.replaceWith("step", wizard.start); } }, @@ -12,6 +13,10 @@ export default Ember.Route.extend({ return getCachedWizard(); }, + renderTemplate() { + this.render('wizard/templates/wizard-index'); + }, + setupController(controller, model) { if (model && model.id) { const completed = model.get("completed"); @@ -19,17 +24,20 @@ export default Ember.Route.extend({ const wizardId = model.get("id"); const user = model.get("user"); const name = model.get("name"); + const requiresLogin = !user; + const notPermitted = !permitted; - controller.setProperties({ - requiresLogin: !user, + const props = { + requiresLogin, user, name, completed, - notPermitted: !permitted, + notPermitted, wizardId, - }); + }; + controller.setProperties(props); } else { controller.set("noWizard", true); } - }, + } }); diff --git a/assets/javascripts/wizard/routes/custom.js.es6 b/assets/javascripts/wizard/routes/wizard.js.es6 similarity index 59% rename from assets/javascripts/wizard/routes/custom.js.es6 rename to assets/javascripts/wizard/routes/wizard.js.es6 index a312db3a..2910ee6d 100644 --- a/assets/javascripts/wizard/routes/custom.js.es6 +++ b/assets/javascripts/wizard/routes/wizard.js.es6 @@ -1,37 +1,26 @@ -/* eslint no-undef: 0*/ - -import { findCustomWizard, updateCachedWizard } from "../models/custom"; +import { findCustomWizard, updateCachedWizard } from "../models/wizard"; import { ajax } from "wizard/lib/ajax"; import WizardI18n from "../lib/wizard-i18n"; +import Route from "@ember/routing/route"; +import { scheduleOnce } from "@ember/runloop"; +import { getOwner } from "discourse-common/lib/get-owner"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel(transition) { - this.set("queryParams", transition.intent.queryParams); + if (transition.intent.queryParams) { + this.set("queryParams", transition.intent.queryParams); + } }, model(params) { return findCustomWizard(params.wizard_id, this.get("queryParams")); }, - renderTemplate() { - this.render("custom"); - const wizardModel = this.modelFor("custom"); - const stepModel = this.modelFor("custom.step"); - - if ( - wizardModel.resume_on_revisit && - wizardModel.submission_last_updated_at && - stepModel.index > 0 - ) { - this.showDialog(wizardModel); - } - }, - showDialog(wizardModel) { const title = WizardI18n("wizard.incomplete_submission.title", { date: moment(wizardModel.submission_last_updated_at).format( "MMMM Do YYYY" - ), + ) }); const buttons = [ @@ -57,28 +46,36 @@ export default Ember.Route.extend({ afterModel(model) { updateCachedWizard(model); + }, - return ajax({ - url: `/site/settings`, - type: "GET", - }).then((result) => { - $.extend(Wizard.SiteSettings, result); - }); + renderTemplate() { + this.render('wizard/templates/wizard'); }, setupController(controller, model) { - const background = model ? model.get("background") : "AliceBlue"; - Ember.run.scheduleOnce("afterRender", this, function () { - $("body.custom-wizard").css("background", background); + const background = model ? model.get("background") : ""; + + scheduleOnce("afterRender", this, function () { + $("body").css("background", background); if (model && model.id) { - $("#custom-wizard-main").addClass(model.id.dasherize()); + $(getOwner(this).rootElement).addClass(model.id.dasherize()); } }); + controller.setProperties({ customWizard: true, - logoUrl: Wizard.SiteSettings.logo_small, + logoUrl: this.siteSettings.logo_small, reset: null, }); + + const stepModel = this.modelFor("step"); + if ( + model.resume_on_revisit && + model.submission_last_updated_at && + stepModel.index > 0 + ) { + this.showDialog(model); + } }, }); diff --git a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index 7ce4c298..42fc63e8 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -3,6 +3,7 @@ value=field.value content=field.content tabindex=field.tabindex + onChange=(action "onChangeValue") options=(hash none="select_kit.default_header_text" )}} diff --git a/assets/javascripts/wizard/templates/custom.index.hbs b/assets/javascripts/wizard/templates/custom.index.hbs deleted file mode 100644 index 3cd02e05..00000000 --- a/assets/javascripts/wizard/templates/custom.index.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#if noWizard}} - {{wizard-no-access text=(wizard-i18n "wizard.none") wizardId=wizardId}} -{{else}} - {{#if requiresLogin}} - {{wizard-no-access text=(wizard-i18n "wizard.requires_login") wizardId=wizardId}} - {{else}} - {{#if notPermitted}} - {{wizard-no-access text=(wizard-i18n "wizard.not_permitted") wizardId=wizardId}} - {{else}} - {{#if completed}} - {{wizard-no-access text=(wizard-i18n "wizard.completed") wizardId=wizardId}} - {{/if}} - {{/if}} - {{/if}} -{{/if}} diff --git a/assets/javascripts/wizard/templates/index.hbs b/assets/javascripts/wizard/templates/index.hbs new file mode 100644 index 00000000..c24cd689 --- /dev/null +++ b/assets/javascripts/wizard/templates/index.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/assets/javascripts/wizard/templates/custom.step.hbs b/assets/javascripts/wizard/templates/step.hbs similarity index 100% rename from assets/javascripts/wizard/templates/custom.step.hbs rename to assets/javascripts/wizard/templates/step.hbs diff --git a/assets/javascripts/wizard/templates/wizard-index.hbs b/assets/javascripts/wizard/templates/wizard-index.hbs new file mode 100644 index 00000000..a8f1b9ba --- /dev/null +++ b/assets/javascripts/wizard/templates/wizard-index.hbs @@ -0,0 +1,3 @@ +{{#if noAccess}} + {{wizard-no-access text=(wizard-i18n noAccessI18nKey) wizardId=wizardId reason=noAccessReason}} +{{/if}} diff --git a/assets/javascripts/wizard/templates/custom.hbs b/assets/javascripts/wizard/templates/wizard.hbs similarity index 86% rename from assets/javascripts/wizard/templates/custom.hbs rename to assets/javascripts/wizard/templates/wizard.hbs index 4701fec2..f6d6127e 100644 --- a/assets/javascripts/wizard/templates/custom.hbs +++ b/assets/javascripts/wizard/templates/wizard.hbs @@ -1,7 +1,3 @@ -{{#if showCanvas}} - {{wizard-canvas}} -{{/if}} -
{{outlet}} diff --git a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 new file mode 100644 index 00000000..f73d1ab7 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 @@ -0,0 +1,135 @@ +import { + visit, + click, + fillIn, + triggerKeyEvent +} from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible, + server +} from "../helpers/acceptance"; +import { + allFieldsWizard, + getWizard +} from "../helpers/wizard"; +import tagsJson from "../fixtures/tags"; +import usersJson from "../fixtures/users"; +import { response } from "../pretender"; + +acceptance("Field | Fields", [ getWizard(allFieldsWizard) ], + function(hooks) { + test("Text", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); + }); + + test("Textarea", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.textarea-field textarea.wizard-focusable")); + }); + + test("Composer", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.composer-field .wizard-field-composer textarea")); + assert.strictEqual(count(".wizard-field.composer-field .d-editor-button-bar button"), 8); + assert.ok(visible(".wizard-btn.toggle-preview")); + + await fillIn(".wizard-field.composer-field .wizard-field-composer textarea", "Input in composer"); + await click(".wizard-btn.toggle-preview"); + assert.strictEqual(query('.wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p').textContent.trim(), "Input in composer"); + }); + + test("Text Only", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.text-only-field label.field-label")); + }); + + test("Date", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-field input.date-picker")); + await click(".wizard-field.date-field input.date-picker"); + assert.ok(visible(".wizard-field.date-field .pika-single")); + }); + + test("Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); + await click(".wizard-field.time-field .d-time-input .select-kit .select-kit-header"); + assert.ok(visible(".wizard-field.time-field .select-kit-collection")); + }); + + test("Date Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-time-field .d-date-time-input .select-kit")); + await click(".wizard-field.date-time-field .d-date-input input.date-picker"); + assert.ok(visible(".wizard-field.date-time-field .d-date-input .pika-single")); + await click(".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header"); + assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); + }); + + test("Number", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.number-field input[type='number']")); + }); + + test("Checkbox", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); + }); + + test("Url", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.url-field input[type='text']")); + }); + + test("Upload", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.upload-field label.wizard-btn-upload-file")); + assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); + }); + + test("Dropdown", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); + await click(".wizard-field.dropdown-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.dropdown-field .select-kit-collection li"), 3); + }); + + test("Tag", async function (assert) { + server.get("/tags/filter/search", () => (response(200, { results: tagsJson['tags']}))); + await visit("/wizard"); + assert.ok(visible(".wizard-field.tag-field .multi-select-header")); + await click(".wizard-field.tag-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.tag-field .select-kit-collection li"), 2); + }); + + test("Category", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.category-field .multi-select-header")); + await click(".wizard-field.category-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.category-field .select-kit-collection li"), 5); + }); + + test("Group", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.group-field .single-select-header")); + await click(".wizard-field.group-field .select-kit-header"); + assert.strictEqual(count(".wizard-field.group-field .select-kit-collection li"), 10); + }); + + test("User", async function (assert) { + server.get("/u/search/users", () => (response(200, usersJson))); + + await visit("/wizard"); + await fillIn(".wizard-field.user-selector-field input.ember-text-field", "a"); + await triggerKeyEvent(".wizard-field.user-selector-field input.ember-text-field", "keyup", "a".charCodeAt(0)); + + assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); + // TODO: add assertion for ac results. autocomplete does not appear in time. + }); + } +); diff --git a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 new file mode 100644 index 00000000..731c9b76 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 @@ -0,0 +1,47 @@ +import { visit, click } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible +} from "../helpers/acceptance"; +import { + stepNotPermitted, + wizard, + getWizard +} from "../helpers/wizard"; +import { + saveStep, + update +} from "../helpers/step"; + +acceptance("Step | Not permitted", [ getWizard(stepNotPermitted) ], + function(hooks) { + test("Shows not permitted message", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".step-message.not-permitted")); + }); + } +); + +acceptance("Step | Step", [ getWizard(wizard), saveStep(update) ], + function(hooks) { + test("Renders the step", async function (assert) { + await visit("/wizard"); + assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); + assert.ok(visible('.wizard-step-footer .wizard-progress'), true); + assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); + }); + + test("Goes to the next step", async function (assert) { + await visit("/wizard"); + assert.ok(visible('.wizard-step.step_1'), true); + await click('.wizard-btn.next'); + assert.ok(visible('.wizard-step.step_2'), true); + }); + } +); diff --git a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 new file mode 100644 index 00000000..e68f59ae --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 @@ -0,0 +1,72 @@ +import { visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + query, + count, + visible +} from "../helpers/acceptance"; +import { + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + wizard, + getWizard +} from "../helpers/wizard"; + +acceptance("Wizard | Not logged in", [ getWizard(wizardNoUser) ], + function(hooks) { + test("Wizard no access requires login", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.requires-login")); + }); + } +); + +acceptance("Wizard | Not permitted", [ getWizard(wizardNotPermitted) ], + function(hooks) { + test("Wizard no access not permitted", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.not-permitted")); + }); + } +); + +acceptance("Wizard | Completed", [ getWizard(wizardCompleted) ], + function(hooks) { + test("Wizard no access completed", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.completed")); + }); + } +); + +acceptance("Wizard | Wizard", [ getWizard(wizard) ], + function(hooks) { + test("Starts", async function (assert) { + await visit("/wizard"); + assert.ok(query('.wizard-column'), true); + }); + + test("Applies the body background color", async function (assert) { + await visit("/wizard"); + assert.ok($("body")[0].style.background); + }); + + test("Renders the wizard form", async function (assert) { + await visit("/wizard"); + assert.ok(visible('.wizard-column-contents .wizard-step'), true); + assert.ok(visible('.wizard-footer img'), true); + }); + + test("Renders the first step", async function (assert) { + await visit("/wizard"); + assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); + assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); + assert.ok(visible('.wizard-step-footer .wizard-progress'), true); + assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); + }); + } +); diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 new file mode 100644 index 00000000..d2c503ad --- /dev/null +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -0,0 +1,17 @@ +// discourse-skip-module + +document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); +}); + +Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); + } +}); diff --git a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 new file mode 100644 index 00000000..e862f54a --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 @@ -0,0 +1,209 @@ +export default { + "categories": [ + { + "id": 1, + "name": "Uncategorized", + "color": "0088CC", + "text_color": "FFFFFF", + "slug": "uncategorized", + "topic_count": 1, + "post_count": 1, + "position": 0, + "description": "Topics that don't need a category, or don't fit into any other existing category.", + "description_text": "Topics that don't need a category, or don't fit into any other existing category.", + "description_excerpt": "Topics that don't need a category, or don't fit into any other existing category.", + "topic_url": "/t/", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 2, + "name": "Site Feedback", + "color": "808281", + "text_color": "FFFFFF", + "slug": "site-feedback", + "topic_count": 20, + "post_count": 21, + "position": 1, + "description": "

Discussion about this site, its organization, how it works, and how we can improve it.

", + "description_text": "Discussion about this site, its organization, how it works, and how we can improve it.", + "description_excerpt": "Discussion about this site, its organization, how it works, and how we can improve it.", + "topic_url": "/t/about-the-site-feedback-category/1", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 3, + "name": "Staff", + "color": "E45735", + "text_color": "FFFFFF", + "slug": "staff", + "topic_count": 4, + "post_count": 7, + "position": 2, + "description": "

Private category for staff discussions. Topics are only visible to admins and moderators.

", + "description_text": "Private category for staff discussions. Topics are only visible to admins and moderators.", + "description_excerpt": "Private category for staff discussions. Topics are only visible to admins and moderators.", + "topic_url": "/t/about-the-staff-category/2", + "read_restricted": true, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 4, + "name": "Lounge", + "color": "A461EF", + "text_color": "652D90", + "slug": "lounge", + "topic_count": 1, + "post_count": 1, + "position": 3, + "description": "

A category exclusive to members with trust level 3 and higher.

", + "description_text": "A category exclusive to members with trust level 3 and higher.", + "description_excerpt": "A category exclusive to members with trust level 3 and higher.", + "topic_url": "/t/about-the-lounge-category/3", + "read_restricted": true, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + }, + { + "id": 5, + "name": "Custom Categories", + "color": "0088CC", + "text_color": "FFFFFF", + "slug": "custom-category", + "topic_count": 0, + "post_count": 0, + "position": 10, + "description": "Description of custom category", + "description_text": "Description of custom category", + "description_excerpt": "Description of custom category", + "topic_url": "/t/about-the-custom-category/5", + "read_restricted": false, + "permission": 1, + "notification_level": 0, + "topic_template": null, + "has_children": false, + "sort_order": null, + "sort_ascending": null, + "show_subcategory_list": false, + "num_featured_topics": 3, + "default_view": null, + "subcategory_list_style": "rows_with_featured_topics", + "default_top_period": "all", + "default_list_filter": "all", + "minimum_required_tags": 0, + "navigate_to_first_post_after_read": false, + "custom_fields": { + "create_topic_wizard": null + }, + "allowed_tags": [], + "allowed_tag_groups": [], + "allow_global_tags": false, + "min_tags_from_required_group": 1, + "required_tag_group_name": null, + "read_only_banner": null, + "uploaded_logo": null, + "uploaded_background": null, + "can_edit": true + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 new file mode 100644 index 00000000..16f8150d --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 @@ -0,0 +1,313 @@ +export default { + "groups": [ + { + "id": 1, + "automatic": true, + "name": "admins", + "display_name": "admins", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 0, + "automatic": true, + "name": "everyone", + "display_name": "everyone", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 3, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 15, + "automatic": false, + "name": "custom_group", + "user_count": 1, + "mentionable_level": 1, + "messageable_level": 2, + "visibility_level": 3, + "primary_group": false, + "title": "Custom Group", + "grant_trust_level": 3, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": "I am prefilled", + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 99, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 2, + "automatic": true, + "name": "moderators", + "display_name": "moderators", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 99, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 2, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 3, + "automatic": true, + "name": "staff", + "display_name": "staff", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 10, + "automatic": true, + "name": "trust_level_0", + "display_name": "trust_level_0", + "user_count": 2, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 11, + "automatic": true, + "name": "trust_level_1", + "display_name": "trust_level_1", + "user_count": 2, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 12, + "automatic": true, + "name": "trust_level_2", + "display_name": "trust_level_2", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 13, + "automatic": true, + "name": "trust_level_3", + "display_name": "trust_level_3", + "user_count": 1, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + }, + { + "id": 14, + "automatic": true, + "name": "trust_level_4", + "display_name": "trust_level_4", + "user_count": 0, + "mentionable_level": 0, + "messageable_level": 0, + "visibility_level": 1, + "primary_group": false, + "title": null, + "grant_trust_level": null, + "incoming_email": null, + "has_messages": false, + "flair_url": null, + "flair_bg_color": null, + "flair_color": null, + "bio_raw": null, + "bio_cooked": null, + "bio_excerpt": null, + "public_admission": false, + "public_exit": false, + "allow_membership_requests": false, + "full_name": null, + "default_notification_level": 3, + "membership_request_template": null, + "members_visibility_level": 0, + "can_see_members": true, + "can_admin_group": true, + "publish_read_state": false + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 new file mode 100644 index 00000000..f71ace8e --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 @@ -0,0 +1,283 @@ +export default { + "default_locale": "en", + "title": "Discourse", + "short_site_description": "", + "exclude_rel_nofollow_domains": "", + "logo": "/images/discourse-logo-sketch.png", + "logo_small": "/images/discourse-logo-sketch-small.png", + "digest_logo": "", + "mobile_logo": "", + "logo_dark": "", + "logo_small_dark": "", + "mobile_logo_dark": "", + "large_icon": "", + "favicon": "", + "apple_touch_icon": "", + "display_local_time_in_user_card": false, + "allow_user_locale": false, + "set_locale_from_accept_language_header": false, + "support_mixed_text_direction": false, + "suggested_topics": 5, + "ga_universal_tracking_code": "", + "ga_universal_domain_name": "auto", + "gtm_container_id": "", + "top_menu": "categories|latest", + "post_menu": "read|like|share|flag|edit|bookmark|delete|admin|reply", + "post_menu_hidden_items": "flag|bookmark|edit|delete|admin", + "share_links": "twitter|facebook|email", + "share_quote_visibility": "anonymous", + "share_quote_buttons": "twitter|email", + "desktop_category_page_style": "categories_and_latest_topics", + "category_colors": "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", + "category_style": "bullet", + "max_category_nesting": 2, + "enable_mobile_theme": true, + "enable_direct_s3_uploads": false, + "enable_upload_debug_mode": false, + "default_dark_mode_color_scheme_id": 1, + "relative_date_duration": 30, + "fixed_category_positions": false, + "fixed_category_positions_on_create": false, + "enable_badges": true, + "enable_badge_sql": true, + "max_favorite_badges": 2, + "enable_whispers": false, + "enable_bookmarks_with_reminders": true, + "push_notifications_prompt": true, + "vapid_public_key_bytes": "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", + "invite_only": false, + "login_required": false, + "must_approve_users": false, + "enable_local_logins": true, + "enable_local_logins_via_email": true, + "allow_new_registrations": true, + "enable_signup_cta": true, + "facebook_app_id": "", + "auth_skip_create_confirm": false, + "auth_overrides_email": false, + "enable_discourse_connect": true, + "discourse_connect_overrides_avatar": false, + "hide_email_address_taken": false, + "min_username_length": 3, + "max_username_length": 20, + "unicode_usernames": false, + "min_password_length": 10, + "min_admin_password_length": 15, + "email_editable": true, + "logout_redirect": "", + "full_name_required": false, + "enable_names": true, + "invite_expiry_days": 90, + "invites_per_page": 40, + "delete_user_max_post_age": 60, + "delete_all_posts_max": 15, + "prioritize_username_in_ux": true, + "enable_user_directory": true, + "allow_anonymous_posting": false, + "anonymous_posting_min_trust_level": 1, + "allow_users_to_hide_profile": true, + "hide_user_profiles_from_public": false, + "allow_featured_topic_on_user_profiles": true, + "hide_suspension_reasons": false, + "ignored_users_count_message_threshold": 5, + "ignored_users_message_gap_days": 365, + "user_selected_primary_groups": false, + "gravatar_name": "Gravatar", + "gravatar_base_url": "www.gravatar.com", + "gravatar_login_url": "/emails", + "enable_group_directory": true, + "enable_category_group_moderation": false, + "min_post_length": 20, + "min_first_post_length": 20, + "min_personal_message_post_length": 10, + "max_post_length": 32000, + "topic_featured_link_enabled": true, + "min_topic_views_for_delete_confirm": 5000, + "min_topic_title_length": 15, + "max_topic_title_length": 255, + "enable_filtered_replies_view": false, + "min_personal_message_title_length": 2, + "allow_uncategorized_topics": true, + "min_title_similar_length": 10, + "enable_personal_messages": true, + "edit_history_visible_to_public": true, + "delete_removed_posts_after": 24, + "traditional_markdown_linebreaks": false, + "enable_markdown_typographer": true, + "enable_markdown_linkify": true, + "markdown_linkify_tlds": "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", + "markdown_typographer_quotation_marks": "“|”|‘|’", + "enable_rich_text_paste": true, + "suppress_reply_directly_below": true, + "suppress_reply_directly_above": true, + "max_reply_history": 1, + "enable_mentions": true, + "here_mention": "here", + "newuser_max_embedded_media": 1, + "newuser_max_attachments": 0, + "show_pinned_excerpt_mobile": true, + "show_pinned_excerpt_desktop": true, + "display_name_on_posts": false, + "show_time_gap_days": 7, + "short_progress_text_threshold": 10000, + "default_code_lang": "auto", + "autohighlight_all_code": false, + "highlighted_languages": "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", + "show_copy_button_on_codeblocks": false, + "enable_emoji": true, + "enable_emoji_shortcuts": true, + "emoji_set": "twitter", + "emoji_autocomplete_min_chars": 0, + "enable_inline_emoji_translation": false, + "code_formatting_style": "code-fences", + "allowed_href_schemes": "", + "watched_words_regular_expressions": false, + "enable_diffhtml_preview": false, + "enable_fast_edit": true, + "old_post_notice_days": 14, + "blur_tl0_flagged_posts_media": true, + "email_time_window_mins": 10, + "disable_digest_emails": false, + "email_in": false, + "enable_imap": false, + "enable_smtp": false, + "disable_emails": "no", + "bounce_score_threshold": 4, + "enable_secondary_emails": true, + "max_image_size_kb": 4096, + "max_attachment_size_kb": 4096, + "authorized_extensions": "jpg|jpeg|png|gif|heic|heif|webp", + "authorized_extensions_for_staff": "", + "max_image_width": 690, + "max_image_height": 500, + "prevent_anons_from_downloading_files": false, + "secure_media": false, + "enable_s3_uploads": false, + "allow_profile_backgrounds": true, + "allow_uploaded_avatars": "0", + "default_avatars": "", + "external_system_avatars_enabled": true, + "external_system_avatars_url": "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", + "external_emoji_url": "", + "selectable_avatars_mode": "disabled", + "selectable_avatars": "", + "allow_staff_to_upload_any_file_in_pm": true, + "simultaneous_uploads": 5, + "composer_media_optimization_image_enabled": true, + "composer_media_optimization_image_bytes_optimization_threshold": 524288, + "composer_media_optimization_image_resize_dimensions_threshold": 1920, + "composer_media_optimization_image_resize_width_target": 1920, + "composer_media_optimization_image_resize_pre_multiply": false, + "composer_media_optimization_image_resize_linear_rgb": false, + "composer_media_optimization_image_encode_quality": 75, + "composer_media_optimization_debug_mode": false, + "min_trust_level_to_allow_profile_background": 0, + "min_trust_level_to_allow_user_card_background": 0, + "min_trust_level_to_allow_ignore": 2, + "tl1_requires_read_posts": 30, + "tl3_links_no_follow": false, + "enforce_second_factor": "no", + "moderators_change_post_ownership": false, + "moderators_view_emails": false, + "use_admin_ip_allowlist": false, + "allowed_iframes": "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", + "can_permanently_delete": false, + "max_oneboxes_per_post": 50, + "reviewable_claiming": "disabled", + "reviewable_default_topics": false, + "reviewable_default_visibility": "low", + "alert_admins_if_errors_per_minute": 0, + "alert_admins_if_errors_per_hour": 0, + "max_prints_per_hour_per_user": 5, + "invite_link_max_redemptions_limit": 5000, + "invite_link_max_redemptions_limit_users": 10, + "enable_long_polling": true, + "enable_chunked_encoding": true, + "long_polling_base_url": "/", + "background_polling_interval": 60000, + "polling_interval": 3000, + "anon_polling_interval": 25000, + "flush_timings_secs": 60, + "verbose_localization": false, + "max_new_topics": 500, + "enable_safe_mode": true, + "tos_url": "", + "privacy_policy_url": "", + "faq_url": "", + "enable_backups": true, + "backup_location": "local", + "maximum_backups": 5, + "use_pg_headlines_for_excerpt": false, + "min_search_term_length": 3, + "log_search_queries": true, + "version_checks": true, + "suppress_uncategorized_badge": true, + "header_dropdown_category_count": 8, + "slug_generation_method": "ascii", + "summary_timeline_button": false, + "topic_views_heat_low": 1000, + "topic_views_heat_medium": 2000, + "topic_views_heat_high": 3500, + "topic_post_like_heat_low": 0.5, + "topic_post_like_heat_medium": 1, + "topic_post_like_heat_high": 2, + "history_hours_low": 12, + "history_hours_medium": 24, + "history_hours_high": 48, + "cold_age_days_low": 14, + "cold_age_days_medium": 90, + "cold_age_days_high": 180, + "global_notice": "", + "show_create_topics_notice": true, + "bootstrap_mode_min_users": 50, + "bootstrap_mode_enabled": true, + "automatically_unpin_topics": true, + "read_time_word_count": 500, + "topic_page_title_includes_category": true, + "svg_icon_subset": "", + "allow_bulk_invite": true, + "disable_mailing_list_mode": true, + "default_topics_automatic_unpin": true, + "mute_all_categories_by_default": false, + "tagging_enabled": true, + "tag_style": "simple", + "max_tags_per_topic": 5, + "max_tag_length": 20, + "min_trust_level_to_tag_topics": "0", + "max_tag_search_results": 5, + "max_tags_in_filter_list": 30, + "tags_sort_alphabetically": false, + "tags_listed_by_group": false, + "suppress_overlapping_tags_in_list": false, + "remove_muted_tags_from_latest": "always", + "force_lowercase_tags": true, + "dashboard_hidden_reports": "", + "dashboard_visible_tabs": "moderation|security|reports", + "dashboard_general_tab_activity_metrics": "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", + "discourse_narrative_bot_enabled": true, + "details_enabled": true, + "custom_wizard_enabled": true, + "wizard_redirect_exclude_paths": "admin", + "wizard_recognised_image_upload_formats": "jpg|jpeg|png|gif", + "wizard_important_notices_on_dashboard": true, + "discourse_local_dates_email_format": "YYYY-MM-DDTHH:mm:ss[Z]", + "discourse_local_dates_enabled": true, + "discourse_local_dates_default_formats": "LLL|LTS|LL|LLLL", + "discourse_local_dates_default_timezones": "Europe/Paris|America/Los_Angeles", + "poll_enabled": true, + "poll_maximum_options": 20, + "poll_minimum_trust_level_to_create": 1, + "poll_groupable_user_fields": "", + "poll_export_data_explorer_query_id": -16, + "presence_enabled": true, + "presence_max_users_shown": 5, + "available_locales": "[{\"name\":\"اللغة العربية\",\"value\":\"ar\"},{\"name\":\"беларуская мова\",\"value\":\"be\"},{\"name\":\"български език\",\"value\":\"bg\"},{\"name\":\"bosanski jezik\",\"value\":\"bs_BA\"},{\"name\":\"català\",\"value\":\"ca\"},{\"name\":\"čeština\",\"value\":\"cs\"},{\"name\":\"dansk\",\"value\":\"da\"},{\"name\":\"Deutsch\",\"value\":\"de\"},{\"name\":\"ελληνικά\",\"value\":\"el\"},{\"name\":\"English (US)\",\"value\":\"en\"},{\"name\":\"English (UK)\",\"value\":\"en_GB\"},{\"name\":\"Español\",\"value\":\"es\"},{\"name\":\"eesti\",\"value\":\"et\"},{\"name\":\"فارسی\",\"value\":\"fa_IR\"},{\"name\":\"suomi\",\"value\":\"fi\"},{\"name\":\"Français\",\"value\":\"fr\"},{\"name\":\"galego\",\"value\":\"gl\"},{\"name\":\"עברית\",\"value\":\"he\"},{\"name\":\"magyar\",\"value\":\"hu\"},{\"name\":\"Հայերեն\",\"value\":\"hy\"},{\"name\":\"Indonesian\",\"value\":\"id\"},{\"name\":\"Italiano\",\"value\":\"it\"},{\"name\":\"日本語\",\"value\":\"ja\"},{\"name\":\"한국어\",\"value\":\"ko\"},{\"name\":\"lietuvių kalba\",\"value\":\"lt\"},{\"name\":\"latviešu valoda\",\"value\":\"lv\"},{\"name\":\"Norsk bokmål\",\"value\":\"nb_NO\"},{\"name\":\"Nederlands\",\"value\":\"nl\"},{\"name\":\"polski\",\"value\":\"pl_PL\"},{\"name\":\"Português\",\"value\":\"pt\"},{\"name\":\"Português (BR)\",\"value\":\"pt_BR\"},{\"name\":\"limba română\",\"value\":\"ro\"},{\"name\":\"Русский\",\"value\":\"ru\"},{\"name\":\"slovenčina\",\"value\":\"sk\"},{\"name\":\"slovenščina\",\"value\":\"sl\"},{\"name\":\"Shqip\",\"value\":\"sq\"},{\"name\":\"српски језик\",\"value\":\"sr\"},{\"name\":\"svenska\",\"value\":\"sv\"},{\"name\":\"Kiswahili\",\"value\":\"sw\"},{\"name\":\"తెలుగు\",\"value\":\"te\"},{\"name\":\"ไทย\",\"value\":\"th\"},{\"name\":\"Türkçe\",\"value\":\"tr_TR\"},{\"name\":\"українська мова\",\"value\":\"uk\"},{\"name\":\"اردو\",\"value\":\"ur\"},{\"name\":\"Việt Nam\",\"value\":\"vi\"},{\"name\":\"简体中文\",\"value\":\"zh_CN\"},{\"name\":\"繁體中文\",\"value\":\"zh_TW\"}]", + "require_invite_code": false, + "site_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", + "site_logo_small_url": "http://localhost:3000/images/discourse-logo-sketch-small.png", + "site_mobile_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", + "site_favicon_url": "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", + "site_logo_dark_url": "", + "site_logo_small_dark_url": "", + "site_mobile_logo_dark_url": "" +} diff --git a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 new file mode 100644 index 00000000..a072772e --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 @@ -0,0 +1,22 @@ +export default { + "tags": [ + { + "id": "tag1", + "text": "tag1", + "name": "tag1", + "description": null, + "count": 1, + "pm_count": 0, + "target_tag": null + }, + { + "id": "tag2", + "text": "tag2", + "name": "tag2", + "description": null, + "count": 1, + "pm_count": 0, + "target_tag": null + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/update.js.es6 b/assets/javascripts/wizard/tests/fixtures/update.js.es6 new file mode 100644 index 00000000..3908525c --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/update.js.es6 @@ -0,0 +1,5 @@ +export default { + "final": false, + "next_step_id": "step_2", + "wizard": {} +} diff --git a/assets/javascripts/wizard/tests/fixtures/user.js.es6 b/assets/javascripts/wizard/tests/fixtures/user.js.es6 new file mode 100644 index 00000000..8acd7392 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/user.js.es6 @@ -0,0 +1,34 @@ +export default { + id: 19, + username: "angus", + uploaded_avatar_id: 5275, + avatar_template: "/user_avatar/localhost/angus/{size}/5275.png", + name: "Angus McLeod", + unread_notifications: 0, + unread_private_messages: 0, + unread_high_priority_notifications: 0, + admin: true, + notification_channel_position: null, + site_flagged_posts_count: 1, + moderator: true, + staff: true, + can_create_group: true, + title: "", + reply_count: 859, + topic_count: 36, + enable_quoting: true, + external_links_in_new_tab: false, + dynamic_favicon: true, + trust_level: 4, + can_edit: true, + can_invite_to_forum: true, + should_be_redirected_to_top: false, + custom_fields: {}, + muted_category_ids: [], + dismissed_banner_key: null, + akismet_review_count: 0, + title_count_mode: "notifications", + timezone: "Australia/Perth", + skip_new_user_tips: false, + can_review: true +} diff --git a/assets/javascripts/wizard/tests/fixtures/users.js.es6 b/assets/javascripts/wizard/tests/fixtures/users.js.es6 new file mode 100644 index 00000000..267d4909 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/users.js.es6 @@ -0,0 +1,14 @@ +export default { + "users": [ + { + "username": "angus", + "name": "Angus", + "avatar_template": "/user_avatar/localhost/angus/{size}/12_2.png" + }, + { + "username": "angus_2", + "name": "Angus 2", + "avatar_template": "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png" + } + ] +} diff --git a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 new file mode 100644 index 00000000..be4fa8b2 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 @@ -0,0 +1,469 @@ +export default { + "id": "wizard", + "name": "Wizard", + "start": "step_1", + "background": "#333333", + "submission_last_updated_at": "2022-03-15T21:11:01+01:00", + "theme_id": 2, + "required": false, + "permitted": true, + "uncategorized_category_id": 1, + "categories": [], + "subscribed": false, + "resume_on_revisit": false, + "steps": [ + { + "id": "step_1", + "index": 0, + "next": "step_2", + "description": "

Text inputs!

", + "title": "Text", + "permitted": true, + "permitted_message": null, + "final": false, + "fields": [ + { + "id": "step_1_field_1", + "index": 0, + "type": "text", + "required": false, + "value": "I am prefilled", + "label": "

Text

", + "description": "Text field description.", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_2", + "index": 0, + "type": "textarea", + "required": false, + "value": "", + "label": "

Textarea

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_3", + "index": 2, + "type": "composer", + "required": false, + "value": "", + "label": "

Composer

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_4", + "index": 3, + "type": "text_only", + "required": false, + "value": null, + "label": "

I’m only text

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 4, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_5", + "index": 4, + "type": "composer_preview", + "required": false, + "value": "", + "label": "

I’m a preview

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": "

I am prefilled

", + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + }, + { + "id": "step_1_field_6", + "index": 5, + "type": "composer_preview", + "required": false, + "value": "", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": "

This is the preview of the composer

", + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_1", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + }, + { + "id": "step_2", + "index": 1, + "next": "step_3", + "previous": "step_1", + "description": "

Because I couldn’t think of another name for this step \":slight_smile:\"

", + "title": "Values", + "permitted": true, + "permitted_message": null, + "final": false, + "fields": [ + { + "id": "step_2_field_1", + "index": 0, + "type": "date", + "required": false, + "value": "", + "label": "

Date

", + "file_types": null, + "format": "YYYY-MM-DD", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_2", + "index": 0, + "type": "time", + "required": false, + "value": "", + "label": "

Time

", + "file_types": null, + "format": "HH:mm", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_3", + "index": 2, + "type": "date_time", + "required": false, + "value": "", + "label": "

Date & Time

", + "file_types": null, + "format": "", + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_4", + "index": 3, + "type": "number", + "required": false, + "value": "", + "label": "

Number

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_5", + "index": 4, + "type": "checkbox", + "required": false, + "value": false, + "label": "

Checkbox

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_6", + "index": 5, + "type": "url", + "required": false, + "value": "", + "label": "

Url

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 7, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + }, + { + "id": "step_2_field_7", + "index": 6, + "type": "upload", + "required": false, + "value": "", + "label": "

Upload

", + "file_types": ".jpg,.jpeg,.png", + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 8, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_2", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + }, + { + "id": "step_3", + "index": 2, + "previous": "step_2", + "description": "

Unfortunately not the edible type \":sushi:\"

", + "title": "Combo-boxes", + "permitted": true, + "permitted_message": null, + "final": true, + "fields": [ + { + "id": "step_3_field_1", + "index": 0, + "type": "dropdown", + "required": false, + "value": "choice1", + "label": "

Custom Dropdown

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": [ + { + "id": "one", + "name": "One" + }, + { + "id": "two", + "name": "Two" + } + ], + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 1, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_2", + "index": 0, + "type": "tag", + "required": false, + "value": null, + "label": "

Tag

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 2, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_3", + "index": 2, + "type": "category", + "required": false, + "value": null, + "label": "

Category

", + "file_types": null, + "format": null, + "limit": 1, + "property": "id", + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 3, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_4", + "index": 3, + "type": "group", + "required": false, + "value": null, + "label": "

Group

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 4, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_5", + "index": 4, + "type": "user_selector", + "required": false, + "value": null, + "label": "

User Selector

", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 5, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + }, + { + "id": "step_3_field_6", + "index": 5, + "type": "user_selector", + "required": false, + "value": null, + "label": "

Conditional User Selector

", + "description": "Shown when checkbox in step_2_field_5 is true", + "file_types": null, + "format": null, + "limit": null, + "property": null, + "content": null, + "validations": {}, + "max_length": null, + "char_counter": null, + "preview_template": null, + "tabindex": 6, + "wizardId": "super_mega_fun_wizard", + "stepId": "step_3", + "_validState": 0 + } + ], + "_validState": 0, + "wizardId": "super_mega_fun_wizard" + } + ], + "groups": [] +} diff --git a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 new file mode 100644 index 00000000..f5c1175f --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 @@ -0,0 +1,53 @@ +import { module } from "qunit"; +import setupPretender, { response } from "../pretender"; +import startApp from "../helpers/start-app"; + +let server; +let app; + +function acceptance(name, requests, cb) { + module(`Acceptance: ${name}`, function(hooks) { + hooks.beforeEach(function() { + server = setupPretender(function(pretender) { + requests.forEach(req => { + pretender[req.verb](req.path, () => (response(req.status, req.response))); + }); + return pretender; + }); + app = startApp(); + }); + + hooks.afterEach(function() { + app.destroy(); + server.shutdown(); + }); + + cb(hooks); + }); +} + +export default acceptance; + +export { + server +}; + +// The discourse/test/helpers/qunit-helpers file has many functions and imports +// we don't need, so there will be some duplciation here. + +export function queryAll(selector, context) { + context = context || "#ember-testing"; + return $(selector, context); +} + +export function query() { + return document.querySelector("#ember-testing").querySelector(...arguments); +} + +export function visible(selector) { + return queryAll(selector + ":visible").length > 0; +} + +export function count(selector) { + return queryAll(selector).length; +} diff --git a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 new file mode 100644 index 00000000..6afe6eb9 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 @@ -0,0 +1,19 @@ +const CustomWizard = requirejs("discourse/plugins/discourse-custom-wizard/wizard/application").default; +const initializer = requirejs("discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard").default; +const siteSettings = requirejs("discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings").default; +const { cloneJSON } = requirejs("discourse-common/lib/object").default; + +let app; + +export default function () { + app = CustomWizard.create({ rootElement: "#ember-testing" }); + app.start(); + + app.SiteSettings = cloneJSON(siteSettings); + initializer.initialize(app); + + app.setupForTesting(); + app.injectTestHelpers(); + + return app; +} diff --git a/assets/javascripts/wizard/tests/helpers/step.js.es6 b/assets/javascripts/wizard/tests/helpers/step.js.es6 new file mode 100644 index 00000000..a24e04e1 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/step.js.es6 @@ -0,0 +1,20 @@ +import updateJson from "../fixtures/update"; +import { cloneJSON } from "discourse-common/lib/object"; +import wizardJson from "../fixtures/wizard"; + +const update = cloneJSON(updateJson); +update.wizard = cloneJSON(wizardJson); + +const saveStep = function(response) { + return { + verb: "put", + path: '/w/wizard/steps/:step_id', + status: 200, + response + } +} + +export { + saveStep, + update +} diff --git a/assets/javascripts/wizard/tests/helpers/test.js.es6 b/assets/javascripts/wizard/tests/helpers/test.js.es6 new file mode 100644 index 00000000..c7401fd5 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/test.js.es6 @@ -0,0 +1,7 @@ +function exists(selector) { + return document.querySelector(selector) !== null; +} + +export { + exists +} diff --git a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 new file mode 100644 index 00000000..997f6c36 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 @@ -0,0 +1,52 @@ +import wizardJson from "../fixtures/wizard"; +import userJson from "../fixtures/user"; +import categoriesJson from "../fixtures/categories"; +import groupsJson from "../fixtures/groups"; +import { cloneJSON } from "discourse-common/lib/object"; + +const wizardNoUser = cloneJSON(wizardJson); +const wizard = cloneJSON(wizardJson); +wizard.user = cloneJSON(userJson); + +const wizardNotPermitted = cloneJSON(wizard); +wizardNotPermitted.permitted = false; + +const wizardCompleted = cloneJSON(wizard); +wizardCompleted.completed = true; + +wizard.start = "step_1"; +wizard.resume_on_revisit = false; +wizard.submission_last_updated_at = "2022-03-11T20:00:18+01:00"; +wizard.subscribed = false; + +const stepNotPermitted = cloneJSON(wizard); +stepNotPermitted.steps[0].permitted = false; + +const allFieldsWizard = cloneJSON(wizard); +allFieldsWizard.steps[0].fields = [ + ...allFieldsWizard.steps[0].fields, + ...allFieldsWizard.steps[1].fields, + ...allFieldsWizard.steps[2].fields +]; +allFieldsWizard.steps = [cloneJSON(allFieldsWizard.steps[0])]; +allFieldsWizard.categories = cloneJSON(categoriesJson['categories']); +allFieldsWizard.groups = cloneJSON(groupsJson['groups']); + +const getWizard = function(response) { + return { + verb: "get", + path: "/w/wizard", + status: 200, + response + } +} + +export { + getWizard, + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + stepNotPermitted, + allFieldsWizard, + wizard +} diff --git a/assets/javascripts/wizard/tests/pretender.js.es6 b/assets/javascripts/wizard/tests/pretender.js.es6 new file mode 100644 index 00000000..88eae666 --- /dev/null +++ b/assets/javascripts/wizard/tests/pretender.js.es6 @@ -0,0 +1,53 @@ +import Pretender from "pretender"; + +function parsePostData(query) { + const result = {}; + query.split("&").forEach(function (part) { + const item = part.split("="); + const firstSeg = decodeURIComponent(item[0]); + const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg); + + const val = decodeURIComponent(item[1]).replace(/\+/g, " "); + if (m) { + result[m[1]] = result[m[1]] || {}; + result[m[1]][m[2]] = val; + } else { + result[firstSeg] = val; + } + }); + return result; +} + +function response(code, obj) { + if (typeof code === "object") { + obj = code; + code = 200; + } + return [code, { "Content-Type": "application/json" }, obj]; +} + +export { response }; + +export default function (cb) { + let server = new Pretender(); + + if (cb) { + server = cb(server); + } + + server.prepareBody = function (body) { + if (body && typeof body === "object") { + return JSON.stringify(body); + } + return body; + }; + + server.unhandledRequest = function (verb, path, request) { + const error = + "Unhandled request in test environment: " + path + " (" + verb + ")"; + window.console.error(error); + throw error; + }; + + return server; +} diff --git a/assets/stylesheets/wizard/custom/composer.scss b/assets/stylesheets/wizard/custom/composer.scss index be866b8a..aea17d63 100644 --- a/assets/stylesheets/wizard/custom/composer.scss +++ b/assets/stylesheets/wizard/custom/composer.scss @@ -46,7 +46,7 @@ position: relative; } -.wizard-field-composer.show-preview .d-editor-textarea-wrapper { +.wizard-field-composer.show-preview .d-editor-textarea-column { display: none; } diff --git a/config/routes.rb b/config/routes.rb index 28fcbb82..1948b799 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true CustomWizard::Engine.routes.draw do + get 'qunit' => 'wizard#qunit' get ':wizard_id' => 'wizard#index' put ':wizard_id/skip' => 'wizard#skip' get ':wizard_id/steps' => 'wizard#index' diff --git a/lib/custom_wizard/extensions/extra_locales_controller.rb b/lib/custom_wizard/extensions/extra_locales_controller.rb index 6242f7ca..f6672f4a 100644 --- a/lib/custom_wizard/extensions/extra_locales_controller.rb +++ b/lib/custom_wizard/extensions/extra_locales_controller.rb @@ -6,6 +6,7 @@ module ExtraLocalesControllerCustomWizard path = URI(request.referer).path wizard_path = path.split('/w/').last wizard_id = wizard_path.split('/').first + return true if wizard_id == "qunit" CustomWizard::Template.exists?(wizard_id.underscore) end end diff --git a/lib/custom_wizard/validators/update.rb b/lib/custom_wizard/validators/update.rb index 2301760f..d71bded1 100644 --- a/lib/custom_wizard/validators/update.rb +++ b/lib/custom_wizard/validators/update.rb @@ -20,7 +20,7 @@ class ::CustomWizard::UpdateValidator field_id = field.id.to_s value = @updater.submission[field_id] min_length = false - label = field.raw[:label] || I18n.t("#{field.key}.label") + label = field.raw[:label] type = field.type required = field.required min_length = field.min_length if is_text_type(field) diff --git a/plugin.rb b/plugin.rb index a1b377ac..6f5719fb 100644 --- a/plugin.rb +++ b/plugin.rb @@ -21,9 +21,8 @@ config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" if Rails.env.production? config.assets.precompile += %w{ wizard-custom-guest.js - wizard-custom-globals.js - wizard-custom.js wizard-custom-start.js + wizard-custom.js wizard-plugin.js.erb wizard-raw-templates.js.erb } From a2106bf592447d4073af50534b98b2725f4866b4 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:09:23 +0100 Subject: [PATCH 249/556] Update workflow to add frontend tests && handle deprecations --- .github/workflows/plugin-tests.yml | 16 +------- .../components/custom-user-selector.js.es6 | 3 +- .../components/wizard-field-category.js.es6 | 5 ++- .../components/wizard-field-composer.js.es6 | 5 ++- .../wizard/components/wizard-step.js.es6 | 6 +-- .../components/wizard-text-field.js.es6 | 3 +- .../lib/initialize/inject-objects.js.es6 | 37 +++++++++++-------- .../lib/initialize/patch-components.js.es6 | 16 ++++++++ .../wizard/lib/initialize/wizard.js.es6 | 1 + .../javascripts/wizard/lib/load-script.js.es6 | 16 ++++---- .../javascripts/wizard/lib/text-lite.js.es6 | 16 +++++--- .../javascripts/wizard/lib/user-search.js.es6 | 3 +- assets/javascripts/wizard/models/step.js.es6 | 3 +- .../javascripts/wizard/models/wizard.js.es6 | 3 +- .../components/wizard-composer-editor.hbs | 2 +- assets/javascripts/wizard/templates/step.hbs | 5 +-- 16 files changed, 82 insertions(+), 58 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 782ebc4f..baf5e381 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -73,18 +73,6 @@ jobs: ref: "${{ github.base_ref }}" fetch-depth: 1 - - name: Check spec existence - id: check_spec - uses: andstor/file-existence-action@v1 - with: - files: "plugins/${{ steps.repo-name.outputs.value }}/spec" - - - name: Check qunit existence - id: check_qunit - uses: andstor/file-existence-action@v1 - with: - files: "plugins/${{ steps.repo-name.outputs.value }}/test/javascripts" - - name: Setup Git run: | git config --global user.email "ci@ci.invalid" @@ -140,7 +128,7 @@ jobs: bin/rake db:migrate - name: Plugin RSpec with Coverage - if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' + if: matrix.build_type == 'backend' run: | if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ] then @@ -150,6 +138,6 @@ jobs: bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] - name: Plugin QUnit - if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' + if: matrix.build_type == 'frontend' run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000'] timeout-minutes: 30 diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index 1698a008..6538cb42 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -7,6 +7,7 @@ import userSearch from "../lib/user-search"; import WizardI18n from "../lib/wizard-i18n"; import Handlebars from "handlebars"; import { isEmpty } from "@ember/utils"; +import TextField from "@ember/component/text-field"; const template = function (params) { const options = params.options; @@ -31,7 +32,7 @@ const template = function (params) { return new Handlebars.SafeString(html).string; }; -export default Ember.TextField.extend({ +export default TextField.extend({ attributeBindings: ["autofocus", "maxLength"], autocorrect: false, autocapitalize: false, diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index dc20538e..441f83d3 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -1,8 +1,9 @@ import { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; +import Component from "@ember/component"; -export default Ember.Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-category', +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-category", didInsertElement() { const property = this.field.property || "id"; diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 0ef5fe20..255982ea 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -3,9 +3,10 @@ import { observes, } from "discourse-common/utils/decorators"; import EmberObject from "@ember/object"; +import Component from "@ember/component"; -export default Ember.Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-composer', +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-composer", showPreview: false, classNameBindings: [ diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 index 4518afee..aa6a25bb 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -87,7 +87,7 @@ export default Component.extend({ @observes("step.message") _handleMessage: function () { const message = this.get("step.message"); - this.sendAction("showMessage", message); + this.showMessage(message); }, keyPress(event) { @@ -162,7 +162,7 @@ export default Component.extend({ if (response["final"]) { CustomWizard.finished(response); } else { - this.sendAction("goNext", response); + this.goNext(response); } }) .catch(() => this.animateInvalidFields()) @@ -181,7 +181,7 @@ export default Component.extend({ }, showMessage(message) { - this.sendAction("showMessage", message); + this.sendAction(message); }, stylingDropdownChanged(id, value) { diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/wizard/components/wizard-text-field.js.es6 index 3d87be09..5991eefc 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-text-field.js.es6 @@ -1,8 +1,9 @@ import computed from "discourse-common/utils/decorators"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import WizardI18n from "../lib/wizard-i18n"; +import TextField from "@ember/component/text-field"; -export default Ember.TextField.extend({ +export default TextField.extend({ attributeBindings: [ "autocorrect", "autocapitalize", diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 index d31efb4d..bd231ff9 100644 --- a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -1,5 +1,11 @@ export default { - run(app, container) { + run(app) { + // siteSettings must always be registered first + if (!app.hasRegistration("site-settings:main")) { + const siteSettings = app.SiteSettings; + app.register("site-settings:main", siteSettings, { instantiate: false }); + } + const Store = requirejs("discourse/services/store").default; const Site = requirejs( "discourse/plugins/discourse-custom-wizard/wizard/models/site" @@ -7,12 +13,13 @@ export default { const Session = requirejs("discourse/models/session").default; const RestAdapter = requirejs("discourse/adapters/rest").default; const messageBus = requirejs("message-bus-client").default; - const sniffCapabilites = requirejs("discourse/pre-initializers/sniff-capabilities").default; + const sniffCapabilites = requirejs( + "discourse/pre-initializers/sniff-capabilities" + ).default; + const site = Site.current(); const session = Session.current(); - const registrations = [ - ["site-settings:main", app.SiteSettings, false], ["message-bus:main", messageBus, false], ["site:main", site, false], ["session:main", session, false], @@ -26,18 +33,18 @@ export default { } }); - const targets = ["controller", "component", "route", "model", "adapter", "mixin"]; - const injections = [ - ["siteSettings", "site-settings:main"], - ["messageBus", "message-bus:main"], - ["site", "site:main"], - ["session", "session:main"], - ["store", "service:store"], - ["appEvents", "service:app-events"] - ]; + const targets = ["controller", "component", "route", "model", "adapter"]; - injections.forEach(injection => { - targets.forEach((t) => app.inject(t, injection[0], injection[1])); + targets.forEach((t) => { + app.inject(t, "appEvents", "service:app-events"); + app.inject(t, "store", "service:store"); + app.inject(t, "site", "site:main"); + }); + + targets.concat("service").forEach((t) => { + app.inject(t, "session", "session:main"); + app.inject(t, "messageBus", "message-bus:main"); + app.inject(t, "siteSettings", "site-settings:main"); }); if (!app.hasRegistration("capabilities:main")) { diff --git a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 529a4cd9..10bbf700 100644 --- a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -20,6 +20,11 @@ export default { const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const toMarkdown = requirejs("discourse/lib/to-markdown").default; + const discourseComputed = requirejs("discourse-common/utils/decorators") + .default; + const WizardI18n = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" + ).default; const isInside = (text, regex) => { const matches = text.match(regex); return matches && matches.length % 2; @@ -44,6 +49,17 @@ export default { } }, + @discourseComputed("placeholder", "placeholderOverride") + placeholderTranslated(placeholder, placeholderOverride) { + if (placeholderOverride) { + return placeholderOverride; + } + if (placeholder) { + return WizardI18n(placeholder); + } + return null; + }, + _wizardInsertText(args = {}) { if (args.fieldId === this.fieldId) { this.insertText(args.text, args.options); diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 index 134257a1..20650ec9 100644 --- a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -35,6 +35,7 @@ export default { const Session = requirejs("discourse/models/session").default; const session = Session.current(); session.set("highlightJsPath", setupData.highlightJsPath); + session.set("markdownItUrl", setupData.markdownItUrl); [ 'register-files', diff --git a/assets/javascripts/wizard/lib/load-script.js.es6 b/assets/javascripts/wizard/lib/load-script.js.es6 index 43d97d0a..d0b07c26 100644 --- a/assets/javascripts/wizard/lib/load-script.js.es6 +++ b/assets/javascripts/wizard/lib/load-script.js.es6 @@ -1,5 +1,7 @@ import { ajax } from "wizard/lib/ajax"; -import getURL from "discourse-common/lib/get-url"; +import getURL, { getURLWithCDN } from "discourse-common/lib/get-url"; +import { run } from "@ember/runloop"; +import { Promise } from "rsvp"; const _loaded = {}; const _loading = {}; @@ -25,7 +27,7 @@ function loadWithTag(path, cb) { ) { s = s.onload = s.onreadystatechange = null; if (!abort) { - Ember.run(null, cb); + run(null, cb); } } }; @@ -38,7 +40,7 @@ export function loadCSS(url) { export default function loadScript(url, opts) { // TODO: Remove this once plugins have been updated not to use it: if (url === "defer/html-sanitizer-bundle") { - return Ember.RSVP.Promise.resolve(); + return Promise.resolve(); } opts = opts || {}; @@ -51,7 +53,7 @@ export default function loadScript(url, opts) { } }); - return new Ember.RSVP.Promise(function (resolve) { + return new Promise(function (resolve) { url = getURL(url); // If we already loaded this url @@ -63,7 +65,7 @@ export default function loadScript(url, opts) { } let done; - _loading[url] = new Ember.RSVP.Promise(function (_done) { + _loading[url] = new Promise(function (_done) { done = _done; }); @@ -84,8 +86,8 @@ export default function loadScript(url, opts) { // Scripts should always load from CDN // CSS is type text, to accept it from a CDN we would need to handle CORS - if (!opts.css && Discourse.CDN && url[0] === "/" && url[1] !== "/") { - cdnUrl = Discourse.CDN.replace(/\/$/, "") + url; + if (!opts.css) { + cdnUrl = getURLWithCDN(url); } // Some javascript depends on the path of where it is loaded (ace editor) diff --git a/assets/javascripts/wizard/lib/text-lite.js.es6 b/assets/javascripts/wizard/lib/text-lite.js.es6 index cc161426..26cfc27a 100644 --- a/assets/javascripts/wizard/lib/text-lite.js.es6 +++ b/assets/javascripts/wizard/lib/text-lite.js.es6 @@ -3,6 +3,8 @@ import { default as PrettyText, buildOptions } from "pretty-text/pretty-text"; import Handlebars from "handlebars"; import getURL from "discourse-common/lib/get-url"; import { getOwner } from "discourse-common/lib/get-owner"; +import { Promise } from "rsvp"; +import Session from "discourse/models/session"; export function cook(text, options) { if (!options) { @@ -18,11 +20,15 @@ export function cook(text, options) { // everything should eventually move to async API and this should be renamed // cook export function cookAsync(text, options) { - if (Discourse.MarkdownItURL) { - return loadScript(Discourse.MarkdownItURL) - .then(() => cook(text, options)) - .catch((e) => Ember.Logger.error(e)); + let markdownItURL = Session.currentProp("markdownItURL"); + if (markdownItURL) { + return ( + loadScript(markdownItURL) + .then(() => cook(text, options)) + // eslint-disable-next-line no-console + .catch((e) => console.error(e)) + ); } else { - return Ember.RSVP.Promise.resolve(cook(text)); + return Promise.resolve(cook(text)); } } diff --git a/assets/javascripts/wizard/lib/user-search.js.es6 b/assets/javascripts/wizard/lib/user-search.js.es6 index e7171f18..04b6f97c 100644 --- a/assets/javascripts/wizard/lib/user-search.js.es6 +++ b/assets/javascripts/wizard/lib/user-search.js.es6 @@ -1,6 +1,7 @@ import { CANCELLED_STATUS } from "discourse/lib/autocomplete"; import { debounce } from "@ember/runloop"; import getUrl from "discourse-common/lib/get-url"; +import { Promise } from "rsvp"; let cache = {}, cacheTopicId, @@ -120,7 +121,7 @@ export default function userSearch(options) { currentTerm = term; - return new Ember.RSVP.Promise(function (resolve) { + return new Promise(function (resolve) { // TODO site setting for allowed regex in username if (term.match(/[^\w_\-\.@\+]/)) { resolve([]); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 index e18657b5..b0829312 100644 --- a/assets/javascripts/wizard/models/step.js.es6 +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -3,6 +3,7 @@ import ValidState from "wizard/mixins/valid-state"; import { ajax } from "wizard/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; +import { later } from "@ember/runloop"; export default EmberObject.extend(ValidState, { id: null, @@ -109,6 +110,6 @@ export default EmberObject.extend(ValidState, { state: "error", text: message, }); - Ember.run.later(() => this.set("message", null), 6000); + later(() => this.set("message", null), 6000); }, }); diff --git a/assets/javascripts/wizard/models/wizard.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 index 7fbe2c10..bc2bff14 100644 --- a/assets/javascripts/wizard/models/wizard.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -112,8 +112,7 @@ CustomWizard.reopenClass({ } }); - Site.currentProp("categoriesList", categories); - Site.currentProp("sortedCategories", categories); + Site.currentProp("categories", categories); Site.currentProp("listByActivity", categories); Site.currentProp("categoriesById", categoriesById); Site.currentProp( diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs index be98db8e..7d453a0b 100644 --- a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs @@ -1,7 +1,7 @@ {{d-editor tabindex=field.tabindex value=composer.reply - placeholderTranslated=replyPlaceholder + placeholderOverride=replyPlaceholder previewUpdated=(action "previewUpdated") markdownOptions=markdownOptions extraButtons=(action "extraButtons") diff --git a/assets/javascripts/wizard/templates/step.hbs b/assets/javascripts/wizard/templates/step.hbs index 6456a59c..5ed14cdf 100644 --- a/assets/javascripts/wizard/templates/step.hbs +++ b/assets/javascripts/wizard/templates/step.hbs @@ -13,8 +13,7 @@ {{#if step.permitted}} {{wizard-step step=step wizard=wizard - goNext="goNext" + goNext=(action "goNext") goBack=(action "goBack") - finished="finished" - showMessage="showMessage"}} + showMessage=(action "showMessage")}} {{/if}} From 39defec8978ecaf96a30d86a3b807ad6a0e7e347 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 16 Mar 2022 14:10:54 +0100 Subject: [PATCH 250/556] Remove duplicated functions --- assets/javascripts/wizard/components/validator.js.es6 | 1 - assets/javascripts/wizard/components/wizard-step.js.es6 | 2 -- 2 files changed, 3 deletions(-) diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index ab442d97..cd1c3abe 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -11,7 +11,6 @@ export default Component.extend({ invalidMessageKey: null, isValid: null, isInvalid: equal("isValid", false), - layoutName: "wizard/templates/components/validator", init() { this._super(...arguments); diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 index aa6a25bb..44fcde26 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -169,8 +169,6 @@ export default Component.extend({ .finally(() => this.set("saving", false)); }, - keyPress() {}, - actions: { quit() { this.get("wizard").skip(); From 8fd07322d01af6be718aa59d798c2070b8ca39be Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 11:02:21 +0200 Subject: [PATCH 251/556] Minor config updates --- .github/workflows/plugin-tests.yml | 12 +- .../javascripts/wizard/tests/bootstrap.js.es6 | 7 + yarn.lock | 1639 +++++++++++++++++ 3 files changed, 1650 insertions(+), 8 deletions(-) create mode 100644 yarn.lock diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index baf5e381..f8105755 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -56,20 +56,16 @@ jobs: if_true: "stable" if_false: "tests-passed" - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: repository: discourse/discourse ref: ${{ steps.discourse_branch.outputs.value }} fetch-depth: 1 - - name: Fetch Repo Name - id: repo-name - run: echo "::set-output name=value::$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" - - name: Install plugin uses: actions/checkout@v2 with: - path: plugins/${{ steps.repo-name.outputs.value }} + path: plugins/${{ github.event.repository.name }} ref: "${{ github.base_ref }}" fetch-depth: 1 @@ -139,5 +135,5 @@ jobs: - name: Plugin QUnit if: matrix.build_type == 'frontend' - run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000'] - timeout-minutes: 30 + run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000','/w/qunit'] + timeout-minutes: 10 diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index d2c503ad..8579e884 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,4 +1,5 @@ // discourse-skip-module +/*global document, Logster, QUnit */ document.addEventListener("DOMContentLoaded", function () { document.body.insertAdjacentHTML( @@ -15,3 +16,9 @@ Object.keys(requirejs.entries).forEach(function (entry) { requirejs(entry); } }); + +if (window.Logster) { + Logster.enabled = false; +} else { + window.Logster = { enabled: false }; +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..b9d7fc0e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1639 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/generator@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" + integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/highlight@^7.16.7": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" + integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.16.7", "@babel/parser@^7.17.9", "@babel/parser@^7.7.0": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" + integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== + +"@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.7.0": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" + integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.9" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.7.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@ember-data/rfc395-data@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843" + integrity sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ== + +"@glimmer/env@0.1.7", "@glimmer/env@^0.1.7": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@glimmer/env/-/env-0.1.7.tgz#fd2d2b55a9029c6b37a6c935e8c8871ae70dfa07" + integrity sha1-/S0rVakCnGs3psk16MiHGucN+gc= + +"@glimmer/global-context@0.65.4": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/global-context/-/global-context-0.65.4.tgz#1da1d59dd4260ce912c40e474cd39c2e82de51b8" + integrity sha512-RSYCPG/uVR5XCDcPREBclncU7R0zkjACbADP+n3FWAH1TfWbXRMDIkvO/ZlwHkjHoCZf6tIM6p5S/MoFzfJEJA== + dependencies: + "@glimmer/env" "^0.1.7" + +"@glimmer/interfaces@0.65.4": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.65.4.tgz#d298cc2b12b8ebcf269f39246ca7ab92816f6680" + integrity sha512-R0kby79tGNKZOojVJa/7y0JH9Eq4SV+L1s6GcZy30QUZ1g1AAGS5XwCIXc9Sc09coGcv//q+6NLeSw7nlx1y4A== + dependencies: + "@simple-dom/interface" "^1.4.0" + +"@glimmer/reference@^0.65.0": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.65.4.tgz#bbc8becd6a1bf01fc189b6489e27446437194711" + integrity sha512-yuRVE4qyqrlCndDMrHKDWUbDmGDCjPzsFtlTmxxnhDMJAdQsnr2cRLITHvQRDm1tXfigVvyKnomeuYhRRbBqYQ== + dependencies: + "@glimmer/env" "^0.1.7" + "@glimmer/global-context" "0.65.4" + "@glimmer/interfaces" "0.65.4" + "@glimmer/util" "0.65.4" + "@glimmer/validator" "0.65.4" + +"@glimmer/syntax@^0.65.0": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.65.4.tgz#49164de5dc9e8b67084ec009bdd865e379d8a971" + integrity sha512-y+/C3e8w96efk3a/Z5If9o4ztKJwrr8RtDpbhV2J8X+DUsn5ic2N3IIdlThbt/Zn6tkP1K3dY6uaFUx3pGTvVQ== + dependencies: + "@glimmer/interfaces" "0.65.4" + "@glimmer/util" "0.65.4" + "@handlebars/parser" "^1.1.0" + simple-html-tokenizer "^0.5.10" + +"@glimmer/util@0.65.4": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.65.4.tgz#e464145078f3f40da9013ff2590a6016515455d2" + integrity sha512-aofe+rdBhkREKP2GZta6jy1UcbRRMfWx7M18zxGxspPoeD08NscD04Kx+WiOKXmC1TcrfITr8jvqMfrKrMzYWQ== + dependencies: + "@glimmer/env" "0.1.7" + "@glimmer/interfaces" "0.65.4" + "@simple-dom/interface" "^1.4.0" + +"@glimmer/validator@0.65.4", "@glimmer/validator@^0.65.0": + version "0.65.4" + resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.65.4.tgz#12c27a9a63706c60e6499fd687940e9d1affb32c" + integrity sha512-0YUjAyo45DF5JkQxdv5kHn96nMNhvZiEwsAD4Jme0kk5Q9MQcPOUtN76pQAS4f+C6GdF9DeUr2yGXZLFMmb+LA== + dependencies: + "@glimmer/env" "^0.1.7" + "@glimmer/global-context" "0.65.4" + +"@handlebars/parser@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-1.1.0.tgz#d6dbc7574774b238114582410e8fee0dc3532bdf" + integrity sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@simple-dom/interface@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@simple-dom/interface/-/interface-1.4.0.tgz#e8feea579232017f89b0138e2726facda6fbb71f" + integrity sha512-l5qumKFWU0S+4ZzMaLXFU8tQZsicHEMEyAxI5kDFGhJsRqDwe0a7/iPA/GdxlGyDKseQQAgIz5kzU7eXTrlSpA== + +acorn-jsx@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-promise-queue@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/async-promise-queue/-/async-promise-queue-1.0.5.tgz#cb23bce9fce903a133946a700cc85f27f09ea49d" + integrity sha512-xi0aQ1rrjPWYmqbwr18rrSKbSaXIeIwSd1J4KAgVfkq8utNbdZoht7GfvfY6swFUAMJ9obkc4WPJmtGwl+B8dw== + dependencies: + async "^2.4.1" + debug "^2.6.8" + +async@^2.4.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0, chalk@^2.1.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^4.0.1, debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +ember-rfc176-data@^0.3.11: + version "0.3.17" + resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.17.tgz#d4fc6c33abd6ef7b3440c107a28e04417b49860a" + integrity sha512-EVzTTKqxv9FZbEh6Ktw56YyWRAA0MijKvl7H8C06wVF+8f/cRRz3dXxa4nkwjzyVwx4rzKGuIGq77hxJAQhWWw== + +ember-template-lint-plugin-discourse@latest: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ember-template-lint-plugin-discourse/-/ember-template-lint-plugin-discourse-2.0.0.tgz#9805dff60763fae68b5df82b92fb431eb739c13e" + integrity sha512-2bPz/47OfuYGj4w2RNyDcXCYA/4JtRAXRIsaA6PTm4Uc44exK/GBd4RfT2ywmq0CImvj2kGkqpuUgkAtVf6aZQ== + +ember-template-lint@^2.11.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/ember-template-lint/-/ember-template-lint-2.21.0.tgz#7e120abf309a8810eeed26c52377943faf15a95b" + integrity sha512-19QbEqJQdMfcRS7PsQsubflRowEtnkbD0tpYR4q/xq4lodmhU7hhOFvlTQgbxD/jwW5Ur+tkOwH4KFy9JwOyXA== + dependencies: + chalk "^4.0.0" + ember-template-recast "^5.0.1" + find-up "^5.0.0" + fuse.js "^6.4.6" + get-stdin "^8.0.0" + globby "^11.0.2" + is-glob "^4.0.1" + micromatch "^4.0.2" + resolve "^1.20.0" + v8-compile-cache "^2.2.0" + yargs "^16.2.0" + +ember-template-recast@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/ember-template-recast/-/ember-template-recast-5.0.3.tgz#79df27a70bdce7be17f14db13886afde1e9d02d6" + integrity sha512-qsJYQhf29Dk6QMfviXhUPE+byMOs6iRQxUDHgkj8yqjeppvjHaFG96hZi/NAXJTm/M7o3PpfF5YlmeaKtI9UeQ== + dependencies: + "@glimmer/reference" "^0.65.0" + "@glimmer/syntax" "^0.65.0" + "@glimmer/validator" "^0.65.0" + async-promise-queue "^1.0.5" + colors "^1.4.0" + commander "^6.2.1" + globby "^11.0.3" + ora "^5.4.0" + slash "^3.0.0" + tmp "^0.2.1" + workerpool "^6.1.4" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-discourse@^1.1.8: + version "1.1.9" + resolved "https://registry.yarnpkg.com/eslint-config-discourse/-/eslint-config-discourse-1.1.9.tgz#9a5ee6b3a4b986e5243f517e7945d1709c4e22df" + integrity sha512-a4KG+/9/7ZhYVV0URGK70K7QtxlydYKjie0ZssHEGxl2auOUTDcdRMBfZQBtIxQr9X8TF0+eeUUsScBXNU6xZw== + dependencies: + babel-eslint "^10.1.0" + ember-template-lint "^2.11.0" + ember-template-lint-plugin-discourse latest + eslint "^6.8.0" + eslint-plugin-discourse-ember latest + eslint-plugin-ember "^6.10.0" + eslint-plugin-lodash "^7.1.0" + eslint-plugin-node "^8.0.0" + prettier "2.2.1" + +eslint-plugin-discourse-ember@latest: + version "0.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-discourse-ember/-/eslint-plugin-discourse-ember-0.0.3.tgz#69e5876c2ece38ab3d6c4a05f0a20a7dc4c21e37" + integrity sha512-EFwWU4FlOSLBa4RolKZL8QD1eGOnvSkACLt4Big+o1ZUIpW7gGvfnJPtxkbaQ4XmhtZ5HetYt6862vVqhUMv9A== + dependencies: + requireindex "~1.1.0" + +eslint-plugin-ember@^6.10.0: + version "6.10.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-6.10.1.tgz#ca7a5cc28b91a247c31b1686421a66281467f238" + integrity sha512-RZI0+UoR4xeD6UE3KQCUwbN2nZOIIPaFCCXqBIRXDr0rFuwvknAHqYtDPJVZicvTzNHa4TEZvAKqfbE8t7SztQ== + dependencies: + "@ember-data/rfc395-data" "^0.0.4" + ember-rfc176-data "^0.3.11" + snake-case "^2.1.0" + +eslint-plugin-es@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998" + integrity sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA== + dependencies: + eslint-utils "^1.4.2" + regexpp "^2.0.1" + +eslint-plugin-lodash@^7.1.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-lodash/-/eslint-plugin-lodash-7.4.0.tgz#14a761547f126c92ff56789662a20a44f8bb6290" + integrity sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A== + dependencies: + lodash "^4.17.21" + +eslint-plugin-node@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz#55ae3560022863d141fa7a11799532340a685964" + integrity sha512-ZjOjbjEi6jd82rIpFSgagv4CHWzG9xsQAVp1ZPlhRnnYxcTgENUVBvhYmkQ7GvT1QFijUSo69RaiOJKhMu6i8w== + dependencies: + eslint-plugin-es "^1.3.1" + eslint-utils "^1.3.1" + ignore "^5.0.2" + minimatch "^3.0.4" + resolve "^1.8.1" + semver "^5.5.0" + +eslint-scope@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^1.3.1, eslint-utils@^1.4.2, eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +fuse.js@^6.4.6: + version "6.5.3" + resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.5.3.tgz#7446c0acbc4ab0ab36fa602e97499bdb69452b93" + integrity sha512-sA5etGE7yD/pOqivZRBvUBd/NaL2sjAu6QuSaFoe1H2BrJSkH/T/UXAJ8CdXdw7DvY3Hs8CXKYkDWX7RiP5KOg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +glob-parent@^5.0.0, glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@^11.0.2, globby@^11.0.3: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.0.2, ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^7.0.0: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + +lru-cache@^7.4.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.1.tgz#68ee3f4807a57d2ba185b7fd90827d5c21ce82bb" + integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +mkdirp@^0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +ora@^5.4.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" + integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +requireindex@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162" + integrity sha1-5UBLgVV+91225JxacgBIk/4D4WI= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.12.0, resolve@^1.20.0, resolve@^1.8.1: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.5: + version "7.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" + integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== + dependencies: + lru-cache "^7.4.0" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +simple-html-tokenizer@^0.5.10: + version "0.5.11" + resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.5.11.tgz#4c5186083c164ba22a7b477b7687ac056ad6b1d9" + integrity sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +snake-case@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" + integrity sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8= + dependencies: + no-case "^2.2.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +workerpool@^6.1.4: + version "6.2.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" + integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 41cfdfb13576da07ff2dccd90c38c1e7a73ee10c Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 15:16:29 +0200 Subject: [PATCH 252/556] Fix tests --- .github/workflows/plugin-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index f8105755..497486ea 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -66,7 +66,7 @@ jobs: uses: actions/checkout@v2 with: path: plugins/${{ github.event.repository.name }} - ref: "${{ github.base_ref }}" + ref: "${{ github.head_ref }}" fetch-depth: 1 - name: Setup Git @@ -135,5 +135,5 @@ jobs: - name: Plugin QUnit if: matrix.build_type == 'frontend' - run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000','/w/qunit'] + run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 QUNIT_SKIP_CORE=1 bin/rake qunit:test['600000','/w/qunit'] timeout-minutes: 10 From 576d96b2ba55db2a16d50641f0da233ac9ffafe2 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 15 Jun 2022 08:36:40 +0200 Subject: [PATCH 253/556] FIX: we're no longer extending the application controller --- app/controllers/custom_wizard/wizard.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index 43db5df8..11df97d7 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -44,7 +44,7 @@ class CustomWizard::WizardController < ::ActionController::Base return render json: { error: I18n.t('wizard.no_skip') } end - result = success_json + result = { success: 'OK' } if current_user && wizard.can_access? submission = wizard.current_submission From 3abb65294c7f8c2635fbbdc745bb385512b5cce3 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 11 Apr 2022 12:17:11 +0200 Subject: [PATCH 254/556] re-add qunit bootstrap conditional --- .../javascripts/wizard/tests/bootstrap.js.es6 | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index 8579e884..29cdfe80 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,24 +1,26 @@ // discourse-skip-module /*global document, Logster, QUnit */ -document.addEventListener("DOMContentLoaded", function () { - document.body.insertAdjacentHTML( - "afterbegin", - ` -
- - ` - ); -}); +if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { + document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); + }); -Object.keys(requirejs.entries).forEach(function (entry) { - if (/\-test/.test(entry)) { - requirejs(entry); + Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); + } + }); + + if (window.Logster) { + Logster.enabled = false; + } else { + window.Logster = { enabled: false }; } -}); - -if (window.Logster) { - Logster.enabled = false; -} else { - window.Logster = { enabled: false }; } From f3c5eeb371f25bc664980f21bc23f52f7feed896 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 15 Jun 2022 08:59:09 +0200 Subject: [PATCH 255/556] Fix linting --- .../components/custom-user-selector.js.es6 | 2 +- .../wizard/components/field-validators.js.es6 | 2 +- .../similar-topics-validator.js.es6 | 2 +- .../wizard/components/validator.js.es6 | 2 +- .../components/wizard-composer-editor.js.es6 | 2 +- .../wizard-composer-hyperlink.js.es6 | 2 +- .../components/wizard-date-input.js.es6 | 2 +- .../components/wizard-date-time-input.js.es6 | 2 +- .../components/wizard-field-checkbox.js.es6 | 2 +- .../wizard-field-composer-preview.js.es6 | 2 +- .../components/wizard-field-date-time.js.es6 | 2 +- .../components/wizard-field-date.js.es6 | 2 +- .../components/wizard-field-dropdown.js.es6 | 2 +- .../components/wizard-field-group.js.es6 | 2 +- .../components/wizard-field-number.js.es6 | 2 +- .../wizard/components/wizard-field-tag.js.es6 | 2 +- .../components/wizard-field-text.js.es6 | 2 +- .../components/wizard-field-textarea.js.es6 | 2 +- .../components/wizard-field-time.js.es6 | 2 +- .../components/wizard-field-upload.js.es6 | 2 +- .../wizard/components/wizard-field-url.js.es6 | 2 +- .../wizard-field-user-selector.js.es6 | 2 +- .../wizard/components/wizard-field.js.es6 | 9 +- .../components/wizard-group-selector.js.es6 | 2 +- .../wizard/components/wizard-no-access.js.es6 | 8 +- .../components/wizard-similar-topics.js.es6 | 2 +- .../wizard/components/wizard-step.js.es6 | 4 +- .../components/wizard-time-input.js.es6 | 2 +- .../wizard/controllers/wizard-index.js.es6 | 14 +- .../lib/initialize/create-contexts.js.es6 | 6 +- .../lib/initialize/inject-objects.js.es6 | 12 +- .../lib/initialize/patch-components.js.es6 | 10 +- .../lib/initialize/register-files.js.es6 | 14 +- .../wizard/lib/initialize/wizard.js.es6 | 29 +- assets/javascripts/wizard/models/field.js.es6 | 2 +- assets/javascripts/wizard/models/step.js.es6 | 6 +- assets/javascripts/wizard/routes/step.js.es6 | 2 +- .../wizard/routes/wizard-index.js.es6 | 12 +- .../javascripts/wizard/routes/wizard.js.es6 | 5 +- .../wizard/tests/acceptance/field-test.js.es6 | 252 +++--- .../wizard/tests/acceptance/step-test.js.es6 | 78 +- .../tests/acceptance/wizard-test.js.es6 | 105 +-- .../javascripts/wizard/tests/bootstrap.js.es6 | 2 +- .../wizard/tests/fixtures/categories.js.es6 | 400 +++++---- .../wizard/tests/fixtures/groups.js.es6 | 586 ++++++------ .../tests/fixtures/site-settings.js.es6 | 575 ++++++------ .../wizard/tests/fixtures/tags.js.es6 | 36 +- .../wizard/tests/fixtures/update.js.es6 | 8 +- .../wizard/tests/fixtures/user.js.es6 | 4 +- .../wizard/tests/fixtures/users.js.es6 | 20 +- .../wizard/tests/fixtures/wizard.js.es6 | 842 +++++++++--------- .../wizard/tests/helpers/acceptance.js.es6 | 18 +- .../wizard/tests/helpers/start-app.js.es6 | 12 +- .../wizard/tests/helpers/step.js.es6 | 15 +- .../wizard/tests/helpers/test.js.es6 | 4 +- .../wizard/tests/helpers/wizard.js.es6 | 18 +- .../javascripts/wizard/tests/pretender.js.es6 | 20 +- 57 files changed, 1620 insertions(+), 1558 deletions(-) diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index 6538cb42..56eb8f57 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -1,6 +1,6 @@ import { default as computed, - observes + observes, } from "discourse-common/utils/decorators"; import { renderAvatar } from "discourse/helpers/user-avatar"; import userSearch from "../lib/user-search"; diff --git a/assets/javascripts/wizard/components/field-validators.js.es6 b/assets/javascripts/wizard/components/field-validators.js.es6 index 15cfc181..7284241c 100644 --- a/assets/javascripts/wizard/components/field-validators.js.es6 +++ b/assets/javascripts/wizard/components/field-validators.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/field-validators', + layoutName: "wizard/templates/components/field-validators", actions: { perform() { diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 index e5133d4f..4f722123 100644 --- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 +++ b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 @@ -10,7 +10,7 @@ import { dasherize } from "@ember/string"; export default WizardFieldValidator.extend({ classNames: ["similar-topics-validator"], - layoutName: 'wizard/templates/components/similar-topics-validator', + layoutName: "wizard/templates/components/similar-topics-validator", similarTopics: null, hasInput: notEmpty("field.value"), hasSimilarTopics: notEmpty("similarTopics"), diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index cd1c3abe..aa68660c 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -6,7 +6,7 @@ import { getToken } from "wizard/lib/ajax"; export default Component.extend({ classNames: ["validator"], classNameBindings: ["isValid", "isInvalid"], - layoutName: 'wizard/templates/components/validator', + layoutName: "wizard/templates/components/validator", validMessageKey: null, invalidMessageKey: null, isValid: null, diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index e4ce3ec0..2c04cd4f 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -13,7 +13,7 @@ import { uploadIcon } from "discourse/lib/uploads"; import { dasherize } from "@ember/string"; export default ComposerEditor.extend({ - layoutName: 'wizard/templates/components/wizard-composer-editor', + layoutName: "wizard/templates/components/wizard-composer-editor", classNameBindings: ["fieldClass"], allowUpload: true, showLink: false, diff --git a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 index c700d3ce..0eeeb176 100644 --- a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; export default Component.extend({ classNames: ["wizard-composer-hyperlink"], - layoutName: 'wizard/templates/components/wizard-composer-hyperlink', + layoutName: "wizard/templates/components/wizard-composer-hyperlink", actions: { addLink() { diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index 375b7195..da2711c7 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -3,7 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DateInput.extend({ useNativePicker: false, - layoutName: 'wizard/templates/components/wizard-date-input', + layoutName: "wizard/templates/components/wizard-date-input", @discourseComputed() placeholder() { diff --git a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 index 0fe1e68a..84a2b03e 100644 --- a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 @@ -2,7 +2,7 @@ import DateTimeInput from "discourse/components/date-time-input"; import discourseComputed from "discourse-common/utils/decorators"; export default DateTimeInput.extend({ - layoutName: 'wizard/templates/components/wizard-date-time-input', + layoutName: "wizard/templates/components/wizard-date-time-input", @discourseComputed("timeFirst", "tabindex") timeTabindex(timeFirst, tabindex) { diff --git a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 index f9653cd2..6f9daba2 100644 --- a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-checkbox' + layoutName: "wizard/templates/components/wizard-field-checkbox", }); diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 index 0aee0d13..a2056a86 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 @@ -7,7 +7,7 @@ import { ajax } from "discourse/lib/ajax"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-composer-preview', + layoutName: "wizard/templates/components/wizard-field-composer-preview", @on("init") updatePreview() { diff --git a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 index a916f18e..eee98892 100644 --- a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-date-time', + layoutName: "wizard/templates/components/wizard-field-date-time", @observes("dateTime") setValue() { diff --git a/assets/javascripts/wizard/components/wizard-field-date.js.es6 b/assets/javascripts/wizard/components/wizard-field-date.js.es6 index a06d582a..df35638c 100644 --- a/assets/javascripts/wizard/components/wizard-field-date.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-date', + layoutName: "wizard/templates/components/wizard-field-date", @observes("date") setValue() { diff --git a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 index 4b8b7e63..e6b08102 100644 --- a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-dropdown', + layoutName: "wizard/templates/components/wizard-field-dropdown", keyPress(e) { e.stopPropagation(); diff --git a/assets/javascripts/wizard/components/wizard-field-group.js.es6 b/assets/javascripts/wizard/components/wizard-field-group.js.es6 index 93538071..65a19719 100644 --- a/assets/javascripts/wizard/components/wizard-field-group.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-group.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-group' + layoutName: "wizard/templates/components/wizard-field-group", }); diff --git a/assets/javascripts/wizard/components/wizard-field-number.js.es6 b/assets/javascripts/wizard/components/wizard-field-number.js.es6 index e7c4d77f..14e1bfcd 100644 --- a/assets/javascripts/wizard/components/wizard-field-number.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-number.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-number' + layoutName: "wizard/templates/components/wizard-field-number", }); diff --git a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 index 45343522..473bba08 100644 --- a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-tag' + layoutName: "wizard/templates/components/wizard-field-tag", }); diff --git a/assets/javascripts/wizard/components/wizard-field-text.js.es6 b/assets/javascripts/wizard/components/wizard-field-text.js.es6 index f9d5b056..d9e7cca8 100644 --- a/assets/javascripts/wizard/components/wizard-field-text.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-text.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-text', + layoutName: "wizard/templates/components/wizard-field-text", keyPress(e) { e.stopPropagation(); diff --git a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 index 54865d3c..e59a1707 100644 --- a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-textarea', + layoutName: "wizard/templates/components/wizard-field-textarea", keyPress(e) { e.stopPropagation(); diff --git a/assets/javascripts/wizard/components/wizard-field-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-time.js.es6 index bf954ec4..a2f2a10d 100644 --- a/assets/javascripts/wizard/components/wizard-field-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-time.js.es6 @@ -2,7 +2,7 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-time', + layoutName: "wizard/templates/components/wizard-field-time", @observes("time") setValue() { diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 index edadb4f0..4774e942 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 @@ -3,7 +3,7 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend(UppyUploadMixin, { - layoutName: 'wizard/templates/components/wizard-field-upload', + layoutName: "wizard/templates/components/wizard-field-upload", classNames: ["wizard-field-upload"], classNameBindings: ["isImage"], uploading: false, diff --git a/assets/javascripts/wizard/components/wizard-field-url.js.es6 b/assets/javascripts/wizard/components/wizard-field-url.js.es6 index 411b5fe9..96c10cc2 100644 --- a/assets/javascripts/wizard/components/wizard-field-url.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-url.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-url' + layoutName: "wizard/templates/components/wizard-field-url", }); diff --git a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 index c2a32f44..7cf5b446 100644 --- a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 @@ -1,5 +1,5 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field-user-selector' + layoutName: "wizard/templates/components/wizard-field-user-selector", }); diff --git a/assets/javascripts/wizard/components/wizard-field.js.es6 b/assets/javascripts/wizard/components/wizard-field.js.es6 index 372ee182..493d7676 100644 --- a/assets/javascripts/wizard/components/wizard-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -4,8 +4,13 @@ import discourseComputed from "discourse-common/utils/decorators"; import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-field', - classNameBindings: [":wizard-field", "typeClasses", "field.invalid", "field.id"], + layoutName: "wizard/templates/components/wizard-field", + classNameBindings: [ + ":wizard-field", + "typeClasses", + "field.invalid", + "field.id", + ], @discourseComputed("field.type", "field.id") typeClasses: (type, id) => diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 index b7523f9a..4ff56ec9 100644 --- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 @@ -3,7 +3,7 @@ import { computed } from "@ember/object"; import { makeArray } from "discourse-common/lib/helpers"; export default ComboBox.extend({ - layoutName: 'wizard/templates/components/wizard-group-selector', + layoutName: "wizard/templates/components/wizard-group-selector", content: computed("groups.[]", "field.content.[]", function () { const whitelist = makeArray(this.field.content); return this.groups diff --git a/assets/javascripts/wizard/components/wizard-no-access.js.es6 b/assets/javascripts/wizard/components/wizard-no-access.js.es6 index 78a23aa8..492a41dc 100644 --- a/assets/javascripts/wizard/components/wizard-no-access.js.es6 +++ b/assets/javascripts/wizard/components/wizard-no-access.js.es6 @@ -4,17 +4,17 @@ import Component from "@ember/component"; import { dasherize } from "@ember/string"; export default Component.extend({ - classNameBindings: [':wizard-no-access', 'reasonClass'], - layoutName: 'wizard/templates/components/wizard-no-access', + classNameBindings: [":wizard-no-access", "reasonClass"], + layoutName: "wizard/templates/components/wizard-no-access", - @discourseComputed('reason') + @discourseComputed("reason") reasonClass(reason) { return dasherize(reason); }, @discourseComputed siteName() { - return (this.siteSettings.title || ''); + return this.siteSettings.title || ""; }, actions: { diff --git a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 index c52bfeae..6a56873e 100644 --- a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 +++ b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 @@ -4,7 +4,7 @@ import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["wizard-similar-topics"], - layoutName: 'wizard/templates/components/wizard-similar-topics', + layoutName: "wizard/templates/components/wizard-similar-topics", showTopics: true, didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 index 44fcde26..cc23c5bf 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -12,7 +12,7 @@ import CustomWizard from "../models/wizard"; const alreadyWarned = {}; export default Component.extend({ - layoutName: 'wizard/templates/components/wizard-step', + layoutName: "wizard/templates/components/wizard-step", classNameBindings: [":wizard-step", "step.id"], saving: null, @@ -27,7 +27,7 @@ export default Component.extend({ }, @discourseComputed("step.index", "wizard.required") - showQuitButton: (index, required) => (index === 0 && !required), + showQuitButton: (index, required) => index === 0 && !required, showNextButton: not("step.final"), showDoneButton: alias("step.final"), diff --git a/assets/javascripts/wizard/components/wizard-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-time-input.js.es6 index ec121002..14b08288 100644 --- a/assets/javascripts/wizard/components/wizard-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-time-input.js.es6 @@ -1,5 +1,5 @@ import TimeInput from "discourse/components/time-input"; export default TimeInput.extend({ - layoutName: 'wizard/templates/components/wizard-time-input' + layoutName: "wizard/templates/components/wizard-time-input", }); diff --git a/assets/javascripts/wizard/controllers/wizard-index.js.es6 b/assets/javascripts/wizard/controllers/wizard-index.js.es6 index 2dfdee40..f56db02d 100644 --- a/assets/javascripts/wizard/controllers/wizard-index.js.es6 +++ b/assets/javascripts/wizard/controllers/wizard-index.js.es6 @@ -6,19 +6,19 @@ const reasons = { noWizard: "none", requiresLogin: "requires_login", notPermitted: "not_permitted", - completed: "completed" -} + completed: "completed", +}; export default Controller.extend({ - noAccess: or('noWizard', 'requiresLogin', 'notPermitted', 'completed'), + noAccess: or("noWizard", "requiresLogin", "notPermitted", "completed"), - @discourseComputed('noAccessReason') + @discourseComputed("noAccessReason") noAccessI18nKey(reason) { - return reason ? `wizard.${reasons[reason]}` : 'wizard.none'; + return reason ? `wizard.${reasons[reason]}` : "wizard.none"; }, @discourseComputed noAccessReason() { - return Object.keys(reasons).find(reason => this.get(reason)); - } + return Object.keys(reasons).find((reason) => this.get(reason)); + }, }); diff --git a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 index 022ac48e..0e637f6c 100644 --- a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 @@ -3,10 +3,10 @@ export default { const { createHelperContext } = requirejs("discourse-common/lib/helpers"); createHelperContext({ - siteSettings: container.lookup('site-settings:main'), + siteSettings: container.lookup("site-settings:main"), site: container.lookup("site:main"), session: container.lookup("session:main"), capabilities: container.lookup("capabilities:main"), }); - } -} + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 index bd231ff9..51dff7e1 100644 --- a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -24,12 +24,14 @@ export default { ["site:main", site, false], ["session:main", session, false], ["service:store", Store, true], - ["adapter:rest", RestAdapter, true] + ["adapter:rest", RestAdapter, true], ]; - registrations.forEach(registration => { + registrations.forEach((registration) => { if (!app.hasRegistration(registration[0])) { - app.register(registration[0], registration[1], { instantiate: registration[2] }); + app.register(registration[0], registration[1], { + instantiate: registration[2], + }); } }); @@ -52,5 +54,5 @@ export default { } site.set("can_create_tag", false); - } -} + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 10bbf700..90ded90c 100644 --- a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,11 +1,13 @@ export default { - run(app, container) { + run(_, container) { const getToken = requirejs("wizard/lib/ajax").getToken; - const isTesting = requirejs("discourse-common/config/environment").isTesting; + const isTesting = requirejs("discourse-common/config/environment") + .isTesting; - if (!isTesting) { + if (!isTesting()) { // Add a CSRF token to all AJAX requests let token = getToken(); + const session = container.lookup("session:main"); session.set("csrfToken", token); let callbacks = $.Callbacks(); $.ajaxPrefilter(callbacks.fire); @@ -156,5 +158,5 @@ export default { return resArray; }; } - } + }, }; diff --git a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 index 8d4b850e..85079270 100644 --- a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 @@ -1,10 +1,14 @@ export default { run(app, container) { - const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default; + const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") + .default; const Handlebars = requirejs("handlebars").default; - const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers; + const registerRawHelpers = requirejs( + "discourse-common/lib/raw-handlebars-helpers" + ).registerRawHelpers; const { registerHelpers } = requirejs("discourse-common/lib/helpers"); - const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins").default; + const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins") + .default; Object.keys(Ember.TEMPLATES).forEach((k) => { if (k.indexOf("select-kit") === 0) { @@ -22,5 +26,5 @@ export default { registerRawHelpers(RawHandlebars, Handlebars); registerHelpers(app); jqueryPlugins.initialize(container, app); - } -} + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 index 20650ec9..915e5f26 100644 --- a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -1,7 +1,8 @@ export default { name: "custom-wizard", initialize(app) { - const isTesting = requirejs("discourse-common/config/environment").isTesting; + const isTesting = requirejs("discourse-common/config/environment") + .isTesting; const isWizard = window.location.pathname.indexOf("/w/") > -1; if (!isWizard && !isTesting()) { @@ -9,14 +10,17 @@ export default { } const container = app.__container__; - const setDefaultOwner = requirejs("discourse-common/lib/get-owner").setDefaultOwner; + const setDefaultOwner = requirejs("discourse-common/lib/get-owner") + .setDefaultOwner; setDefaultOwner(container); if (!isTesting()) { const PreloadStore = requirejs("discourse/lib/preload-store").default; let preloaded; - const preloadedDataElement = document.getElementById("data-preloaded-wizard"); + const preloadedDataElement = document.getElementById( + "data-preloaded-wizard" + ); if (preloadedDataElement) { preloaded = JSON.parse(preloadedDataElement.dataset.preloadedWizard); } @@ -28,7 +32,8 @@ export default { app.SiteSettings = PreloadStore.get("siteSettings"); } - const setEnvironment = requirejs("discourse-common/config/environment").setEnvironment; + const setEnvironment = requirejs("discourse-common/config/environment") + .setEnvironment; const setupData = document.getElementById("data-discourse-setup").dataset; setEnvironment(setupData.environment); @@ -38,13 +43,15 @@ export default { session.set("markdownItUrl", setupData.markdownItUrl); [ - 'register-files', - 'inject-objects', - 'create-contexts', - 'patch-components' - ].forEach(fileName => { - const initializer = requirejs(`discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}`).default; + "register-files", + "inject-objects", + "create-contexts", + "patch-components", + ].forEach((fileName) => { + const initializer = requirejs( + `discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}` + ).default; initializer.run(app, container); }); - } + }, }; diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/wizard/models/field.js.es6 index 5f76074e..2b88140e 100644 --- a/assets/javascripts/wizard/models/field.js.es6 +++ b/assets/javascripts/wizard/models/field.js.es6 @@ -75,5 +75,5 @@ export default EmberObject.extend(ValidState, { this.setValid(valid); return valid; - } + }, }); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 index b0829312..36503276 100644 --- a/assets/javascripts/wizard/models/step.js.es6 +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -72,11 +72,7 @@ export default EmberObject.extend(ValidState, { type: "PUT", data: { fields }, }).catch((response) => { - if ( - response && - response.responseJSON && - response.responseJSON.errors - ) { + if (response && response.responseJSON && response.responseJSON.errors) { let wizardErrors = []; response.responseJSON.errors.forEach((err) => { if (err.field === wizardId) { diff --git a/assets/javascripts/wizard/routes/step.js.es6 b/assets/javascripts/wizard/routes/step.js.es6 index 2454fc95..a076951f 100644 --- a/assets/javascripts/wizard/routes/step.js.es6 +++ b/assets/javascripts/wizard/routes/step.js.es6 @@ -26,7 +26,7 @@ export default Route.extend({ }, renderTemplate() { - this.render('wizard/templates/step'); + this.render("wizard/templates/step"); }, setupController(controller, model) { diff --git a/assets/javascripts/wizard/routes/wizard-index.js.es6 b/assets/javascripts/wizard/routes/wizard-index.js.es6 index 16b1140a..264cb0a2 100644 --- a/assets/javascripts/wizard/routes/wizard-index.js.es6 +++ b/assets/javascripts/wizard/routes/wizard-index.js.es6 @@ -4,7 +4,13 @@ import Route from "@ember/routing/route"; export default Route.extend({ beforeModel() { const wizard = getCachedWizard(); - if (wizard && wizard.user && wizard.permitted && !wizard.completed && wizard.start) { + if ( + wizard && + wizard.user && + wizard.permitted && + !wizard.completed && + wizard.start + ) { this.replaceWith("step", wizard.start); } }, @@ -14,7 +20,7 @@ export default Route.extend({ }, renderTemplate() { - this.render('wizard/templates/wizard-index'); + this.render("wizard/templates/wizard-index"); }, setupController(controller, model) { @@ -39,5 +45,5 @@ export default Route.extend({ } else { controller.set("noWizard", true); } - } + }, }); diff --git a/assets/javascripts/wizard/routes/wizard.js.es6 b/assets/javascripts/wizard/routes/wizard.js.es6 index 2910ee6d..a2c34f13 100644 --- a/assets/javascripts/wizard/routes/wizard.js.es6 +++ b/assets/javascripts/wizard/routes/wizard.js.es6 @@ -1,5 +1,4 @@ import { findCustomWizard, updateCachedWizard } from "../models/wizard"; -import { ajax } from "wizard/lib/ajax"; import WizardI18n from "../lib/wizard-i18n"; import Route from "@ember/routing/route"; import { scheduleOnce } from "@ember/runloop"; @@ -20,7 +19,7 @@ export default Route.extend({ const title = WizardI18n("wizard.incomplete_submission.title", { date: moment(wizardModel.submission_last_updated_at).format( "MMMM Do YYYY" - ) + ), }); const buttons = [ @@ -49,7 +48,7 @@ export default Route.extend({ }, renderTemplate() { - this.render('wizard/templates/wizard'); + this.render("wizard/templates/wizard"); }, setupController(controller, model) { diff --git a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 index f73d1ab7..9300fc09 100644 --- a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 +++ b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 @@ -1,135 +1,173 @@ -import { - visit, - click, - fillIn, - triggerKeyEvent -} from "@ember/test-helpers"; +import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers"; import { test } from "qunit"; import { exists } from "../helpers/test"; import acceptance, { - query, count, + query, + server, visible, - server } from "../helpers/acceptance"; -import { - allFieldsWizard, - getWizard -} from "../helpers/wizard"; +import { allFieldsWizard, getWizard } from "../helpers/wizard"; import tagsJson from "../fixtures/tags"; import usersJson from "../fixtures/users"; import { response } from "../pretender"; -acceptance("Field | Fields", [ getWizard(allFieldsWizard) ], - function(hooks) { - test("Text", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); - }); +acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { + test("Text", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); + }); - test("Textarea", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.textarea-field textarea.wizard-focusable")); - }); + test("Textarea", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.textarea-field textarea.wizard-focusable") + ); + }); - test("Composer", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.composer-field .wizard-field-composer textarea")); - assert.strictEqual(count(".wizard-field.composer-field .d-editor-button-bar button"), 8); - assert.ok(visible(".wizard-btn.toggle-preview")); + test("Composer", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.composer-field .wizard-field-composer textarea") + ); + assert.strictEqual( + count(".wizard-field.composer-field .d-editor-button-bar button"), + 8 + ); + assert.ok(visible(".wizard-btn.toggle-preview")); - await fillIn(".wizard-field.composer-field .wizard-field-composer textarea", "Input in composer"); - await click(".wizard-btn.toggle-preview"); - assert.strictEqual(query('.wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p').textContent.trim(), "Input in composer"); - }); + await fillIn( + ".wizard-field.composer-field .wizard-field-composer textarea", + "Input in composer" + ); + await click(".wizard-btn.toggle-preview"); + assert.strictEqual( + query( + ".wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p" + ).textContent.trim(), + "Input in composer" + ); + }); - test("Text Only", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.text-only-field label.field-label")); - }); + test("Text Only", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.text-only-field label.field-label")); + }); - test("Date", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.date-field input.date-picker")); - await click(".wizard-field.date-field input.date-picker"); - assert.ok(visible(".wizard-field.date-field .pika-single")); - }); + test("Date", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-field input.date-picker")); + await click(".wizard-field.date-field input.date-picker"); + assert.ok(visible(".wizard-field.date-field .pika-single")); + }); - test("Time", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); - await click(".wizard-field.time-field .d-time-input .select-kit .select-kit-header"); - assert.ok(visible(".wizard-field.time-field .select-kit-collection")); - }); + test("Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); + await click( + ".wizard-field.time-field .d-time-input .select-kit .select-kit-header" + ); + assert.ok(visible(".wizard-field.time-field .select-kit-collection")); + }); - test("Date Time", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.date-time-field .d-date-time-input .select-kit")); - await click(".wizard-field.date-time-field .d-date-input input.date-picker"); - assert.ok(visible(".wizard-field.date-time-field .d-date-input .pika-single")); - await click(".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header"); - assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); - }); + test("Date Time", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.date-time-field .d-date-time-input .select-kit") + ); + await click( + ".wizard-field.date-time-field .d-date-input input.date-picker" + ); + assert.ok( + visible(".wizard-field.date-time-field .d-date-input .pika-single") + ); + await click( + ".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header" + ); + assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); + }); - test("Number", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.number-field input[type='number']")); - }); + test("Number", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.number-field input[type='number']")); + }); - test("Checkbox", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); - }); + test("Checkbox", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); + }); - test("Url", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.url-field input[type='text']")); - }); + test("Url", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.url-field input[type='text']")); + }); - test("Upload", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.upload-field label.wizard-btn-upload-file")); - assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); - }); + test("Upload", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.upload-field label.wizard-btn-upload-file") + ); + assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); + }); - test("Dropdown", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); - await click(".wizard-field.dropdown-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.dropdown-field .select-kit-collection li"), 3); - }); + test("Dropdown", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); + await click(".wizard-field.dropdown-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.dropdown-field .select-kit-collection li"), + 3 + ); + }); - test("Tag", async function (assert) { - server.get("/tags/filter/search", () => (response(200, { results: tagsJson['tags']}))); - await visit("/wizard"); - assert.ok(visible(".wizard-field.tag-field .multi-select-header")); - await click(".wizard-field.tag-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.tag-field .select-kit-collection li"), 2); - }); + test("Tag", async function (assert) { + server.get("/tags/filter/search", () => + response(200, { results: tagsJson["tags"] }) + ); + await visit("/wizard"); + assert.ok(visible(".wizard-field.tag-field .multi-select-header")); + await click(".wizard-field.tag-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.tag-field .select-kit-collection li"), + 2 + ); + }); - test("Category", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.category-field .multi-select-header")); - await click(".wizard-field.category-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.category-field .select-kit-collection li"), 5); - }); + test("Category", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.category-field .multi-select-header")); + await click(".wizard-field.category-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.category-field .select-kit-collection li"), + 5 + ); + }); - test("Group", async function (assert) { - await visit("/wizard"); - assert.ok(visible(".wizard-field.group-field .single-select-header")); - await click(".wizard-field.group-field .select-kit-header"); - assert.strictEqual(count(".wizard-field.group-field .select-kit-collection li"), 10); - }); + test("Group", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.group-field .single-select-header")); + await click(".wizard-field.group-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.group-field .select-kit-collection li"), + 10 + ); + }); - test("User", async function (assert) { - server.get("/u/search/users", () => (response(200, usersJson))); + test("User", async function (assert) { + server.get("/u/search/users", () => response(200, usersJson)); - await visit("/wizard"); - await fillIn(".wizard-field.user-selector-field input.ember-text-field", "a"); - await triggerKeyEvent(".wizard-field.user-selector-field input.ember-text-field", "keyup", "a".charCodeAt(0)); + await visit("/wizard"); + await fillIn( + ".wizard-field.user-selector-field input.ember-text-field", + "a" + ); + await triggerKeyEvent( + ".wizard-field.user-selector-field input.ember-text-field", + "keyup", + "a".charCodeAt(0) + ); - assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); - // TODO: add assertion for ac results. autocomplete does not appear in time. - }); - } -); + assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); + // TODO: add assertion for ac results. autocomplete does not appear in time. + }); +}); diff --git a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 index 731c9b76..1537f1c7 100644 --- a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 +++ b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 @@ -1,47 +1,41 @@ -import { visit, click } from "@ember/test-helpers"; +import { click, visit } from "@ember/test-helpers"; import { test } from "qunit"; import { exists } from "../helpers/test"; -import acceptance, { - query, - count, - visible -} from "../helpers/acceptance"; -import { - stepNotPermitted, - wizard, - getWizard -} from "../helpers/wizard"; -import { - saveStep, - update -} from "../helpers/step"; +import acceptance, { count, query, visible } from "../helpers/acceptance"; +import { getWizard, stepNotPermitted, wizard } from "../helpers/wizard"; +import { saveStep, update } from "../helpers/step"; -acceptance("Step | Not permitted", [ getWizard(stepNotPermitted) ], - function(hooks) { - test("Shows not permitted message", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".step-message.not-permitted")); - }); - } -); +acceptance("Step | Not permitted", [getWizard(stepNotPermitted)], function () { + test("Shows not permitted message", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".step-message.not-permitted")); + }); +}); -acceptance("Step | Step", [ getWizard(wizard), saveStep(update) ], - function(hooks) { - test("Renders the step", async function (assert) { - await visit("/wizard"); - assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); - assert.ok(visible('.wizard-step-footer .wizard-progress'), true); - assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); - }); +acceptance("Step | Step", [getWizard(wizard), saveStep(update)], function () { + test("Renders the step", async function (assert) { + await visit("/wizard"); + assert.strictEqual( + query(".wizard-step-title p").textContent.trim(), + "Text" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual(count(".wizard-step-form .wizard-field"), 6); + assert.ok(visible(".wizard-step-footer .wizard-progress"), true); + assert.ok(visible(".wizard-step-footer .wizard-buttons"), true); + }); - test("Goes to the next step", async function (assert) { - await visit("/wizard"); - assert.ok(visible('.wizard-step.step_1'), true); - await click('.wizard-btn.next'); - assert.ok(visible('.wizard-step.step_2'), true); - }); - } -); + test("Goes to the next step", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-step.step_1"), true); + await click(".wizard-btn.next"); + assert.ok(visible(".wizard-step.step_2"), true); + }); +}); diff --git a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 index e68f59ae..8fd82d11 100644 --- a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 +++ b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 @@ -1,30 +1,26 @@ import { visit } from "@ember/test-helpers"; import { test } from "qunit"; import { exists } from "../helpers/test"; -import acceptance, { - query, - count, - visible -} from "../helpers/acceptance"; +import acceptance, { count, query, visible } from "../helpers/acceptance"; import { + getWizard, + wizard, + wizardCompleted, wizardNoUser, wizardNotPermitted, - wizardCompleted, - wizard, - getWizard } from "../helpers/wizard"; -acceptance("Wizard | Not logged in", [ getWizard(wizardNoUser) ], - function(hooks) { - test("Wizard no access requires login", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".wizard-no-access.requires-login")); - }); - } -); +acceptance("Wizard | Not logged in", [getWizard(wizardNoUser)], function () { + test("Wizard no access requires login", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.requires-login")); + }); +}); -acceptance("Wizard | Not permitted", [ getWizard(wizardNotPermitted) ], - function(hooks) { +acceptance( + "Wizard | Not permitted", + [getWizard(wizardNotPermitted)], + function () { test("Wizard no access not permitted", async function (assert) { await visit("/wizard"); assert.ok(exists(".wizard-no-access.not-permitted")); @@ -32,41 +28,46 @@ acceptance("Wizard | Not permitted", [ getWizard(wizardNotPermitted) ], } ); -acceptance("Wizard | Completed", [ getWizard(wizardCompleted) ], - function(hooks) { - test("Wizard no access completed", async function (assert) { - await visit("/wizard"); - assert.ok(exists(".wizard-no-access.completed")); - }); - } -); +acceptance("Wizard | Completed", [getWizard(wizardCompleted)], function () { + test("Wizard no access completed", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.completed")); + }); +}); -acceptance("Wizard | Wizard", [ getWizard(wizard) ], - function(hooks) { - test("Starts", async function (assert) { - await visit("/wizard"); - assert.ok(query('.wizard-column'), true); - }); +acceptance("Wizard | Wizard", [getWizard(wizard)], function () { + test("Starts", async function (assert) { + await visit("/wizard"); + assert.ok(query(".wizard-column"), true); + }); - test("Applies the body background color", async function (assert) { - await visit("/wizard"); - assert.ok($("body")[0].style.background); - }); + test("Applies the body background color", async function (assert) { + await visit("/wizard"); + assert.ok($("body")[0].style.background); + }); - test("Renders the wizard form", async function (assert) { - await visit("/wizard"); - assert.ok(visible('.wizard-column-contents .wizard-step'), true); - assert.ok(visible('.wizard-footer img'), true); - }); + test("Renders the wizard form", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-column-contents .wizard-step"), true); + assert.ok(visible(".wizard-footer img"), true); + }); - test("Renders the first step", async function (assert) { - await visit("/wizard"); - assert.strictEqual(query('.wizard-step-title p').textContent.trim(), "Text"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(query('.wizard-step-description p').textContent.trim(), "Text inputs!"); - assert.strictEqual(count('.wizard-step-form .wizard-field'), 6); - assert.ok(visible('.wizard-step-footer .wizard-progress'), true); - assert.ok(visible('.wizard-step-footer .wizard-buttons'), true); - }); - } -); + test("Renders the first step", async function (assert) { + await visit("/wizard"); + assert.strictEqual( + query(".wizard-step-title p").textContent.trim(), + "Text" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual(count(".wizard-step-form .wizard-field"), 6); + assert.ok(visible(".wizard-step-footer .wizard-progress"), true); + assert.ok(visible(".wizard-step-footer .wizard-buttons"), true); + }); +}); diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 index 29cdfe80..a3343b9a 100644 --- a/assets/javascripts/wizard/tests/bootstrap.js.es6 +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -1,5 +1,5 @@ // discourse-skip-module -/*global document, Logster, QUnit */ +/*global document, Logster */ if (window.location.pathname.indexOf("/w/") > -1 && Ember.testing) { document.addEventListener("DOMContentLoaded", function () { diff --git a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 index e862f54a..e553f860 100644 --- a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 @@ -1,209 +1,221 @@ export default { - "categories": [ + categories: [ { - "id": 1, - "name": "Uncategorized", - "color": "0088CC", - "text_color": "FFFFFF", - "slug": "uncategorized", - "topic_count": 1, - "post_count": 1, - "position": 0, - "description": "Topics that don't need a category, or don't fit into any other existing category.", - "description_text": "Topics that don't need a category, or don't fit into any other existing category.", - "description_excerpt": "Topics that don't need a category, or don't fit into any other existing category.", - "topic_url": "/t/", - "read_restricted": false, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 1, + name: "Uncategorized", + color: "0088CC", + text_color: "FFFFFF", + slug: "uncategorized", + topic_count: 1, + post_count: 1, + position: 0, + description: + "Topics that don't need a category, or don't fit into any other existing category.", + description_text: + "Topics that don't need a category, or don't fit into any other existing category.", + description_excerpt: + "Topics that don't need a category, or don't fit into any other existing category.", + topic_url: "/t/", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 2, - "name": "Site Feedback", - "color": "808281", - "text_color": "FFFFFF", - "slug": "site-feedback", - "topic_count": 20, - "post_count": 21, - "position": 1, - "description": "

Discussion about this site, its organization, how it works, and how we can improve it.

", - "description_text": "Discussion about this site, its organization, how it works, and how we can improve it.", - "description_excerpt": "Discussion about this site, its organization, how it works, and how we can improve it.", - "topic_url": "/t/about-the-site-feedback-category/1", - "read_restricted": false, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 2, + name: "Site Feedback", + color: "808281", + text_color: "FFFFFF", + slug: "site-feedback", + topic_count: 20, + post_count: 21, + position: 1, + description: + "

Discussion about this site, its organization, how it works, and how we can improve it.

", + description_text: + "Discussion about this site, its organization, how it works, and how we can improve it.", + description_excerpt: + "Discussion about this site, its organization, how it works, and how we can improve it.", + topic_url: "/t/about-the-site-feedback-category/1", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 3, - "name": "Staff", - "color": "E45735", - "text_color": "FFFFFF", - "slug": "staff", - "topic_count": 4, - "post_count": 7, - "position": 2, - "description": "

Private category for staff discussions. Topics are only visible to admins and moderators.

", - "description_text": "Private category for staff discussions. Topics are only visible to admins and moderators.", - "description_excerpt": "Private category for staff discussions. Topics are only visible to admins and moderators.", - "topic_url": "/t/about-the-staff-category/2", - "read_restricted": true, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 3, + name: "Staff", + color: "E45735", + text_color: "FFFFFF", + slug: "staff", + topic_count: 4, + post_count: 7, + position: 2, + description: + "

Private category for staff discussions. Topics are only visible to admins and moderators.

", + description_text: + "Private category for staff discussions. Topics are only visible to admins and moderators.", + description_excerpt: + "Private category for staff discussions. Topics are only visible to admins and moderators.", + topic_url: "/t/about-the-staff-category/2", + read_restricted: true, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 4, - "name": "Lounge", - "color": "A461EF", - "text_color": "652D90", - "slug": "lounge", - "topic_count": 1, - "post_count": 1, - "position": 3, - "description": "

A category exclusive to members with trust level 3 and higher.

", - "description_text": "A category exclusive to members with trust level 3 and higher.", - "description_excerpt": "A category exclusive to members with trust level 3 and higher.", - "topic_url": "/t/about-the-lounge-category/3", - "read_restricted": true, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 4, + name: "Lounge", + color: "A461EF", + text_color: "652D90", + slug: "lounge", + topic_count: 1, + post_count: 1, + position: 3, + description: + "

A category exclusive to members with trust level 3 and higher.

", + description_text: + "A category exclusive to members with trust level 3 and higher.", + description_excerpt: + "A category exclusive to members with trust level 3 and higher.", + topic_url: "/t/about-the-lounge-category/3", + read_restricted: true, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, }, { - "id": 5, - "name": "Custom Categories", - "color": "0088CC", - "text_color": "FFFFFF", - "slug": "custom-category", - "topic_count": 0, - "post_count": 0, - "position": 10, - "description": "Description of custom category", - "description_text": "Description of custom category", - "description_excerpt": "Description of custom category", - "topic_url": "/t/about-the-custom-category/5", - "read_restricted": false, - "permission": 1, - "notification_level": 0, - "topic_template": null, - "has_children": false, - "sort_order": null, - "sort_ascending": null, - "show_subcategory_list": false, - "num_featured_topics": 3, - "default_view": null, - "subcategory_list_style": "rows_with_featured_topics", - "default_top_period": "all", - "default_list_filter": "all", - "minimum_required_tags": 0, - "navigate_to_first_post_after_read": false, - "custom_fields": { - "create_topic_wizard": null + id: 5, + name: "Custom Categories", + color: "0088CC", + text_color: "FFFFFF", + slug: "custom-category", + topic_count: 0, + post_count: 0, + position: 10, + description: "Description of custom category", + description_text: "Description of custom category", + description_excerpt: "Description of custom category", + topic_url: "/t/about-the-custom-category/5", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, }, - "allowed_tags": [], - "allowed_tag_groups": [], - "allow_global_tags": false, - "min_tags_from_required_group": 1, - "required_tag_group_name": null, - "read_only_banner": null, - "uploaded_logo": null, - "uploaded_background": null, - "can_edit": true - } - ] -} + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 index 16f8150d..a770cf58 100644 --- a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 @@ -1,313 +1,313 @@ export default { - "groups": [ + groups: [ { - "id": 1, - "automatic": true, - "name": "admins", - "display_name": "admins", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 1, + automatic: true, + name: "admins", + display_name: "admins", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 0, - "automatic": true, - "name": "everyone", - "display_name": "everyone", - "user_count": 0, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 3, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 0, + automatic: true, + name: "everyone", + display_name: "everyone", + user_count: 0, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 3, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 15, - "automatic": false, - "name": "custom_group", - "user_count": 1, - "mentionable_level": 1, - "messageable_level": 2, - "visibility_level": 3, - "primary_group": false, - "title": "Custom Group", - "grant_trust_level": 3, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": "I am prefilled", - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 99, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 15, + automatic: false, + name: "custom_group", + user_count: 1, + mentionable_level: 1, + messageable_level: 2, + visibility_level: 3, + primary_group: false, + title: "Custom Group", + grant_trust_level: 3, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: "I am prefilled", + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 99, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 2, - "automatic": true, - "name": "moderators", - "display_name": "moderators", - "user_count": 0, - "mentionable_level": 0, - "messageable_level": 99, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 2, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 2, + automatic: true, + name: "moderators", + display_name: "moderators", + user_count: 0, + mentionable_level: 0, + messageable_level: 99, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 2, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 3, - "automatic": true, - "name": "staff", - "display_name": "staff", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 3, + automatic: true, + name: "staff", + display_name: "staff", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 10, - "automatic": true, - "name": "trust_level_0", - "display_name": "trust_level_0", - "user_count": 2, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 10, + automatic: true, + name: "trust_level_0", + display_name: "trust_level_0", + user_count: 2, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 11, - "automatic": true, - "name": "trust_level_1", - "display_name": "trust_level_1", - "user_count": 2, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 11, + automatic: true, + name: "trust_level_1", + display_name: "trust_level_1", + user_count: 2, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 12, - "automatic": true, - "name": "trust_level_2", - "display_name": "trust_level_2", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 12, + automatic: true, + name: "trust_level_2", + display_name: "trust_level_2", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 13, - "automatic": true, - "name": "trust_level_3", - "display_name": "trust_level_3", - "user_count": 1, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false + id: 13, + automatic: true, + name: "trust_level_3", + display_name: "trust_level_3", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, }, { - "id": 14, - "automatic": true, - "name": "trust_level_4", - "display_name": "trust_level_4", - "user_count": 0, - "mentionable_level": 0, - "messageable_level": 0, - "visibility_level": 1, - "primary_group": false, - "title": null, - "grant_trust_level": null, - "incoming_email": null, - "has_messages": false, - "flair_url": null, - "flair_bg_color": null, - "flair_color": null, - "bio_raw": null, - "bio_cooked": null, - "bio_excerpt": null, - "public_admission": false, - "public_exit": false, - "allow_membership_requests": false, - "full_name": null, - "default_notification_level": 3, - "membership_request_template": null, - "members_visibility_level": 0, - "can_see_members": true, - "can_admin_group": true, - "publish_read_state": false - } - ] -} + id: 14, + automatic: true, + name: "trust_level_4", + display_name: "trust_level_4", + user_count: 0, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 index f71ace8e..7568cd49 100644 --- a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 @@ -1,283 +1,294 @@ export default { - "default_locale": "en", - "title": "Discourse", - "short_site_description": "", - "exclude_rel_nofollow_domains": "", - "logo": "/images/discourse-logo-sketch.png", - "logo_small": "/images/discourse-logo-sketch-small.png", - "digest_logo": "", - "mobile_logo": "", - "logo_dark": "", - "logo_small_dark": "", - "mobile_logo_dark": "", - "large_icon": "", - "favicon": "", - "apple_touch_icon": "", - "display_local_time_in_user_card": false, - "allow_user_locale": false, - "set_locale_from_accept_language_header": false, - "support_mixed_text_direction": false, - "suggested_topics": 5, - "ga_universal_tracking_code": "", - "ga_universal_domain_name": "auto", - "gtm_container_id": "", - "top_menu": "categories|latest", - "post_menu": "read|like|share|flag|edit|bookmark|delete|admin|reply", - "post_menu_hidden_items": "flag|bookmark|edit|delete|admin", - "share_links": "twitter|facebook|email", - "share_quote_visibility": "anonymous", - "share_quote_buttons": "twitter|email", - "desktop_category_page_style": "categories_and_latest_topics", - "category_colors": "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", - "category_style": "bullet", - "max_category_nesting": 2, - "enable_mobile_theme": true, - "enable_direct_s3_uploads": false, - "enable_upload_debug_mode": false, - "default_dark_mode_color_scheme_id": 1, - "relative_date_duration": 30, - "fixed_category_positions": false, - "fixed_category_positions_on_create": false, - "enable_badges": true, - "enable_badge_sql": true, - "max_favorite_badges": 2, - "enable_whispers": false, - "enable_bookmarks_with_reminders": true, - "push_notifications_prompt": true, - "vapid_public_key_bytes": "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", - "invite_only": false, - "login_required": false, - "must_approve_users": false, - "enable_local_logins": true, - "enable_local_logins_via_email": true, - "allow_new_registrations": true, - "enable_signup_cta": true, - "facebook_app_id": "", - "auth_skip_create_confirm": false, - "auth_overrides_email": false, - "enable_discourse_connect": true, - "discourse_connect_overrides_avatar": false, - "hide_email_address_taken": false, - "min_username_length": 3, - "max_username_length": 20, - "unicode_usernames": false, - "min_password_length": 10, - "min_admin_password_length": 15, - "email_editable": true, - "logout_redirect": "", - "full_name_required": false, - "enable_names": true, - "invite_expiry_days": 90, - "invites_per_page": 40, - "delete_user_max_post_age": 60, - "delete_all_posts_max": 15, - "prioritize_username_in_ux": true, - "enable_user_directory": true, - "allow_anonymous_posting": false, - "anonymous_posting_min_trust_level": 1, - "allow_users_to_hide_profile": true, - "hide_user_profiles_from_public": false, - "allow_featured_topic_on_user_profiles": true, - "hide_suspension_reasons": false, - "ignored_users_count_message_threshold": 5, - "ignored_users_message_gap_days": 365, - "user_selected_primary_groups": false, - "gravatar_name": "Gravatar", - "gravatar_base_url": "www.gravatar.com", - "gravatar_login_url": "/emails", - "enable_group_directory": true, - "enable_category_group_moderation": false, - "min_post_length": 20, - "min_first_post_length": 20, - "min_personal_message_post_length": 10, - "max_post_length": 32000, - "topic_featured_link_enabled": true, - "min_topic_views_for_delete_confirm": 5000, - "min_topic_title_length": 15, - "max_topic_title_length": 255, - "enable_filtered_replies_view": false, - "min_personal_message_title_length": 2, - "allow_uncategorized_topics": true, - "min_title_similar_length": 10, - "enable_personal_messages": true, - "edit_history_visible_to_public": true, - "delete_removed_posts_after": 24, - "traditional_markdown_linebreaks": false, - "enable_markdown_typographer": true, - "enable_markdown_linkify": true, - "markdown_linkify_tlds": "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", - "markdown_typographer_quotation_marks": "“|”|‘|’", - "enable_rich_text_paste": true, - "suppress_reply_directly_below": true, - "suppress_reply_directly_above": true, - "max_reply_history": 1, - "enable_mentions": true, - "here_mention": "here", - "newuser_max_embedded_media": 1, - "newuser_max_attachments": 0, - "show_pinned_excerpt_mobile": true, - "show_pinned_excerpt_desktop": true, - "display_name_on_posts": false, - "show_time_gap_days": 7, - "short_progress_text_threshold": 10000, - "default_code_lang": "auto", - "autohighlight_all_code": false, - "highlighted_languages": "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", - "show_copy_button_on_codeblocks": false, - "enable_emoji": true, - "enable_emoji_shortcuts": true, - "emoji_set": "twitter", - "emoji_autocomplete_min_chars": 0, - "enable_inline_emoji_translation": false, - "code_formatting_style": "code-fences", - "allowed_href_schemes": "", - "watched_words_regular_expressions": false, - "enable_diffhtml_preview": false, - "enable_fast_edit": true, - "old_post_notice_days": 14, - "blur_tl0_flagged_posts_media": true, - "email_time_window_mins": 10, - "disable_digest_emails": false, - "email_in": false, - "enable_imap": false, - "enable_smtp": false, - "disable_emails": "no", - "bounce_score_threshold": 4, - "enable_secondary_emails": true, - "max_image_size_kb": 4096, - "max_attachment_size_kb": 4096, - "authorized_extensions": "jpg|jpeg|png|gif|heic|heif|webp", - "authorized_extensions_for_staff": "", - "max_image_width": 690, - "max_image_height": 500, - "prevent_anons_from_downloading_files": false, - "secure_media": false, - "enable_s3_uploads": false, - "allow_profile_backgrounds": true, - "allow_uploaded_avatars": "0", - "default_avatars": "", - "external_system_avatars_enabled": true, - "external_system_avatars_url": "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", - "external_emoji_url": "", - "selectable_avatars_mode": "disabled", - "selectable_avatars": "", - "allow_staff_to_upload_any_file_in_pm": true, - "simultaneous_uploads": 5, - "composer_media_optimization_image_enabled": true, - "composer_media_optimization_image_bytes_optimization_threshold": 524288, - "composer_media_optimization_image_resize_dimensions_threshold": 1920, - "composer_media_optimization_image_resize_width_target": 1920, - "composer_media_optimization_image_resize_pre_multiply": false, - "composer_media_optimization_image_resize_linear_rgb": false, - "composer_media_optimization_image_encode_quality": 75, - "composer_media_optimization_debug_mode": false, - "min_trust_level_to_allow_profile_background": 0, - "min_trust_level_to_allow_user_card_background": 0, - "min_trust_level_to_allow_ignore": 2, - "tl1_requires_read_posts": 30, - "tl3_links_no_follow": false, - "enforce_second_factor": "no", - "moderators_change_post_ownership": false, - "moderators_view_emails": false, - "use_admin_ip_allowlist": false, - "allowed_iframes": "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", - "can_permanently_delete": false, - "max_oneboxes_per_post": 50, - "reviewable_claiming": "disabled", - "reviewable_default_topics": false, - "reviewable_default_visibility": "low", - "alert_admins_if_errors_per_minute": 0, - "alert_admins_if_errors_per_hour": 0, - "max_prints_per_hour_per_user": 5, - "invite_link_max_redemptions_limit": 5000, - "invite_link_max_redemptions_limit_users": 10, - "enable_long_polling": true, - "enable_chunked_encoding": true, - "long_polling_base_url": "/", - "background_polling_interval": 60000, - "polling_interval": 3000, - "anon_polling_interval": 25000, - "flush_timings_secs": 60, - "verbose_localization": false, - "max_new_topics": 500, - "enable_safe_mode": true, - "tos_url": "", - "privacy_policy_url": "", - "faq_url": "", - "enable_backups": true, - "backup_location": "local", - "maximum_backups": 5, - "use_pg_headlines_for_excerpt": false, - "min_search_term_length": 3, - "log_search_queries": true, - "version_checks": true, - "suppress_uncategorized_badge": true, - "header_dropdown_category_count": 8, - "slug_generation_method": "ascii", - "summary_timeline_button": false, - "topic_views_heat_low": 1000, - "topic_views_heat_medium": 2000, - "topic_views_heat_high": 3500, - "topic_post_like_heat_low": 0.5, - "topic_post_like_heat_medium": 1, - "topic_post_like_heat_high": 2, - "history_hours_low": 12, - "history_hours_medium": 24, - "history_hours_high": 48, - "cold_age_days_low": 14, - "cold_age_days_medium": 90, - "cold_age_days_high": 180, - "global_notice": "", - "show_create_topics_notice": true, - "bootstrap_mode_min_users": 50, - "bootstrap_mode_enabled": true, - "automatically_unpin_topics": true, - "read_time_word_count": 500, - "topic_page_title_includes_category": true, - "svg_icon_subset": "", - "allow_bulk_invite": true, - "disable_mailing_list_mode": true, - "default_topics_automatic_unpin": true, - "mute_all_categories_by_default": false, - "tagging_enabled": true, - "tag_style": "simple", - "max_tags_per_topic": 5, - "max_tag_length": 20, - "min_trust_level_to_tag_topics": "0", - "max_tag_search_results": 5, - "max_tags_in_filter_list": 30, - "tags_sort_alphabetically": false, - "tags_listed_by_group": false, - "suppress_overlapping_tags_in_list": false, - "remove_muted_tags_from_latest": "always", - "force_lowercase_tags": true, - "dashboard_hidden_reports": "", - "dashboard_visible_tabs": "moderation|security|reports", - "dashboard_general_tab_activity_metrics": "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", - "discourse_narrative_bot_enabled": true, - "details_enabled": true, - "custom_wizard_enabled": true, - "wizard_redirect_exclude_paths": "admin", - "wizard_recognised_image_upload_formats": "jpg|jpeg|png|gif", - "wizard_important_notices_on_dashboard": true, - "discourse_local_dates_email_format": "YYYY-MM-DDTHH:mm:ss[Z]", - "discourse_local_dates_enabled": true, - "discourse_local_dates_default_formats": "LLL|LTS|LL|LLLL", - "discourse_local_dates_default_timezones": "Europe/Paris|America/Los_Angeles", - "poll_enabled": true, - "poll_maximum_options": 20, - "poll_minimum_trust_level_to_create": 1, - "poll_groupable_user_fields": "", - "poll_export_data_explorer_query_id": -16, - "presence_enabled": true, - "presence_max_users_shown": 5, - "available_locales": "[{\"name\":\"اللغة العربية\",\"value\":\"ar\"},{\"name\":\"беларуская мова\",\"value\":\"be\"},{\"name\":\"български език\",\"value\":\"bg\"},{\"name\":\"bosanski jezik\",\"value\":\"bs_BA\"},{\"name\":\"català\",\"value\":\"ca\"},{\"name\":\"čeština\",\"value\":\"cs\"},{\"name\":\"dansk\",\"value\":\"da\"},{\"name\":\"Deutsch\",\"value\":\"de\"},{\"name\":\"ελληνικά\",\"value\":\"el\"},{\"name\":\"English (US)\",\"value\":\"en\"},{\"name\":\"English (UK)\",\"value\":\"en_GB\"},{\"name\":\"Español\",\"value\":\"es\"},{\"name\":\"eesti\",\"value\":\"et\"},{\"name\":\"فارسی\",\"value\":\"fa_IR\"},{\"name\":\"suomi\",\"value\":\"fi\"},{\"name\":\"Français\",\"value\":\"fr\"},{\"name\":\"galego\",\"value\":\"gl\"},{\"name\":\"עברית\",\"value\":\"he\"},{\"name\":\"magyar\",\"value\":\"hu\"},{\"name\":\"Հայերեն\",\"value\":\"hy\"},{\"name\":\"Indonesian\",\"value\":\"id\"},{\"name\":\"Italiano\",\"value\":\"it\"},{\"name\":\"日本語\",\"value\":\"ja\"},{\"name\":\"한국어\",\"value\":\"ko\"},{\"name\":\"lietuvių kalba\",\"value\":\"lt\"},{\"name\":\"latviešu valoda\",\"value\":\"lv\"},{\"name\":\"Norsk bokmål\",\"value\":\"nb_NO\"},{\"name\":\"Nederlands\",\"value\":\"nl\"},{\"name\":\"polski\",\"value\":\"pl_PL\"},{\"name\":\"Português\",\"value\":\"pt\"},{\"name\":\"Português (BR)\",\"value\":\"pt_BR\"},{\"name\":\"limba română\",\"value\":\"ro\"},{\"name\":\"Русский\",\"value\":\"ru\"},{\"name\":\"slovenčina\",\"value\":\"sk\"},{\"name\":\"slovenščina\",\"value\":\"sl\"},{\"name\":\"Shqip\",\"value\":\"sq\"},{\"name\":\"српски језик\",\"value\":\"sr\"},{\"name\":\"svenska\",\"value\":\"sv\"},{\"name\":\"Kiswahili\",\"value\":\"sw\"},{\"name\":\"తెలుగు\",\"value\":\"te\"},{\"name\":\"ไทย\",\"value\":\"th\"},{\"name\":\"Türkçe\",\"value\":\"tr_TR\"},{\"name\":\"українська мова\",\"value\":\"uk\"},{\"name\":\"اردو\",\"value\":\"ur\"},{\"name\":\"Việt Nam\",\"value\":\"vi\"},{\"name\":\"简体中文\",\"value\":\"zh_CN\"},{\"name\":\"繁體中文\",\"value\":\"zh_TW\"}]", - "require_invite_code": false, - "site_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", - "site_logo_small_url": "http://localhost:3000/images/discourse-logo-sketch-small.png", - "site_mobile_logo_url": "http://localhost:3000/images/discourse-logo-sketch.png", - "site_favicon_url": "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", - "site_logo_dark_url": "", - "site_logo_small_dark_url": "", - "site_mobile_logo_dark_url": "" -} + default_locale: "en", + title: "Discourse", + short_site_description: "", + exclude_rel_nofollow_domains: "", + logo: "/images/discourse-logo-sketch.png", + logo_small: "/images/discourse-logo-sketch-small.png", + digest_logo: "", + mobile_logo: "", + logo_dark: "", + logo_small_dark: "", + mobile_logo_dark: "", + large_icon: "", + favicon: "", + apple_touch_icon: "", + display_local_time_in_user_card: false, + allow_user_locale: false, + set_locale_from_accept_language_header: false, + support_mixed_text_direction: false, + suggested_topics: 5, + ga_universal_tracking_code: "", + ga_universal_domain_name: "auto", + gtm_container_id: "", + top_menu: "categories|latest", + post_menu: "read|like|share|flag|edit|bookmark|delete|admin|reply", + post_menu_hidden_items: "flag|bookmark|edit|delete|admin", + share_links: "twitter|facebook|email", + share_quote_visibility: "anonymous", + share_quote_buttons: "twitter|email", + desktop_category_page_style: "categories_and_latest_topics", + category_colors: + "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", + category_style: "bullet", + max_category_nesting: 2, + enable_mobile_theme: true, + enable_direct_s3_uploads: false, + enable_upload_debug_mode: false, + default_dark_mode_color_scheme_id: 1, + relative_date_duration: 30, + fixed_category_positions: false, + fixed_category_positions_on_create: false, + enable_badges: true, + enable_badge_sql: true, + max_favorite_badges: 2, + enable_whispers: false, + enable_bookmarks_with_reminders: true, + push_notifications_prompt: true, + vapid_public_key_bytes: + "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", + invite_only: false, + login_required: false, + must_approve_users: false, + enable_local_logins: true, + enable_local_logins_via_email: true, + allow_new_registrations: true, + enable_signup_cta: true, + facebook_app_id: "", + auth_skip_create_confirm: false, + auth_overrides_email: false, + enable_discourse_connect: true, + discourse_connect_overrides_avatar: false, + hide_email_address_taken: false, + min_username_length: 3, + max_username_length: 20, + unicode_usernames: false, + min_password_length: 10, + min_admin_password_length: 15, + email_editable: true, + logout_redirect: "", + full_name_required: false, + enable_names: true, + invite_expiry_days: 90, + invites_per_page: 40, + delete_user_max_post_age: 60, + delete_all_posts_max: 15, + prioritize_username_in_ux: true, + enable_user_directory: true, + allow_anonymous_posting: false, + anonymous_posting_min_trust_level: 1, + allow_users_to_hide_profile: true, + hide_user_profiles_from_public: false, + allow_featured_topic_on_user_profiles: true, + hide_suspension_reasons: false, + ignored_users_count_message_threshold: 5, + ignored_users_message_gap_days: 365, + user_selected_primary_groups: false, + gravatar_name: "Gravatar", + gravatar_base_url: "www.gravatar.com", + gravatar_login_url: "/emails", + enable_group_directory: true, + enable_category_group_moderation: false, + min_post_length: 20, + min_first_post_length: 20, + min_personal_message_post_length: 10, + max_post_length: 32000, + topic_featured_link_enabled: true, + min_topic_views_for_delete_confirm: 5000, + min_topic_title_length: 15, + max_topic_title_length: 255, + enable_filtered_replies_view: false, + min_personal_message_title_length: 2, + allow_uncategorized_topics: true, + min_title_similar_length: 10, + enable_personal_messages: true, + edit_history_visible_to_public: true, + delete_removed_posts_after: 24, + traditional_markdown_linebreaks: false, + enable_markdown_typographer: true, + enable_markdown_linkify: true, + markdown_linkify_tlds: + "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", + markdown_typographer_quotation_marks: "“|”|‘|’", + enable_rich_text_paste: true, + suppress_reply_directly_below: true, + suppress_reply_directly_above: true, + max_reply_history: 1, + enable_mentions: true, + here_mention: "here", + newuser_max_embedded_media: 1, + newuser_max_attachments: 0, + show_pinned_excerpt_mobile: true, + show_pinned_excerpt_desktop: true, + display_name_on_posts: false, + show_time_gap_days: 7, + short_progress_text_threshold: 10000, + default_code_lang: "auto", + autohighlight_all_code: false, + highlighted_languages: + "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", + show_copy_button_on_codeblocks: false, + enable_emoji: true, + enable_emoji_shortcuts: true, + emoji_set: "twitter", + emoji_autocomplete_min_chars: 0, + enable_inline_emoji_translation: false, + code_formatting_style: "code-fences", + allowed_href_schemes: "", + watched_words_regular_expressions: false, + enable_diffhtml_preview: false, + enable_fast_edit: true, + old_post_notice_days: 14, + blur_tl0_flagged_posts_media: true, + email_time_window_mins: 10, + disable_digest_emails: false, + email_in: false, + enable_imap: false, + enable_smtp: false, + disable_emails: "no", + bounce_score_threshold: 4, + enable_secondary_emails: true, + max_image_size_kb: 4096, + max_attachment_size_kb: 4096, + authorized_extensions: "jpg|jpeg|png|gif|heic|heif|webp", + authorized_extensions_for_staff: "", + max_image_width: 690, + max_image_height: 500, + prevent_anons_from_downloading_files: false, + secure_media: false, + enable_s3_uploads: false, + allow_profile_backgrounds: true, + allow_uploaded_avatars: "0", + default_avatars: "", + external_system_avatars_enabled: true, + external_system_avatars_url: + "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", + external_emoji_url: "", + selectable_avatars_mode: "disabled", + selectable_avatars: "", + allow_staff_to_upload_any_file_in_pm: true, + simultaneous_uploads: 5, + composer_media_optimization_image_enabled: true, + composer_media_optimization_image_bytes_optimization_threshold: 524288, + composer_media_optimization_image_resize_dimensions_threshold: 1920, + composer_media_optimization_image_resize_width_target: 1920, + composer_media_optimization_image_resize_pre_multiply: false, + composer_media_optimization_image_resize_linear_rgb: false, + composer_media_optimization_image_encode_quality: 75, + composer_media_optimization_debug_mode: false, + min_trust_level_to_allow_profile_background: 0, + min_trust_level_to_allow_user_card_background: 0, + min_trust_level_to_allow_ignore: 2, + tl1_requires_read_posts: 30, + tl3_links_no_follow: false, + enforce_second_factor: "no", + moderators_change_post_ownership: false, + moderators_view_emails: false, + use_admin_ip_allowlist: false, + allowed_iframes: + "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", + can_permanently_delete: false, + max_oneboxes_per_post: 50, + reviewable_claiming: "disabled", + reviewable_default_topics: false, + reviewable_default_visibility: "low", + alert_admins_if_errors_per_minute: 0, + alert_admins_if_errors_per_hour: 0, + max_prints_per_hour_per_user: 5, + invite_link_max_redemptions_limit: 5000, + invite_link_max_redemptions_limit_users: 10, + enable_long_polling: true, + enable_chunked_encoding: true, + long_polling_base_url: "/", + background_polling_interval: 60000, + polling_interval: 3000, + anon_polling_interval: 25000, + flush_timings_secs: 60, + verbose_localization: false, + max_new_topics: 500, + enable_safe_mode: true, + tos_url: "", + privacy_policy_url: "", + faq_url: "", + enable_backups: true, + backup_location: "local", + maximum_backups: 5, + use_pg_headlines_for_excerpt: false, + min_search_term_length: 3, + log_search_queries: true, + version_checks: true, + suppress_uncategorized_badge: true, + header_dropdown_category_count: 8, + slug_generation_method: "ascii", + summary_timeline_button: false, + topic_views_heat_low: 1000, + topic_views_heat_medium: 2000, + topic_views_heat_high: 3500, + topic_post_like_heat_low: 0.5, + topic_post_like_heat_medium: 1, + topic_post_like_heat_high: 2, + history_hours_low: 12, + history_hours_medium: 24, + history_hours_high: 48, + cold_age_days_low: 14, + cold_age_days_medium: 90, + cold_age_days_high: 180, + global_notice: "", + show_create_topics_notice: true, + bootstrap_mode_min_users: 50, + bootstrap_mode_enabled: true, + automatically_unpin_topics: true, + read_time_word_count: 500, + topic_page_title_includes_category: true, + svg_icon_subset: "", + allow_bulk_invite: true, + disable_mailing_list_mode: true, + default_topics_automatic_unpin: true, + mute_all_categories_by_default: false, + tagging_enabled: true, + tag_style: "simple", + max_tags_per_topic: 5, + max_tag_length: 20, + min_trust_level_to_tag_topics: "0", + max_tag_search_results: 5, + max_tags_in_filter_list: 30, + tags_sort_alphabetically: false, + tags_listed_by_group: false, + suppress_overlapping_tags_in_list: false, + remove_muted_tags_from_latest: "always", + force_lowercase_tags: true, + dashboard_hidden_reports: "", + dashboard_visible_tabs: "moderation|security|reports", + dashboard_general_tab_activity_metrics: + "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", + discourse_narrative_bot_enabled: true, + details_enabled: true, + custom_wizard_enabled: true, + wizard_redirect_exclude_paths: "admin", + wizard_recognised_image_upload_formats: "jpg|jpeg|png|gif", + wizard_important_notices_on_dashboard: true, + discourse_local_dates_email_format: "YYYY-MM-DDTHH:mm:ss[Z]", + discourse_local_dates_enabled: true, + discourse_local_dates_default_formats: "LLL|LTS|LL|LLLL", + discourse_local_dates_default_timezones: "Europe/Paris|America/Los_Angeles", + poll_enabled: true, + poll_maximum_options: 20, + poll_minimum_trust_level_to_create: 1, + poll_groupable_user_fields: "", + poll_export_data_explorer_query_id: -16, + presence_enabled: true, + presence_max_users_shown: 5, + available_locales: + '[{"name":"اللغة العربية","value":"ar"},{"name":"беларуская мова","value":"be"},{"name":"български език","value":"bg"},{"name":"bosanski jezik","value":"bs_BA"},{"name":"català","value":"ca"},{"name":"čeština","value":"cs"},{"name":"dansk","value":"da"},{"name":"Deutsch","value":"de"},{"name":"ελληνικά","value":"el"},{"name":"English (US)","value":"en"},{"name":"English (UK)","value":"en_GB"},{"name":"Español","value":"es"},{"name":"eesti","value":"et"},{"name":"فارسی","value":"fa_IR"},{"name":"suomi","value":"fi"},{"name":"Français","value":"fr"},{"name":"galego","value":"gl"},{"name":"עברית","value":"he"},{"name":"magyar","value":"hu"},{"name":"Հայերեն","value":"hy"},{"name":"Indonesian","value":"id"},{"name":"Italiano","value":"it"},{"name":"日本語","value":"ja"},{"name":"한국어","value":"ko"},{"name":"lietuvių kalba","value":"lt"},{"name":"latviešu valoda","value":"lv"},{"name":"Norsk bokmål","value":"nb_NO"},{"name":"Nederlands","value":"nl"},{"name":"polski","value":"pl_PL"},{"name":"Português","value":"pt"},{"name":"Português (BR)","value":"pt_BR"},{"name":"limba română","value":"ro"},{"name":"Русский","value":"ru"},{"name":"slovenčina","value":"sk"},{"name":"slovenščina","value":"sl"},{"name":"Shqip","value":"sq"},{"name":"српски језик","value":"sr"},{"name":"svenska","value":"sv"},{"name":"Kiswahili","value":"sw"},{"name":"తెలుగు","value":"te"},{"name":"ไทย","value":"th"},{"name":"Türkçe","value":"tr_TR"},{"name":"українська мова","value":"uk"},{"name":"اردو","value":"ur"},{"name":"Việt Nam","value":"vi"},{"name":"简体中文","value":"zh_CN"},{"name":"繁體中文","value":"zh_TW"}]', + require_invite_code: false, + site_logo_url: "http://localhost:3000/images/discourse-logo-sketch.png", + site_logo_small_url: + "http://localhost:3000/images/discourse-logo-sketch-small.png", + site_mobile_logo_url: + "http://localhost:3000/images/discourse-logo-sketch.png", + site_favicon_url: + "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", + site_logo_dark_url: "", + site_logo_small_dark_url: "", + site_mobile_logo_dark_url: "", +}; diff --git a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 index a072772e..61e51994 100644 --- a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 @@ -1,22 +1,22 @@ export default { - "tags": [ + tags: [ { - "id": "tag1", - "text": "tag1", - "name": "tag1", - "description": null, - "count": 1, - "pm_count": 0, - "target_tag": null + id: "tag1", + text: "tag1", + name: "tag1", + description: null, + count: 1, + pm_count: 0, + target_tag: null, }, { - "id": "tag2", - "text": "tag2", - "name": "tag2", - "description": null, - "count": 1, - "pm_count": 0, - "target_tag": null - } - ] -} + id: "tag2", + text: "tag2", + name: "tag2", + description: null, + count: 1, + pm_count: 0, + target_tag: null, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/update.js.es6 b/assets/javascripts/wizard/tests/fixtures/update.js.es6 index 3908525c..5b20788c 100644 --- a/assets/javascripts/wizard/tests/fixtures/update.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/update.js.es6 @@ -1,5 +1,5 @@ export default { - "final": false, - "next_step_id": "step_2", - "wizard": {} -} + final: false, + next_step_id: "step_2", + wizard: {}, +}; diff --git a/assets/javascripts/wizard/tests/fixtures/user.js.es6 b/assets/javascripts/wizard/tests/fixtures/user.js.es6 index 8acd7392..d954905c 100644 --- a/assets/javascripts/wizard/tests/fixtures/user.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/user.js.es6 @@ -30,5 +30,5 @@ export default { title_count_mode: "notifications", timezone: "Australia/Perth", skip_new_user_tips: false, - can_review: true -} + can_review: true, +}; diff --git a/assets/javascripts/wizard/tests/fixtures/users.js.es6 b/assets/javascripts/wizard/tests/fixtures/users.js.es6 index 267d4909..437631a2 100644 --- a/assets/javascripts/wizard/tests/fixtures/users.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/users.js.es6 @@ -1,14 +1,14 @@ export default { - "users": [ + users: [ { - "username": "angus", - "name": "Angus", - "avatar_template": "/user_avatar/localhost/angus/{size}/12_2.png" + username: "angus", + name: "Angus", + avatar_template: "/user_avatar/localhost/angus/{size}/12_2.png", }, { - "username": "angus_2", - "name": "Angus 2", - "avatar_template": "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png" - } - ] -} + username: "angus_2", + name: "Angus 2", + avatar_template: "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png", + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 index be4fa8b2..73fe45c1 100644 --- a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 +++ b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 @@ -1,469 +1,471 @@ export default { - "id": "wizard", - "name": "Wizard", - "start": "step_1", - "background": "#333333", - "submission_last_updated_at": "2022-03-15T21:11:01+01:00", - "theme_id": 2, - "required": false, - "permitted": true, - "uncategorized_category_id": 1, - "categories": [], - "subscribed": false, - "resume_on_revisit": false, - "steps": [ + id: "wizard", + name: "Wizard", + start: "step_1", + background: "#333333", + submission_last_updated_at: "2022-03-15T21:11:01+01:00", + theme_id: 2, + required: false, + permitted: true, + uncategorized_category_id: 1, + categories: [], + subscribed: false, + resume_on_revisit: false, + steps: [ { - "id": "step_1", - "index": 0, - "next": "step_2", - "description": "

Text inputs!

", - "title": "Text", - "permitted": true, - "permitted_message": null, - "final": false, - "fields": [ + id: "step_1", + index: 0, + next: "step_2", + description: "

Text inputs!

", + title: "Text", + permitted: true, + permitted_message: null, + final: false, + fields: [ { - "id": "step_1_field_1", - "index": 0, - "type": "text", - "required": false, - "value": "I am prefilled", - "label": "

Text

", - "description": "Text field description.", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 1, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_1", + index: 0, + type: "text", + required: false, + value: "I am prefilled", + label: "

Text

", + description: "Text field description.", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_2", - "index": 0, - "type": "textarea", - "required": false, - "value": "", - "label": "

Textarea

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 2, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_2", + index: 0, + type: "textarea", + required: false, + value: "", + label: "

Textarea

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_3", - "index": 2, - "type": "composer", - "required": false, - "value": "", - "label": "

Composer

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 3, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_3", + index: 2, + type: "composer", + required: false, + value: "", + label: "

Composer

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_4", - "index": 3, - "type": "text_only", - "required": false, - "value": null, - "label": "

I’m only text

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 4, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_4", + index: 3, + type: "text_only", + required: false, + value: null, + label: "

I’m only text

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 4, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_5", - "index": 4, - "type": "composer_preview", - "required": false, - "value": "", - "label": "

I’m a preview

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": "

I am prefilled

", - "tabindex": 5, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 + id: "step_1_field_5", + index: 4, + type: "composer_preview", + required: false, + value: "", + label: "

I’m a preview

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: "

I am prefilled

", + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, }, { - "id": "step_1_field_6", - "index": 5, - "type": "composer_preview", - "required": false, - "value": "", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": "

This is the preview of the composer

", - "tabindex": 6, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_1", - "_validState": 0 - } + id: "step_1_field_6", + index: 5, + type: "composer_preview", + required: false, + value: "", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: "

This is the preview of the composer

", + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, ], - "_validState": 0, - "wizardId": "super_mega_fun_wizard" + _validState: 0, + wizardId: "super_mega_fun_wizard", }, { - "id": "step_2", - "index": 1, - "next": "step_3", - "previous": "step_1", - "description": "

Because I couldn’t think of another name for this step \":slight_smile:\"

", - "title": "Values", - "permitted": true, - "permitted_message": null, - "final": false, - "fields": [ + id: "step_2", + index: 1, + next: "step_3", + previous: "step_1", + description: + '

Because I couldn’t think of another name for this step :slight_smile:

', + title: "Values", + permitted: true, + permitted_message: null, + final: false, + fields: [ { - "id": "step_2_field_1", - "index": 0, - "type": "date", - "required": false, - "value": "", - "label": "

Date

", - "file_types": null, - "format": "YYYY-MM-DD", - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 1, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_1", + index: 0, + type: "date", + required: false, + value: "", + label: "

Date

", + file_types: null, + format: "YYYY-MM-DD", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_2", - "index": 0, - "type": "time", - "required": false, - "value": "", - "label": "

Time

", - "file_types": null, - "format": "HH:mm", - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 2, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_2", + index: 0, + type: "time", + required: false, + value: "", + label: "

Time

", + file_types: null, + format: "HH:mm", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_3", - "index": 2, - "type": "date_time", - "required": false, - "value": "", - "label": "

Date & Time

", - "file_types": null, - "format": "", - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 3, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_3", + index: 2, + type: "date_time", + required: false, + value: "", + label: "

Date & Time

", + file_types: null, + format: "", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_4", - "index": 3, - "type": "number", - "required": false, - "value": "", - "label": "

Number

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 5, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_4", + index: 3, + type: "number", + required: false, + value: "", + label: "

Number

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_5", - "index": 4, - "type": "checkbox", - "required": false, - "value": false, - "label": "

Checkbox

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 6, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_5", + index: 4, + type: "checkbox", + required: false, + value: false, + label: "

Checkbox

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_6", - "index": 5, - "type": "url", - "required": false, - "value": "", - "label": "

Url

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 7, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 + id: "step_2_field_6", + index: 5, + type: "url", + required: false, + value: "", + label: "

Url

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 7, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, }, { - "id": "step_2_field_7", - "index": 6, - "type": "upload", - "required": false, - "value": "", - "label": "

Upload

", - "file_types": ".jpg,.jpeg,.png", - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 8, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_2", - "_validState": 0 - } + id: "step_2_field_7", + index: 6, + type: "upload", + required: false, + value: "", + label: "

Upload

", + file_types: ".jpg,.jpeg,.png", + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 8, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, ], - "_validState": 0, - "wizardId": "super_mega_fun_wizard" + _validState: 0, + wizardId: "super_mega_fun_wizard", }, { - "id": "step_3", - "index": 2, - "previous": "step_2", - "description": "

Unfortunately not the edible type \":sushi:\"

", - "title": "Combo-boxes", - "permitted": true, - "permitted_message": null, - "final": true, - "fields": [ + id: "step_3", + index: 2, + previous: "step_2", + description: + '

Unfortunately not the edible type :sushi:

', + title: "Combo-boxes", + permitted: true, + permitted_message: null, + final: true, + fields: [ { - "id": "step_3_field_1", - "index": 0, - "type": "dropdown", - "required": false, - "value": "choice1", - "label": "

Custom Dropdown

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": [ + id: "step_3_field_1", + index: 0, + type: "dropdown", + required: false, + value: "choice1", + label: "

Custom Dropdown

", + file_types: null, + format: null, + limit: null, + property: null, + content: [ { - "id": "one", - "name": "One" + id: "one", + name: "One", }, { - "id": "two", - "name": "Two" - } + id: "two", + name: "Two", + }, ], - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 1, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_2", - "index": 0, - "type": "tag", - "required": false, - "value": null, - "label": "

Tag

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 2, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_2", + index: 0, + type: "tag", + required: false, + value: null, + label: "

Tag

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_3", - "index": 2, - "type": "category", - "required": false, - "value": null, - "label": "

Category

", - "file_types": null, - "format": null, - "limit": 1, - "property": "id", - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 3, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_3", + index: 2, + type: "category", + required: false, + value: null, + label: "

Category

", + file_types: null, + format: null, + limit: 1, + property: "id", + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_4", - "index": 3, - "type": "group", - "required": false, - "value": null, - "label": "

Group

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 4, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_4", + index: 3, + type: "group", + required: false, + value: null, + label: "

Group

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 4, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_5", - "index": 4, - "type": "user_selector", - "required": false, - "value": null, - "label": "

User Selector

", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 5, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 + id: "step_3_field_5", + index: 4, + type: "user_selector", + required: false, + value: null, + label: "

User Selector

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, }, { - "id": "step_3_field_6", - "index": 5, - "type": "user_selector", - "required": false, - "value": null, - "label": "

Conditional User Selector

", - "description": "Shown when checkbox in step_2_field_5 is true", - "file_types": null, - "format": null, - "limit": null, - "property": null, - "content": null, - "validations": {}, - "max_length": null, - "char_counter": null, - "preview_template": null, - "tabindex": 6, - "wizardId": "super_mega_fun_wizard", - "stepId": "step_3", - "_validState": 0 - } + id: "step_3_field_6", + index: 5, + type: "user_selector", + required: false, + value: null, + label: "

Conditional User Selector

", + description: "Shown when checkbox in step_2_field_5 is true", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, ], - "_validState": 0, - "wizardId": "super_mega_fun_wizard" - } + _validState: 0, + wizardId: "super_mega_fun_wizard", + }, ], - "groups": [] -} + groups: [], +}; diff --git a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 index f5c1175f..913e8c7a 100644 --- a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 @@ -6,18 +6,20 @@ let server; let app; function acceptance(name, requests, cb) { - module(`Acceptance: ${name}`, function(hooks) { - hooks.beforeEach(function() { - server = setupPretender(function(pretender) { - requests.forEach(req => { - pretender[req.verb](req.path, () => (response(req.status, req.response))); + module(`Acceptance: ${name}`, function (hooks) { + hooks.beforeEach(function () { + server = setupPretender(function (pretender) { + requests.forEach((req) => { + pretender[req.verb](req.path, () => + response(req.status, req.response) + ); }); return pretender; }); app = startApp(); }); - hooks.afterEach(function() { + hooks.afterEach(function () { app.destroy(); server.shutdown(); }); @@ -28,9 +30,7 @@ function acceptance(name, requests, cb) { export default acceptance; -export { - server -}; +export { server }; // The discourse/test/helpers/qunit-helpers file has many functions and imports // we don't need, so there will be some duplciation here. diff --git a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 index 6afe6eb9..821d8033 100644 --- a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 @@ -1,6 +1,12 @@ -const CustomWizard = requirejs("discourse/plugins/discourse-custom-wizard/wizard/application").default; -const initializer = requirejs("discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard").default; -const siteSettings = requirejs("discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings").default; +const CustomWizard = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/application" +).default; +const initializer = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard" +).default; +const siteSettings = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings" +).default; const { cloneJSON } = requirejs("discourse-common/lib/object").default; let app; diff --git a/assets/javascripts/wizard/tests/helpers/step.js.es6 b/assets/javascripts/wizard/tests/helpers/step.js.es6 index a24e04e1..41fe16cd 100644 --- a/assets/javascripts/wizard/tests/helpers/step.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/step.js.es6 @@ -5,16 +5,13 @@ import wizardJson from "../fixtures/wizard"; const update = cloneJSON(updateJson); update.wizard = cloneJSON(wizardJson); -const saveStep = function(response) { +const saveStep = function (response) { return { verb: "put", - path: '/w/wizard/steps/:step_id', + path: "/w/wizard/steps/:step_id", status: 200, - response - } -} + response, + }; +}; -export { - saveStep, - update -} +export { saveStep, update }; diff --git a/assets/javascripts/wizard/tests/helpers/test.js.es6 b/assets/javascripts/wizard/tests/helpers/test.js.es6 index c7401fd5..360c883a 100644 --- a/assets/javascripts/wizard/tests/helpers/test.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/test.js.es6 @@ -2,6 +2,4 @@ function exists(selector) { return document.querySelector(selector) !== null; } -export { - exists -} +export { exists }; diff --git a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 index 997f6c36..4cd2e003 100644 --- a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 +++ b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 @@ -26,20 +26,20 @@ const allFieldsWizard = cloneJSON(wizard); allFieldsWizard.steps[0].fields = [ ...allFieldsWizard.steps[0].fields, ...allFieldsWizard.steps[1].fields, - ...allFieldsWizard.steps[2].fields + ...allFieldsWizard.steps[2].fields, ]; allFieldsWizard.steps = [cloneJSON(allFieldsWizard.steps[0])]; -allFieldsWizard.categories = cloneJSON(categoriesJson['categories']); -allFieldsWizard.groups = cloneJSON(groupsJson['groups']); +allFieldsWizard.categories = cloneJSON(categoriesJson["categories"]); +allFieldsWizard.groups = cloneJSON(groupsJson["groups"]); -const getWizard = function(response) { +const getWizard = function (response) { return { verb: "get", path: "/w/wizard", status: 200, - response - } -} + response, + }; +}; export { getWizard, @@ -48,5 +48,5 @@ export { wizardCompleted, stepNotPermitted, allFieldsWizard, - wizard -} + wizard, +}; diff --git a/assets/javascripts/wizard/tests/pretender.js.es6 b/assets/javascripts/wizard/tests/pretender.js.es6 index 88eae666..1f1d4a7d 100644 --- a/assets/javascripts/wizard/tests/pretender.js.es6 +++ b/assets/javascripts/wizard/tests/pretender.js.es6 @@ -1,23 +1,5 @@ import Pretender from "pretender"; -function parsePostData(query) { - const result = {}; - query.split("&").forEach(function (part) { - const item = part.split("="); - const firstSeg = decodeURIComponent(item[0]); - const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg); - - const val = decodeURIComponent(item[1]).replace(/\+/g, " "); - if (m) { - result[m[1]] = result[m[1]] || {}; - result[m[1]][m[2]] = val; - } else { - result[firstSeg] = val; - } - }); - return result; -} - function response(code, obj) { if (typeof code === "object") { obj = code; @@ -42,7 +24,7 @@ export default function (cb) { return body; }; - server.unhandledRequest = function (verb, path, request) { + server.unhandledRequest = function (verb, path) { const error = "Unhandled request in test environment: " + path + " (" + verb + ")"; window.console.error(error); From af93a67bb1b41d839de4d26989d0f9763e37328a Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 15 Jun 2022 09:08:28 +0200 Subject: [PATCH 256/556] Fixes from the cherry picks --- .../javascripts/wizard/components/wizard-composer-editor.js.es6 | 2 +- .../javascripts/wizard/lib/initialize/patch-components.js.es6 | 2 +- assets/javascripts/wizard/models/wizard.js.es6 | 1 + .../wizard/templates/components/wizard-field-text.hbs | 2 +- .../wizard/templates/components/wizard-field-textarea.hbs | 2 +- assets/javascripts/wizard/templates/components/wizard-field.hbs | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 2c04cd4f..4f44d439 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -25,7 +25,7 @@ export default ComposerEditor.extend({ lastValidatedAt: "lastValidatedAt", popupMenuOptions: [], draftStatus: "null", - replyPlaceholder: alias("field.placeholder"), + replyPlaceholder: alias("field.translatedPlaceholder"), uploadingFieldId: null, @on("didInsertElement") diff --git a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 90ded90c..9d13160e 100644 --- a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,5 +1,5 @@ export default { - run(_, container) { + run(app, container) { const getToken = requirejs("wizard/lib/ajax").getToken; const isTesting = requirejs("discourse-common/config/environment") .isTesting; diff --git a/assets/javascripts/wizard/models/wizard.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 index bc2bff14..15bcbb8c 100644 --- a/assets/javascripts/wizard/models/wizard.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -57,6 +57,7 @@ CustomWizard.reopenClass({ wizardJson.steps = wizardJson.steps .map((step) => { const stepObj = Step.create(step); + stepObj.wizardId = wizardJson.id; stepObj.fields.sort((a, b) => { return parseFloat(a.number) - parseFloat(b.number); diff --git a/assets/javascripts/wizard/templates/components/wizard-field-text.hbs b/assets/javascripts/wizard/templates/components/wizard-field-text.hbs index be44e8e7..08733d3f 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-text.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-text.hbs @@ -1 +1 @@ -{{input id=field.id value=field.value class=fieldClass placeholder=field.placeholder tabindex=field.tabindex autocomplete=autocomplete}} +{{input id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex autocomplete=autocomplete}} diff --git a/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs b/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs index 6efb7560..dda299bc 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs @@ -1 +1 @@ -{{textarea id=field.id value=field.value class=fieldClass placeholder=field.placeholder tabindex=field.tabindex}} +{{textarea id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex}} diff --git a/assets/javascripts/wizard/templates/components/wizard-field.hbs b/assets/javascripts/wizard/templates/components/wizard-field.hbs index e0dd1551..efda8629 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field.hbs @@ -1,5 +1,5 @@ {{#if field.image}} From 47eed48ad5d8d3c9b7b06cc07d8a33e86b7eae53 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 15 Jun 2022 09:10:33 +0200 Subject: [PATCH 257/556] Update wizard-i18n.js.es6 --- assets/javascripts/wizard/lib/wizard-i18n.js.es6 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 index fdefab77..9b9f8dd9 100644 --- a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 +++ b/assets/javascripts/wizard/lib/wizard-i18n.js.es6 @@ -13,6 +13,11 @@ const getThemeId = () => { } }; +const getThemeKey = (key) => { + const themeId = getThemeId(); + return `theme_translations.${themeId}.${key}`; +}; + const translationExists = (key) => { return ( I18n.findTranslation(key, { locale: I18n.locale }) || @@ -20,13 +25,20 @@ const translationExists = (key) => { ); }; +const translatedText = (key, value) => { + const themeKey = getThemeKey(key); + return translationExists(themeKey) ? I18n.t(themeKey) : value; +}; + +export { translatedText }; + const WizardI18n = (key, params = {}) => { const themeId = getThemeId(); if (!themeId) { return I18n.t(key, params); } - const themeKey = `theme_translations.${themeId}.${key}`; + let themeKey = getThemeKey(key); if (translationExists(themeKey)) { return I18n.t(themeKey, params); From 3136b779b40a0a8423acabfce7554e1b3e6339fe Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 15 Jun 2022 09:14:42 +0200 Subject: [PATCH 258/556] More fixes from the cherry picks --- assets/javascripts/wizard-qunit.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js index 2866cb5d..8aaf23ab 100644 --- a/assets/javascripts/wizard-qunit.js +++ b/assets/javascripts/wizard-qunit.js @@ -1,11 +1,12 @@ +//= require env +//= require jquery.debug +//= require ember.debug //= require route-recognizer //= require fake_xml_http_request //= require pretender //= require qunit //= require ember-qunit //= require test-shims -//= require jquery.debug -//= require ember.debug //= require ember-template-compiler //= require_tree ./wizard/tests/fixtures From 7cf99f2391af39e20d61fbcd990540a4ca2d8a97 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 15 Jun 2022 09:57:20 +0200 Subject: [PATCH 259/556] Final cherry pick fixes --- app/controllers/custom_wizard/wizard.rb | 8 +++----- plugin.rb | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index 11df97d7..bb53670b 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -6,7 +6,7 @@ class CustomWizard::WizardController < ::ActionController::Base include CanonicalURL::ControllerExtensions include GlobalPath - prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'views')) + prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views')) layout :set_wizard_layout before_action :preload_wizard_json @@ -47,10 +47,8 @@ class CustomWizard::WizardController < ::ActionController::Base result = { success: 'OK' } if current_user && wizard.can_access? - submission = wizard.current_submission - - if submission.present? && submission.redirect_to - result.merge!(redirect_to: submission.redirect_to) + if redirect_to = wizard.current_submission&.redirect_to + result.merge!(redirect_to: redirect_to) end wizard.cleanup_on_skip! diff --git a/plugin.rb b/plugin.rb index 6f5719fb..41d7a1db 100644 --- a/plugin.rb +++ b/plugin.rb @@ -52,6 +52,24 @@ class ::Sprockets::DirectiveProcessor end end +## Override necessary due to 'assets/javascripts/wizard', particularly its tests. +def each_globbed_asset + if @path + root_path = "#{File.dirname(@path)}/assets/javascripts/discourse" + + Dir.glob(["#{root_path}/**/*"]).sort.each do |f| + f_str = f.to_s + if File.directory?(f) + yield [f, true] + elsif f_str.end_with?(".js.es6") || f_str.end_with?(".hbs") || f_str.end_with?(".hbr") + yield [f, false] + elsif transpile_js && f_str.end_with?(".js") + yield [f, false] + end + end + end +end + after_initialize do %w[ ../lib/custom_wizard/engine.rb From aea2df9b06e91717f2d24cc5d85908c609f369fc Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 15 Jun 2022 09:59:40 +0200 Subject: [PATCH 260/556] Bump version --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 41d7a1db..bdb86b64 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards -# version: 1.18.4 +# version: 1.19.0 # authors: Angus McLeod # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io From 70329f209a88b64d1cb8121fbe4cbb6ad370dccb Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 17 Jun 2022 16:00:22 +0200 Subject: [PATCH 261/556] COMPATIBILITY: The core wizard is now an ember addon --- assets/javascripts/wizard-custom.js | 3 +- assets/javascripts/wizard-shims.js | 47 +++++++++++++++++++ assets/javascripts/wizard-vendor.js | 10 ++++ .../wizard/components/validator.js.es6 | 2 +- assets/javascripts/wizard/lib/ajax.js.es6 | 33 +++++++++++++ .../lib/initialize/patch-components.js.es6 | 4 +- .../javascripts/wizard/lib/load-script.js.es6 | 2 +- .../wizard/mixins/valid-state.js.es6 | 36 ++++++++++++++ assets/javascripts/wizard/models/field.js.es6 | 2 +- assets/javascripts/wizard/models/step.js.es6 | 4 +- .../javascripts/wizard/models/wizard.js.es6 | 2 +- plugin.rb | 2 +- 12 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 assets/javascripts/wizard-shims.js create mode 100644 assets/javascripts/wizard-vendor.js create mode 100644 assets/javascripts/wizard/lib/ajax.js.es6 create mode 100644 assets/javascripts/wizard/mixins/valid-state.js.es6 diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 667d23fd..1e487273 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,8 +1,6 @@ //= require_tree_discourse truth-helpers/addon //= require_tree_discourse discourse-common/addon //= require_tree_discourse select-kit/addon -//= require_tree_discourse wizard/lib -//= require_tree_discourse wizard/mixins //= require_tree_discourse discourse/app/lib //= require_tree_discourse discourse/app/mixins @@ -76,6 +74,7 @@ //= require_tree ./wizard/controllers //= require_tree ./wizard/helpers //= require_tree ./wizard/lib +//= require_tree ./wizard/mixins //= require_tree ./wizard/models //= require_tree ./wizard/routes //= require_tree ./wizard/templates diff --git a/assets/javascripts/wizard-shims.js b/assets/javascripts/wizard-shims.js new file mode 100644 index 00000000..b5063933 --- /dev/null +++ b/assets/javascripts/wizard-shims.js @@ -0,0 +1,47 @@ +define("@popperjs/core", ["exports"], function (__exports__) { + __exports__.default = window.Popper; + __exports__.createPopper = window.Popper.createPopper; + __exports__.defaultModifiers = window.Popper.defaultModifiers; + __exports__.popperGenerator = window.Popper.popperGenerator; +}); + +define("tippy.js", ["exports"], function (__exports__) { + __exports__.default = window.tippy; +}); + +define("@uppy/core", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.Core; + __exports__.BasePlugin = window.Uppy.Core.BasePlugin; +}); + +define("@uppy/aws-s3", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.AwsS3; +}); + +define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.AwsS3Multipart; +}); + +define("@uppy/xhr-upload", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.XHRUpload; +}); + +define("@uppy/drop-target", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.DropTarget; +}); + +define("@uppy/utils/lib/delay", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.Utils.delay; +}); + +define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.Utils.EventTracker; +}); + +define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) { + __exports__.AbortController = + window.Uppy.Utils.AbortControllerLib.AbortController; + __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal; + __exports__.createAbortError = + window.Uppy.Utils.AbortControllerLib.createAbortError; +}); diff --git a/assets/javascripts/wizard-vendor.js b/assets/javascripts/wizard-vendor.js new file mode 100644 index 00000000..5d175a6b --- /dev/null +++ b/assets/javascripts/wizard-vendor.js @@ -0,0 +1,10 @@ +//= require ember_jquery +//= require template_include.js +//= require uppy.js +//= require bootstrap-modal.js +//= require bootbox.js +//= require virtual-dom +//= require virtual-dom-amd +//= require popper.js +//= require tippy.umd.js +//= require wizard-shims diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index aa68660c..2e8498ef 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -1,7 +1,7 @@ import Component from "@ember/component"; import { equal } from "@ember/object/computed"; import { ajax } from "discourse/lib/ajax"; -import { getToken } from "wizard/lib/ajax"; +import { getToken } from "../lib/ajax"; export default Component.extend({ classNames: ["validator"], diff --git a/assets/javascripts/wizard/lib/ajax.js.es6 b/assets/javascripts/wizard/lib/ajax.js.es6 new file mode 100644 index 00000000..3889069f --- /dev/null +++ b/assets/javascripts/wizard/lib/ajax.js.es6 @@ -0,0 +1,33 @@ +import { Promise } from "rsvp"; +import getUrl from "discourse-common/lib/get-url"; +import jQuery from "jquery"; +import { run } from "@ember/runloop"; + +let token; + +export function getToken() { + if (!token) { + token = document.querySelector('meta[name="csrf-token"]')?.content; + } + + return token; +} + +export function ajax(args) { + let url; + + if (arguments.length === 2) { + url = arguments[0]; + args = arguments[1]; + } else { + url = args.url; + } + + return new Promise((resolve, reject) => { + args.headers = { "X-CSRF-Token": getToken() }; + args.success = (data) => run(null, resolve, data); + args.error = (xhr) => run(null, reject, xhr); + args.url = getUrl(url); + jQuery.ajax(args); + }); +} diff --git a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 9d13160e..8cb5e4b3 100644 --- a/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,6 +1,8 @@ export default { run(app, container) { - const getToken = requirejs("wizard/lib/ajax").getToken; + const getToken = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/ajax" + ).getToken; const isTesting = requirejs("discourse-common/config/environment") .isTesting; diff --git a/assets/javascripts/wizard/lib/load-script.js.es6 b/assets/javascripts/wizard/lib/load-script.js.es6 index d0b07c26..aeabdf06 100644 --- a/assets/javascripts/wizard/lib/load-script.js.es6 +++ b/assets/javascripts/wizard/lib/load-script.js.es6 @@ -1,4 +1,4 @@ -import { ajax } from "wizard/lib/ajax"; +import { ajax } from "./ajax"; import getURL, { getURLWithCDN } from "discourse-common/lib/get-url"; import { run } from "@ember/runloop"; import { Promise } from "rsvp"; diff --git a/assets/javascripts/wizard/mixins/valid-state.js.es6 b/assets/javascripts/wizard/mixins/valid-state.js.es6 new file mode 100644 index 00000000..ca86d7e4 --- /dev/null +++ b/assets/javascripts/wizard/mixins/valid-state.js.es6 @@ -0,0 +1,36 @@ +import discourseComputed from "discourse-common/utils/decorators"; + +export const States = { + UNCHECKED: 0, + INVALID: 1, + VALID: 2, +}; + +export default { + _validState: null, + errorDescription: null, + + init() { + this._super(...arguments); + this.set("_validState", States.UNCHECKED); + }, + + @discourseComputed("_validState") + valid: (state) => state === States.VALID, + + @discourseComputed("_validState") + invalid: (state) => state === States.INVALID, + + @discourseComputed("_validState") + unchecked: (state) => state === States.UNCHECKED, + + setValid(valid, description) { + this.set("_validState", valid ? States.VALID : States.INVALID); + + if (!valid && description && description.length) { + this.set("errorDescription", description); + } else { + this.set("errorDescription", null); + } + }, +}; diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/wizard/models/field.js.es6 index 2b88140e..9d0be1ff 100644 --- a/assets/javascripts/wizard/models/field.js.es6 +++ b/assets/javascripts/wizard/models/field.js.es6 @@ -1,5 +1,5 @@ import EmberObject from "@ember/object"; -import ValidState from "wizard/mixins/valid-state"; +import ValidState from "discourse/plugins/discourse-custom-wizard/wizard/mixins/valid-state"; import discourseComputed from "discourse-common/utils/decorators"; import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 index 36503276..87f2d4b6 100644 --- a/assets/javascripts/wizard/models/step.js.es6 +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -1,6 +1,6 @@ import EmberObject from "@ember/object"; -import ValidState from "wizard/mixins/valid-state"; -import { ajax } from "wizard/lib/ajax"; +import ValidState from "discourse/plugins/discourse-custom-wizard/wizard/mixins/valid-state"; +import { ajax } from "../lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; import { later } from "@ember/runloop"; diff --git a/assets/javascripts/wizard/models/wizard.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 index 15bcbb8c..d742c244 100644 --- a/assets/javascripts/wizard/models/wizard.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -1,7 +1,7 @@ import { default as computed } from "discourse-common/utils/decorators"; import getUrl from "discourse-common/lib/get-url"; import Field from "./field"; -import { ajax } from "wizard/lib/ajax"; +import { ajax } from "../lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import Step from "./step"; import EmberObject from "@ember/object"; diff --git a/plugin.rb b/plugin.rb index bdb86b64..a0e6dd7b 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards -# version: 1.19.0 +# version: 1.20.0 # authors: Angus McLeod # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io From ce3d2ced557e60146f82c742030b24f42c4395c6 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 22 Jun 2022 09:14:55 +0200 Subject: [PATCH 262/556] COMPATIBILITY: Various core file changes The completion of the move to ember-cli in core requires a move to an independent asset pipeline. The wizard app itself will be upgraded to ember-cli when I have time. See further: - https://github.com/discourse/discourse/commit/fcb4e5a1a15631cef9c20489866a68600ecf768d - https://github.com/discourse/discourse/commit/1b4692039e89392731ca5f75cfc4c4febaa3fa9a - https://github.com/discourse/discourse/commit/a69b9147107aa7858044f0db666da5b727980638 --- .github/workflows/plugin-tests.yml | 5 - app/views/layouts/wizard.html.erb | 4 +- assets/javascripts/legacy/discourse-loader.js | 432 + assets/javascripts/legacy/discourse-shims.js | 67 + .../javascripts/legacy/ember_include.js.erb | 7 + assets/javascripts/legacy/ember_jquery.js | 5 + assets/javascripts/legacy/env.js | 3 + .../javascripts/legacy/handlebars.runtime.js | 1800 +++ assets/javascripts/legacy/itsatrap.js | 1159 ++ assets/javascripts/legacy/jquery.js | 10872 ++++++++++++++++ assets/javascripts/legacy/popper.js | 1948 +++ .../legacy/set-prototype-polyfill.js | 8 + assets/javascripts/legacy/template_include.js | 2 + assets/javascripts/legacy/tippy.umd.js | 2496 ++++ assets/javascripts/legacy/uppy.js | 7616 +++++++++++ assets/javascripts/legacy/virtual-dom-amd.js | 4 + assets/javascripts/legacy/virtual-dom.js | 1668 +++ assets/javascripts/wizard-custom.js | 12 +- assets/javascripts/wizard-vendor.js | 16 +- assets/stylesheets/wizard/custom/field.scss | 2 +- assets/stylesheets/wizard/custom/mobile.scss | 2 +- assets/stylesheets/wizard/custom/step.scss | 4 +- assets/stylesheets/wizard/custom/wizard.scss | 8 +- assets/stylesheets/wizard/wizard_custom.scss | 5 + 24 files changed, 28114 insertions(+), 31 deletions(-) create mode 100644 assets/javascripts/legacy/discourse-loader.js create mode 100644 assets/javascripts/legacy/discourse-shims.js create mode 100644 assets/javascripts/legacy/ember_include.js.erb create mode 100644 assets/javascripts/legacy/ember_jquery.js create mode 100644 assets/javascripts/legacy/env.js create mode 100644 assets/javascripts/legacy/handlebars.runtime.js create mode 100644 assets/javascripts/legacy/itsatrap.js create mode 100644 assets/javascripts/legacy/jquery.js create mode 100644 assets/javascripts/legacy/popper.js create mode 100644 assets/javascripts/legacy/set-prototype-polyfill.js create mode 100644 assets/javascripts/legacy/template_include.js create mode 100644 assets/javascripts/legacy/tippy.umd.js create mode 100644 assets/javascripts/legacy/uppy.js create mode 100644 assets/javascripts/legacy/virtual-dom-amd.js create mode 100644 assets/javascripts/legacy/virtual-dom.js diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 497486ea..a7951188 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -132,8 +132,3 @@ jobs: export COVERAGE=1 fi bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] - - - name: Plugin QUnit - if: matrix.build_type == 'frontend' - run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 QUNIT_SKIP_CORE=1 bin/rake qunit:test['600000','/w/qunit'] - timeout-minutes: 10 diff --git a/app/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb index f909ae84..cab255c3 100644 --- a/app/views/layouts/wizard.html.erb +++ b/app/views/layouts/wizard.html.erb @@ -15,7 +15,7 @@ <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-plugin" %> <%= preload_script "pretty-text-bundle" %> - + <%= preload_script_url ExtraLocalesController.url("wizard") %> <%= csrf_meta_tags %> <%- unless customization_disabled? %> @@ -34,7 +34,7 @@ <%= wizard_page_title %> - + <%- unless customization_disabled? %> <%= raw wizard_theme_lookup("header") %> <%- end %> diff --git a/assets/javascripts/legacy/discourse-loader.js b/assets/javascripts/legacy/discourse-loader.js new file mode 100644 index 00000000..edcbf27f --- /dev/null +++ b/assets/javascripts/legacy/discourse-loader.js @@ -0,0 +1,432 @@ +var define, requirejs; + +(function () { + let JS_MODULES = {}; + let ALIASES = { + "ember-addons/ember-computed-decorators": + "discourse-common/utils/decorators", + "discourse/lib/raw-templates": "discourse-common/lib/raw-templates", + "preload-store": "discourse/lib/preload-store", + "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures", + }; + let ALIAS_PREPEND = { + fixtures: "discourse/tests/", + helpers: "discourse/tests/", + }; + + // In future versions of ember we don't need this + if (typeof Ember !== "undefined") { + JS_MODULES = { + jquery: { default: $ }, + "@ember/array": { + default: Ember.Array, + A: Ember.A, + isArray: Ember.isArray, + }, + "@ember/array/proxy": { + default: Ember.ArrayProxy, + }, + "@ember/component": { + default: Ember.Component, + }, + "@ember/controller": { + default: Ember.Controller, + inject: Ember.inject.controller, + }, + "@ember/debug": { + assert: Ember.assert, + runInDebug: Ember.runInDebug, + warn: Ember.warn, + }, + "@ember/object": { + action: Ember._action, + default: Ember.Object, + get: Ember.get, + getProperties: Ember.getProperties, + set: Ember.set, + setProperties: Ember.setProperties, + computed: Ember.computed, + defineProperty: Ember.defineProperty, + observer: Ember.observer, + }, + "@ember/object/computed": { + alias: Ember.computed.alias, + and: Ember.computed.and, + bool: Ember.computed.bool, + collect: Ember.computed.collect, + deprecatingAlias: Ember.computed.deprecatingAlias, + empty: Ember.computed.empty, + equal: Ember.computed.equal, + filter: Ember.computed.filter, + filterBy: Ember.computed.filterBy, + gt: Ember.computed.gt, + gte: Ember.computed.gte, + intersect: Ember.computed.intersect, + lt: Ember.computed.lt, + lte: Ember.computed.lte, + map: Ember.computed.map, + mapBy: Ember.computed.mapBy, + match: Ember.computed.match, + max: Ember.computed.max, + min: Ember.computed.min, + none: Ember.computed.none, + not: Ember.computed.not, + notEmpty: Ember.computed.notEmpty, + oneWay: Ember.computed.oneWay, + or: Ember.computed.or, + readOnly: Ember.computed.readOnly, + reads: Ember.computed.reads, + setDiff: Ember.computed.setDiff, + sort: Ember.computed.sort, + sum: Ember.computed.sum, + union: Ember.computed.union, + uniq: Ember.computed.uniq, + uniqBy: Ember.computed.uniqBy, + }, + "@ember/object/mixin": { default: Ember.Mixin }, + "@ember/object/proxy": { default: Ember.ObjectProxy }, + "@ember/object/promise-proxy-mixin": { default: Ember.PromiseProxyMixin }, + "@ember/object/evented": { + default: Ember.Evented, + on: Ember.on, + }, + "@ember/routing/route": { default: Ember.Route }, + "@ember/routing/router": { default: Ember.Router }, + "@ember/runloop": { + bind: Ember.run.bind, + cancel: Ember.run.cancel, + debounce: Ember.testing ? Ember.run : Ember.run.debounce, + later: Ember.run.later, + next: Ember.run.next, + once: Ember.run.once, + run: Ember.run, + schedule: Ember.run.schedule, + scheduleOnce: Ember.run.scheduleOnce, + throttle: Ember.run.throttle, + }, + "@ember/service": { + default: Ember.Service, + inject: Ember.inject.service, + }, + "@ember/utils": { + isBlank: Ember.isBlank, + isEmpty: Ember.isEmpty, + isNone: Ember.isNone, + isPresent: Ember.isPresent, + }, + rsvp: { + asap: Ember.RSVP.asap, + all: Ember.RSVP.all, + allSettled: Ember.RSVP.allSettled, + race: Ember.RSVP.race, + hash: Ember.RSVP.hash, + hashSettled: Ember.RSVP.hashSettled, + rethrow: Ember.RSVP.rethrow, + defer: Ember.RSVP.defer, + denodeify: Ember.RSVP.denodeify, + resolve: Ember.RSVP.resolve, + reject: Ember.RSVP.reject, + map: Ember.RSVP.map, + filter: Ember.RSVP.filter, + default: Ember.RSVP, + Promise: Ember.RSVP.Promise, + EventTarget: Ember.RSVP.EventTarget, + }, + "@ember/string": { + w: Ember.String.w, + dasherize: Ember.String.dasherize, + decamelize: Ember.String.decamelize, + camelize: Ember.String.camelize, + classify: Ember.String.classify, + underscore: Ember.String.underscore, + capitalize: Ember.String.capitalize, + }, + "@ember/template": { + htmlSafe: Ember.String.htmlSafe, + }, + "@ember/application": { + default: Ember.Application, + setOwner: Ember.setOwner, + getOwner: Ember.getOwner, + }, + "@ember/component/helper": { + default: Ember.Helper, + }, + "@ember/component/text-field": { + default: Ember.TextField, + }, + "@ember/component/text-area": { + default: Ember.TextArea, + }, + "@ember/error": { + default: Ember.error, + }, + "@ember/object/internals": { + guidFor: Ember.guidFor, + }, + "@ember/test": { + registerWaiter: Ember.Test && Ember.Test.registerWaiter, + unregisterWaiter: Ember.Test && Ember.Test.unregisterWaiter, + }, + I18n: { + // eslint-disable-next-line + default: I18n, + }, + }; + } + + let _isArray; + if (!Array.isArray) { + _isArray = function (x) { + return Object.prototype.toString.call(x) === "[object Array]"; + }; + } else { + _isArray = Array.isArray; + } + + let registry = {}; + let seen = {}; + let FAILED = false; + + let uuid = 0; + + function tryFinally(tryable, finalizer) { + try { + return tryable(); + } finally { + finalizer(); + } + } + + function unsupportedModule(length) { + throw new Error( + "an unsupported module was defined, expected `define(name, deps, module)` instead got: `" + + length + + "` arguments to define`" + ); + } + + function deprecatedModule(depricated, useInstead) { + let warning = "[DEPRECATION] `" + depricated + "` is deprecated."; + if (useInstead) { + warning += " Please use `" + useInstead + "` instead."; + } + // eslint-disable-next-line no-console + console.warn(warning); + } + + let defaultDeps = ["require", "exports", "module"]; + + function Module(name, deps, callback, exports) { + this.id = uuid++; + this.name = name; + this.deps = !deps.length && callback.length ? defaultDeps : deps; + this.exports = exports || {}; + this.callback = callback; + this.state = undefined; + this._require = undefined; + } + + Module.prototype.makeRequire = function () { + let name = transformForAliases(this.name); + + return ( + this._require || + (this._require = function (dep) { + return requirejs(resolve(dep, name)); + }) + ); + }; + + define = function (name, deps, callback) { + if (arguments.length < 2) { + unsupportedModule(arguments.length); + } + + if (!_isArray(deps)) { + callback = deps; + deps = []; + } + + registry[name] = new Module(name, deps, callback); + }; + + // we don't support all of AMD + // define.amd = {}; + // we will support petals... + define.petal = {}; + + function Alias(path) { + this.name = path; + } + + define.alias = function (path) { + return new Alias(path); + }; + + function reify(mod, name, rseen) { + let deps = mod.deps; + let length = deps.length; + let reified = new Array(length); + let dep; + // TODO: new Module + // TODO: seen refactor + let module = {}; + + for (let i = 0, l = length; i < l; i++) { + dep = deps[i]; + if (dep === "exports") { + module.exports = reified[i] = rseen; + } else if (dep === "require") { + reified[i] = mod.makeRequire(); + } else if (dep === "module") { + mod.exports = rseen; + module = reified[i] = mod; + } else { + reified[i] = requireFrom(resolve(dep, name), name); + } + } + + return { + deps: reified, + module, + }; + } + + function requireFrom(name, origin) { + name = transformForAliases(name); + + if (name === "discourse") { + // eslint-disable-next-line no-console + console.log( + "discourse has been moved to `discourse/app` - please update your code" + ); + name = "discourse/app"; + } + + if (name === "discourse/models/input-validation") { + // eslint-disable-next-line no-console + console.log( + "input-validation has been removed and should be replaced with `@ember/object`" + ); + name = "@ember/object"; + } + + let mod = JS_MODULES[name] || registry[name]; + if (!mod) { + throw new Error( + "Could not find module `" + name + "` imported from `" + origin + "`" + ); + } + return requirejs(name); + } + + function missingModule(name) { + throw new Error("Could not find module " + name); + } + + function transformForAliases(name) { + let alias = ALIASES[name]; + if (!alias) { + let segment = name.split("/")[0]; + let prepend = ALIAS_PREPEND[segment]; + if (!prepend) { + return name; + } + alias = prepend + name; + } + deprecatedModule(name, alias); + return alias; + } + + requirejs = require = function (name) { + name = transformForAliases(name); + if (JS_MODULES[name]) { + return JS_MODULES[name]; + } + + let mod = registry[name]; + + if (mod && mod.callback instanceof Alias) { + mod = registry[mod.callback.name]; + } + + if (!mod) { + missingModule(name); + } + + if (mod.state !== FAILED && seen.hasOwnProperty(name)) { + return seen[name]; + } + + let reified; + let module; + let loaded = false; + + seen[name] = {}; // placeholder for run-time cycles + + tryFinally( + function () { + reified = reify(mod, name, seen[name]); + module = mod.callback.apply(this, reified.deps); + loaded = true; + }, + function () { + if (!loaded) { + mod.state = FAILED; + } + } + ); + + let obj; + if (module === undefined && reified.module.exports) { + obj = reified.module.exports; + } else { + obj = seen[name] = module; + } + + if ( + obj !== null && + (typeof obj === "object" || typeof obj === "function") && + obj["default"] === undefined + ) { + obj["default"] = obj; + } + + return (seen[name] = obj); + }; + window.requireModule = requirejs; + + function resolve(child, name) { + if (child.charAt(0) !== ".") { + return child; + } + + let parts = child.split("/"); + let nameParts = name.split("/"); + let parentBase = nameParts.slice(0, -1); + + for (let i = 0, l = parts.length; i < l; i++) { + let part = parts[i]; + + if (part === "..") { + if (parentBase.length === 0) { + throw new Error("Cannot access parent module of root"); + } + parentBase.pop(); + } else if (part === ".") { + continue; + } else { + parentBase.push(part); + } + } + + return parentBase.join("/"); + } + + requirejs.entries = requirejs._eak_seen = registry; + requirejs.clear = function () { + requirejs.entries = requirejs._eak_seen = registry = {}; + seen = {}; + }; +})(); diff --git a/assets/javascripts/legacy/discourse-shims.js b/assets/javascripts/legacy/discourse-shims.js new file mode 100644 index 00000000..7794b099 --- /dev/null +++ b/assets/javascripts/legacy/discourse-shims.js @@ -0,0 +1,67 @@ +define("message-bus-client", ["exports"], function (__exports__) { + __exports__.default = window.MessageBus; +}); + +define("ember-buffered-proxy/proxy", ["exports"], function (__exports__) { + __exports__.default = window.BufferedProxy; +}); + +define("bootbox", ["exports"], function (__exports__) { + __exports__.default = window.bootbox; +}); + +define("xss", ["exports"], function (__exports__) { + __exports__.default = window.filterXSS; +}); + +define("@discourse/itsatrap", ["exports"], function (__exports__) { + __exports__.default = window.ItsATrap; +}); + +define("@popperjs/core", ["exports"], function (__exports__) { + __exports__.default = window.Popper; + __exports__.createPopper = window.Popper.createPopper; + __exports__.defaultModifiers = window.Popper.defaultModifiers; + __exports__.popperGenerator = window.Popper.popperGenerator; +}); + +define("tippy.js", ["exports"], function (__exports__) { + __exports__.default = window.tippy; +}); + +define("@uppy/core", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.Core; + __exports__.BasePlugin = window.Uppy.Core.BasePlugin; +}); + +define("@uppy/aws-s3", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.AwsS3; +}); + +define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.AwsS3Multipart; +}); + +define("@uppy/xhr-upload", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.XHRUpload; +}); + +define("@uppy/drop-target", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.DropTarget; +}); + +define("@uppy/utils/lib/delay", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.Utils.delay; +}); + +define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) { + __exports__.default = window.Uppy.Utils.EventTracker; +}); + +define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) { + __exports__.AbortController = + window.Uppy.Utils.AbortControllerLib.AbortController; + __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal; + __exports__.createAbortError = + window.Uppy.Utils.AbortControllerLib.createAbortError; +}); diff --git a/assets/javascripts/legacy/ember_include.js.erb b/assets/javascripts/legacy/ember_include.js.erb new file mode 100644 index 00000000..56883bef --- /dev/null +++ b/assets/javascripts/legacy/ember_include.js.erb @@ -0,0 +1,7 @@ +<% + if @force_ember_debug || Rails.env.development? || Rails.env.test? + require_asset("ember.debug.js") + else + require_asset("ember.prod.js") + end +%> diff --git a/assets/javascripts/legacy/ember_jquery.js b/assets/javascripts/legacy/ember_jquery.js new file mode 100644 index 00000000..7506aa62 --- /dev/null +++ b/assets/javascripts/legacy/ember_jquery.js @@ -0,0 +1,5 @@ +//= require legacy/set-prototype-polyfill +//= require legacy/env +//= require legacy/jquery +//= require legacy/ember_include +//= require legacy/discourse-loader diff --git a/assets/javascripts/legacy/env.js b/assets/javascripts/legacy/env.js new file mode 100644 index 00000000..8b897bbe --- /dev/null +++ b/assets/javascripts/legacy/env.js @@ -0,0 +1,3 @@ +window.ENV = {}; +window.EmberENV = window.EmberENV || {}; +window.EmberENV.FORCE_JQUERY = true; diff --git a/assets/javascripts/legacy/handlebars.runtime.js b/assets/javascripts/legacy/handlebars.runtime.js new file mode 100644 index 00000000..da377f0f --- /dev/null +++ b/assets/javascripts/legacy/handlebars.runtime.js @@ -0,0 +1,1800 @@ +/**! + + @license + handlebars v4.7.7 + +Copyright (C) 2011-2019 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + {module.exports = factory();} + else if(typeof define === 'function' && define.amd) + {define([], factory);} + else if(typeof exports === 'object') + {exports["Handlebars"] = factory();} + else + {root["Handlebars"] = factory();} +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ let installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ {return installedModules[moduleId].exports;} + +/******/ // Create a new module (and put it into the cache) +/******/ let module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _interopRequireWildcard = __webpack_require__(1)['default']; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + + let _handlebarsBase = __webpack_require__(3); + + let base = _interopRequireWildcard(_handlebarsBase); + + // Each of these augment the Handlebars object. No need to setup here. + // (This is done to easily share code between commonjs and browse envs) + + let _handlebarsSafeString = __webpack_require__(36); + + let _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString); + + let _handlebarsException = __webpack_require__(5); + + let _handlebarsException2 = _interopRequireDefault(_handlebarsException); + + let _handlebarsUtils = __webpack_require__(4); + + let Utils = _interopRequireWildcard(_handlebarsUtils); + + let _handlebarsRuntime = __webpack_require__(37); + + let runtime = _interopRequireWildcard(_handlebarsRuntime); + + let _handlebarsNoConflict = __webpack_require__(43); + + let _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); + + // For compatibility and usage outside of module systems, make the Handlebars object a namespace + function create() { + let hb = new base.HandlebarsEnvironment(); + + Utils.extend(hb, base); + hb.SafeString = _handlebarsSafeString2['default']; + hb.Exception = _handlebarsException2['default']; + hb.Utils = Utils; + hb.escapeExpression = Utils.escapeExpression; + + hb.VM = runtime; + hb.template = function (spec) { + return runtime.template(spec, hb); + }; + + return hb; + } + + let inst = create(); + inst.create = create; + + _handlebarsNoConflict2['default'](inst); + + inst['default'] = inst; + + exports['default'] = inst; + module.exports = exports['default']; + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + if (obj && obj.__esModule) { + return obj; + } else { + let newObj = {}; + + if (obj != null) { + for (let key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) {newObj[key] = obj[key];} + } + } + + newObj["default"] = obj; + return newObj; + } + }; + + exports.__esModule = true; + +/***/ }), +/* 2 */ +/***/ (function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + return obj && obj.__esModule ? obj : { + "default": obj + }; + }; + + exports.__esModule = true; + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + exports.HandlebarsEnvironment = HandlebarsEnvironment; + + let _utils = __webpack_require__(4); + + let _exception = __webpack_require__(5); + + let _exception2 = _interopRequireDefault(_exception); + + let _helpers = __webpack_require__(9); + + let _decorators = __webpack_require__(29); + + let _logger = __webpack_require__(31); + + let _logger2 = _interopRequireDefault(_logger); + + let _internalProtoAccess = __webpack_require__(32); + + let VERSION = '4.7.7'; + exports.VERSION = VERSION; + let COMPILER_REVISION = 8; + exports.COMPILER_REVISION = COMPILER_REVISION; + let LAST_COMPATIBLE_COMPILER_REVISION = 7; + + exports.LAST_COMPATIBLE_COMPILER_REVISION = LAST_COMPATIBLE_COMPILER_REVISION; + let REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '== 1.x.x', + 5: '== 2.0.0-alpha.x', + 6: '>= 2.0.0-beta.1', + 7: '>= 4.0.0 <4.3.0', + 8: '>= 4.3.0' + }; + + exports.REVISION_CHANGES = REVISION_CHANGES; + let objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials, decorators) { + this.helpers = helpers || {}; + this.partials = partials || {}; + this.decorators = decorators || {}; + + _helpers.registerDefaultHelpers(this); + _decorators.registerDefaultDecorators(this); + } + + HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: _logger2['default'], + log: _logger2['default'].log, + + registerHelper: function registerHelper(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple helpers'); + } + _utils.extend(this.helpers, name); + } else { + this.helpers[name] = fn; + } + }, + unregisterHelper: function unregisterHelper(name) { + delete this.helpers[name]; + }, + + registerPartial: function registerPartial(name, partial) { + if (_utils.toString.call(name) === objectType) { + _utils.extend(this.partials, name); + } else { + if (typeof partial === 'undefined') { + throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined'); + } + this.partials[name] = partial; + } + }, + unregisterPartial: function unregisterPartial(name) { + delete this.partials[name]; + }, + + registerDecorator: function registerDecorator(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple decorators'); + } + _utils.extend(this.decorators, name); + } else { + this.decorators[name] = fn; + } + }, + unregisterDecorator: function unregisterDecorator(name) { + delete this.decorators[name]; + }, + /** + * Reset the memory of illegal property accesses that have already been logged. + * @deprecated should only be used in handlebars test-cases + */ + resetLoggedPropertyAccesses: function resetLoggedPropertyAccesses() { + _internalProtoAccess.resetLoggedProperties(); + } + }; + + let log = _logger2['default'].log; + + exports.log = log; + exports.createFrame = _utils.createFrame; + exports.logger = _logger2['default']; + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.extend = extend; + exports.indexOf = indexOf; + exports.escapeExpression = escapeExpression; + exports.isEmpty = isEmpty; + exports.createFrame = createFrame; + exports.blockParams = blockParams; + exports.appendContextPath = appendContextPath; + let escape = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`', + '=': '=' + }; + + let badChars = /[&<>"'`=]/g, + possible = /[&<>"'`=]/; + + function escapeChar(chr) { + return escape[chr]; + } + + function extend(obj /* , ...source */) { + for (let i = 1; i < arguments.length; i++) { + for (let key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } + } + } + + return obj; + } + + let toString = Object.prototype.toString; + + exports.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + /* eslint-disable func-style */ + let isFunction = function isFunction(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + /* istanbul ignore next */ + if (isFunction(/x/)) { + exports.isFunction = isFunction = function (value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + exports.isFunction = isFunction; + + /* eslint-enable func-style */ + + /* istanbul ignore next */ + let isArray = Array.isArray || function (value) { + return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; + }; + + exports.isArray = isArray; + // Older IE versions do not directly support indexOf so we must implement our own, sadly. + + function indexOf(array, value) { + for (let i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + return -1; + } + + function escapeExpression(string) { + if (typeof string !== 'string') { + // don't escape SafeStrings, since they're already safe + if (string && string.toHTML) { + return string.toHTML(); + } else if (string == null) { + return ''; + } else if (!string) { + return string + ''; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = '' + string; + } + + if (!possible.test(string)) { + return string; + } + return string.replace(badChars, escapeChar); + } + + function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + function createFrame(object) { + let frame = extend({}, object); + frame._parent = object; + return frame; + } + + function blockParams(params, ids) { + params.path = ids; + return params; + } + + function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; + } + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _Object$defineProperty = __webpack_require__(6)['default']; + + exports.__esModule = true; + let errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + let loc = node && node.loc, + line = undefined, + endLineNumber = undefined, + column = undefined, + endColumn = undefined; + + if (loc) { + line = loc.start.line; + endLineNumber = loc.end.line; + column = loc.start.column; + endColumn = loc.end.column; + + message += ' - ' + line + ':' + column; + } + + let tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (let idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + /* istanbul ignore else */ + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Exception); + } + + try { + if (loc) { + this.lineNumber = line; + this.endLineNumber = endLineNumber; + + // Work around issue under safari where we can't directly set the column value + /* istanbul ignore next */ + if (_Object$defineProperty) { + Object.defineProperty(this, 'column', { + value: column, + enumerable: true + }); + Object.defineProperty(this, 'endColumn', { + value: endColumn, + enumerable: true + }); + } else { + this.column = column; + this.endColumn = endColumn; + } + } + } catch (nop) { + /* Ignore if the browser is very particular */ + } + } + + Exception.prototype = new Error(); + + exports['default'] = Exception; + module.exports = exports['default']; + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(7), __esModule: true }; + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + + let $ = __webpack_require__(8); + module.exports = function defineProperty(it, key, desc){ + return $.setDesc(it, key, desc); + }; + +/***/ }), +/* 8 */ +/***/ (function(module, exports) { + + let $Object = Object; + module.exports = { + create: $Object.create, + getProto: $Object.getPrototypeOf, + isEnum: {}.propertyIsEnumerable, + getDesc: $Object.getOwnPropertyDescriptor, + setDesc: $Object.defineProperty, + setDescs: $Object.defineProperties, + getKeys: $Object.keys, + getNames: $Object.getOwnPropertyNames, + getSymbols: $Object.getOwnPropertySymbols, + each: [].forEach + }; + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + exports.registerDefaultHelpers = registerDefaultHelpers; + exports.moveHelperToHooks = moveHelperToHooks; + + let _helpersBlockHelperMissing = __webpack_require__(10); + + let _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing); + + let _helpersEach = __webpack_require__(11); + + let _helpersEach2 = _interopRequireDefault(_helpersEach); + + let _helpersHelperMissing = __webpack_require__(24); + + let _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing); + + let _helpersIf = __webpack_require__(25); + + let _helpersIf2 = _interopRequireDefault(_helpersIf); + + let _helpersLog = __webpack_require__(26); + + let _helpersLog2 = _interopRequireDefault(_helpersLog); + + let _helpersLookup = __webpack_require__(27); + + let _helpersLookup2 = _interopRequireDefault(_helpersLookup); + + let _helpersWith = __webpack_require__(28); + + let _helpersWith2 = _interopRequireDefault(_helpersWith); + + function registerDefaultHelpers(instance) { + _helpersBlockHelperMissing2['default'](instance); + _helpersEach2['default'](instance); + _helpersHelperMissing2['default'](instance); + _helpersIf2['default'](instance); + _helpersLog2['default'](instance); + _helpersLookup2['default'](instance); + _helpersWith2['default'](instance); + } + + function moveHelperToHooks(instance, helperName, keepHelper) { + if (instance.helpers[helperName]) { + instance.hooks[helperName] = instance.helpers[helperName]; + if (!keepHelper) { + delete instance.helpers[helperName]; + } + } + } + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + let _utils = __webpack_require__(4); + + exports['default'] = function (instance) { + instance.registerHelper('blockHelperMissing', function (context, options) { + let inverse = options.inverse, + fn = options.fn; + + if (context === true) { + return fn(this); + } else if (context === false || context == null) { + return inverse(this); + } else if (_utils.isArray(context)) { + if (context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + if (options.data && options.ids) { + let data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name); + options = { data }; + } + + return fn(context, options); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + + /* WEBPACK VAR INJECTION */(function(global) {'use strict'; + + let _Object$keys = __webpack_require__(12)['default']; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + + let _utils = __webpack_require__(4); + + let _exception = __webpack_require__(5); + + let _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('each', function (context, options) { + if (!options) { + throw new _exception2['default']('Must pass iterator to #each'); + } + + let fn = options.fn, + inverse = options.inverse, + i = 0, + ret = '', + data = undefined, + contextPath = undefined; + + if (options.data && options.ids) { + contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + + if (_utils.isFunction(context)) { + context = context.call(this); + } + + if (options.data) { + data = _utils.createFrame(options.data); + } + + function execIteration(field, index, last) { + if (data) { + data.key = field; + data.index = index; + data.first = index === 0; + data.last = !!last; + + if (contextPath) { + data.contextPath = contextPath + field; + } + } + + ret = ret + fn(context[field], { + data, + blockParams: _utils.blockParams([context[field], field], [contextPath + field, null]) + }); + } + + if (context && typeof context === 'object') { + if (_utils.isArray(context)) { + for (var j = context.length; i < j; i++) { + if (i in context) { + execIteration(i, i, i === context.length - 1); + } + } + } else if (global.Symbol && context[global.Symbol.iterator]) { + let newContext = []; + let iterator = context[global.Symbol.iterator](); + for (let it = iterator.next(); !it.done; it = iterator.next()) { + newContext.push(it.value); + } + context = newContext; + for (var j = context.length; i < j; i++) { + execIteration(i, i, i === context.length - 1); + } + } else { + (function () { + let priorKey = undefined; + + _Object$keys(context).forEach(function (key) { + // We're running the iterations one step out of sync so we can detect + // the last iteration without have to scan the object twice and create + // an itermediate keys array. + if (priorKey !== undefined) { + execIteration(priorKey, i - 1); + } + priorKey = key; + i++; + }); + if (priorKey !== undefined) { + execIteration(priorKey, i - 1, true); + } + })(); + } + } + + if (i === 0) { + ret = inverse(this); + } + + return ret; + }); + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; })())); + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(13), __esModule: true }; + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + + __webpack_require__(14); + module.exports = __webpack_require__(20).Object.keys; + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + + // 19.1.2.14 Object.keys(O) + let toObject = __webpack_require__(15); + + __webpack_require__(17)('keys', function($keys){ + return function keys(it){ + return $keys(toObject(it)); + }; + }); + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + + // 7.1.13 ToObject(argument) + let defined = __webpack_require__(16); + module.exports = function(it){ + return Object(defined(it)); + }; + +/***/ }), +/* 16 */ +/***/ (function(module, exports) { + + // 7.2.1 RequireObjectCoercible(argument) + module.exports = function(it){ + if(it == undefined){throw TypeError("Can't call method on " + it);} + return it; + }; + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + + // most Object methods by ES6 should accept primitives + let $export = __webpack_require__(18) + , core = __webpack_require__(20) + , fails = __webpack_require__(23); + module.exports = function(KEY, exec){ + let fn = (core.Object || {})[KEY] || Object[KEY] + , exp = {}; + exp[KEY] = exec(fn); + $export($export.S + $export.F * fails(function(){ fn(1); }), 'Object', exp); + }; + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + + let global = __webpack_require__(19) + , core = __webpack_require__(20) + , ctx = __webpack_require__(21) + , PROTOTYPE = 'prototype'; + + var $export = function(type, name, source){ + let IS_FORCED = type & $export.F + , IS_GLOBAL = type & $export.G + , IS_STATIC = type & $export.S + , IS_PROTO = type & $export.P + , IS_BIND = type & $export.B + , IS_WRAP = type & $export.W + , exports = IS_GLOBAL ? core : core[name] || (core[name] = {}) + , target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE] + , key, own, out; + if(IS_GLOBAL){source = name;} + for(key in source){ + // contains in native + own = !IS_FORCED && target && key in target; + if(own && key in exports){continue;} + // export native or passed + out = own ? target[key] : source[key]; + // prevent global pollution for namespaces + exports[key] = IS_GLOBAL && typeof target[key] !== 'function' ? source[key] + // bind timers to global for call from export context + : IS_BIND && own ? ctx(out, global) + // wrap global constructors for prevent change them in library + : IS_WRAP && target[key] == out ? (function(C){ + let F = function(param){ + return this instanceof C ? new C(param) : C(param); + }; + F[PROTOTYPE] = C[PROTOTYPE]; + return F; + // make static versions for prototype methods + })(out) : IS_PROTO && typeof out === 'function' ? ctx(Function.call, out) : out; + if(IS_PROTO){(exports[PROTOTYPE] || (exports[PROTOTYPE] = {}))[key] = out;} + } + }; + // type bitmap + $export.F = 1; // forced + $export.G = 2; // global + $export.S = 4; // static + $export.P = 8; // proto + $export.B = 16; // bind + $export.W = 32; // wrap + module.exports = $export; + +/***/ }), +/* 19 */ +/***/ (function(module, exports) { + + // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 + let global = module.exports = typeof window !== 'undefined' && window.Math == Math + ? window : typeof self !== 'undefined' && self.Math == Math ? self : Function('return this')(); + if(typeof __g === 'number'){__g = global;} // eslint-disable-line no-undef + +/***/ }), +/* 20 */ +/***/ (function(module, exports) { + + let core = module.exports = {version: '1.2.6'}; + if(typeof __e === 'number'){__e = core;} // eslint-disable-line no-undef + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + + // optional / simple context binding + let aFunction = __webpack_require__(22); + module.exports = function(fn, that, length){ + aFunction(fn); + if(that === undefined){return fn;} + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + case 2: return function(a, b){ + return fn.call(that, a, b); + }; + case 3: return function(a, b, c){ + return fn.call(that, a, b, c); + }; + } + return function(/* ...args */){ + return fn.apply(that, arguments); + }; + }; + +/***/ }), +/* 22 */ +/***/ (function(module, exports) { + + module.exports = function(it){ + if(typeof it !== 'function'){throw TypeError(it + ' is not a function!');} + return it; + }; + +/***/ }), +/* 23 */ +/***/ (function(module, exports) { + + module.exports = function(exec){ + try { + return !!exec(); + } catch(e){ + return true; + } + }; + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + + let _exception = __webpack_require__(5); + + let _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('helperMissing', function () /* [args, ]options */{ + if (arguments.length === 1) { + // A missing field in a {{foo}} construct. + return undefined; + } else { + // Someone is actually trying to call something, blow up. + throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"'); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 25 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + + let _utils = __webpack_require__(4); + + let _exception = __webpack_require__(5); + + let _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('if', function (conditional, options) { + if (arguments.length != 2) { + throw new _exception2['default']('#if requires exactly one argument'); + } + if (_utils.isFunction(conditional)) { + conditional = conditional.call(this); + } + + // Default behavior is to render the positive path if the value is truthy and not empty. + // The `includeZero` option may be set to treat the condtional as purely not empty based on the + // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. + if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) { + return options.inverse(this); + } else { + return options.fn(this); + } + }); + + instance.registerHelper('unless', function (conditional, options) { + if (arguments.length != 2) { + throw new _exception2['default']('#unless requires exactly one argument'); + } + return instance.helpers['if'].call(this, conditional, { + fn: options.inverse, + inverse: options.fn, + hash: options.hash + }); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 26 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('log', function () /* message, options */{ + let args = [undefined], + options = arguments[arguments.length - 1]; + for (let i = 0; i < arguments.length - 1; i++) { + args.push(arguments[i]); + } + + let level = 1; + if (options.hash.level != null) { + level = options.hash.level; + } else if (options.data && options.data.level != null) { + level = options.data.level; + } + args[0] = level; + + instance.log.apply(instance, args); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 27 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('lookup', function (obj, field, options) { + if (!obj) { + // Note for 5.0: Change to "obj == null" in 5.0 + return obj; + } + return options.lookupProperty(obj, field); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + + let _utils = __webpack_require__(4); + + let _exception = __webpack_require__(5); + + let _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('with', function (context, options) { + if (arguments.length != 2) { + throw new _exception2['default']('#with requires exactly one argument'); + } + if (_utils.isFunction(context)) { + context = context.call(this); + } + + let fn = options.fn; + + if (!_utils.isEmpty(context)) { + let data = options.data; + if (options.data && options.ids) { + data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]); + } + + return fn(context, { + data, + blockParams: _utils.blockParams([context], [data && data.contextPath]) + }); + } else { + return options.inverse(this); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + exports.registerDefaultDecorators = registerDefaultDecorators; + + let _decoratorsInline = __webpack_require__(30); + + let _decoratorsInline2 = _interopRequireDefault(_decoratorsInline); + + function registerDefaultDecorators(instance) { + _decoratorsInline2['default'](instance); + } + +/***/ }), +/* 30 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + let _utils = __webpack_require__(4); + + exports['default'] = function (instance) { + instance.registerDecorator('inline', function (fn, props, container, options) { + let ret = fn; + if (!props.partials) { + props.partials = {}; + ret = function (context, options) { + // Create a new partials stack frame prior to exec. + let original = container.partials; + container.partials = _utils.extend({}, original, props.partials); + let ret = fn(context, options); + container.partials = original; + return ret; + }; + } + + props.partials[options.args[0]] = options.fn; + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + let _utils = __webpack_require__(4); + + var logger = { + methodMap: ['debug', 'info', 'warn', 'error'], + level: 'info', + + // Maps a given level value to the `methodMap` indexes above. + lookupLevel: function lookupLevel(level) { + if (typeof level === 'string') { + let levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase()); + if (levelMap >= 0) { + level = levelMap; + } else { + level = parseInt(level, 10); + } + } + + return level; + }, + + // Can be overridden in the host environment + log: function log(level) { + level = logger.lookupLevel(level); + + if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { + let method = logger.methodMap[level]; + // eslint-disable-next-line no-console + if (!console[method]) { + method = 'log'; + } + + for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + message[_key - 1] = arguments[_key]; + } + + console[method].apply(console, message); // eslint-disable-line no-console + } + } + }; + + exports['default'] = logger; + module.exports = exports['default']; + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _Object$create = __webpack_require__(33)['default']; + + let _Object$keys = __webpack_require__(12)['default']; + + let _interopRequireWildcard = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.createProtoAccessControl = createProtoAccessControl; + exports.resultIsAllowed = resultIsAllowed; + exports.resetLoggedProperties = resetLoggedProperties; + + let _createNewLookupObject = __webpack_require__(35); + + let _logger = __webpack_require__(31); + + let logger = _interopRequireWildcard(_logger); + + let loggedProperties = _Object$create(null); + + function createProtoAccessControl(runtimeOptions) { + let defaultMethodWhiteList = _Object$create(null); + defaultMethodWhiteList['constructor'] = false; + defaultMethodWhiteList['__defineGetter__'] = false; + defaultMethodWhiteList['__defineSetter__'] = false; + defaultMethodWhiteList['__lookupGetter__'] = false; + + let defaultPropertyWhiteList = _Object$create(null); + // eslint-disable-next-line no-proto + defaultPropertyWhiteList['__proto__'] = false; + + return { + properties: { + whitelist: _createNewLookupObject.createNewLookupObject(defaultPropertyWhiteList, runtimeOptions.allowedProtoProperties), + defaultValue: runtimeOptions.allowProtoPropertiesByDefault + }, + methods: { + whitelist: _createNewLookupObject.createNewLookupObject(defaultMethodWhiteList, runtimeOptions.allowedProtoMethods), + defaultValue: runtimeOptions.allowProtoMethodsByDefault + } + }; + } + + function resultIsAllowed(result, protoAccessControl, propertyName) { + if (typeof result === 'function') { + return checkWhiteList(protoAccessControl.methods, propertyName); + } else { + return checkWhiteList(protoAccessControl.properties, propertyName); + } + } + + function checkWhiteList(protoAccessControlForType, propertyName) { + if (protoAccessControlForType.whitelist[propertyName] !== undefined) { + return protoAccessControlForType.whitelist[propertyName] === true; + } + if (protoAccessControlForType.defaultValue !== undefined) { + return protoAccessControlForType.defaultValue; + } + logUnexpecedPropertyAccessOnce(propertyName); + return false; + } + + function logUnexpecedPropertyAccessOnce(propertyName) { + if (loggedProperties[propertyName] !== true) { + loggedProperties[propertyName] = true; + logger.log('error', 'Handlebars: Access has been denied to resolve the property "' + propertyName + '" because it is not an "own property" of its parent.\n' + 'You can add a runtime option to disable the check or this warning:\n' + 'See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details'); + } + } + + function resetLoggedProperties() { + _Object$keys(loggedProperties).forEach(function (propertyName) { + delete loggedProperties[propertyName]; + }); + } + +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(34), __esModule: true }; + +/***/ }), +/* 34 */ +/***/ (function(module, exports, __webpack_require__) { + + let $ = __webpack_require__(8); + module.exports = function create(P, D){ + return $.create(P, D); + }; + +/***/ }), +/* 35 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _Object$create = __webpack_require__(33)['default']; + + exports.__esModule = true; + exports.createNewLookupObject = createNewLookupObject; + + let _utils = __webpack_require__(4); + + /** + * Create a new object with "null"-prototype to avoid truthy results on prototype properties. + * The resulting object can be used with "object[property]" to check if a property exists + * @param {...object} sources a varargs parameter of source objects that will be merged + * @returns {object} + */ + + function createNewLookupObject() { + for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) { + sources[_key] = arguments[_key]; + } + + return _utils.extend.apply(undefined, [_Object$create(null)].concat(sources)); + } + +/***/ }), +/* 36 */ +/***/ (function(module, exports) { + + // Build out our basic SafeString type + 'use strict'; + + exports.__esModule = true; + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = SafeString.prototype.toHTML = function () { + return '' + this.string; + }; + + exports['default'] = SafeString; + module.exports = exports['default']; + +/***/ }), +/* 37 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + let _Object$seal = __webpack_require__(38)['default']; + + let _Object$keys = __webpack_require__(12)['default']; + + let _interopRequireWildcard = __webpack_require__(1)['default']; + + let _interopRequireDefault = __webpack_require__(2)['default']; + + exports.__esModule = true; + exports.checkRevision = checkRevision; + exports.template = template; + exports.wrapProgram = wrapProgram; + exports.resolvePartial = resolvePartial; + exports.invokePartial = invokePartial; + exports.noop = noop; + + let _utils = __webpack_require__(4); + + let Utils = _interopRequireWildcard(_utils); + + let _exception = __webpack_require__(5); + + let _exception2 = _interopRequireDefault(_exception); + + let _base = __webpack_require__(3); + + let _helpers = __webpack_require__(9); + + let _internalWrapHelper = __webpack_require__(42); + + let _internalProtoAccess = __webpack_require__(32); + + function checkRevision(compilerInfo) { + let compilerRevision = compilerInfo && compilerInfo[0] || 1, + currentRevision = _base.COMPILER_REVISION; + + if (compilerRevision >= _base.LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= _base.COMPILER_REVISION) { + return; + } + + if (compilerRevision < _base.LAST_COMPATIBLE_COMPILER_REVISION) { + let runtimeVersions = _base.REVISION_CHANGES[currentRevision], + compilerVersions = _base.REVISION_CHANGES[compilerRevision]; + throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); + } + } + + function template(templateSpec, env) { + /* istanbul ignore next */ + if (!env) { + throw new _exception2['default']('No environment passed to template'); + } + if (!templateSpec || !templateSpec.main) { + throw new _exception2['default']('Unknown template object: ' + typeof templateSpec); + } + + templateSpec.main.decorator = templateSpec.main_d; + + // Note: Using env.VM references rather than local var references throughout this section to allow + // for external users to override these as pseudo-supported APIs. + env.VM.checkRevision(templateSpec.compiler); + + // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0) + let templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7; + + function invokePartialWrapper(partial, context, options) { + if (options.hash) { + context = Utils.extend({}, context, options.hash); + if (options.ids) { + options.ids[0] = true; + } + } + partial = env.VM.resolvePartial.call(this, partial, context, options); + + let extendedOptions = Utils.extend({}, options, { + hooks: this.hooks, + protoAccessControl: this.protoAccessControl + }); + + let result = env.VM.invokePartial.call(this, partial, context, extendedOptions); + + if (result == null && env.compile) { + options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); + result = options.partials[options.name](context, extendedOptions); + } + if (result != null) { + if (options.indent) { + let lines = result.split('\n'); + for (let i = 0, l = lines.length; i < l; i++) { + if (!lines[i] && i + 1 === l) { + break; + } + + lines[i] = options.indent + lines[i]; + } + result = lines.join('\n'); + } + return result; + } else { + throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); + } + } + + // Just add water + var container = { + strict: function strict(obj, name, loc) { + if (!obj || !(name in obj)) { + throw new _exception2['default']('"' + name + '" not defined in ' + obj, { + loc + }); + } + return container.lookupProperty(obj, name); + }, + lookupProperty: function lookupProperty(parent, propertyName) { + let result = parent[propertyName]; + if (result == null) { + return result; + } + if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { + return result; + } + + if (_internalProtoAccess.resultIsAllowed(result, container.protoAccessControl, propertyName)) { + return result; + } + return undefined; + }, + lookup: function lookup(depths, name) { + let len = depths.length; + for (let i = 0; i < len; i++) { + let result = depths[i] && container.lookupProperty(depths[i], name); + if (result != null) { + return depths[i][name]; + } + } + }, + lambda: function lambda(current, context) { + return typeof current === 'function' ? current.call(context) : current; + }, + + escapeExpression: Utils.escapeExpression, + invokePartial: invokePartialWrapper, + + fn: function fn(i) { + let ret = templateSpec[i]; + ret.decorator = templateSpec[i + '_d']; + return ret; + }, + + programs: [], + program: function program(i, data, declaredBlockParams, blockParams, depths) { + let programWrapper = this.programs[i], + fn = this.fn(i); + if (data || depths || blockParams || declaredBlockParams) { + programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); + } else if (!programWrapper) { + programWrapper = this.programs[i] = wrapProgram(this, i, fn); + } + return programWrapper; + }, + + data: function data(value, depth) { + while (value && depth--) { + value = value._parent; + } + return value; + }, + mergeIfNeeded: function mergeIfNeeded(param, common) { + let obj = param || common; + + if (param && common && param !== common) { + obj = Utils.extend({}, common, param); + } + + return obj; + }, + // An empty object to use as replacement for null-contexts + nullContext: _Object$seal({}), + + noop: env.VM.noop, + compilerInfo: templateSpec.compiler + }; + + function ret(context) { + let options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + let data = options.data; + + ret._setup(options); + if (!options.partial && templateSpec.useData) { + data = initData(context, data); + } + let depths = undefined, + blockParams = templateSpec.useBlockParams ? [] : undefined; + if (templateSpec.useDepths) { + if (options.depths) { + depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths; + } else { + depths = [context]; + } + } + + function main(context /*, options*/) { + return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); + } + + main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); + return main(context, options); + } + + ret.isTop = true; + + ret._setup = function (options) { + if (!options.partial) { + let mergedHelpers = Utils.extend({}, env.helpers, options.helpers); + wrapHelpersToPassLookupProperty(mergedHelpers, container); + container.helpers = mergedHelpers; + + if (templateSpec.usePartial) { + // Use mergeIfNeeded here to prevent compiling global partials multiple times + container.partials = container.mergeIfNeeded(options.partials, env.partials); + } + if (templateSpec.usePartial || templateSpec.useDecorators) { + container.decorators = Utils.extend({}, env.decorators, options.decorators); + } + + container.hooks = {}; + container.protoAccessControl = _internalProtoAccess.createProtoAccessControl(options); + + let keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7; + _helpers.moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers); + _helpers.moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers); + } else { + container.protoAccessControl = options.protoAccessControl; // internal option + container.helpers = options.helpers; + container.partials = options.partials; + container.decorators = options.decorators; + container.hooks = options.hooks; + } + }; + + ret._child = function (i, data, blockParams, depths) { + if (templateSpec.useBlockParams && !blockParams) { + throw new _exception2['default']('must pass block params'); + } + if (templateSpec.useDepths && !depths) { + throw new _exception2['default']('must pass parent depths'); + } + + return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); + }; + return ret; + } + + function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { + function prog(context) { + let options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + let currentDepths = depths; + if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) { + currentDepths = [context].concat(depths); + } + + return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths); + } + + prog = executeDecorators(fn, prog, container, depths, data, blockParams); + + prog.program = i; + prog.depth = depths ? depths.length : 0; + prog.blockParams = declaredBlockParams || 0; + return prog; + } + + /** + * This is currently part of the official API, therefore implementation details should not be changed. + */ + + function resolvePartial(partial, context, options) { + if (!partial) { + if (options.name === '@partial-block') { + partial = options.data['partial-block']; + } else { + partial = options.partials[options.name]; + } + } else if (!partial.call && !options.name) { + // This is a dynamic partial that returned a string + options.name = partial; + partial = options.partials[partial]; + } + return partial; + } + + function invokePartial(partial, context, options) { + // Use the current closure context to save the partial-block if this partial + let currentPartialBlock = options.data && options.data['partial-block']; + options.partial = true; + if (options.ids) { + options.data.contextPath = options.ids[0] || options.data.contextPath; + } + + let partialBlock = undefined; + if (options.fn && options.fn !== noop) { + (function () { + options.data = _base.createFrame(options.data); + // Wrapper function to get access to currentPartialBlock from the closure + let fn = options.fn; + partialBlock = options.data['partial-block'] = function partialBlockWrapper(context) { + let options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + // Restore the partial-block from the closure for the execution of the block + // i.e. the part inside the block of the partial call. + options.data = _base.createFrame(options.data); + options.data['partial-block'] = currentPartialBlock; + return fn(context, options); + }; + if (fn.partials) { + options.partials = Utils.extend({}, options.partials, fn.partials); + } + })(); + } + + if (partial === undefined && partialBlock) { + partial = partialBlock; + } + + if (partial === undefined) { + throw new _exception2['default']('The partial ' + options.name + ' could not be found'); + } else if (partial instanceof Function) { + return partial(context, options); + } + } + + function noop() { + return ''; + } + + function initData(context, data) { + if (!data || !('root' in data)) { + data = data ? _base.createFrame(data) : {}; + data.root = context; + } + return data; + } + + function executeDecorators(fn, prog, container, depths, data, blockParams) { + if (fn.decorator) { + let props = {}; + prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); + Utils.extend(prog, props); + } + return prog; + } + + function wrapHelpersToPassLookupProperty(mergedHelpers, container) { + _Object$keys(mergedHelpers).forEach(function (helperName) { + let helper = mergedHelpers[helperName]; + mergedHelpers[helperName] = passLookupPropertyOption(helper, container); + }); + } + + function passLookupPropertyOption(helper, container) { + let lookupProperty = container.lookupProperty; + return _internalWrapHelper.wrapHelper(helper, function (options) { + return Utils.extend({ lookupProperty }, options); + }); + } + +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(39), __esModule: true }; + +/***/ }), +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { + + __webpack_require__(40); + module.exports = __webpack_require__(20).Object.seal; + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + + // 19.1.2.17 Object.seal(O) + let isObject = __webpack_require__(41); + + __webpack_require__(17)('seal', function($seal){ + return function seal(it){ + return $seal && isObject(it) ? $seal(it) : it; + }; + }); + +/***/ }), +/* 41 */ +/***/ (function(module, exports) { + + module.exports = function(it){ + return typeof it === 'object' ? it !== null : typeof it === 'function'; + }; + +/***/ }), +/* 42 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.wrapHelper = wrapHelper; + + function wrapHelper(helper, transformOptionsFn) { + if (typeof helper !== 'function') { + // This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639 + // We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function. + return helper; + } + let wrapper = function wrapper() /* dynamic arguments */{ + let options = arguments[arguments.length - 1]; + arguments[arguments.length - 1] = transformOptionsFn(options); + return helper.apply(this, arguments); + }; + return wrapper; + } + +/***/ }), +/* 43 */ +/***/ (function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {'use strict'; + + exports.__esModule = true; + + exports['default'] = function (Handlebars) { + /* istanbul ignore next */ + let root = typeof global !== 'undefined' ? global : window, + $Handlebars = root.Handlebars; + /* istanbul ignore next */ + Handlebars.noConflict = function () { + if (root.Handlebars === Handlebars) { + root.Handlebars = $Handlebars; + } + return Handlebars; + }; + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; })())); + +/***/ }) +/******/ ]); +}); +; diff --git a/assets/javascripts/legacy/itsatrap.js b/assets/javascripts/legacy/itsatrap.js new file mode 100644 index 00000000..e4f847e9 --- /dev/null +++ b/assets/javascripts/legacy/itsatrap.js @@ -0,0 +1,1159 @@ +/*global define:false */ +/** + * Copyright 2012-2017 Craig Campbell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ItsATrap is a simple keyboard shortcut library for Javascript with + * no external dependencies + * + * @version 2.0.1 + * @url github.com/discourse/itsatrap + */ +(function(window, document, undefined) { + // Check if itsatrap is used inside browser, if not, return + if (!window) { + return; + } + + /** + * mapping of special keycodes to their corresponding keys + * + * everything in this dictionary cannot use keypress events + * so it has to be here to map to the correct keycodes for + * keyup/keydown events + * + * @type {Object} + */ + let _MAP = { + 8: "backspace", + 9: "tab", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 20: "capslock", + 27: "esc", + 32: "space", + 33: "pageup", + 34: "pagedown", + 35: "end", + 36: "home", + 37: "left", + 38: "up", + 39: "right", + 40: "down", + 45: "ins", + 46: "del", + 91: "meta", + 93: "meta", + 224: "meta" + }; + + /** + * mapping for special characters so they can support + * + * this dictionary is only used incase you want to bind a + * keyup or keydown event to one of these keys + * + * @type {Object} + */ + let _KEYCODE_MAP = { + 106: "*", + 107: "+", + 109: "-", + 110: ".", + 111: "/", + 186: ";", + 187: "=", + 188: ",", + 189: "-", + 190: ".", + 191: "/", + 192: "`", + 219: "[", + 220: "\\", + 221: "]", + 222: "'" + }; + + /** + * this is a mapping of keys that require shift on a US keypad + * back to the non shift equivelents + * + * this is so you can use keyup events with these keys + * + * note that this will only work reliably on US keyboards + * + * @type {Object} + */ + let _SHIFT_MAP = { + "~": "`", + "!": "1", + "@": "2", + "#": "3", + $: "4", + "%": "5", + "^": "6", + "&": "7", + "*": "8", + "(": "9", + ")": "0", + _: "-", + "+": "=", + ":": ";", + '"': "'", + "<": ",", + ">": ".", + "?": "/", + "|": "\\" + }; + + /** + * this is a list of special strings you can use to map + * to modifier keys when you specify your keyboard shortcuts + * + * @type {Object} + */ + let _SPECIAL_ALIASES = { + option: "alt", + command: "meta", + return: "enter", + escape: "esc", + plus: "+", + mod: /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? "meta" : "ctrl" + }; + + /** + * variable to store the flipped version of _MAP from above + * needed to check if we should use keypress or not when no action + * is specified + * + * @type {Object|undefined} + */ + let _REVERSE_MAP; + + /** + * holds a reference to global bindings + * + * @type {Object|undefined} + */ + let _globalCallbacks = {}; + + /** + * loop through the f keys, f1 to f19 and add them to the map + * programatically + */ + for (var i = 1; i < 20; ++i) { + _MAP[111 + i] = "f" + i; + } + + /** + * loop through to map numbers on the numeric keypad + */ + for (i = 0; i <= 9; ++i) { + // This needs to use a string cause otherwise since 0 is falsey + // itsatrap will never fire for numpad 0 pressed as part of a keydown + // event. + // + // @see https://github.com/ccampbell/itsatrap/pull/258 + _MAP[i + 96] = i.toString(); + } + + /** + * cross browser add event method + * + * @param {Element|HTMLDocument} object + * @param {string} type + * @param {EventListenerOrEventListenerObject} callback + * @returns void + */ + function _addEvent(object, type, callback) { + if (object.addEventListener) { + object.addEventListener(type, callback, false); + return; + } + + object.attachEvent("on" + type, callback); + } + + /** + * cross browser remove event method + * + * @param {Element|HTMLDocument} object + * @param {string} type + * @param {EventListenerOrEventListenerObject} callback + * @returns void + */ + function _removeEvent(object, type, callback) { + if (object.removeEventListener) { + object.removeEventListener(type, callback, false); + return; + } + object.detachEvent("on" + type, callback); + } + + /** + * takes the event and returns the key character + * + * @param {Event} e + * @return {string} + */ + function _characterFromEvent(e) { + // for keypress events we should return the character as is + if (e.type == "keypress") { + let character = String.fromCharCode(e.which); + + // if the shift key is not pressed then it is safe to assume + // that we want the character to be lowercase. this means if + // you accidentally have caps lock on then your key bindings + // will continue to work + // + // the only side effect that might not be desired is if you + // bind something like 'A' cause you want to trigger an + // event when capital A is pressed caps lock will no longer + // trigger the event. shift+a will though. + if (!e.shiftKey) { + character = character.toLowerCase(); + } + + return character; + } + + // for non keypress events the special maps are needed + if (_MAP[e.which]) { + return _MAP[e.which]; + } + + if (_KEYCODE_MAP[e.which]) { + return _KEYCODE_MAP[e.which]; + } + + // if it is not in the special map + + // with keydown and keyup events the character seems to always + // come in as an uppercase character whether you are pressing shift + // or not. we should make sure it is always lowercase for comparisons + return String.fromCharCode(e.which).toLowerCase(); + } + + /** + * checks if two arrays are equal + * + * @param {Array} modifiers1 + * @param {Array} modifiers2 + * @returns {boolean} + */ + function _modifiersMatch(modifiers1, modifiers2) { + return modifiers1.sort().join(",") === modifiers2.sort().join(","); + } + + /** + * takes a key event and figures out what the modifiers are + * + * @param {Event} e + * @returns {Array} + */ + function _eventModifiers(e) { + let modifiers = []; + + if (e.shiftKey) { + modifiers.push("shift"); + } + + if (e.altKey) { + modifiers.push("alt"); + } + + if (e.ctrlKey) { + modifiers.push("ctrl"); + } + + if (e.metaKey) { + modifiers.push("meta"); + } + + return modifiers; + } + + /** + * prevents default for this event + * + * @param {Event} e + * @returns void + */ + function _preventDefault(e) { + if (e.preventDefault) { + e.preventDefault(); + return; + } + + e.returnValue = false; + } + + /** + * stops propogation for this event + * + * @param {Event} e + * @returns void + */ + function _stopPropagation(e) { + if (e.stopPropagation) { + e.stopPropagation(); + return; + } + + e.cancelBubble = true; + } + + /** + * determines if the keycode specified is a modifier key or not + * + * @param {string} key + * @returns {boolean} + */ + function _isModifier(key) { + return key == "shift" || key == "ctrl" || key == "alt" || key == "meta"; + } + + /** + * reverses the map lookup so that we can look for specific keys + * to see what can and can't use keypress + * + * @return {Object} + */ + function _getReverseMap() { + if (!_REVERSE_MAP) { + _REVERSE_MAP = {}; + for (let key in _MAP) { + // pull out the numeric keypad from here cause keypress should + // be able to detect the keys from the character + if (key > 95 && key < 112) { + continue; + } + + if (_MAP.hasOwnProperty(key)) { + _REVERSE_MAP[_MAP[key]] = key; + } + } + } + return _REVERSE_MAP; + } + + /** + * picks the best action based on the key combination + * + * @param {string} key - character for key + * @param {Array} modifiers + * @param {string=} action passed in + */ + function _pickBestAction(key, modifiers, action) { + // if no action was picked in we should try to pick the one + // that we think would work best for this key + if (!action) { + action = _getReverseMap()[key] ? "keydown" : "keypress"; + } + + // modifier keys don't work as expected with keypress, + // switch to keydown + if (action == "keypress" && modifiers.length) { + action = "keydown"; + } + + return action; + } + + /** + * Converts from a string key combination to an array + * + * @param {string} combination like "command+shift+l" + * @return {Array} + */ + function _keysFromString(combination) { + if (combination === "+") { + return ["+"]; + } + + combination = combination.replace(/\+{2}/g, "+plus"); + return combination.split("+"); + } + + /** + * Gets info for a specific key combination + * + * @param {string} combination key combination ("command+s" or "a" or "*") + * @param {string=} action + * @returns {Object} + */ + function _getKeyInfo(combination, action) { + let keys; + let key; + let i; + let modifiers = []; + + // take the keys from this pattern and figure out what the actual + // pattern is all about + keys = _keysFromString(combination); + + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + + // normalize key names + if (_SPECIAL_ALIASES[key]) { + key = _SPECIAL_ALIASES[key]; + } + + // if this is not a keypress event then we should + // be smart about using shift keys + // this will only work for US keyboards however + if (action && action != "keypress" && _SHIFT_MAP[key]) { + key = _SHIFT_MAP[key]; + modifiers.push("shift"); + } + + // if this key is a modifier then add it to the list of modifiers + if (_isModifier(key)) { + modifiers.push(key); + } + } + + // depending on what the key combination is + // we will try to pick the best event for it + action = _pickBestAction(key, modifiers, action); + + return { + key, + modifiers, + action + }; + } + + function _belongsTo(element, ancestor) { + if (element === null || element === document) { + return false; + } + + if (element === ancestor) { + return true; + } + + return _belongsTo(element.parentNode, ancestor); + } + + function ItsATrap(targetElement) { + let self = this; + + targetElement = targetElement || document; + + if (!(self instanceof ItsATrap)) { + return new ItsATrap(targetElement); + } + + /** + * element to attach key events to + * + * @type {Element} + */ + self.target = targetElement; + + /** + * a list of all the callbacks setup via ItsATrap.bind() + * + * @type {Object} + */ + self._callbacks = {}; + + /** + * direct map of string combinations to callbacks used for trigger() + * + * @type {Object} + */ + self._directMap = {}; + + /** + * keeps track of what level each sequence is at since multiple + * sequences can start out with the same sequence + * + * @type {Object} + */ + let _sequenceLevels = {}; + + /** + * variable to store the setTimeout call + * + * @type {null|number} + */ + let _resetTimer; + + /** + * temporary state where we will ignore the next keyup + * + * @type {boolean|string} + */ + let _ignoreNextKeyup = false; + + /** + * temporary state where we will ignore the next keypress + * + * @type {boolean} + */ + let _ignoreNextKeypress = false; + + /** + * are we currently inside of a sequence? + * type of action ("keyup" or "keydown" or "keypress") or false + * + * @type {boolean|string} + */ + let _nextExpectedAction = false; + + /** + * resets all sequence counters except for the ones passed in + * + * @param {Object} doNotReset + * @returns void + */ + function _resetSequences(doNotReset) { + doNotReset = doNotReset || {}; + + let activeSequences = false, + key; + + for (key in _sequenceLevels) { + if (doNotReset[key]) { + activeSequences = true; + continue; + } + _sequenceLevels[key] = 0; + } + + if (!activeSequences) { + _nextExpectedAction = false; + } + } + + /** + * finds all callbacks that match based on the keycode, modifiers, + * and action + * + * @param {string} character + * @param {Array} modifiers + * @param {Event|Object} e + * @param {string=} sequenceName - name of the sequence we are looking for + * @param {string=} combination + * @param {number=} level + * @returns {Array} + */ + function _getMatches( + character, + modifiers, + e, + sequenceName, + combination, + level + ) { + let i; + let callback; + let matches = []; + let action = e.type; + + // if there are no events related to this keycode + if (!self._callbacks[character]) { + return []; + } + + // if a modifier key is coming up on its own we should allow it + if (action == "keyup" && _isModifier(character)) { + modifiers = [character]; + } + + // loop through all callbacks for the key that was pressed + // and see if any of them match + for (i = 0; i < self._callbacks[character].length; ++i) { + callback = self._callbacks[character][i]; + + // if a sequence name is not specified, but this is a sequence at + // the wrong level then move onto the next match + if ( + !sequenceName && + callback.seq && + _sequenceLevels[callback.seq] != callback.level + ) { + continue; + } + + // if the action we are looking for doesn't match the action we got + // then we should keep going + if (action != callback.action) { + continue; + } + + // if this is a keypress event and the meta key and control key + // are not pressed that means that we need to only look at the + // character, otherwise check the modifiers as well + // + // chrome will not fire a keypress if meta or control is down + // safari will fire a keypress if meta or meta+shift is down + // firefox will fire a keypress if meta or control is down + if ( + (action == "keypress" && !e.metaKey && !e.ctrlKey) || + _modifiersMatch(modifiers, callback.modifiers) + ) { + // when you bind a combination or sequence a second time it + // should overwrite the first one. if a sequenceName or + // combination is specified in this call it does just that + // + // @todo make deleting its own method? + let deleteCombo = !sequenceName && callback.combo == combination; + let deleteSequence = + sequenceName && + callback.seq == sequenceName && + callback.level == level; + if (deleteCombo || deleteSequence) { + self._callbacks[character].splice(i, 1); + } + + matches.push(callback); + } + } + + return matches; + } + + /** + * actually calls the callback function + * + * if your callback function returns false this will use the jquery + * convention - prevent default and stop propogation on the event + * + * @param {Function} callback + * @param {Event} e + * @returns void + */ + function _fireCallback(callback, e, combo, sequence) { + // if this event should not happen stop here + if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) { + return; + } + + if (callback(e, combo) === false) { + _preventDefault(e); + _stopPropagation(e); + } + } + + /** + * handles a character key event + * + * @param {string} character + * @param {Array} modifiers + * @param {Event} e + * @returns void + */ + self._handleKey = function(character, modifiers, e) { + let callbacks = _getMatches(character, modifiers, e); + let i; + let doNotReset = {}; + let maxLevel = 0; + let processedSequenceCallback = false; + + // Calculate the maxLevel for sequences so we can only execute the longest callback sequence + for (i = 0; i < callbacks.length; ++i) { + if (callbacks[i].seq) { + maxLevel = Math.max(maxLevel, callbacks[i].level); + } + } + + // loop through matching callbacks for this key event + for (i = 0; i < callbacks.length; ++i) { + // fire for all sequence callbacks + // this is because if for example you have multiple sequences + // bound such as "g i" and "g t" they both need to fire the + // callback for matching g cause otherwise you can only ever + // match the first one + if (callbacks[i].seq) { + // only fire callbacks for the maxLevel to prevent + // subsequences from also firing + // + // for example 'a option b' should not cause 'option b' to fire + // even though 'option b' is part of the other sequence + // + // any sequences that do not match here will be discarded + // below by the _resetSequences call + if (callbacks[i].level != maxLevel) { + continue; + } + + processedSequenceCallback = true; + + // keep a list of which sequences were matches for later + doNotReset[callbacks[i].seq] = 1; + _fireCallback( + callbacks[i].callback, + e, + callbacks[i].combo, + callbacks[i].seq + ); + continue; + } + + // if there were no sequence matches but we are still here + // that means this is a regular match so we should fire that + if (!processedSequenceCallback) { + _fireCallback(callbacks[i].callback, e, callbacks[i].combo); + } + } + + // if the key you pressed matches the type of sequence without + // being a modifier (ie "keyup" or "keypress") then we should + // reset all sequences that were not matched by this event + // + // this is so, for example, if you have the sequence "h a t" and you + // type "h e a r t" it does not match. in this case the "e" will + // cause the sequence to reset + // + // modifier keys are ignored because you can have a sequence + // that contains modifiers such as "enter ctrl+space" and in most + // cases the modifier key will be pressed before the next key + // + // also if you have a sequence such as "ctrl+b a" then pressing the + // "b" key will trigger a "keypress" and a "keydown" + // + // the "keydown" is expected when there is a modifier, but the + // "keypress" ends up matching the _nextExpectedAction since it occurs + // after and that causes the sequence to reset + // + // we ignore keypresses in a sequence that directly follow a keydown + // for the same character + let ignoreThisKeypress = e.type == "keypress" && _ignoreNextKeypress; + if ( + e.type == _nextExpectedAction && + !_isModifier(character) && + !ignoreThisKeypress + ) { + _resetSequences(doNotReset); + } + + _ignoreNextKeypress = processedSequenceCallback && e.type == "keydown"; + }; + + /** + * handles a keydown event + * + * @param {Event} e + * @returns void + */ + self._handleKeyEvent = function(e) { + // normalize e.which for key events + // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion + if (typeof e.which !== "number") { + e.which = e.keyCode; + } + + let character = _characterFromEvent(e); + + // no character found then stop + if (!character) { + return; + } + + // need to use === for the character check because the character can be 0 + if (e.type == "keyup" && _ignoreNextKeyup === character) { + _ignoreNextKeyup = false; + return; + } + + self.handleKey(character, _eventModifiers(e), e); + }; + + /** + * called to set a 1 second timeout on the specified sequence + * + * this is so after each key press in the sequence you have 1 second + * to press the next key before you have to start over + * + * @returns void + */ + function _resetSequenceTimer() { + clearTimeout(_resetTimer); + _resetTimer = setTimeout(_resetSequences, 1000); + } + + /** + * binds a key sequence to an event + * + * @param {string} combo - combo specified in bind call + * @param {Array} keys + * @param {Function} callback + * @param {string=} action + * @returns void + */ + function _bindSequence(combo, keys, callback, action) { + // start off by adding a sequence level record for this combination + // and setting the level to 0 + _sequenceLevels[combo] = 0; + + /** + * callback to increase the sequence level for this sequence and reset + * all other sequences that were active + * + * @param {string} nextAction + * @returns {Function} + */ + function _increaseSequence(nextAction) { + return function() { + _nextExpectedAction = nextAction; + ++_sequenceLevels[combo]; + _resetSequenceTimer(); + }; + } + + /** + * wraps the specified callback inside of another function in order + * to reset all sequence counters as soon as this sequence is done + * + * @param {Event} e + * @returns void + */ + function _callbackAndReset(e) { + _fireCallback(callback, e, combo); + + // we should ignore the next key up if the action is key down + // or keypress. this is so if you finish a sequence and + // release the key the final key will not trigger a keyup + if (action !== "keyup") { + _ignoreNextKeyup = _characterFromEvent(e); + } + + // weird race condition if a sequence ends with the key + // another sequence begins with + setTimeout(_resetSequences, 10); + } + + // loop through keys one at a time and bind the appropriate callback + // function. for any key leading up to the final one it should + // increase the sequence. after the final, it should reset all sequences + // + // if an action is specified in the original bind call then that will + // be used throughout. otherwise we will pass the action that the + // next key in the sequence should match. this allows a sequence + // to mix and match keypress and keydown events depending on which + // ones are better suited to the key provided + for (let i = 0; i < keys.length; ++i) { + let isFinal = i + 1 === keys.length; + let wrappedCallback = isFinal + ? _callbackAndReset + : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); + _bindSingle(keys[i], wrappedCallback, action, combo, i); + } + } + + /** + * binds a single keyboard combination + * + * @param {string} combination + * @param {Function} callback + * @param {string=} action + * @param {string=} sequenceName - name of sequence if part of sequence + * @param {number=} level - what part of the sequence the command is + * @returns void + */ + function _bindSingle(combination, callback, action, sequenceName, level) { + // store a direct mapped reference for use with ItsATrap.trigger + self._directMap[combination + ":" + action] = callback; + + // make sure multiple spaces in a row become a single space + combination = combination.replace(/\s+/g, " "); + + let sequence = combination.split(" "); + let info; + + // if this pattern is a sequence of keys then run through this method + // to reprocess each pattern one key at a time + if (sequence.length > 1) { + _bindSequence(combination, sequence, callback, action); + return; + } + + info = _getKeyInfo(combination, action); + + // make sure to initialize array if this is the first time + // a callback is added for this key + self._callbacks[info.key] = self._callbacks[info.key] || []; + + // remove an existing match if there is one + _getMatches( + info.key, + info.modifiers, + { type: info.action }, + sequenceName, + combination, + level + ); + + // add this call back to the array + // if it is a sequence put it at the beginning + // if not put it at the end + // + // this is important because the way these are processed expects + // the sequence ones to come first + self._callbacks[info.key][sequenceName ? "unshift" : "push"]({ + callback, + modifiers: info.modifiers, + action: info.action, + seq: sequenceName, + level, + combo: combination + }); + } + + /** + * binds multiple combinations to the same callback + * + * @param {Array} combinations + * @param {Function} callback + * @param {string|undefined} action + * @returns void + */ + self._bindMultiple = function(combinations, callback, action) { + for (let i = 0; i < combinations.length; ++i) { + _bindSingle(combinations[i], callback, action); + } + }; + + // start! + _addEvent(targetElement, "keypress", self._handleKeyEvent); + _addEvent(targetElement, "keydown", self._handleKeyEvent); + _addEvent(targetElement, "keyup", self._handleKeyEvent); + } + + /** + * binds an event to itsatrap + * + * can be a single key, a combination of keys separated with +, + * an array of keys, or a sequence of keys separated by spaces + * + * be sure to list the modifier keys first to make sure that the + * correct key ends up getting bound (the last key in the pattern) + * + * @param {string|Array} keys + * @param {Function} callback + * @param {string=} action - 'keypress', 'keydown', or 'keyup' + * @returns void + */ + ItsATrap.prototype.bind = function(keys, callback, action) { + let self = this; + keys = keys instanceof Array ? keys : [keys]; + self._bindMultiple.call(self, keys, callback, action); + return self; + }; + + /** + * unbinds an event to itsatrap + * + * the unbinding sets the callback function of the specified key combo + * to an empty function and deletes the corresponding key in the + * _directMap dict. + * + * TODO: actually remove this from the _callbacks dictionary instead + * of binding an empty function + * + * the keycombo+action has to be exactly the same as + * it was defined in the bind method + * + * @param {string|Array} keys + * @param {string} action + * @returns void + */ + ItsATrap.prototype.unbind = function(keys, action) { + let self = this; + return self.bind.call(self, keys, function() {}, action); + }; + + /** + * triggers an event that has already been bound + * + * @param {string} keys + * @param {string=} action + * @returns void + */ + ItsATrap.prototype.trigger = function(keys, action) { + let self = this; + if (self._directMap[keys + ":" + action]) { + self._directMap[keys + ":" + action]({}, keys); + } + return self; + }; + + /** + * resets the library back to its initial state. this is useful + * if you want to clear out the current keyboard shortcuts and bind + * new ones - for example if you switch to another page + * + * @returns void + */ + ItsATrap.prototype.reset = function() { + let self = this; + self._callbacks = {}; + self._directMap = {}; + return self; + }; + + /** + * destroy itsatrap object + * + * - call reset on the itsatrap object ( removing all binding ) + * - remove all javascript event listener from target element or document + * - remove all reference to target + * + * @return void + */ + + ItsATrap.prototype.destroy = function() { + let self = this; + + self.reset(); + + _removeEvent(self.target, "keypress", self._handleKeyEvent); + _removeEvent(self.target, "keydown", self._handleKeyEvent); + _removeEvent(self.target, "keyup", self._handleKeyEvent); + + self.target = undefined; + self._handleKeyEvent = undefined; + }; + + /** + * should we stop this event before firing off callbacks + * + * @param {Event} e + * @param {Element} element + * @return {boolean} + */ + ItsATrap.prototype.stopCallback = function(e, element, combo, sequence) { + let self = this; + + if (self.paused) { + return true; + } + + if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { + return false; + } + + // if the element has the class "itsatrap" then no need to stop + if ((" " + element.className + " ").indexOf(" itsatrap ") > -1) { + return false; + } + + if (_belongsTo(element, self.target)) { + return false; + } + + // Events originating from a shadow DOM are re-targetted and `e.target` is the shadow host, + // not the initial event target in the shadow tree. Note that not all events cross the + // shadow boundary. + // For shadow trees with `mode: 'open'`, the initial event target is the first element in + // the event’s composed path. For shadow trees with `mode: 'closed'`, the initial event + // target cannot be obtained. + if ("composedPath" in e && typeof e.composedPath === "function") { + // For open shadow trees, update `element` so that the following check works. + let initialEventTarget = e.composedPath()[0]; + if (initialEventTarget !== e.target) { + element = initialEventTarget; + } + } + + // stop for input, select, and textarea + return ( + element.tagName == "INPUT" || + element.tagName == "SELECT" || + element.tagName == "TEXTAREA" || + element.isContentEditable + ); + }; + + /** + * exposes _handleKey publicly so it can be overwritten by extensions + */ + ItsATrap.prototype.handleKey = function() { + let self = this; + return self._handleKey.apply(self, arguments); + }; + + /** + * allow custom key mappings + */ + ItsATrap.addKeycodes = function(object) { + for (let key in object) { + if (object.hasOwnProperty(key)) { + _MAP[key] = object[key]; + } + } + _REVERSE_MAP = null; + }; + + /** + * adds a pause and unpause method to ItsATrap + * this allows you to enable or disable keyboard shortcuts + * without having to reset ItsATrap and rebind everything + */ + ItsATrap.prototype.pause = function() { + let self = this; + self.paused = true; + }; + + ItsATrap.prototype.unpause = function() { + let self = this; + self.paused = false; + }; + + /** + * adds a bindGlobal method to ItsATrap that allows you to + * bind specific keyboard shortcuts that will still work + * inside a text input field + * + * usage: + * ItsATrap.bindGlobal('ctrl+s', _saveChanges); + */ + ItsATrap.prototype.bindGlobal = function(keys, callback, action) { + let self = this; + self.bind(keys, callback, action); + + if (keys instanceof Array) { + for (let i = 0; i < keys.length; i++) { + _globalCallbacks[keys[i]] = true; + } + return; + } + + _globalCallbacks[keys] = true; + }; + + // expose itsatrap to the global object + window.ItsATrap = ItsATrap; + + // expose as a common js module + if (typeof module !== "undefined" && module.exports) { + module.exports = ItsATrap; + } + + // expose itsatrap as an AMD module + if (typeof define === "function" && define.amd) { + define(function() { + return ItsATrap; + }); + } +})( + typeof window !== "undefined" ? window : null, + typeof window !== "undefined" ? document : null +); diff --git a/assets/javascripts/legacy/jquery.js b/assets/javascripts/legacy/jquery.js new file mode 100644 index 00000000..de3b2064 --- /dev/null +++ b/assets/javascripts/legacy/jquery.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +let arr = []; + +let getProto = Object.getPrototypeOf; + +let slice = arr.slice; + +let flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +let push = arr.push; + +let indexOf = arr.indexOf; + +let class2type = {}; + +let toString = class2type.toString; + +let hasOwn = class2type.hasOwnProperty; + +let fnToString = hasOwn.toString; + +let ObjectFunctionString = fnToString.call( Object ); + +let support = {}; + +let isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +let isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +let document = window.document; + + + + let preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + let i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + let ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + let len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + let options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + let proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + let name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + let length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + let ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + let len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + let callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + let length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + let length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +let Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +let i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + let i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + let high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + let j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + let m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + let keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + let el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + let arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + let cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + let name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + let name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + let j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + let namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + let hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + let attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + let elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + let attrId = id.replace( runescape, funescape ); + return function( elem ) { + let node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + let node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + let elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + let input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + let input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + let adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + let compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + let cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + let ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + let fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + let elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + let node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + let excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + let nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + let pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + let result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + let simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + let cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + let args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + let idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + let input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + let elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + let elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + let hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + let nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + let name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + let attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + let i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + let i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + let i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + let i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + let matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + let i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + let dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + let oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + let i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + let i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + let elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + let temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + let checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + let ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + let bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + let elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + let i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + let i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + let val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +let dir = function( elem, dir, until ) { + let matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +let siblings = function( n, elem ) { + let matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +let rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +let rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + let elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + let i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +let rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + let match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +let rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + let targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + let i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + let cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + let parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + let matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +let rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + let object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + let index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + let method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + let fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + let fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + let returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + let maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + let returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + let list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + let + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +let rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +let readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + let i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +let rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +let acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + let value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + let prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + let i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + let cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +let dataPriv = new Data(); + +let dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +let rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + let name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + let i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + let data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + let queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + let queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + let key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + let setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + let queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + let tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +let pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +let rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +let cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +let documentElement = document.documentElement; + + + + let isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +let isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + let adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +let defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + let temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + let display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +let rcheckableType = ( /^(?:checkbox|radio)$/i ); + +let rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +let rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + let fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +let wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + let ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + let i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +let rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + let elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +let + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + let origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + let handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type, + origType, + data, + handler, + guid: handler.guid, + selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + let j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + let i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + let i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + let el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + let el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + let target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + let notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + let e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + let e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + let e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + let button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + let ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + let handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +let + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + let i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + let nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + let fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + let self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + let node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + let i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + let data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + let target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + let target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + let elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + let elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + let ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + let parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + let elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +let rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +let getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + let view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +let swap = function( elem, options, callback ) { + let ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +let rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + let divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + let table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + let width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +let cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + let capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + let final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +let + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + let matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + let i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + let styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + let ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + let ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + let val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + let matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + let i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + let styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + let hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + let eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + let result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +let + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + let which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + let tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + let prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + let index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + let currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + let tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + let index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + let tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + let prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + let opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + let empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + let anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + let stopQueue = function( hooks ) { + let stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + let dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + let index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + let cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + let timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + let timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + let input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +let boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + let ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + let val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + let name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + let getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + let ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +let rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + let ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + let tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + let parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + let parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + let tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + let classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + let classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + let type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + let className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + let className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +let rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + let hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + let val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + let val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + let value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + let optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +let rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + let i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + let e = jQuery.extend( + new jQuery.Event(), + event, + { + type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + let elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + let handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + let doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + let doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +let location = window.location; + +let nonce = { guid: Date.now() }; + +let rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + let xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +let + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + let name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + let prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + let value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + let elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + let type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + let val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +let + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + let dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + let inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + let selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + let dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + let key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + let ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + let conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + let match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + let code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + let finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + let isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url, + type: method, + dataType: type, + data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + let i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + let wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + let elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + let self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + let htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +let xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + let callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + let i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + let script, callback; + return { + send: function( _, complete ) { + script = jQuery( " diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js index e6692f08..37b63a3d 100644 --- a/assets/javascripts/wizard-qunit.js +++ b/assets/javascripts/wizard-qunit.js @@ -1,5 +1,5 @@ //= require legacy/env -//= require jquery.debug +//= require legacy/jquery //= require ember.debug //= require route-recognizer //= require fake_xml_http_request From 8496e15728a2cb66db96eefde3fd88e8a9156fad Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 22 Jun 2022 10:22:20 +0200 Subject: [PATCH 265/556] Remove qunit entirely for now Re-add when migration to ember-cli is made --- app/views/layouts/qunit.html.erb | 28 ---------------------------- app/views/layouts/wizard.html.erb | 1 - assets/javascripts/wizard-qunit.js | 17 ----------------- 3 files changed, 46 deletions(-) delete mode 100644 app/views/layouts/qunit.html.erb delete mode 100644 assets/javascripts/wizard-qunit.js diff --git a/app/views/layouts/qunit.html.erb b/app/views/layouts/qunit.html.erb deleted file mode 100644 index 780dc7d6..00000000 --- a/app/views/layouts/qunit.html.erb +++ /dev/null @@ -1,28 +0,0 @@ - - - - Custom Wizard QUnit Test Runner - <%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %> - <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> - <%= discourse_stylesheet_link_tag :wizard_custom %> - <%= preload_script "locales/en" %> - <%= preload_script "ember_jquery" %> - <%= preload_script "wizard-vendor" %> - <%= preload_script "wizard-custom" %> - <%= preload_script "wizard-raw-templates" %> - <%= preload_script "wizard-plugin" %> - <%= preload_script "pretty-text-bundle" %> - <%#<%= preload_script "wizard-qunit" %>%> - <%= csrf_meta_tags %> - - - - <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> - - - - -
-
- - diff --git a/app/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb index cab255c3..325a94e8 100644 --- a/app/views/layouts/wizard.html.erb +++ b/app/views/layouts/wizard.html.erb @@ -9,7 +9,6 @@ <%- end %> <%= preload_script "locales/#{I18n.locale}" %> - <%= preload_script "ember_jquery" %> <%= preload_script "wizard-vendor" %> <%= preload_script "wizard-custom" %> <%= preload_script "wizard-raw-templates" %> diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js deleted file mode 100644 index 37b63a3d..00000000 --- a/assets/javascripts/wizard-qunit.js +++ /dev/null @@ -1,17 +0,0 @@ -//= require legacy/env -//= require legacy/jquery -//= require ember.debug -//= require route-recognizer -//= require fake_xml_http_request -//= require pretender -//= require qunit -//= require ember-qunit -//= require test-shims -//= require ember-template-compiler - -//= require_tree ./wizard/tests/fixtures -//= require ./wizard/tests/pretender -//= require_tree ./wizard/tests/helpers -//= require_tree ./wizard/tests/acceptance - -//= require ./wizard/tests/bootstrap From b8329f0fa06f47a7a75f0fd0b1cd5e5fc0e3d206 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 22 Jun 2022 10:37:02 +0200 Subject: [PATCH 266/556] wizard-vendor needs to be precompiled --- plugin.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin.rb b/plugin.rb index a0e6dd7b..136056a6 100644 --- a/plugin.rb +++ b/plugin.rb @@ -25,6 +25,7 @@ if Rails.env.production? wizard-custom.js wizard-plugin.js.erb wizard-raw-templates.js.erb + wizard-vendor.js } end From b7d332e0e562f2b5c7a4a51b26fa790ec43b9326 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 22 Jun 2022 12:19:50 +0200 Subject: [PATCH 267/556] COMPATIBILITY: everything is transpiled by default now. https://github.com/discourse/discourse/commit/624c684d51d215a7b0969598d94c6c04f7224e22 --- app/views/layouts/wizard.html.erb | 1 + .../javascripts/{legacy => }/ember_jquery.js | 0 assets/javascripts/legacy/discourse-loader.js | 4 +- assets/javascripts/legacy/discourse-shims.js | 2 + .../javascripts/legacy/ember_include.js.erb | 2 + assets/javascripts/legacy/env.js | 2 + .../javascripts/legacy/handlebars.runtime.js | 2 + assets/javascripts/legacy/jquery.js | 2 + assets/javascripts/legacy/popper.js | 2 + .../legacy/set-prototype-polyfill.js | 2 + assets/javascripts/legacy/template_include.js | 2 + assets/javascripts/legacy/virtual-dom-amd.js | 2 + assets/javascripts/legacy/virtual-dom.js | 2 + assets/javascripts/wizard-custom-guest.js | 2 + assets/javascripts/wizard-custom-start.js | 2 + assets/javascripts/wizard-custom.js | 3 +- assets/javascripts/wizard-plugin.js.erb | 2 + .../javascripts/wizard-raw-templates.js.erb | 14 +++--- assets/javascripts/wizard-shims.js | 47 ------------------- assets/javascripts/wizard-vendor.js | 5 +- 20 files changed, 43 insertions(+), 57 deletions(-) rename assets/javascripts/{legacy => }/ember_jquery.js (100%) delete mode 100644 assets/javascripts/wizard-shims.js diff --git a/app/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb index 325a94e8..cab255c3 100644 --- a/app/views/layouts/wizard.html.erb +++ b/app/views/layouts/wizard.html.erb @@ -9,6 +9,7 @@ <%- end %> <%= preload_script "locales/#{I18n.locale}" %> + <%= preload_script "ember_jquery" %> <%= preload_script "wizard-vendor" %> <%= preload_script "wizard-custom" %> <%= preload_script "wizard-raw-templates" %> diff --git a/assets/javascripts/legacy/ember_jquery.js b/assets/javascripts/ember_jquery.js similarity index 100% rename from assets/javascripts/legacy/ember_jquery.js rename to assets/javascripts/ember_jquery.js diff --git a/assets/javascripts/legacy/discourse-loader.js b/assets/javascripts/legacy/discourse-loader.js index edcbf27f..ec77a6d2 100644 --- a/assets/javascripts/legacy/discourse-loader.js +++ b/assets/javascripts/legacy/discourse-loader.js @@ -1,3 +1,5 @@ +// discourse-skip-module + var define, requirejs; (function () { @@ -171,7 +173,7 @@ var define, requirejs; I18n: { // eslint-disable-next-line default: I18n, - }, + } }; } diff --git a/assets/javascripts/legacy/discourse-shims.js b/assets/javascripts/legacy/discourse-shims.js index 7794b099..a6b25771 100644 --- a/assets/javascripts/legacy/discourse-shims.js +++ b/assets/javascripts/legacy/discourse-shims.js @@ -1,3 +1,5 @@ +// discourse-skip-module + define("message-bus-client", ["exports"], function (__exports__) { __exports__.default = window.MessageBus; }); diff --git a/assets/javascripts/legacy/ember_include.js.erb b/assets/javascripts/legacy/ember_include.js.erb index 56883bef..0d6ddf43 100644 --- a/assets/javascripts/legacy/ember_include.js.erb +++ b/assets/javascripts/legacy/ember_include.js.erb @@ -1,3 +1,5 @@ +// discourse-skip-module + <% if @force_ember_debug || Rails.env.development? || Rails.env.test? require_asset("ember.debug.js") diff --git a/assets/javascripts/legacy/env.js b/assets/javascripts/legacy/env.js index 8b897bbe..0ae0e456 100644 --- a/assets/javascripts/legacy/env.js +++ b/assets/javascripts/legacy/env.js @@ -1,3 +1,5 @@ +// discourse-skip-module + window.ENV = {}; window.EmberENV = window.EmberENV || {}; window.EmberENV.FORCE_JQUERY = true; diff --git a/assets/javascripts/legacy/handlebars.runtime.js b/assets/javascripts/legacy/handlebars.runtime.js index da377f0f..145aa7c1 100644 --- a/assets/javascripts/legacy/handlebars.runtime.js +++ b/assets/javascripts/legacy/handlebars.runtime.js @@ -1,3 +1,5 @@ +// discourse-skip-module + /**! @license diff --git a/assets/javascripts/legacy/jquery.js b/assets/javascripts/legacy/jquery.js index de3b2064..956c6699 100644 --- a/assets/javascripts/legacy/jquery.js +++ b/assets/javascripts/legacy/jquery.js @@ -1,3 +1,5 @@ +// discourse-skip-module + /*! * jQuery JavaScript Library v3.5.1 * https://jquery.com/ diff --git a/assets/javascripts/legacy/popper.js b/assets/javascripts/legacy/popper.js index 95d3428c..25298b6d 100644 --- a/assets/javascripts/legacy/popper.js +++ b/assets/javascripts/legacy/popper.js @@ -1,3 +1,5 @@ +// discourse-skip-module + /** * @popperjs/core v2.10.2 - MIT License */ diff --git a/assets/javascripts/legacy/set-prototype-polyfill.js b/assets/javascripts/legacy/set-prototype-polyfill.js index 355fa28e..669b4458 100644 --- a/assets/javascripts/legacy/set-prototype-polyfill.js +++ b/assets/javascripts/legacy/set-prototype-polyfill.js @@ -1,3 +1,5 @@ +// discourse-skip-module + /* eslint-disable */ Object.setPrototypeOf = Object.setPrototypeOf || diff --git a/assets/javascripts/legacy/template_include.js b/assets/javascripts/legacy/template_include.js index 7188ba8e..273eb7a2 100644 --- a/assets/javascripts/legacy/template_include.js +++ b/assets/javascripts/legacy/template_include.js @@ -1,2 +1,4 @@ +// discourse-skip-module + //= require legacy/handlebars.runtime //= require handlebars-shim diff --git a/assets/javascripts/legacy/virtual-dom-amd.js b/assets/javascripts/legacy/virtual-dom-amd.js index 75ee9dcc..d9047402 100644 --- a/assets/javascripts/legacy/virtual-dom-amd.js +++ b/assets/javascripts/legacy/virtual-dom-amd.js @@ -1,3 +1,5 @@ +// discourse-skip-module + // Just a wrapper from the standalone browserified virtual-dom define("virtual-dom", [], function() { return virtualDom; diff --git a/assets/javascripts/legacy/virtual-dom.js b/assets/javascripts/legacy/virtual-dom.js index bece554b..231a7622 100644 --- a/assets/javascripts/legacy/virtual-dom.js +++ b/assets/javascripts/legacy/virtual-dom.js @@ -1,3 +1,5 @@ +// discourse-skip-module + !(function(e){if("object"===typeof exports&&"undefined"!==typeof module){module.exports=e();}else if("function"===typeof define&&define.amd){define([],e);}else{let f;"undefined"!==typeof window?f=window:"undefined"!==typeof global?f=global:"undefined"!==typeof self&&(f=self),f.virtualDom=e();}})(function(){let define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){let a=typeof require==="function"&&require;if(!u&&a){return a(o,!0);}if(i){return i(o,!0);}let f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f;}let l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){let n=t[o][1][e];return s(n?n:e);},l,l.exports,e,t,n,r);}return n[o].exports;}var i=typeof require==="function"&&require;for(let o=0;o Date: Wed, 22 Jun 2022 12:29:54 +0200 Subject: [PATCH 268/556] Add ember_jquery to precompilation pipeline --- plugin.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin.rb b/plugin.rb index 136056a6..a5ffc914 100644 --- a/plugin.rb +++ b/plugin.rb @@ -20,6 +20,7 @@ config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" if Rails.env.production? config.assets.precompile += %w{ + ember_jquery.js wizard-custom-guest.js wizard-custom-start.js wizard-custom.js From 44f5d39e0a96497df9504c535e4461de760afe8d Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Wed, 22 Jun 2022 12:45:35 +0200 Subject: [PATCH 269/556] Skip more vendor modules and tweak uppy --- assets/javascripts/legacy/itsatrap.js | 2 ++ assets/javascripts/legacy/tippy.umd.js | 2 ++ assets/javascripts/legacy/uppy.js | 6 ++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/assets/javascripts/legacy/itsatrap.js b/assets/javascripts/legacy/itsatrap.js index e4f847e9..ae5ad391 100644 --- a/assets/javascripts/legacy/itsatrap.js +++ b/assets/javascripts/legacy/itsatrap.js @@ -1,3 +1,5 @@ +// discourse-skip-module + /*global define:false */ /** * Copyright 2012-2017 Craig Campbell diff --git a/assets/javascripts/legacy/tippy.umd.js b/assets/javascripts/legacy/tippy.umd.js index 90f09c38..4d5b9276 100644 --- a/assets/javascripts/legacy/tippy.umd.js +++ b/assets/javascripts/legacy/tippy.umd.js @@ -1,3 +1,5 @@ +// discourse-skip-module + /**! * tippy.js v6.3.7 * (c) 2017-2021 atomiks diff --git a/assets/javascripts/legacy/uppy.js b/assets/javascripts/legacy/uppy.js index accccdf2..d037c696 100644 --- a/assets/javascripts/legacy/uppy.js +++ b/assets/javascripts/legacy/uppy.js @@ -1,3 +1,5 @@ +// discourse-skip-module + (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){let c="function"===typeof require&&require;if(!f&&c){return c(i,!0);}if(u){return u(i,!0);}let a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a;}let p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){let n=e[i][1][r];return o(n||r);},p,p.exports,r,e,n,t);}return n[i].exports;}for(var u="function"===typeof require&&require,i=0;i Date: Wed, 22 Jun 2022 12:56:41 +0200 Subject: [PATCH 270/556] Bump version Plugin supports all recent asset pipeline changes in Discourse. --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index a5ffc914..ef227f12 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards -# version: 1.20.0 +# version: 1.21.0 # authors: Angus McLeod # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io From 85c9629da680bedcb3347dc452efd790a69df774 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 27 Jun 2022 11:36:00 +0200 Subject: [PATCH 271/556] COMPATIBILITY: XSS lib has also been moved to a node module https://github.com/discourse/discourse/commit/d1d6868325bdc98245a6e8aa67a3bf0d3f4b00ff --- assets/javascripts/legacy/xss.min.js | 3 +++ assets/javascripts/wizard-vendor.js | 1 + 2 files changed, 4 insertions(+) create mode 100644 assets/javascripts/legacy/xss.min.js diff --git a/assets/javascripts/legacy/xss.min.js b/assets/javascripts/legacy/xss.min.js new file mode 100644 index 00000000..d84aedcb --- /dev/null +++ b/assets/javascripts/legacy/xss.min.js @@ -0,0 +1,3 @@ +// discourse-skip-module + +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i/g;var REGEXP_QUOTE=/"/g;var REGEXP_QUOTE_2=/"/g;var REGEXP_ATTR_VALUE_1=/&#([a-zA-Z0-9]*);?/gim;var REGEXP_ATTR_VALUE_COLON=/:?/gim;var REGEXP_ATTR_VALUE_NEWLINE=/&newline;?/gim;var REGEXP_DEFAULT_ON_TAG_ATTR_3=/\/\*|\*\//gm;var REGEXP_DEFAULT_ON_TAG_ATTR_4=/((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi;var REGEXP_DEFAULT_ON_TAG_ATTR_5=/^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/gi;var REGEXP_DEFAULT_ON_TAG_ATTR_6=/^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//gi;var REGEXP_DEFAULT_ON_TAG_ATTR_7=/e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi;var REGEXP_DEFAULT_ON_TAG_ATTR_8=/u\s*r\s*l\s*\(.*/gi;function escapeQuote(str){return str.replace(REGEXP_QUOTE,""")}function unescapeQuote(str){return str.replace(REGEXP_QUOTE_2,'"')}function escapeHtmlEntities(str){return str.replace(REGEXP_ATTR_VALUE_1,function replaceUnicode(str,code){return code[0]==="x"||code[0]==="X"?String.fromCharCode(parseInt(code.substr(1),16)):String.fromCharCode(parseInt(code,10))})}function escapeDangerHtml5Entities(str){return str.replace(REGEXP_ATTR_VALUE_COLON,":").replace(REGEXP_ATTR_VALUE_NEWLINE," ")}function clearNonPrintableCharacter(str){var str2="";for(var i=0,len=str.length;i"){rethtml+=escapeHtml(html.slice(lastPos,tagStart));currentHtml=html.slice(tagStart,currentPos+1);currentTagName=getTagName(currentHtml);rethtml+=onTag(tagStart,rethtml.length,currentTagName,currentHtml,isClosing(currentHtml));lastPos=currentPos+1;tagStart=false;continue}if(c==='"'||c==="'"){var i=1;var ic=html.charAt(currentPos-i);while(ic.trim()===""||ic==="="){if(ic==="="){quoteStart=c;continue chariterator}ic=html.charAt(currentPos-++i)}}}else{if(c===quoteStart){quoteStart=false;continue}}}}if(lastPos0;i--){var c=str[i];if(c===" ")continue;if(c==="=")return i;return-1}}function isQuoteWrapString(text){if(text[0]==='"'&&text[text.length-1]==='"'||text[0]==="'"&&text[text.length-1]==="'"){return true}else{return false}}function stripQuoteWrap(text){if(isQuoteWrapString(text)){return text.substr(1,text.length-2)}else{return text}}exports.parseTag=parseTag;exports.parseAttr=parseAttr},{"./util":4}],4:[function(require,module,exports){module.exports={indexOf:function(arr,item){var i,j;if(Array.prototype.indexOf){return arr.indexOf(item)}for(i=0,j=arr.length;i"}var attrs=getAttrs(html);var whiteAttrList=whiteList[tag];var attrsHtml=parseAttr(attrs.html,function(name,value){var isWhiteAttr=_.indexOf(whiteAttrList,name)!==-1;var ret=onTagAttr(tag,name,value,isWhiteAttr);if(!isNull(ret))return ret;if(isWhiteAttr){value=safeAttrValue(tag,name,value,cssFilter);if(value){return name+'="'+value+'"'}else{return name}}else{var ret=onIgnoreTagAttr(tag,name,value,isWhiteAttr);if(!isNull(ret))return ret;return}});var html="<"+tag;if(attrsHtml)html+=" "+attrsHtml;if(attrs.closing)html+=" /";html+=">";return html}else{var ret=onIgnoreTag(tag,html,info);if(!isNull(ret))return ret;return escapeHtml(html)}},escapeHtml);if(stripIgnoreTagBody){retHtml=stripIgnoreTagBody.remove(retHtml)}return retHtml};module.exports=FilterXSS},{"./default":1,"./parser":3,"./util":4,cssfilter:8}],6:[function(require,module,exports){var DEFAULT=require("./default");var parseStyle=require("./parser");var _=require("./util");function isNull(obj){return obj===undefined||obj===null}function shallowCopyObject(obj){var ret={};for(var i in obj){ret[i]=obj[i]}return ret}function FilterCSS(options){options=shallowCopyObject(options||{});options.whiteList=options.whiteList||DEFAULT.whiteList;options.onAttr=options.onAttr||DEFAULT.onAttr;options.onIgnoreAttr=options.onIgnoreAttr||DEFAULT.onIgnoreAttr;options.safeAttrValue=options.safeAttrValue||DEFAULT.safeAttrValue;this.options=options}FilterCSS.prototype.process=function(css){css=css||"";css=css.toString();if(!css)return"";var me=this;var options=me.options;var whiteList=options.whiteList;var onAttr=options.onAttr;var onIgnoreAttr=options.onIgnoreAttr;var safeAttrValue=options.safeAttrValue;var retCSS=parseStyle(css,function(sourcePosition,position,name,value,source){var check=whiteList[name];var isWhite=false;if(check===true)isWhite=check;else if(typeof check==="function")isWhite=check(value);else if(check instanceof RegExp)isWhite=check.test(value);if(isWhite!==true)isWhite=false;value=safeAttrValue(name,value);if(!value)return;var opts={position:position,sourcePosition:sourcePosition,source:source,isWhite:isWhite};if(isWhite){var ret=onAttr(name,value,opts);if(isNull(ret)){return name+":"+value}else{return ret}}else{var ret=onIgnoreAttr(name,value,opts);if(!isNull(ret)){return ret}}});return retCSS};module.exports=FilterCSS},{"./default":7,"./parser":9,"./util":10}],7:[function(require,module,exports){function getDefaultWhiteList(){var whiteList={};whiteList["align-content"]=false;whiteList["align-items"]=false;whiteList["align-self"]=false;whiteList["alignment-adjust"]=false;whiteList["alignment-baseline"]=false;whiteList["all"]=false;whiteList["anchor-point"]=false;whiteList["animation"]=false;whiteList["animation-delay"]=false;whiteList["animation-direction"]=false;whiteList["animation-duration"]=false;whiteList["animation-fill-mode"]=false;whiteList["animation-iteration-count"]=false;whiteList["animation-name"]=false;whiteList["animation-play-state"]=false;whiteList["animation-timing-function"]=false;whiteList["azimuth"]=false;whiteList["backface-visibility"]=false;whiteList["background"]=true;whiteList["background-attachment"]=true;whiteList["background-clip"]=true;whiteList["background-color"]=true;whiteList["background-image"]=true;whiteList["background-origin"]=true;whiteList["background-position"]=true;whiteList["background-repeat"]=true;whiteList["background-size"]=true;whiteList["baseline-shift"]=false;whiteList["binding"]=false;whiteList["bleed"]=false;whiteList["bookmark-label"]=false;whiteList["bookmark-level"]=false;whiteList["bookmark-state"]=false;whiteList["border"]=true;whiteList["border-bottom"]=true;whiteList["border-bottom-color"]=true;whiteList["border-bottom-left-radius"]=true;whiteList["border-bottom-right-radius"]=true;whiteList["border-bottom-style"]=true;whiteList["border-bottom-width"]=true;whiteList["border-collapse"]=true;whiteList["border-color"]=true;whiteList["border-image"]=true;whiteList["border-image-outset"]=true;whiteList["border-image-repeat"]=true;whiteList["border-image-slice"]=true;whiteList["border-image-source"]=true;whiteList["border-image-width"]=true;whiteList["border-left"]=true;whiteList["border-left-color"]=true;whiteList["border-left-style"]=true;whiteList["border-left-width"]=true;whiteList["border-radius"]=true;whiteList["border-right"]=true;whiteList["border-right-color"]=true;whiteList["border-right-style"]=true;whiteList["border-right-width"]=true;whiteList["border-spacing"]=true;whiteList["border-style"]=true;whiteList["border-top"]=true;whiteList["border-top-color"]=true;whiteList["border-top-left-radius"]=true;whiteList["border-top-right-radius"]=true;whiteList["border-top-style"]=true;whiteList["border-top-width"]=true;whiteList["border-width"]=true;whiteList["bottom"]=false;whiteList["box-decoration-break"]=true;whiteList["box-shadow"]=true;whiteList["box-sizing"]=true;whiteList["box-snap"]=true;whiteList["box-suppress"]=true;whiteList["break-after"]=true;whiteList["break-before"]=true;whiteList["break-inside"]=true;whiteList["caption-side"]=false;whiteList["chains"]=false;whiteList["clear"]=true;whiteList["clip"]=false;whiteList["clip-path"]=false;whiteList["clip-rule"]=false;whiteList["color"]=true;whiteList["color-interpolation-filters"]=true;whiteList["column-count"]=false;whiteList["column-fill"]=false;whiteList["column-gap"]=false;whiteList["column-rule"]=false;whiteList["column-rule-color"]=false;whiteList["column-rule-style"]=false;whiteList["column-rule-width"]=false;whiteList["column-span"]=false;whiteList["column-width"]=false;whiteList["columns"]=false;whiteList["contain"]=false;whiteList["content"]=false;whiteList["counter-increment"]=false;whiteList["counter-reset"]=false;whiteList["counter-set"]=false;whiteList["crop"]=false;whiteList["cue"]=false;whiteList["cue-after"]=false;whiteList["cue-before"]=false;whiteList["cursor"]=false;whiteList["direction"]=false;whiteList["display"]=true;whiteList["display-inside"]=true;whiteList["display-list"]=true;whiteList["display-outside"]=true;whiteList["dominant-baseline"]=false;whiteList["elevation"]=false;whiteList["empty-cells"]=false;whiteList["filter"]=false;whiteList["flex"]=false;whiteList["flex-basis"]=false;whiteList["flex-direction"]=false;whiteList["flex-flow"]=false;whiteList["flex-grow"]=false;whiteList["flex-shrink"]=false;whiteList["flex-wrap"]=false;whiteList["float"]=false;whiteList["float-offset"]=false;whiteList["flood-color"]=false;whiteList["flood-opacity"]=false;whiteList["flow-from"]=false;whiteList["flow-into"]=false;whiteList["font"]=true;whiteList["font-family"]=true;whiteList["font-feature-settings"]=true;whiteList["font-kerning"]=true;whiteList["font-language-override"]=true;whiteList["font-size"]=true;whiteList["font-size-adjust"]=true;whiteList["font-stretch"]=true;whiteList["font-style"]=true;whiteList["font-synthesis"]=true;whiteList["font-variant"]=true;whiteList["font-variant-alternates"]=true;whiteList["font-variant-caps"]=true;whiteList["font-variant-east-asian"]=true;whiteList["font-variant-ligatures"]=true;whiteList["font-variant-numeric"]=true;whiteList["font-variant-position"]=true;whiteList["font-weight"]=true;whiteList["grid"]=false;whiteList["grid-area"]=false;whiteList["grid-auto-columns"]=false;whiteList["grid-auto-flow"]=false;whiteList["grid-auto-rows"]=false;whiteList["grid-column"]=false;whiteList["grid-column-end"]=false;whiteList["grid-column-start"]=false;whiteList["grid-row"]=false;whiteList["grid-row-end"]=false;whiteList["grid-row-start"]=false;whiteList["grid-template"]=false;whiteList["grid-template-areas"]=false;whiteList["grid-template-columns"]=false;whiteList["grid-template-rows"]=false;whiteList["hanging-punctuation"]=false;whiteList["height"]=true;whiteList["hyphens"]=false;whiteList["icon"]=false;whiteList["image-orientation"]=false;whiteList["image-resolution"]=false;whiteList["ime-mode"]=false;whiteList["initial-letters"]=false;whiteList["inline-box-align"]=false;whiteList["justify-content"]=false;whiteList["justify-items"]=false;whiteList["justify-self"]=false;whiteList["left"]=false;whiteList["letter-spacing"]=true;whiteList["lighting-color"]=true;whiteList["line-box-contain"]=false;whiteList["line-break"]=false;whiteList["line-grid"]=false;whiteList["line-height"]=false;whiteList["line-snap"]=false;whiteList["line-stacking"]=false;whiteList["line-stacking-ruby"]=false;whiteList["line-stacking-shift"]=false;whiteList["line-stacking-strategy"]=false;whiteList["list-style"]=true;whiteList["list-style-image"]=true;whiteList["list-style-position"]=true;whiteList["list-style-type"]=true;whiteList["margin"]=true;whiteList["margin-bottom"]=true;whiteList["margin-left"]=true;whiteList["margin-right"]=true;whiteList["margin-top"]=true;whiteList["marker-offset"]=false;whiteList["marker-side"]=false;whiteList["marks"]=false;whiteList["mask"]=false;whiteList["mask-box"]=false;whiteList["mask-box-outset"]=false;whiteList["mask-box-repeat"]=false;whiteList["mask-box-slice"]=false;whiteList["mask-box-source"]=false;whiteList["mask-box-width"]=false;whiteList["mask-clip"]=false;whiteList["mask-image"]=false;whiteList["mask-origin"]=false;whiteList["mask-position"]=false;whiteList["mask-repeat"]=false;whiteList["mask-size"]=false;whiteList["mask-source-type"]=false;whiteList["mask-type"]=false;whiteList["max-height"]=true;whiteList["max-lines"]=false;whiteList["max-width"]=true;whiteList["min-height"]=true;whiteList["min-width"]=true;whiteList["move-to"]=false;whiteList["nav-down"]=false;whiteList["nav-index"]=false;whiteList["nav-left"]=false;whiteList["nav-right"]=false;whiteList["nav-up"]=false;whiteList["object-fit"]=false;whiteList["object-position"]=false;whiteList["opacity"]=false;whiteList["order"]=false;whiteList["orphans"]=false;whiteList["outline"]=false;whiteList["outline-color"]=false;whiteList["outline-offset"]=false;whiteList["outline-style"]=false;whiteList["outline-width"]=false;whiteList["overflow"]=false;whiteList["overflow-wrap"]=false;whiteList["overflow-x"]=false;whiteList["overflow-y"]=false;whiteList["padding"]=true;whiteList["padding-bottom"]=true;whiteList["padding-left"]=true;whiteList["padding-right"]=true;whiteList["padding-top"]=true;whiteList["page"]=false;whiteList["page-break-after"]=false;whiteList["page-break-before"]=false;whiteList["page-break-inside"]=false;whiteList["page-policy"]=false;whiteList["pause"]=false;whiteList["pause-after"]=false;whiteList["pause-before"]=false;whiteList["perspective"]=false;whiteList["perspective-origin"]=false;whiteList["pitch"]=false;whiteList["pitch-range"]=false;whiteList["play-during"]=false;whiteList["position"]=false;whiteList["presentation-level"]=false;whiteList["quotes"]=false;whiteList["region-fragment"]=false;whiteList["resize"]=false;whiteList["rest"]=false;whiteList["rest-after"]=false;whiteList["rest-before"]=false;whiteList["richness"]=false;whiteList["right"]=false;whiteList["rotation"]=false;whiteList["rotation-point"]=false;whiteList["ruby-align"]=false;whiteList["ruby-merge"]=false;whiteList["ruby-position"]=false;whiteList["shape-image-threshold"]=false;whiteList["shape-outside"]=false;whiteList["shape-margin"]=false;whiteList["size"]=false;whiteList["speak"]=false;whiteList["speak-as"]=false;whiteList["speak-header"]=false;whiteList["speak-numeral"]=false;whiteList["speak-punctuation"]=false;whiteList["speech-rate"]=false;whiteList["stress"]=false;whiteList["string-set"]=false;whiteList["tab-size"]=false;whiteList["table-layout"]=false;whiteList["text-align"]=true;whiteList["text-align-last"]=true;whiteList["text-combine-upright"]=true;whiteList["text-decoration"]=true;whiteList["text-decoration-color"]=true;whiteList["text-decoration-line"]=true;whiteList["text-decoration-skip"]=true;whiteList["text-decoration-style"]=true;whiteList["text-emphasis"]=true;whiteList["text-emphasis-color"]=true;whiteList["text-emphasis-position"]=true;whiteList["text-emphasis-style"]=true;whiteList["text-height"]=true;whiteList["text-indent"]=true;whiteList["text-justify"]=true;whiteList["text-orientation"]=true;whiteList["text-overflow"]=true;whiteList["text-shadow"]=true;whiteList["text-space-collapse"]=true;whiteList["text-transform"]=true;whiteList["text-underline-position"]=true;whiteList["text-wrap"]=true;whiteList["top"]=false;whiteList["transform"]=false;whiteList["transform-origin"]=false;whiteList["transform-style"]=false;whiteList["transition"]=false;whiteList["transition-delay"]=false;whiteList["transition-duration"]=false;whiteList["transition-property"]=false;whiteList["transition-timing-function"]=false;whiteList["unicode-bidi"]=false;whiteList["vertical-align"]=false;whiteList["visibility"]=false;whiteList["voice-balance"]=false;whiteList["voice-duration"]=false;whiteList["voice-family"]=false;whiteList["voice-pitch"]=false;whiteList["voice-range"]=false;whiteList["voice-rate"]=false;whiteList["voice-stress"]=false;whiteList["voice-volume"]=false;whiteList["volume"]=false;whiteList["white-space"]=false;whiteList["widows"]=false;whiteList["width"]=true;whiteList["will-change"]=false;whiteList["word-break"]=true;whiteList["word-spacing"]=true;whiteList["word-wrap"]=true;whiteList["wrap-flow"]=false;whiteList["wrap-through"]=false;whiteList["writing-mode"]=false;whiteList["z-index"]=false;return whiteList}function onAttr(name,value,options){}function onIgnoreAttr(name,value,options){}var REGEXP_URL_JAVASCRIPT=/javascript\s*\:/gim;function safeAttrValue(name,value){if(REGEXP_URL_JAVASCRIPT.test(value))return"";return value}exports.whiteList=getDefaultWhiteList();exports.getDefaultWhiteList=getDefaultWhiteList;exports.onAttr=onAttr;exports.onIgnoreAttr=onIgnoreAttr;exports.safeAttrValue=safeAttrValue},{}],8:[function(require,module,exports){var DEFAULT=require("./default");var FilterCSS=require("./css");function filterCSS(html,options){var xss=new FilterCSS(options);return xss.process(html)}exports=module.exports=filterCSS;exports.FilterCSS=FilterCSS;for(var i in DEFAULT)exports[i]=DEFAULT[i];if(typeof window!=="undefined"){window.filterCSS=module.exports}},{"./css":6,"./default":7}],9:[function(require,module,exports){var _=require("./util");function parseStyle(css,onAttr){css=_.trimRight(css);if(css[css.length-1]!==";")css+=";";var cssLength=css.length;var isParenthesisOpen=false;var lastPos=0;var i=0;var retCSS="";function addNewAttr(){if(!isParenthesisOpen){var source=_.trim(css.slice(lastPos,i));var j=source.indexOf(":");if(j!==-1){var name=_.trim(source.slice(0,j));var value=_.trim(source.slice(j+1));if(name){var ret=onAttr(lastPos,retCSS.length,name,value,source);if(ret)retCSS+=ret+"; "}}}lastPos=i+1}for(;i Date: Wed, 29 Jun 2022 09:55:21 +0200 Subject: [PATCH 272/556] Update linting workflow --- .github/workflows/plugin-linting.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml index a915ea7f..a7cbe811 100644 --- a/.github/workflows/plugin-linting.yml +++ b/.github/workflows/plugin-linting.yml @@ -37,20 +37,17 @@ jobs: run: yarn install --dev - name: ESLint - run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts + run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts/{discourse,wizard} - name: Prettier run: | yarn prettier -v if [ -d "assets" ]; then \ - yarn prettier --list-different "assets/**/*.{scss,js,es6}" ; \ - fi - if [ -d "test" ]; then \ - yarn prettier --list-different "test/**/*.{js,es6}" ; \ + yarn prettier --list-different "assets/javascripts/{discourse,wizard}/**/*.{scss,js,es6}" ; \ fi - name: Ember template lint run: yarn ember-template-lint assets/javascripts - name: Rubocop - run: bundle exec rubocop . \ No newline at end of file + run: bundle exec rubocop . From 180fc0b3ea9592abc2958b417ec98f16cbf9967d Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 5 Jul 2022 08:40:55 +0200 Subject: [PATCH 273/556] Create bootstrap-modal.js --- assets/javascripts/legacy/bootstrap-modal.js | 360 +++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 assets/javascripts/legacy/bootstrap-modal.js diff --git a/assets/javascripts/legacy/bootstrap-modal.js b/assets/javascripts/legacy/bootstrap-modal.js new file mode 100644 index 00000000..61c76fbd --- /dev/null +++ b/assets/javascripts/legacy/bootstrap-modal.js @@ -0,0 +1,360 @@ +// discourse-skip-module + +/* ======================================================================== + * Bootstrap: modal.js v3.4.1 + * https://getbootstrap.com/docs/3.4/javascript/#modals + * ======================================================================== + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom' + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.4.1' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (document !== e.target && + this.$element[0] !== e.target && + !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + var scrollbarWidth = this.scrollbarWidth + if (this.bodyIsOverflowing) { + this.$body.css('padding-right', bodyPad + scrollbarWidth) + $(this.fixedContent).each(function (index, element) { + var actualPadding = element.style.paddingRight + var calculatedPadding = $(element).css('padding-right') + $(element) + .data('padding-right', actualPadding) + .css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px') + }) + } + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + $(this.fixedContent).each(function (index, element) { + var padding = $(element).data('padding-right') + $(element).removeData('padding-right') + element.style.paddingRight = padding ? padding : '' + }) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var target = $this.attr('data-target') || + (href && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + + var $target = $(document).find(target) + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); From 5d445ecd8640cd19096432efe8bf4f4b6bd2f6d2 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 5 Jul 2022 08:45:31 +0200 Subject: [PATCH 274/556] COMPATIBILITY: Move all remaining vendor assets into legacy support --- assets/javascripts/legacy/bootbox.js | 717 ++++++++++++++++++++ assets/javascripts/legacy/caret_position.js | 164 +++++ assets/javascripts/wizard-vendor.js | 6 +- 3 files changed, 884 insertions(+), 3 deletions(-) create mode 100644 assets/javascripts/legacy/bootbox.js create mode 100644 assets/javascripts/legacy/caret_position.js diff --git a/assets/javascripts/legacy/bootbox.js b/assets/javascripts/legacy/bootbox.js new file mode 100644 index 00000000..c2520af8 --- /dev/null +++ b/assets/javascripts/legacy/bootbox.js @@ -0,0 +1,717 @@ +// discourse-skip-module + +/** + * bootbox.js v3.2.0 + * + * http://bootboxjs.com/license.txt + */ +var bootbox = + window.bootbox || + (function (document, $) { + /*jshint scripturl:true sub:true */ + + var _locale = "en", + _defaultLocale = "en", + _animate = true, + _backdrop = "static", + _defaultHref = "javascript:;", + _classes = "", + _btnClasses = {}, + _icons = {}, + /* last var should always be the public object we'll return */ + that = {}; + + /** + * public API + */ + that.setLocale = function (locale) { + for (var i in _locales) { + if (i == locale) { + _locale = locale; + return; + } + } + throw new Error("Invalid locale: " + locale); + }; + + that.addLocale = function (locale, translations) { + if (typeof _locales[locale] === "undefined") { + _locales[locale] = {}; + } + for (var str in translations) { + _locales[locale][str] = translations[str]; + } + }; + + that.setIcons = function (icons) { + _icons = icons; + if (typeof _icons !== "object" || _icons === null) { + _icons = {}; + } + }; + + that.setBtnClasses = function (btnClasses) { + _btnClasses = btnClasses; + if (typeof _btnClasses !== "object" || _btnClasses === null) { + _btnClasses = {}; + } + }; + + that.alert = function (/*str, label, cb*/) { + var str = "", + label = _translate("OK"), + cb = null; + + switch (arguments.length) { + case 1: + // no callback, default button label + str = arguments[0]; + break; + case 2: + // callback *or* custom button label dependent on type + str = arguments[0]; + if (typeof arguments[1] == "function") { + cb = arguments[1]; + } else { + label = arguments[1]; + } + break; + case 3: + // callback and custom button label + str = arguments[0]; + label = arguments[1]; + cb = arguments[2]; + break; + default: + throw new Error("Incorrect number of arguments: expected 1-3"); + } + + return that.dialog( + str, + { + // only button (ok) + label: label, + icon: _icons.OK, + class: _btnClasses.OK, + callback: cb, + }, + { + // ensure that the escape key works; either invoking the user's + // callback or true to just close the dialog + onEscape: cb || true, + } + ); + }; + + that.confirm = function (/*str, labelCancel, labelOk, cb*/) { + var str = "", + labelCancel = _translate("CANCEL"), + labelOk = _translate("CONFIRM"), + cb = null; + + switch (arguments.length) { + case 1: + str = arguments[0]; + break; + case 2: + str = arguments[0]; + if (typeof arguments[1] == "function") { + cb = arguments[1]; + } else { + labelCancel = arguments[1]; + } + break; + case 3: + str = arguments[0]; + labelCancel = arguments[1]; + if (typeof arguments[2] == "function") { + cb = arguments[2]; + } else { + labelOk = arguments[2]; + } + break; + case 4: + str = arguments[0]; + labelCancel = arguments[1]; + labelOk = arguments[2]; + cb = arguments[3]; + break; + default: + throw new Error("Incorrect number of arguments: expected 1-4"); + } + + var cancelCallback = function () { + if (typeof cb === "function") { + return cb(false); + } + }; + + var confirmCallback = function () { + if (typeof cb === "function") { + return cb(true); + } + }; + + return that.dialog( + str, + [ + { + // first button (cancel) + label: labelCancel, + icon: _icons.CANCEL, + class: _btnClasses.CANCEL, + callback: cancelCallback, + }, + { + // second button (confirm) + label: labelOk, + icon: _icons.CONFIRM, + class: _btnClasses.CONFIRM, + callback: confirmCallback, + }, + ], + { + // escape key bindings + onEscape: cancelCallback, + } + ); + }; + + that.prompt = function (/*str, labelCancel, labelOk, cb, defaultVal*/) { + var str = "", + labelCancel = _translate("CANCEL"), + labelOk = _translate("CONFIRM"), + cb = null, + defaultVal = ""; + + switch (arguments.length) { + case 1: + str = arguments[0]; + break; + case 2: + str = arguments[0]; + if (typeof arguments[1] == "function") { + cb = arguments[1]; + } else { + labelCancel = arguments[1]; + } + break; + case 3: + str = arguments[0]; + labelCancel = arguments[1]; + if (typeof arguments[2] == "function") { + cb = arguments[2]; + } else { + labelOk = arguments[2]; + } + break; + case 4: + str = arguments[0]; + labelCancel = arguments[1]; + labelOk = arguments[2]; + cb = arguments[3]; + break; + case 5: + str = arguments[0]; + labelCancel = arguments[1]; + labelOk = arguments[2]; + cb = arguments[3]; + defaultVal = arguments[4]; + break; + default: + throw new Error("Incorrect number of arguments: expected 1-5"); + } + + var header = str; + + // let's keep a reference to the form object for later + var form = $("
"); + form.append( + "" + ); + + var cancelCallback = function () { + if (typeof cb === "function") { + // yep, native prompts dismiss with null, whereas native + // confirms dismiss with false... + return cb(null); + } + }; + + var confirmCallback = function () { + if (typeof cb === "function") { + return cb(form.find("input[type=text]").val()); + } + }; + + var div = that.dialog( + form, + [ + { + // first button (cancel) + label: labelCancel, + icon: _icons.CANCEL, + class: _btnClasses.CANCEL, + callback: cancelCallback, + }, + { + // second button (confirm) + label: labelOk, + icon: _icons.CONFIRM, + class: _btnClasses.CONFIRM, + callback: confirmCallback, + }, + ], + { + // prompts need a few extra options + header: header, + // explicitly tell dialog NOT to show the dialog... + show: false, + onEscape: cancelCallback, + } + ); + + // ... the reason the prompt needs to be hidden is because we need + // to bind our own "shown" handler, after creating the modal but + // before any show(n) events are triggered + // @see https://github.com/makeusabrew/bootbox/issues/69 + + div.on("shown", function () { + form.find("input[type=text]").focus(); + + // ensure that submitting the form (e.g. with the enter key) + // replicates the behaviour of a normal prompt() + form.on("submit", function (e) { + e.preventDefault(); + div.find(".btn-primary").click(); + }); + }); + + div.modal("show"); + + return div; + }; + + that.dialog = function (str, handlers, options) { + var buttons = "", + callbacks = []; + + if (!options) { + options = {}; + } + + // check for single object and convert to array if necessary + if (typeof handlers === "undefined") { + handlers = []; + } else if (typeof handlers.length == "undefined") { + handlers = [handlers]; + } + + var i = handlers.length; + while (i--) { + var label = null, + href = null, + _class = "btn-default", + icon = "", + callback = null; + + if ( + typeof handlers[i]["label"] == "undefined" && + typeof handlers[i]["class"] == "undefined" && + typeof handlers[i]["callback"] == "undefined" + ) { + // if we've got nothing we expect, check for condensed format + + var propCount = 0, // condensed will only match if this == 1 + property = null; // save the last property we found + + // be nicer to count the properties without this, but don't think it's possible... + for (var j in handlers[i]) { + property = j; + if (++propCount > 1) { + // forget it, too many properties + break; + } + } + + if (propCount == 1 && typeof handlers[i][j] == "function") { + // matches condensed format of label -> function + handlers[i]["label"] = property; + handlers[i]["callback"] = handlers[i][j]; + } + } + + if (typeof handlers[i]["callback"] == "function") { + callback = handlers[i]["callback"]; + } + + if (handlers[i]["class"]) { + _class = handlers[i]["class"]; + } else if (i == handlers.length - 1 && handlers.length <= 2) { + // always add a primary to the main option in a two-button dialog + _class = "btn-primary"; + } + + // See: https://github.com/makeusabrew/bootbox/pull/114 + // Upgrade to official bootbox release when it gets merged. + if (handlers[i]["link"] !== true) { + _class = "btn " + _class; + } + + if (handlers[i]["label"]) { + label = handlers[i]["label"]; + } else { + label = "Option " + (i + 1); + } + + if (handlers[i]["icon"]) { + icon = handlers[i]["icon"]; + } + + if (handlers[i]["href"]) { + href = handlers[i]["href"]; + } else { + href = _defaultHref; + } + + buttons = + buttons + + "" + + icon + + "" + + label + + ""; + + callbacks[i] = callback; + } + + // @see https://github.com/makeusabrew/bootbox/issues/46#issuecomment-8235302 + // and https://github.com/twitter/bootstrap/issues/4474 + // for an explanation of the inline overflow: hidden + // @see https://github.com/twitter/bootstrap/issues/4854 + // for an explanation of tabIndex=-1 + + var parts = [ + ""); + + var div = $(parts.join("\n")); + + // check whether we should fade in/out + var shouldFade = + typeof options.animate === "undefined" ? _animate : options.animate; + + if (shouldFade) { + div.addClass("fade"); + } + + var optionalClasses = + typeof options.classes === "undefined" ? _classes : options.classes; + if (optionalClasses) { + div.addClass(optionalClasses); + } + + // now we've built up the div properly we can inject the content whether it was a string or a jQuery object + div.find(".modal-body").html(str); + + function onCancel(source) { + // for now source is unused, but it will be in future + var hideModal = null; + if (typeof options.onEscape === "function") { + // @see https://github.com/makeusabrew/bootbox/issues/91 + hideModal = options.onEscape(); + } + + if (hideModal !== false) { + div.modal("hide"); + } + } + + // hook into the modal's keyup trigger to check for the escape key + div.on("keyup.dismiss.modal", function (e) { + // any truthy value passed to onEscape will dismiss the dialog + // as long as the onEscape function (if defined) doesn't prevent it + if (e.which === 27 && options.onEscape !== false) { + onCancel("escape"); + } + }); + + // handle close buttons too + div.on("click", "a.close", function (e) { + e.preventDefault(); + onCancel("close"); + }); + + // well, *if* we have a primary - give the first dom element focus + div.on("shown.bs.modal", function () { + div.find("a.btn-primary:first").focus(); + }); + + div.on("hidden.bs.modal", function () { + div.remove(); + }); + + // wire up button handlers + div.on("click", ".modal-footer a", function (e) { + var self = this; + Ember.run(function () { + var handler = $(self).data("handler"), + cb = callbacks[handler], + hideModal = null; + + // sort of @see https://github.com/makeusabrew/bootbox/pull/68 - heavily adapted + // if we've got a custom href attribute, all bets are off + if ( + typeof handler !== "undefined" && + typeof handlers[handler]["href"] !== "undefined" + ) { + return; + } + + e.preventDefault(); + + if (typeof cb === "function") { + hideModal = cb(e); + } + + // the only way hideModal *will* be false is if a callback exists and + // returns it as a value. in those situations, don't hide the dialog + // @see https://github.com/makeusabrew/bootbox/pull/25 + if (hideModal !== false) { + div.modal("hide"); + } + }); + }); + + // stick the modal right at the bottom of the main body out of the way + (that.$body || $("body")).append(div); + + div.modal({ + // unless explicitly overridden take whatever our default backdrop value is + backdrop: + typeof options.backdrop === "undefined" + ? _backdrop + : options.backdrop, + // ignore bootstrap's keyboard options; we'll handle this ourselves (more fine-grained control) + keyboard: false, + // @ see https://github.com/makeusabrew/bootbox/issues/69 + // we *never* want the modal to be shown before we can bind stuff to it + // this method can also take a 'show' option, but we'll only use that + // later if we need to + show: false, + }); + + // @see https://github.com/makeusabrew/bootbox/issues/64 + // @see https://github.com/makeusabrew/bootbox/issues/60 + // ...caused by... + // @see https://github.com/twitter/bootstrap/issues/4781 + div.on("show", function (e) { + $(document).off("focusin.modal"); + }); + + if (typeof options.show === "undefined" || options.show === true) { + div.modal("show"); + } + + return div; + }; + + /** + * #modal is deprecated in v3; it can still be used but no guarantees are + * made - have never been truly convinced of its merit but perhaps just + * needs a tidyup and some TLC + */ + that.modal = function (/*str, label, options*/) { + var str; + var label; + var options; + + var defaultOptions = { + onEscape: null, + keyboard: true, + backdrop: _backdrop, + }; + + switch (arguments.length) { + case 1: + str = arguments[0]; + break; + case 2: + str = arguments[0]; + if (typeof arguments[1] == "object") { + options = arguments[1]; + } else { + label = arguments[1]; + } + break; + case 3: + str = arguments[0]; + label = arguments[1]; + options = arguments[2]; + break; + default: + throw new Error("Incorrect number of arguments: expected 1-3"); + } + + defaultOptions["header"] = label; + + if (typeof options == "object") { + options = $.extend(defaultOptions, options); + } else { + options = defaultOptions; + } + + return that.dialog(str, [], options); + }; + + that.hideAll = function () { + $(".bootbox").modal("hide"); + }; + + that.animate = function (animate) { + _animate = animate; + }; + + that.backdrop = function (backdrop) { + _backdrop = backdrop; + }; + + that.classes = function (classes) { + _classes = classes; + }; + + /** + * private API + */ + + /** + * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are + * unlikely to be required. If this gets too large it can be split out into separate JS files. + */ + var _locales = { + br: { + OK: "OK", + CANCEL: "Cancelar", + CONFIRM: "Sim", + }, + da: { + OK: "OK", + CANCEL: "Annuller", + CONFIRM: "Accepter", + }, + de: { + OK: "OK", + CANCEL: "Abbrechen", + CONFIRM: "Akzeptieren", + }, + en: { + OK: "OK", + CANCEL: "Cancel", + CONFIRM: "OK", + }, + es: { + OK: "OK", + CANCEL: "Cancelar", + CONFIRM: "Aceptar", + }, + fr: { + OK: "OK", + CANCEL: "Annuler", + CONFIRM: "D'accord", + }, + it: { + OK: "OK", + CANCEL: "Annulla", + CONFIRM: "Conferma", + }, + nl: { + OK: "OK", + CANCEL: "Annuleren", + CONFIRM: "Accepteren", + }, + pl: { + OK: "OK", + CANCEL: "Anuluj", + CONFIRM: "Potwierdź", + }, + ru: { + OK: "OK", + CANCEL: "Отмена", + CONFIRM: "Применить", + }, + zh_CN: { + OK: "OK", + CANCEL: "取消", + CONFIRM: "确认", + }, + zh_TW: { + OK: "OK", + CANCEL: "取消", + CONFIRM: "確認", + }, + }; + + function _translate(str, locale) { + // we assume if no target locale is probided then we should take it from current setting + if (typeof locale === "undefined") { + locale = _locale; + } + if (typeof _locales[locale][str] === "string") { + return _locales[locale][str]; + } + + // if we couldn't find a lookup then try and fallback to a default translation + + if (locale != _defaultLocale) { + return _translate(str, _defaultLocale); + } + + // if we can't do anything then bail out with whatever string was passed in - last resort + return str; + } + + return that; + })(document, window.jQuery); + +// @see https://github.com/makeusabrew/bootbox/issues/71 +window.bootbox = bootbox; + +define("bootbox", ["exports"], function (__exports__) { + __exports__.default = window.bootbox; +}); diff --git a/assets/javascripts/legacy/caret_position.js b/assets/javascripts/legacy/caret_position.js new file mode 100644 index 00000000..2ae02794 --- /dev/null +++ b/assets/javascripts/legacy/caret_position.js @@ -0,0 +1,164 @@ +// discourse-skip-module + +// TODO: This code should be moved to lib, it was heavily modified by us over the years, and mostly written by us +// except for the little snippet from StackOverflow +// +// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea +var clone = null; + +$.fn.caret = function(elem) { + var getCaret = function(el) { + if (el.selectionStart) { + return el.selectionStart; + } + return 0; + }; + return getCaret(elem || this[0]); +}; + +/** + This is a jQuery plugin to retrieve the caret position in a textarea + + @module $.fn.caretPosition +**/ +$.fn.caretPosition = function(options) { + var after, + before, + getStyles, + guard, + html, + important, + insertSpaceAfterBefore, + letter, + makeCursor, + p, + pPos, + pos, + span, + styles, + textarea, + val; + if (clone) { + clone.remove(); + } + span = $("#pos span"); + textarea = $(this); + + getStyles = function(el) { + if (el.currentStyle) { + return el.currentStyle; + } else { + return document.defaultView.getComputedStyle(el, ""); + } + }; + + important = function(prop) { + return styles.getPropertyValue(prop); + }; + + styles = getStyles(textarea[0]); + clone = $("

").appendTo("body"); + p = clone.find("p"); + + var isRTL = $("html").hasClass("rtl"); + clone.css({ + border: "1px solid black", + padding: important("padding"), + resize: important("resize"), + "max-height": textarea.height() + "px", + "overflow-y": "auto", + "word-wrap": "break-word", + position: "absolute", + left: isRTL ? "auto" : "-7000px", + right: isRTL ? "-7000px" : "auto" + }); + + p.css({ + margin: 0, + padding: 0, + "word-wrap": "break-word", + "letter-spacing": important("letter-spacing"), + "font-family": important("font-family"), + "font-size": important("font-size"), + "line-height": important("line-height") + }); + + clone.width(textarea.width()); + clone.height(textarea.height()); + + pos = + options && (options.pos || options.pos === 0) + ? options.pos + : $.caret(textarea[0]); + + val = textarea.val().replace("\r", ""); + if (options && options.key) { + val = val.substring(0, pos) + options.key + val.substring(pos); + } + before = pos - 1; + after = pos; + insertSpaceAfterBefore = false; + + // if before and after are \n insert a space + if (val[before] === "\n" && val[after] === "\n") { + insertSpaceAfterBefore = true; + } + + guard = function(v) { + var buf; + buf = v.replace(//g, ">"); + buf = buf.replace(/[ ]/g, "​ ​"); + return buf.replace(/\n/g, "
"); + }; + + makeCursor = function(pos, klass, color) { + var l; + l = val.substring(pos, pos + 1); + if (l === "\n") return "
"; + return ( + "" + + guard(l) + + "" + ); + }; + + html = ""; + + if (before >= 0) { + html += + guard(val.substring(0, pos - 1)) + + makeCursor(before, "before", "#d0ffff"); + if (insertSpaceAfterBefore) { + html += makeCursor(0, "post-before", "#d0ffff"); + } + } + + if (after >= 0) { + html += makeCursor(after, "after", "#ffd0ff"); + if (after - 1 < val.length) { + html += guard(val.substring(after + 1)); + } + } + + p.html(html); + clone.scrollTop(textarea.scrollTop()); + letter = p.find("span:first"); + pos = letter.offset(); + if (letter.hasClass("before")) { + pos.left = pos.left + letter.width(); + } + + pPos = p.offset(); + var position = { + left: pos.left - pPos.left, + top: pos.top - pPos.top - clone.scrollTop() + }; + + clone.remove(); + return position; +}; diff --git a/assets/javascripts/wizard-vendor.js b/assets/javascripts/wizard-vendor.js index 8b1cf56f..a0f25d0c 100644 --- a/assets/javascripts/wizard-vendor.js +++ b/assets/javascripts/wizard-vendor.js @@ -2,12 +2,12 @@ //= require legacy/template_include.js //= require legacy/uppy.js -//= require bootstrap-modal.js -//= require bootbox.js +//= require legacy/bootstrap-modal.js +//= require legacy/bootbox.js //= require legacy/virtual-dom //= require legacy/virtual-dom-amd //= require legacy/itsatrap.js -//= require caret_position.js +//= require legacy/caret_position.js //= require legacy/popper.js //= require legacy/tippy.umd.js //= require legacy/discourse-shims.js From e761276ed57acfcb17476dcb73f8b3badbe33cd6 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 5 Jul 2022 09:06:09 +0200 Subject: [PATCH 275/556] COMPATIBILITY: plugin-outlet no longer works as a helper. --- assets/javascripts/wizard/helpers/plugin-outlet.js.es6 | 6 ------ .../wizard/templates/components/plugin-outlet.hbs | 0 2 files changed, 6 deletions(-) delete mode 100644 assets/javascripts/wizard/helpers/plugin-outlet.js.es6 create mode 100644 assets/javascripts/wizard/templates/components/plugin-outlet.hbs diff --git a/assets/javascripts/wizard/helpers/plugin-outlet.js.es6 b/assets/javascripts/wizard/helpers/plugin-outlet.js.es6 deleted file mode 100644 index e6ff81a6..00000000 --- a/assets/javascripts/wizard/helpers/plugin-outlet.js.es6 +++ /dev/null @@ -1,6 +0,0 @@ -import { registerUnbound } from "discourse-common/lib/helpers"; -import Handlebars from "handlebars"; - -export default registerUnbound("plugin-outlet", function () { - return new Handlebars.SafeString(""); -}); diff --git a/assets/javascripts/wizard/templates/components/plugin-outlet.hbs b/assets/javascripts/wizard/templates/components/plugin-outlet.hbs new file mode 100644 index 00000000..e69de29b From d64b6b50dd5a16c4737e9e28a41c3653019784d8 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Sat, 9 Jul 2022 10:24:27 +0200 Subject: [PATCH 276/556] COMPATIBILITY: The ember resolver has been "modernized".. https://github.com/discourse/discourse/commit/fc36ac6cde2afaf8c5e9e6df22e71a7b3044c0a2 --- assets/javascripts/legacy/discourse-loader.js | 3 +- assets/javascripts/legacy/raw-templates.js | 42 +++ assets/javascripts/legacy/resolver.js | 337 ++++++++++++++++++ assets/javascripts/wizard-custom.js | 22 +- .../javascripts/wizard-raw-templates.js.erb | 4 +- assets/javascripts/wizard/application.js.es6 | 2 +- .../components/wizard-composer-editor.js.es6 | 2 +- 7 files changed, 406 insertions(+), 6 deletions(-) create mode 100644 assets/javascripts/legacy/raw-templates.js create mode 100644 assets/javascripts/legacy/resolver.js diff --git a/assets/javascripts/legacy/discourse-loader.js b/assets/javascripts/legacy/discourse-loader.js index ec77a6d2..ee9a2a07 100644 --- a/assets/javascripts/legacy/discourse-loader.js +++ b/assets/javascripts/legacy/discourse-loader.js @@ -7,7 +7,8 @@ var define, requirejs; let ALIASES = { "ember-addons/ember-computed-decorators": "discourse-common/utils/decorators", - "discourse/lib/raw-templates": "discourse-common/lib/raw-templates", + "discourse/lib/raw-templates": "discourse/plugins/discourse-custom-wizard/legacy/raw-templates", + "discourse-common/lib/raw-templates": "discourse/plugins/discourse-custom-wizard/legacy/raw-templates", "preload-store": "discourse/lib/preload-store", "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures", }; diff --git a/assets/javascripts/legacy/raw-templates.js b/assets/javascripts/legacy/raw-templates.js new file mode 100644 index 00000000..5e67bbd8 --- /dev/null +++ b/assets/javascripts/legacy/raw-templates.js @@ -0,0 +1,42 @@ +import { getResolverOption } from "./resolver"; + +export const __DISCOURSE_RAW_TEMPLATES = {}; + +export function addRawTemplate(name, template, opts = {}) { + // Core templates should never overwrite themes / plugins + if (opts.core && __DISCOURSE_RAW_TEMPLATES[name]) { + return; + } + __DISCOURSE_RAW_TEMPLATES[name] = template; +} + +export function removeRawTemplate(name) { + delete __DISCOURSE_RAW_TEMPLATES[name]; +} + +export function findRawTemplate(name) { + if (getResolverOption("mobileView")) { + return ( + __DISCOURSE_RAW_TEMPLATES[`javascripts/mobile/${name}`] || + __DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] || + __DISCOURSE_RAW_TEMPLATES[`mobile/${name}`] || + __DISCOURSE_RAW_TEMPLATES[name] + ); + } + + return ( + __DISCOURSE_RAW_TEMPLATES[`javascripts/${name}`] || + __DISCOURSE_RAW_TEMPLATES[name] + ); +} + +export function buildRawConnectorCache(findOutlets) { + let result = {}; + findOutlets(__DISCOURSE_RAW_TEMPLATES, (outletName, resource) => { + result[outletName] = result[outletName] || []; + result[outletName].push({ + template: __DISCOURSE_RAW_TEMPLATES[resource], + }); + }); + return result; +} diff --git a/assets/javascripts/legacy/resolver.js b/assets/javascripts/legacy/resolver.js new file mode 100644 index 00000000..5980b39b --- /dev/null +++ b/assets/javascripts/legacy/resolver.js @@ -0,0 +1,337 @@ + +/* global Ember */ +import { classify, dasherize, decamelize } from "@ember/string"; +import deprecated from "discourse-common/lib/deprecated"; +import { findHelper } from "discourse-common/lib/helpers"; +import { get } from "@ember/object"; +import SuffixTrie from "discourse-common/lib/suffix-trie"; + +let _options = {}; +let moduleSuffixTrie = null; + +export function setResolverOption(name, value) { + _options[name] = value; +} + +export function getResolverOption(name) { + return _options[name]; +} + +export function clearResolverOptions() { + _options = {}; +} + +function parseName(fullName) { + const nameParts = fullName.split(":"); + const type = nameParts[0]; + let fullNameWithoutType = nameParts[1]; + const namespace = get(this, "namespace"); + const root = namespace; + + return { + fullName, + type, + fullNameWithoutType, + name: fullNameWithoutType, + root, + resolveMethodName: "resolve" + classify(type), + }; +} + +function lookupModuleBySuffix(suffix) { + if (!moduleSuffixTrie) { + moduleSuffixTrie = new SuffixTrie("/"); + Object.keys(requirejs.entries).forEach((name) => { + if (!name.includes("/templates/")) { + moduleSuffixTrie.add(name); + } + }); + } + return moduleSuffixTrie.withSuffix(suffix, 1)[0]; +} + +export function buildResolver(baseName) { + return Ember.DefaultResolver.extend({ + parseName, + + resolveRouter(parsedName) { + const routerPath = `${baseName}/router`; + if (requirejs.entries[routerPath]) { + const module = requirejs(routerPath, null, null, true); + return module.default; + } + return this._super(parsedName); + }, + + normalize(fullName) { + if (fullName === "app-events:main") { + deprecated( + "`app-events:main` has been replaced with `service:app-events`", + { since: "2.4.0", dropFrom: "2.9.0.beta1" } + ); + return "service:app-events"; + } + + for (const [key, value] of Object.entries({ + "controller:discovery.categoryWithID": "controller:discovery.category", + "controller:discovery.parentCategory": "controller:discovery.category", + "controller:tags-show": "controller:tag-show", + "controller:tags.show": "controller:tag.show", + "controller:tagsShow": "controller:tagShow", + "route:discovery.categoryWithID": "route:discovery.category", + "route:discovery.parentCategory": "route:discovery.category", + "route:tags-show": "route:tag-show", + "route:tags.show": "route:tag.show", + "route:tagsShow": "route:tagShow", + })) { + if (fullName === key) { + deprecated(`${key} was replaced with ${value}`, { since: "2.6.0" }); + return value; + } + } + + const split = fullName.split(":"); + if (split.length > 1) { + const appBase = `${baseName}/${split[0]}s/`; + const adminBase = "admin/" + split[0] + "s/"; + const wizardBase = "wizard/" + split[0] + "s/"; + + // Allow render 'admin/templates/xyz' too + split[1] = split[1].replace(".templates", "").replace("/templates", ""); + + // Try slashes + let dashed = dasherize(split[1].replace(/\./g, "/")); + if ( + requirejs.entries[appBase + dashed] || + requirejs.entries[adminBase + dashed] || + requirejs.entries[wizardBase + dashed] + ) { + return split[0] + ":" + dashed; + } + + // Try with dashes instead of slashes + dashed = dasherize(split[1].replace(/\./g, "-")); + if ( + requirejs.entries[appBase + dashed] || + requirejs.entries[adminBase + dashed] || + requirejs.entries[wizardBase + dashed] + ) { + return split[0] + ":" + dashed; + } + } + return this._super(fullName); + }, + + customResolve(parsedName) { + // If we end with the name we want, use it. This allows us to define components within plugins. + const suffix = parsedName.type + "s/" + parsedName.fullNameWithoutType, + dashed = dasherize(suffix), + moduleName = lookupModuleBySuffix(dashed); + + let module; + if (moduleName) { + module = requirejs(moduleName, null, null, true /* force sync */); + if (module && module["default"]) { + module = module["default"]; + } + } + return module; + }, + + resolveWidget(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveAdapter(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveModel(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveView(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveHelper(parsedName) { + return ( + findHelper(parsedName.fullNameWithoutType) || + this.customResolve(parsedName) || + this._super(parsedName) + ); + }, + + resolveController(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveComponent(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveService(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveRawView(parsedName) { + return this.customResolve(parsedName) || this._super(parsedName); + }, + + resolveRoute(parsedName) { + if (parsedName.fullNameWithoutType === "basic") { + return requirejs("discourse/routes/discourse", null, null, true) + .default; + } + + return this.customResolve(parsedName) || this._super(parsedName); + }, + + findLoadingTemplate(parsedName) { + if (parsedName.fullNameWithoutType.match(/loading$/)) { + return Ember.TEMPLATES.loading; + } + }, + + findConnectorTemplate(parsedName) { + const full = parsedName.fullNameWithoutType.replace("components/", ""); + if (full.indexOf("connectors") === 0) { + return Ember.TEMPLATES[`javascripts/${full}`]; + } + }, + + resolveTemplate(parsedName) { + return ( + this.findPluginMobileTemplate(parsedName) || + this.findPluginTemplate(parsedName) || + this.findMobileTemplate(parsedName) || + this.findTemplate(parsedName) || + this.findLoadingTemplate(parsedName) || + this.findConnectorTemplate(parsedName) || + Ember.TEMPLATES.not_found + ); + }, + + findPluginTemplate(parsedName) { + const pluginParsedName = this.parseName( + parsedName.fullName.replace("template:", "template:javascripts/") + ); + return this.findTemplate(pluginParsedName); + }, + + findPluginMobileTemplate(parsedName) { + if (_options.mobileView) { + let pluginParsedName = this.parseName( + parsedName.fullName.replace( + "template:", + "template:javascripts/mobile/" + ) + ); + return this.findTemplate(pluginParsedName); + } + }, + + findMobileTemplate(parsedName) { + if (_options.mobileView) { + let mobileParsedName = this.parseName( + parsedName.fullName.replace("template:", "template:mobile/") + ); + return this.findTemplate(mobileParsedName); + } + }, + + findTemplate(parsedName) { + const withoutType = parsedName.fullNameWithoutType, + slashedType = withoutType.replace(/\./g, "/"), + decamelized = decamelize(withoutType), + dashed = decamelized.replace(/\./g, "-").replace(/\_/g, "-"), + templates = Ember.TEMPLATES; + + return ( + this._super(parsedName) || + templates[slashedType] || + templates[withoutType] || + templates[withoutType.replace(/\.raw$/, "")] || + templates[dashed] || + templates[decamelized.replace(/\./, "/")] || + templates[decamelized.replace(/\_/, "/")] || + templates[`${baseName}/templates/${withoutType}`] || + this.findAdminTemplate(parsedName) || + this.findWizardTemplate(parsedName) || + this.findUnderscoredTemplate(parsedName) + ); + }, + + findUnderscoredTemplate(parsedName) { + let decamelized = decamelize(parsedName.fullNameWithoutType); + let underscored = decamelized.replace(/\-/g, "_"); + return Ember.TEMPLATES[underscored]; + }, + + // Try to find a template within a special admin namespace, e.g. adminEmail => admin/templates/email + // (similar to how discourse lays out templates) + findAdminTemplate(parsedName) { + let decamelized = decamelize(parsedName.fullNameWithoutType); + if (decamelized.indexOf("components") === 0) { + let comPath = `admin/templates/${decamelized}`; + const compTemplate = + Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath]; + if (compTemplate) { + return compTemplate; + } + } + + if (decamelized === "javascripts/admin") { + return Ember.TEMPLATES["admin/templates/admin"]; + } + + if ( + decamelized.indexOf("admin") === 0 || + decamelized.indexOf("javascripts/admin") === 0 + ) { + decamelized = decamelized.replace(/^admin\_/, "admin/templates/"); + decamelized = decamelized.replace(/^admin\./, "admin/templates/"); + decamelized = decamelized.replace(/\./g, "_"); + + const dashed = decamelized.replace(/_/g, "-"); + return ( + Ember.TEMPLATES[decamelized] || + Ember.TEMPLATES[dashed] || + Ember.TEMPLATES[dashed.replace("admin-", "admin/")] + ); + } + }, + + findWizardTemplate(parsedName) { + let decamelized = decamelize(parsedName.fullNameWithoutType); + if (decamelized.startsWith("components")) { + let comPath = `wizard/templates/${decamelized}`; + const compTemplate = + Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath]; + if (compTemplate) { + return compTemplate; + } + } + + if (decamelized === "javascripts/wizard") { + return Ember.TEMPLATES["wizard/templates/wizard"]; + } + + if ( + decamelized.startsWith("wizard") || + decamelized.startsWith("javascripts/wizard") + ) { + decamelized = decamelized.replace(/^wizard\_/, "wizard/templates/"); + decamelized = decamelized.replace(/^wizard\./, "wizard/templates/"); + decamelized = decamelized.replace(/\./g, "_"); + + const dashed = decamelized.replace(/_/g, "-"); + return ( + Ember.TEMPLATES[decamelized] || + Ember.TEMPLATES[dashed] || + Ember.TEMPLATES[dashed.replace("wizard-", "wizard/")] + ); + } + }, + }); +} diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 05a6fcb3..0dd67833 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,7 +1,27 @@ // discourse-skip-module //= require_tree_discourse truth-helpers/addon -//= require_tree_discourse discourse-common/addon + +//= require legacy/resolver +//= require legacy/raw-templates + +//= require_tree_discourse discourse-common/addon/config +//= require_tree_discourse discourse-common/addon/helpers +//= require discourse-common/addon/lib/get-owner +//= require discourse-common/addon/lib/object +//= require discourse-common/addon/lib/helpers +//= require discourse-common/addon/lib/get-url +//= require discourse-common/addon/lib/deprecated +//= require discourse-common/addon/lib/suffix-trie +//= require discourse-common/addon/lib/debounce +//= require discourse-common/addon/lib/raw-handlebars +//= require discourse-common/addon/lib/raw-handlebars-helpers +//= require discourse-common/addon/lib/escape +//= require discourse-common/addon/lib/icon-library +//= require discourse-common/addon/lib/attribute-hook +//= require discourse-common/addon/lib/dom-from-string +//= require_tree_discourse discourse-common/addon/utils + //= require_tree_discourse select-kit/addon //= require_tree_discourse discourse/app/lib //= require_tree_discourse discourse/app/mixins diff --git a/assets/javascripts/wizard-raw-templates.js.erb b/assets/javascripts/wizard-raw-templates.js.erb index 55b255e0..ff216180 100644 --- a/assets/javascripts/wizard-raw-templates.js.erb +++ b/assets/javascripts/wizard-raw-templates.js.erb @@ -22,14 +22,14 @@ Discourse.unofficial_plugins.each do |plugin| compiled = Barber::Precompiler.new().compile(File.read(f)) result << " (function() { - requirejs('discourse-common/lib/raw-templates').addRawTemplate(#{compiled}); + requirejs('discourse/plugins/discourse-custom-wizard/legacy/raw-templates').addRawTemplate(#{compiled}); })(); " end result << " (function() { - window.__DISCOURSE_RAW_TEMPLATES = requirejs('discourse-common/lib/raw-templates').__DISCOURSE_RAW_TEMPLATES; + window.__DISCOURSE_RAW_TEMPLATES = requirejs('discourse/plugins/discourse-custom-wizard/legacy/raw-templates').__DISCOURSE_RAW_TEMPLATES; })(); " end diff --git a/assets/javascripts/wizard/application.js.es6 b/assets/javascripts/wizard/application.js.es6 index 012949b3..dd2d09f8 100644 --- a/assets/javascripts/wizard/application.js.es6 +++ b/assets/javascripts/wizard/application.js.es6 @@ -1,4 +1,4 @@ -import { buildResolver } from "discourse-common/resolver"; +import { buildResolver } from "discourse/plugins/discourse-custom-wizard/legacy/resolver"; import Application from "@ember/application"; import WizardInitializer from "./lib/initialize/wizard"; import { isTesting } from "discourse-common/config/environment"; diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 4f44d439..de70986b 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -3,7 +3,7 @@ import { default as discourseComputed, on, } from "discourse-common/utils/decorators"; -import { findRawTemplate } from "discourse-common/lib/raw-templates"; +import { findRawTemplate } from "discourse/plugins/discourse-custom-wizard/legacy/raw-templates"; import { scheduleOnce } from "@ember/runloop"; import { caretPosition, inCodeBlock } from "discourse/lib/utilities"; import highlightSyntax from "discourse/lib/highlight-syntax"; From 0b3c71c621acf7381f9740e207c26a8a9d4b01be Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Fri, 15 Jul 2022 13:23:32 +0100 Subject: [PATCH 277/556] FIX: adopt a simplified with_resolved_locale to ensure user's locale is always loaded --- app/controllers/custom_wizard/wizard.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index bb53670b..4dbb5b69 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -12,6 +12,7 @@ class CustomWizard::WizardController < ::ActionController::Base before_action :preload_wizard_json before_action :ensure_plugin_enabled before_action :ensure_logged_in, only: [:skip] + around_action :with_resolved_locale helper_method :wizard_page_title helper_method :wizard_theme_id @@ -115,6 +116,13 @@ class CustomWizard::WizardController < ::ActionController::Base @preloaded[key] = json.gsub(" Date: Fri, 15 Jul 2022 16:41:04 +0100 Subject: [PATCH 278/556] Version bump --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index ef227f12..572a5b54 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards -# version: 1.21.0 +# version: 1.21.1 # authors: Angus McLeod # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io From c7097d5775d57405003a03b860083aaedcae8955 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 18 Jul 2022 07:37:10 +0100 Subject: [PATCH 279/556] COMPATIBILITY: Add new common library --- assets/javascripts/wizard-custom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 0dd67833..ee67b388 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -20,6 +20,7 @@ //= require discourse-common/addon/lib/icon-library //= require discourse-common/addon/lib/attribute-hook //= require discourse-common/addon/lib/dom-from-string +//= require discourse-common/addon/lib/later //= require_tree_discourse discourse-common/addon/utils //= require_tree_discourse select-kit/addon From f373e2ad815c4cc42ad290fcfacd3419fb5aa1d1 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 18 Jul 2022 07:41:28 +0100 Subject: [PATCH 280/556] COMPATIBILITY: remove overly-active keyPress --- .../javascripts/wizard/components/wizard-step.js.es6 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 index cc23c5bf..44b388bb 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -90,16 +90,6 @@ export default Component.extend({ this.showMessage(message); }, - keyPress(event) { - if (event.key === "Enter") { - if (this.showDoneButton) { - this.send("quit"); - } else { - this.send("nextStep"); - } - } - }, - @discourseComputed("step.index", "wizard.totalSteps") barStyle(displayIndex, totalSteps) { let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1); From ac279e4a67a0593a77f908448a194ed8f6a8bb03 Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Mon, 18 Jul 2022 07:41:46 +0100 Subject: [PATCH 281/556] Version bump --- plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.rb b/plugin.rb index 572a5b54..17ef4d5f 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards -# version: 1.21.1 +# version: 1.21.2 # authors: Angus McLeod # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io From 11ff38ae90417f75264692e6f22aeac7747bb4dc Mon Sep 17 00:00:00 2001 From: Angus McLeod Date: Tue, 26 Jul 2022 15:18:09 +0100 Subject: [PATCH 282/556] Move wizard into core app first step: move the files --- app/controllers/custom_wizard/wizard.rb | 37 +- .../components/custom-user-selector.js.es6 | 6 +- .../custom-wizard-category-selector.js.es6} | 0 .../custom-wizard-composer-editor.js.es6} | 5 +- .../custom-wizard-composer-hyperlink.js.es6} | 1 - .../custom-wizard-date-input.js.es6} | 1 - .../custom-wizard-date-time-input.js.es6} | 2 - .../custom-wizard-field-category.js.es6} | 2 - .../custom-wizard-field-checkbox.js.es6 | 3 + ...stom-wizard-field-composer-preview.js.es6} | 2 - .../custom-wizard-field-composer.js.es6} | 2 - .../custom-wizard-field-date-time.js.es6} | 2 - .../custom-wizard-field-date.js.es6} | 2 - .../custom-wizard-field-dropdown.js.es6} | 2 - .../custom-wizard-field-group.js.es6 | 3 + .../custom-wizard-field-number.js.es6 | 3 + .../components/custom-wizard-field-tag.js.es6 | 3 + .../custom-wizard-field-text.js.es6} | 2 - .../custom-wizard-field-textarea.js.es6} | 2 - .../custom-wizard-field-time.js.es6} | 2 - .../custom-wizard-field-upload.js.es6} | 1 - .../components/custom-wizard-field-url.js.es6 | 3 + .../custom-wizard-field-user-selector.js.es6 | 3 + .../components/custom-wizard-field.js.es6} | 18 +- .../custom-wizard-group-selector.js.es6} | 1 - .../custom-wizard-no-access.js.es6} | 1 - .../custom-wizard-similar-topics.js.es6} | 1 - .../custom-wizard-step-form.js.es6} | 0 .../components/custom-wizard-step.js.es6} | 29 +- .../custom-wizard-tag-chooser.js.es6} | 0 .../custom-wizard-tag-selector.js.es6} | 0 .../custom-wizard-text-field.js.es6} | 4 +- .../custom-wizard-time-input.js.es6 | 3 + .../components/field-validators.js.es6 | 2 - .../similar-topics-validator.js.es6 | 3 +- .../components/validator.js.es6 | 4 +- .../controllers/custom-wizard-index.js.es6} | 0 .../controllers/custom-wizard-step.js.es6} | 4 +- .../controllers/custom-wizard.js.es6} | 0 .../custom-wizard-admin-route-map.js.es6 | 55 - .../discourse/custom-wizard-route-map.js.es6 | 63 + .../helpers/char-counter.js.es6 | 0 .../helpers/date-node.js.es6 | 0 .../helpers/dir-span.js.es6 | 0 .../helpers/loading-spinner.es6 | 0 .../javascripts/discourse/lib/wizard.js.es6 | 6 + .../mixins/valid-state.js.es6 | 0 .../{wizard => discourse}/models/field.js.es6 | 10 +- .../{wizard => discourse}/models/site.js.es6 | 0 .../{wizard => discourse}/models/step.js.es6 | 10 +- .../models/wizard.js.es6 | 2 +- .../routes/custom-wizard-index.js.es6} | 6 +- .../routes/custom-wizard-step.js.es6} | 9 +- .../routes/custom-wizard.js.es6} | 51 +- .../custom-wizard-composer-editor.hbs} | 0 .../custom-wizard-composer-hyperlink.hbs} | 6 +- .../components/custom-wizard-date-input.hbs} | 0 .../custom-wizard-date-time-input.hbs} | 6 +- .../components/custom-wizard-editor.hbs} | 2 +- .../custom-wizard-field-category.hbs} | 2 +- .../custom-wizard-field-checkbox.hbs} | 0 .../custom-wizard-field-composer-preview.hbs} | 0 .../custom-wizard-field-composer.hbs} | 4 +- .../custom-wizard-field-date-time.hbs} | 2 +- .../components/custom-wizard-field-date.hbs} | 2 +- .../custom-wizard-field-dropdown.hbs} | 0 .../components/custom-wizard-field-group.hbs} | 2 +- .../custom-wizard-field-number.hbs} | 0 .../components/custom-wizard-field-tag.hbs} | 2 +- .../components/custom-wizard-field-text.hbs} | 0 .../custom-wizard-field-textarea.hbs} | 0 .../components/custom-wizard-field-time.hbs} | 2 +- .../custom-wizard-field-upload.hbs} | 4 +- .../components/custom-wizard-field-url.hbs} | 0 .../custom-wizard-field-user-selector.hbs} | 0 .../components/custom-wizard-field.hbs} | 0 .../components/custom-wizard-no-access.hbs} | 2 +- .../custom-wizard-similar-topic.hbs} | 0 .../custom-wizard-similar-topics.hbs} | 4 +- .../components/custom-wizard-step.hbs} | 12 +- .../components/custom-wizard-time-input.hbs} | 0 .../templates/components/field-validators.hbs | 0 .../components/similar-topics-validator.hbs | 6 +- .../templates/components/validator.hbs | 5 + .../templates/custom-wizard-index.hbs | 3 + .../templates/custom-wizard-step.hbs} | 13 +- .../templates/custom-wizard.hbs} | 0 assets/javascripts/ember_jquery.js | 5 - assets/javascripts/legacy/bootbox.js | 717 - assets/javascripts/legacy/bootstrap-modal.js | 360 - assets/javascripts/legacy/caret_position.js | 164 - assets/javascripts/legacy/discourse-loader.js | 435 - assets/javascripts/legacy/discourse-shims.js | 69 - .../javascripts/legacy/ember_include.js.erb | 9 - assets/javascripts/legacy/env.js | 5 - .../javascripts/legacy/handlebars.runtime.js | 1802 --- assets/javascripts/legacy/itsatrap.js | 1161 -- assets/javascripts/legacy/jquery.js | 10874 ---------------- assets/javascripts/legacy/popper.js | 1950 --- assets/javascripts/legacy/raw-templates.js | 42 - assets/javascripts/legacy/resolver.js | 337 - .../legacy/set-prototype-polyfill.js | 10 - assets/javascripts/legacy/template_include.js | 4 - assets/javascripts/legacy/tippy.umd.js | 2498 ---- assets/javascripts/legacy/uppy.js | 7618 ----------- assets/javascripts/legacy/virtual-dom-amd.js | 6 - assets/javascripts/legacy/virtual-dom.js | 1670 --- assets/javascripts/legacy/xss.min.js | 3 - assets/javascripts/wizard-custom-guest.js | 6 - assets/javascripts/wizard-custom-start.js | 6 - assets/javascripts/wizard-custom.js | 94 - assets/javascripts/wizard-plugin.js.erb | 21 - .../javascripts/wizard-raw-templates.js.erb | 38 - assets/javascripts/wizard-vendor.js | 14 - assets/javascripts/wizard/application.js.es6 | 19 - .../components/wizard-field-checkbox.js.es6 | 5 - .../components/wizard-field-group.js.es6 | 5 - .../components/wizard-field-number.js.es6 | 5 - .../wizard/components/wizard-field-tag.js.es6 | 5 - .../wizard/components/wizard-field-url.js.es6 | 5 - .../wizard-field-user-selector.js.es6 | 5 - .../components/wizard-time-input.js.es6 | 5 - .../wizard/helpers/wizard-i18n.js.es6 | 6 - assets/javascripts/wizard/lib/ajax.js.es6 | 33 - .../lib/initialize/create-contexts.js.es6 | 12 - .../lib/initialize/inject-objects.js.es6 | 58 - .../lib/initialize/patch-components.js.es6 | 164 - .../lib/initialize/register-files.js.es6 | 30 - .../wizard/lib/initialize/wizard.js.es6 | 57 - .../javascripts/wizard/lib/load-script.js.es6 | 109 - .../javascripts/wizard/lib/text-lite.js.es6 | 34 - .../javascripts/wizard/lib/user-search.js.es6 | 161 - .../javascripts/wizard/lib/wizard-i18n.js.es6 | 50 - assets/javascripts/wizard/router.js.es6 | 17 - .../wizard/routes/application.js.es6 | 3 - assets/javascripts/wizard/routes/index.js.es6 | 9 - assets/javascripts/wizard/routes/steps.js.es6 | 7 - .../wizard/templates/application.hbs | 1 - .../templates/components/plugin-outlet.hbs | 0 .../wizard/templates/components/validator.hbs | 5 - assets/javascripts/wizard/templates/index.hbs | 1 - .../wizard/templates/wizard-index.hbs | 3 - .../javascripts/wizard/tests/bootstrap.js.es6 | 26 - .../wizard/tests/helpers/acceptance.js.es6 | 53 - .../wizard/tests/helpers/start-app.js.es6 | 25 - .../wizard/tests/helpers/step.js.es6 | 17 - .../wizard/tests/helpers/test.js.es6 | 5 - .../wizard/tests/helpers/wizard.js.es6 | 52 - .../javascripts/wizard/tests/pretender.js.es6 | 35 - .../custom/autocomplete.scss | 0 .../{wizard => common}/custom/badges.scss | 0 .../{wizard => common}/custom/base.scss | 0 assets/stylesheets/common/custom/buttons.scss | 88 + .../{wizard => common}/custom/composer.scss | 0 .../{wizard => common}/custom/events.scss | 0 .../{wizard => common}/custom/field.scss | 1 + .../{wizard => common}/custom/locations.scss | 0 .../custom/mentionables.scss | 0 .../{wizard => common}/custom/mobile.scss | 0 .../{wizard => common}/custom/step.scss | 0 .../{wizard => common}/custom/validators.scss | 0 assets/stylesheets/common/custom/wizard.scss | 167 + assets/stylesheets/common/wizard-custom.scss | 13 + assets/stylesheets/wizard/custom/wizard.scss | 76 - assets/stylesheets/wizard/wizard_custom.scss | 28 - plugin.rb | 52 +- .../javascripts}/acceptance/field-test.js.es6 | 0 .../javascripts}/acceptance/step-test.js.es6 | 0 .../acceptance/wizard-test.js.es6 | 0 .../javascripts}/fixtures/categories.js.es6 | 0 .../javascripts}/fixtures/groups.js.es6 | 0 .../fixtures/site-settings.js.es6 | 0 .../javascripts}/fixtures/tags.js.es6 | 0 .../javascripts}/fixtures/update.js.es6 | 0 .../javascripts}/fixtures/user.js.es6 | 0 .../javascripts}/fixtures/users.js.es6 | 0 .../javascripts}/fixtures/wizard.js.es6 | 0 177 files changed, 490 insertions(+), 31330 deletions(-) rename assets/javascripts/{wizard => discourse}/components/custom-user-selector.js.es6 (96%) rename assets/javascripts/{wizard/components/wizard-category-selector.js.es6 => discourse/components/custom-wizard-category-selector.js.es6} (100%) rename assets/javascripts/{wizard/components/wizard-composer-editor.js.es6 => discourse/components/custom-wizard-composer-editor.js.es6} (97%) rename assets/javascripts/{wizard/components/wizard-composer-hyperlink.js.es6 => discourse/components/custom-wizard-composer-hyperlink.js.es6} (78%) rename assets/javascripts/{wizard/components/wizard-date-input.js.es6 => discourse/components/custom-wizard-date-input.js.es6} (84%) rename assets/javascripts/{wizard/components/wizard-date-time-input.js.es6 => discourse/components/custom-wizard-date-time-input.js.es6} (86%) rename assets/javascripts/{wizard/components/wizard-field-category.js.es6 => discourse/components/custom-wizard-field-category.js.es6} (93%) create mode 100644 assets/javascripts/discourse/components/custom-wizard-field-checkbox.js.es6 rename assets/javascripts/{wizard/components/wizard-field-composer-preview.js.es6 => discourse/components/custom-wizard-field-composer-preview.js.es6} (93%) rename assets/javascripts/{wizard/components/wizard-field-composer.js.es6 => discourse/components/custom-wizard-field-composer.js.es6} (93%) rename assets/javascripts/{wizard/components/wizard-field-date-time.js.es6 => discourse/components/custom-wizard-field-date-time.js.es6} (83%) rename assets/javascripts/{wizard/components/wizard-field-date.js.es6 => discourse/components/custom-wizard-field-date.js.es6} (84%) rename assets/javascripts/{wizard/components/wizard-field-dropdown.js.es6 => discourse/components/custom-wizard-field-dropdown.js.es6} (76%) create mode 100644 assets/javascripts/discourse/components/custom-wizard-field-group.js.es6 create mode 100644 assets/javascripts/discourse/components/custom-wizard-field-number.js.es6 create mode 100644 assets/javascripts/discourse/components/custom-wizard-field-tag.js.es6 rename assets/javascripts/{wizard/components/wizard-field-text.js.es6 => discourse/components/custom-wizard-field-text.js.es6} (66%) rename assets/javascripts/{wizard/components/wizard-field-textarea.js.es6 => discourse/components/custom-wizard-field-textarea.js.es6} (65%) rename assets/javascripts/{wizard/components/wizard-field-time.js.es6 => discourse/components/custom-wizard-field-time.js.es6} (87%) rename assets/javascripts/{wizard/components/wizard-field-upload.js.es6 => discourse/components/custom-wizard-field-upload.js.es6} (92%) create mode 100644 assets/javascripts/discourse/components/custom-wizard-field-url.js.es6 create mode 100644 assets/javascripts/discourse/components/custom-wizard-field-user-selector.js.es6 rename assets/javascripts/{wizard/components/wizard-field.js.es6 => discourse/components/custom-wizard-field.js.es6} (70%) rename assets/javascripts/{wizard/components/wizard-group-selector.js.es6 => discourse/components/custom-wizard-group-selector.js.es6} (89%) rename assets/javascripts/{wizard/components/wizard-no-access.js.es6 => discourse/components/custom-wizard-no-access.js.es6} (89%) rename assets/javascripts/{wizard/components/wizard-similar-topics.js.es6 => discourse/components/custom-wizard-similar-topics.js.es6} (92%) rename assets/javascripts/{wizard/components/wizard-step-form.js.es6 => discourse/components/custom-wizard-step-form.js.es6} (100%) rename assets/javascripts/{wizard/components/wizard-step.js.es6 => discourse/components/custom-wizard-step.js.es6} (90%) rename assets/javascripts/{wizard/components/wizard-tag-chooser.js.es6 => discourse/components/custom-wizard-tag-chooser.js.es6} (100%) rename assets/javascripts/{wizard/components/wizard-tag-selector.js.es6 => discourse/components/custom-wizard-tag-selector.js.es6} (100%) rename assets/javascripts/{wizard/components/wizard-text-field.js.es6 => discourse/components/custom-wizard-text-field.js.es6} (89%) create mode 100644 assets/javascripts/discourse/components/custom-wizard-time-input.js.es6 rename assets/javascripts/{wizard => discourse}/components/field-validators.js.es6 (73%) rename assets/javascripts/{wizard => discourse}/components/similar-topics-validator.js.es6 (96%) rename assets/javascripts/{wizard => discourse}/components/validator.js.es6 (88%) rename assets/javascripts/{wizard/controllers/wizard-index.js.es6 => discourse/controllers/custom-wizard-index.js.es6} (100%) rename assets/javascripts/{wizard/controllers/step.js.es6 => discourse/controllers/custom-wizard-step.js.es6} (85%) rename assets/javascripts/{wizard/controllers/wizard.js.es6 => discourse/controllers/custom-wizard.js.es6} (100%) delete mode 100644 assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 create mode 100644 assets/javascripts/discourse/custom-wizard-route-map.js.es6 rename assets/javascripts/{wizard => discourse}/helpers/char-counter.js.es6 (100%) rename assets/javascripts/{wizard => discourse}/helpers/date-node.js.es6 (100%) rename assets/javascripts/{wizard => discourse}/helpers/dir-span.js.es6 (100%) rename assets/javascripts/{wizard => discourse}/helpers/loading-spinner.es6 (100%) rename assets/javascripts/{wizard => discourse}/mixins/valid-state.js.es6 (100%) rename assets/javascripts/{wizard => discourse}/models/field.js.es6 (82%) rename assets/javascripts/{wizard => discourse}/models/site.js.es6 (100%) rename assets/javascripts/{wizard => discourse}/models/step.js.es6 (90%) rename assets/javascripts/{wizard => discourse}/models/wizard.js.es6 (98%) rename assets/javascripts/{wizard/routes/wizard-index.js.es6 => discourse/routes/custom-wizard-index.js.es6} (88%) rename assets/javascripts/{wizard/routes/step.js.es6 => discourse/routes/custom-wizard-step.js.es6} (83%) rename assets/javascripts/{wizard/routes/wizard.js.es6 => discourse/routes/custom-wizard.js.es6} (58%) rename assets/javascripts/{wizard/templates/components/wizard-composer-editor.hbs => discourse/templates/components/custom-wizard-composer-editor.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-composer-hyperlink.hbs => discourse/templates/components/custom-wizard-composer-hyperlink.hbs} (71%) rename assets/javascripts/{wizard/templates/components/wizard-date-input.hbs => discourse/templates/components/custom-wizard-date-input.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-date-time-input.hbs => discourse/templates/components/custom-wizard-date-time-input.hbs} (86%) rename assets/javascripts/{wizard/templates/components/wizard-editor.hbs => discourse/templates/components/custom-wizard-editor.hbs} (94%) rename assets/javascripts/{wizard/templates/components/wizard-field-category.hbs => discourse/templates/components/custom-wizard-field-category.hbs} (80%) rename assets/javascripts/{wizard/templates/components/wizard-field-checkbox.hbs => discourse/templates/components/custom-wizard-field-checkbox.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field-composer-preview.hbs => discourse/templates/components/custom-wizard-field-composer-preview.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field-composer.hbs => discourse/templates/components/custom-wizard-field-composer.hbs} (82%) rename assets/javascripts/{wizard/templates/components/wizard-field-date-time.hbs => discourse/templates/components/custom-wizard-field-date-time.hbs} (70%) rename assets/javascripts/{wizard/templates/components/wizard-field-date.hbs => discourse/templates/components/custom-wizard-field-date.hbs} (77%) rename assets/javascripts/{wizard/templates/components/wizard-field-dropdown.hbs => discourse/templates/components/custom-wizard-field-dropdown.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field-group.hbs => discourse/templates/components/custom-wizard-field-group.hbs} (86%) rename assets/javascripts/{wizard/templates/components/wizard-field-number.hbs => discourse/templates/components/custom-wizard-field-number.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field-tag.hbs => discourse/templates/components/custom-wizard-field-tag.hbs} (80%) rename assets/javascripts/{wizard/templates/components/wizard-field-text.hbs => discourse/templates/components/custom-wizard-field-text.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field-textarea.hbs => discourse/templates/components/custom-wizard-field-textarea.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field-time.hbs => discourse/templates/components/custom-wizard-field-time.hbs} (72%) rename assets/javascripts/{wizard/templates/components/wizard-field-upload.hbs => discourse/templates/components/custom-wizard-field-upload.hbs} (86%) rename assets/javascripts/{wizard/templates/components/wizard-field-url.hbs => discourse/templates/components/custom-wizard-field-url.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field-user-selector.hbs => discourse/templates/components/custom-wizard-field-user-selector.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-field.hbs => discourse/templates/components/custom-wizard-field.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-no-access.hbs => discourse/templates/components/custom-wizard-no-access.hbs} (74%) rename assets/javascripts/{wizard/templates/components/wizard-similar-topic.hbs => discourse/templates/components/custom-wizard-similar-topic.hbs} (100%) rename assets/javascripts/{wizard/templates/components/wizard-similar-topics.hbs => discourse/templates/components/custom-wizard-similar-topics.hbs} (58%) rename assets/javascripts/{wizard/templates/components/wizard-step.hbs => discourse/templates/components/custom-wizard-step.hbs} (79%) rename assets/javascripts/{wizard/templates/components/wizard-time-input.hbs => discourse/templates/components/custom-wizard-time-input.hbs} (100%) rename assets/javascripts/{wizard => discourse}/templates/components/field-validators.hbs (100%) rename assets/javascripts/{wizard => discourse}/templates/components/similar-topics-validator.hbs (54%) create mode 100644 assets/javascripts/discourse/templates/components/validator.hbs create mode 100644 assets/javascripts/discourse/templates/custom-wizard-index.hbs rename assets/javascripts/{wizard/templates/step.hbs => discourse/templates/custom-wizard-step.hbs} (56%) rename assets/javascripts/{wizard/templates/wizard.hbs => discourse/templates/custom-wizard.hbs} (100%) delete mode 100644 assets/javascripts/ember_jquery.js delete mode 100644 assets/javascripts/legacy/bootbox.js delete mode 100644 assets/javascripts/legacy/bootstrap-modal.js delete mode 100644 assets/javascripts/legacy/caret_position.js delete mode 100644 assets/javascripts/legacy/discourse-loader.js delete mode 100644 assets/javascripts/legacy/discourse-shims.js delete mode 100644 assets/javascripts/legacy/ember_include.js.erb delete mode 100644 assets/javascripts/legacy/env.js delete mode 100644 assets/javascripts/legacy/handlebars.runtime.js delete mode 100644 assets/javascripts/legacy/itsatrap.js delete mode 100644 assets/javascripts/legacy/jquery.js delete mode 100644 assets/javascripts/legacy/popper.js delete mode 100644 assets/javascripts/legacy/raw-templates.js delete mode 100644 assets/javascripts/legacy/resolver.js delete mode 100644 assets/javascripts/legacy/set-prototype-polyfill.js delete mode 100644 assets/javascripts/legacy/template_include.js delete mode 100644 assets/javascripts/legacy/tippy.umd.js delete mode 100644 assets/javascripts/legacy/uppy.js delete mode 100644 assets/javascripts/legacy/virtual-dom-amd.js delete mode 100644 assets/javascripts/legacy/virtual-dom.js delete mode 100644 assets/javascripts/legacy/xss.min.js delete mode 100644 assets/javascripts/wizard-custom-guest.js delete mode 100644 assets/javascripts/wizard-custom-start.js delete mode 100644 assets/javascripts/wizard-custom.js delete mode 100644 assets/javascripts/wizard-plugin.js.erb delete mode 100644 assets/javascripts/wizard-raw-templates.js.erb delete mode 100644 assets/javascripts/wizard-vendor.js delete mode 100644 assets/javascripts/wizard/application.js.es6 delete mode 100644 assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 delete mode 100644 assets/javascripts/wizard/components/wizard-field-group.js.es6 delete mode 100644 assets/javascripts/wizard/components/wizard-field-number.js.es6 delete mode 100644 assets/javascripts/wizard/components/wizard-field-tag.js.es6 delete mode 100644 assets/javascripts/wizard/components/wizard-field-url.js.es6 delete mode 100644 assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 delete mode 100644 assets/javascripts/wizard/components/wizard-time-input.js.es6 delete mode 100644 assets/javascripts/wizard/helpers/wizard-i18n.js.es6 delete mode 100644 assets/javascripts/wizard/lib/ajax.js.es6 delete mode 100644 assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 delete mode 100644 assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 delete mode 100644 assets/javascripts/wizard/lib/initialize/patch-components.js.es6 delete mode 100644 assets/javascripts/wizard/lib/initialize/register-files.js.es6 delete mode 100644 assets/javascripts/wizard/lib/initialize/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/lib/load-script.js.es6 delete mode 100644 assets/javascripts/wizard/lib/text-lite.js.es6 delete mode 100644 assets/javascripts/wizard/lib/user-search.js.es6 delete mode 100644 assets/javascripts/wizard/lib/wizard-i18n.js.es6 delete mode 100644 assets/javascripts/wizard/router.js.es6 delete mode 100644 assets/javascripts/wizard/routes/application.js.es6 delete mode 100644 assets/javascripts/wizard/routes/index.js.es6 delete mode 100644 assets/javascripts/wizard/routes/steps.js.es6 delete mode 100644 assets/javascripts/wizard/templates/application.hbs delete mode 100644 assets/javascripts/wizard/templates/components/plugin-outlet.hbs delete mode 100644 assets/javascripts/wizard/templates/components/validator.hbs delete mode 100644 assets/javascripts/wizard/templates/index.hbs delete mode 100644 assets/javascripts/wizard/templates/wizard-index.hbs delete mode 100644 assets/javascripts/wizard/tests/bootstrap.js.es6 delete mode 100644 assets/javascripts/wizard/tests/helpers/acceptance.js.es6 delete mode 100644 assets/javascripts/wizard/tests/helpers/start-app.js.es6 delete mode 100644 assets/javascripts/wizard/tests/helpers/step.js.es6 delete mode 100644 assets/javascripts/wizard/tests/helpers/test.js.es6 delete mode 100644 assets/javascripts/wizard/tests/helpers/wizard.js.es6 delete mode 100644 assets/javascripts/wizard/tests/pretender.js.es6 rename assets/stylesheets/{wizard => common}/custom/autocomplete.scss (100%) rename assets/stylesheets/{wizard => common}/custom/badges.scss (100%) rename assets/stylesheets/{wizard => common}/custom/base.scss (100%) create mode 100644 assets/stylesheets/common/custom/buttons.scss rename assets/stylesheets/{wizard => common}/custom/composer.scss (100%) rename assets/stylesheets/{wizard => common}/custom/events.scss (100%) rename assets/stylesheets/{wizard => common}/custom/field.scss (99%) rename assets/stylesheets/{wizard => common}/custom/locations.scss (100%) rename assets/stylesheets/{wizard => common}/custom/mentionables.scss (100%) rename assets/stylesheets/{wizard => common}/custom/mobile.scss (100%) rename assets/stylesheets/{wizard => common}/custom/step.scss (100%) rename assets/stylesheets/{wizard => common}/custom/validators.scss (100%) create mode 100644 assets/stylesheets/common/custom/wizard.scss create mode 100644 assets/stylesheets/common/wizard-custom.scss delete mode 100644 assets/stylesheets/wizard/custom/wizard.scss delete mode 100644 assets/stylesheets/wizard/wizard_custom.scss rename {assets/javascripts/wizard/tests => test/javascripts}/acceptance/field-test.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/acceptance/step-test.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/acceptance/wizard-test.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/categories.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/groups.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/site-settings.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/tags.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/update.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/user.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/users.js.es6 (100%) rename {assets/javascripts/wizard/tests => test/javascripts}/fixtures/wizard.js.es6 (100%) diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index 4dbb5b69..d6375f64 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -1,40 +1,13 @@ # frozen_string_literal: true -class CustomWizard::WizardController < ::ActionController::Base - helper ApplicationHelper - - include CurrentUser - include CanonicalURL::ControllerExtensions - include GlobalPath - - prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views')) - layout :set_wizard_layout - - before_action :preload_wizard_json +class CustomWizard::WizardController < ::ApplicationController before_action :ensure_plugin_enabled before_action :ensure_logged_in, only: [:skip] - around_action :with_resolved_locale - - helper_method :wizard_page_title - helper_method :wizard_theme_id - helper_method :wizard_theme_lookup - helper_method :wizard_theme_translations_lookup - - def set_wizard_layout - action_name === 'qunit' ? 'qunit' : 'wizard' - end def index - respond_to do |format| - format.json do - if wizard.present? - render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200 - else - render json: { error: I18n.t('wizard.none') } - end - end - format.html do - render "default/empty" - end + if wizard.present? + render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200 + else + render json: { error: I18n.t('wizard.none') } end end diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/discourse/components/custom-user-selector.js.es6 similarity index 96% rename from assets/javascripts/wizard/components/custom-user-selector.js.es6 rename to assets/javascripts/discourse/components/custom-user-selector.js.es6 index 56eb8f57..3bb1fb3d 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/discourse/components/custom-user-selector.js.es6 @@ -3,8 +3,8 @@ import { observes, } from "discourse-common/utils/decorators"; import { renderAvatar } from "discourse/helpers/user-avatar"; -import userSearch from "../lib/user-search"; -import WizardI18n from "../lib/wizard-i18n"; +import userSearch from "discourse/lib/user-search"; +import I18n from "I18n"; import Handlebars from "handlebars"; import { isEmpty } from "@ember/utils"; import TextField from "@ember/component/text-field"; @@ -41,7 +41,7 @@ export default TextField.extend({ @computed("placeholderKey") placeholder(placeholderKey) { - return placeholderKey ? WizardI18n(placeholderKey) : ""; + return placeholderKey ? I18n.t(placeholderKey) : ""; }, @observes("usernames") diff --git a/assets/javascripts/wizard/components/wizard-category-selector.js.es6 b/assets/javascripts/discourse/components/custom-wizard-category-selector.js.es6 similarity index 100% rename from assets/javascripts/wizard/components/wizard-category-selector.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-category-selector.js.es6 diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/discourse/components/custom-wizard-composer-editor.js.es6 similarity index 97% rename from assets/javascripts/wizard/components/wizard-composer-editor.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-composer-editor.js.es6 index de70986b..e41a03d8 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-composer-editor.js.es6 @@ -3,17 +3,16 @@ import { default as discourseComputed, on, } from "discourse-common/utils/decorators"; -import { findRawTemplate } from "discourse/plugins/discourse-custom-wizard/legacy/raw-templates"; +import { findRawTemplate } from "discourse-common/lib/raw-templates"; import { scheduleOnce } from "@ember/runloop"; import { caretPosition, inCodeBlock } from "discourse/lib/utilities"; import highlightSyntax from "discourse/lib/highlight-syntax"; import { alias } from "@ember/object/computed"; -import Site from "../models/site"; +import Site from "discourse/models/site"; import { uploadIcon } from "discourse/lib/uploads"; import { dasherize } from "@ember/string"; export default ComposerEditor.extend({ - layoutName: "wizard/templates/components/wizard-composer-editor", classNameBindings: ["fieldClass"], allowUpload: true, showLink: false, diff --git a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 b/assets/javascripts/discourse/components/custom-wizard-composer-hyperlink.js.es6 similarity index 78% rename from assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-composer-hyperlink.js.es6 index 0eeeb176..a56b7aff 100644 --- a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-composer-hyperlink.js.es6 @@ -2,7 +2,6 @@ import Component from "@ember/component"; export default Component.extend({ classNames: ["wizard-composer-hyperlink"], - layoutName: "wizard/templates/components/wizard-composer-hyperlink", actions: { addLink() { diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/discourse/components/custom-wizard-date-input.js.es6 similarity index 84% rename from assets/javascripts/wizard/components/wizard-date-input.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-date-input.js.es6 index da2711c7..9c8e4bff 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-date-input.js.es6 @@ -3,7 +3,6 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DateInput.extend({ useNativePicker: false, - layoutName: "wizard/templates/components/wizard-date-input", @discourseComputed() placeholder() { diff --git a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 b/assets/javascripts/discourse/components/custom-wizard-date-time-input.js.es6 similarity index 86% rename from assets/javascripts/wizard/components/wizard-date-time-input.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-date-time-input.js.es6 index 84a2b03e..44b675b0 100644 --- a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-date-time-input.js.es6 @@ -2,8 +2,6 @@ import DateTimeInput from "discourse/components/date-time-input"; import discourseComputed from "discourse-common/utils/decorators"; export default DateTimeInput.extend({ - layoutName: "wizard/templates/components/wizard-date-time-input", - @discourseComputed("timeFirst", "tabindex") timeTabindex(timeFirst, tabindex) { return timeFirst ? tabindex : tabindex + 1; diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-category.js.es6 similarity index 93% rename from assets/javascripts/wizard/components/wizard-field-category.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-category.js.es6 index 441f83d3..65e4a1c7 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-category.js.es6 @@ -3,8 +3,6 @@ import Category from "discourse/models/category"; import Component from "@ember/component"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-category", - didInsertElement() { const property = this.field.property || "id"; const value = this.field.value; diff --git a/assets/javascripts/discourse/components/custom-wizard-field-checkbox.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-checkbox.js.es6 new file mode 100644 index 00000000..87d5ddb0 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-field-checkbox.js.es6 @@ -0,0 +1,3 @@ +import Component from "@ember/component"; + +export default Component.extend({}); diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-composer-preview.js.es6 similarity index 93% rename from assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-composer-preview.js.es6 index a2056a86..b49233f2 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-composer-preview.js.es6 @@ -7,8 +7,6 @@ import { ajax } from "discourse/lib/ajax"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-composer-preview", - @on("init") updatePreview() { if (this.isDestroyed) { diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-composer.js.es6 similarity index 93% rename from assets/javascripts/wizard/components/wizard-field-composer.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-composer.js.es6 index 255982ea..1a25344c 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-composer.js.es6 @@ -6,8 +6,6 @@ import EmberObject from "@ember/object"; import Component from "@ember/component"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-composer", - showPreview: false, classNameBindings: [ ":wizard-field-composer", diff --git a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-date-time.js.es6 similarity index 83% rename from assets/javascripts/wizard/components/wizard-field-date-time.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-date-time.js.es6 index eee98892..2d918636 100644 --- a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-date-time.js.es6 @@ -2,8 +2,6 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-date-time", - @observes("dateTime") setValue() { this.set("field.value", this.dateTime.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-date.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-date.js.es6 similarity index 84% rename from assets/javascripts/wizard/components/wizard-field-date.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-date.js.es6 index df35638c..d5d0a830 100644 --- a/assets/javascripts/wizard/components/wizard-field-date.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-date.js.es6 @@ -2,8 +2,6 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-date", - @observes("date") setValue() { this.set("field.value", this.date.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-dropdown.js.es6 similarity index 76% rename from assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-dropdown.js.es6 index e6b08102..659b8f29 100644 --- a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-dropdown.js.es6 @@ -1,8 +1,6 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-dropdown", - keyPress(e) { e.stopPropagation(); }, diff --git a/assets/javascripts/discourse/components/custom-wizard-field-group.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-group.js.es6 new file mode 100644 index 00000000..87d5ddb0 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-field-group.js.es6 @@ -0,0 +1,3 @@ +import Component from "@ember/component"; + +export default Component.extend({}); diff --git a/assets/javascripts/discourse/components/custom-wizard-field-number.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-number.js.es6 new file mode 100644 index 00000000..87d5ddb0 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-field-number.js.es6 @@ -0,0 +1,3 @@ +import Component from "@ember/component"; + +export default Component.extend({}); diff --git a/assets/javascripts/discourse/components/custom-wizard-field-tag.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-tag.js.es6 new file mode 100644 index 00000000..87d5ddb0 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-field-tag.js.es6 @@ -0,0 +1,3 @@ +import Component from "@ember/component"; + +export default Component.extend({}); diff --git a/assets/javascripts/wizard/components/wizard-field-text.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-text.js.es6 similarity index 66% rename from assets/javascripts/wizard/components/wizard-field-text.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-text.js.es6 index d9e7cca8..3e49cb35 100644 --- a/assets/javascripts/wizard/components/wizard-field-text.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-text.js.es6 @@ -1,8 +1,6 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-text", - keyPress(e) { e.stopPropagation(); }, diff --git a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-textarea.js.es6 similarity index 65% rename from assets/javascripts/wizard/components/wizard-field-textarea.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-textarea.js.es6 index e59a1707..3e49cb35 100644 --- a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-textarea.js.es6 @@ -1,8 +1,6 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-textarea", - keyPress(e) { e.stopPropagation(); }, diff --git a/assets/javascripts/wizard/components/wizard-field-time.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-time.js.es6 similarity index 87% rename from assets/javascripts/wizard/components/wizard-field-time.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-time.js.es6 index a2f2a10d..82f9c68b 100644 --- a/assets/javascripts/wizard/components/wizard-field-time.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-time.js.es6 @@ -2,8 +2,6 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field-time", - @observes("time") setValue() { this.set("field.value", this.time.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-upload.js.es6 similarity index 92% rename from assets/javascripts/wizard/components/wizard-field-upload.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field-upload.js.es6 index 4774e942..eb5d318b 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field-upload.js.es6 @@ -3,7 +3,6 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend(UppyUploadMixin, { - layoutName: "wizard/templates/components/wizard-field-upload", classNames: ["wizard-field-upload"], classNameBindings: ["isImage"], uploading: false, diff --git a/assets/javascripts/discourse/components/custom-wizard-field-url.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-url.js.es6 new file mode 100644 index 00000000..87d5ddb0 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-field-url.js.es6 @@ -0,0 +1,3 @@ +import Component from "@ember/component"; + +export default Component.extend({}); diff --git a/assets/javascripts/discourse/components/custom-wizard-field-user-selector.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-user-selector.js.es6 new file mode 100644 index 00000000..87d5ddb0 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-field-user-selector.js.es6 @@ -0,0 +1,3 @@ +import Component from "@ember/component"; + +export default Component.extend({}); diff --git a/assets/javascripts/wizard/components/wizard-field.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field.js.es6 similarity index 70% rename from assets/javascripts/wizard/components/wizard-field.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-field.js.es6 index 493d7676..d706ecd8 100644 --- a/assets/javascripts/wizard/components/wizard-field.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-field.js.es6 @@ -1,10 +1,9 @@ import Component from "@ember/component"; import { dasherize } from "@ember/string"; import discourseComputed from "discourse-common/utils/decorators"; -import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; +import { cookAsync } from "discourse/lib/text"; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-field", classNameBindings: [ ":wizard-field", "typeClasses", @@ -12,6 +11,14 @@ export default Component.extend({ "field.id", ], + didReceiveAttrs() { + this._super(...arguments); + + cookAsync(this.field.translatedDescription).then((cookedDescription) => { + this.set("cookedDescription", cookedDescription); + }); + }, + @discourseComputed("field.type", "field.id") typeClasses: (type, id) => `${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`, @@ -24,12 +31,7 @@ export default Component.extend({ if (["text_only"].includes(type)) { return false; } - return dasherize(type === "component" ? id : `wizard-field-${type}`); - }, - - @discourseComputed("field.translatedDescription") - cookedDescription(description) { - return cook(description); + return dasherize(type === "component" ? id : `custom-wizard-field-${type}`); }, @discourseComputed("field.type") diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/discourse/components/custom-wizard-group-selector.js.es6 similarity index 89% rename from assets/javascripts/wizard/components/wizard-group-selector.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-group-selector.js.es6 index 4ff56ec9..cb613107 100644 --- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-group-selector.js.es6 @@ -3,7 +3,6 @@ import { computed } from "@ember/object"; import { makeArray } from "discourse-common/lib/helpers"; export default ComboBox.extend({ - layoutName: "wizard/templates/components/wizard-group-selector", content: computed("groups.[]", "field.content.[]", function () { const whitelist = makeArray(this.field.content); return this.groups diff --git a/assets/javascripts/wizard/components/wizard-no-access.js.es6 b/assets/javascripts/discourse/components/custom-wizard-no-access.js.es6 similarity index 89% rename from assets/javascripts/wizard/components/wizard-no-access.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-no-access.js.es6 index 492a41dc..9b3170fb 100644 --- a/assets/javascripts/wizard/components/wizard-no-access.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-no-access.js.es6 @@ -5,7 +5,6 @@ import { dasherize } from "@ember/string"; export default Component.extend({ classNameBindings: [":wizard-no-access", "reasonClass"], - layoutName: "wizard/templates/components/wizard-no-access", @discourseComputed("reason") reasonClass(reason) { diff --git a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 b/assets/javascripts/discourse/components/custom-wizard-similar-topics.js.es6 similarity index 92% rename from assets/javascripts/wizard/components/wizard-similar-topics.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-similar-topics.js.es6 index 6a56873e..687cfa86 100644 --- a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-similar-topics.js.es6 @@ -4,7 +4,6 @@ import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["wizard-similar-topics"], - layoutName: "wizard/templates/components/wizard-similar-topics", showTopics: true, didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-step-form.js.es6 b/assets/javascripts/discourse/components/custom-wizard-step-form.js.es6 similarity index 100% rename from assets/javascripts/wizard/components/wizard-step-form.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-step-form.js.es6 diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/discourse/components/custom-wizard-step.js.es6 similarity index 90% rename from assets/javascripts/wizard/components/wizard-step.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-step.js.es6 index 44b388bb..54c1b9eb 100644 --- a/assets/javascripts/wizard/components/wizard-step.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-step.js.es6 @@ -4,15 +4,15 @@ import I18n from "I18n"; import getUrl from "discourse-common/lib/get-url"; import { htmlSafe } from "@ember/template"; import { schedule } from "@ember/runloop"; -import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; -import { updateCachedWizard } from "discourse/plugins/discourse-custom-wizard/wizard/models/wizard"; +import { cookAsync } from "discourse/lib/text"; +import CustomWizard, { + updateCachedWizard, +} from "discourse/plugins/discourse-custom-wizard/discourse/models/wizard"; import { alias, not } from "@ember/object/computed"; -import CustomWizard from "../models/wizard"; const alreadyWarned = {}; export default Component.extend({ - layoutName: "wizard/templates/components/wizard-step", classNameBindings: [":wizard-step", "step.id"], saving: null, @@ -21,6 +21,17 @@ export default Component.extend({ this.set("stylingDropdown", {}); }, + didReceiveAttrs() { + this._super(...arguments); + + cookAsync(this.step.translatedTitle).then((cookedTitle) => { + this.set("cookedTitle", cookedTitle); + }); + cookAsync(this.step.translatedDescription).then((cookedDescription) => { + this.set("cookedDescription", cookedDescription); + }); + }, + didInsertElement() { this._super(...arguments); this.autoFocus(); @@ -32,16 +43,6 @@ export default Component.extend({ showNextButton: not("step.final"), showDoneButton: alias("step.final"), - @discourseComputed("step.translatedTitle") - cookedTitle(title) { - return cook(title); - }, - - @discourseComputed("step.translatedDescription") - cookedDescription(description) { - return cook(description); - }, - @discourseComputed( "step.index", "step.displayIndex", diff --git a/assets/javascripts/wizard/components/wizard-tag-chooser.js.es6 b/assets/javascripts/discourse/components/custom-wizard-tag-chooser.js.es6 similarity index 100% rename from assets/javascripts/wizard/components/wizard-tag-chooser.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-tag-chooser.js.es6 diff --git a/assets/javascripts/wizard/components/wizard-tag-selector.js.es6 b/assets/javascripts/discourse/components/custom-wizard-tag-selector.js.es6 similarity index 100% rename from assets/javascripts/wizard/components/wizard-tag-selector.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-tag-selector.js.es6 diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/discourse/components/custom-wizard-text-field.js.es6 similarity index 89% rename from assets/javascripts/wizard/components/wizard-text-field.js.es6 rename to assets/javascripts/discourse/components/custom-wizard-text-field.js.es6 index 5991eefc..e8c22e93 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/discourse/components/custom-wizard-text-field.js.es6 @@ -1,6 +1,6 @@ import computed from "discourse-common/utils/decorators"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; -import WizardI18n from "../lib/wizard-i18n"; +import I18n from "I18n"; import TextField from "@ember/component/text-field"; export default TextField.extend({ @@ -39,6 +39,6 @@ export default TextField.extend({ @computed("placeholderKey") placeholder(placeholderKey) { - return placeholderKey ? WizardI18n(placeholderKey) : ""; + return placeholderKey ? I18n.t(placeholderKey) : ""; }, }); diff --git a/assets/javascripts/discourse/components/custom-wizard-time-input.js.es6 b/assets/javascripts/discourse/components/custom-wizard-time-input.js.es6 new file mode 100644 index 00000000..c09ed110 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-time-input.js.es6 @@ -0,0 +1,3 @@ +import TimeInput from "discourse/components/time-input"; + +export default TimeInput.extend({}); diff --git a/assets/javascripts/wizard/components/field-validators.js.es6 b/assets/javascripts/discourse/components/field-validators.js.es6 similarity index 73% rename from assets/javascripts/wizard/components/field-validators.js.es6 rename to assets/javascripts/discourse/components/field-validators.js.es6 index 7284241c..8b9b39da 100644 --- a/assets/javascripts/wizard/components/field-validators.js.es6 +++ b/assets/javascripts/discourse/components/field-validators.js.es6 @@ -1,8 +1,6 @@ import Component from "@ember/component"; export default Component.extend({ - layoutName: "wizard/templates/components/field-validators", - actions: { perform() { this.appEvents.trigger("custom-wizard:validate"); diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/discourse/components/similar-topics-validator.js.es6 similarity index 96% rename from assets/javascripts/wizard/components/similar-topics-validator.js.es6 rename to assets/javascripts/discourse/components/similar-topics-validator.js.es6 index 4f722123..634af983 100644 --- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 +++ b/assets/javascripts/discourse/components/similar-topics-validator.js.es6 @@ -1,4 +1,4 @@ -import WizardFieldValidator from "../../wizard/components/validator"; +import WizardFieldValidator from "discourse/plugins/discourse-custom-wizard/discourse/components/validator"; import { deepMerge } from "discourse-common/lib/object"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; import { cancel, later } from "@ember/runloop"; @@ -10,7 +10,6 @@ import { dasherize } from "@ember/string"; export default WizardFieldValidator.extend({ classNames: ["similar-topics-validator"], - layoutName: "wizard/templates/components/similar-topics-validator", similarTopics: null, hasInput: notEmpty("field.value"), hasSimilarTopics: notEmpty("similarTopics"), diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/discourse/components/validator.js.es6 similarity index 88% rename from assets/javascripts/wizard/components/validator.js.es6 rename to assets/javascripts/discourse/components/validator.js.es6 index 2e8498ef..3c19cc3d 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/discourse/components/validator.js.es6 @@ -1,12 +1,10 @@ import Component from "@ember/component"; import { equal } from "@ember/object/computed"; -import { ajax } from "discourse/lib/ajax"; -import { getToken } from "../lib/ajax"; +import { ajax, getToken } from "discourse/lib/ajax"; export default Component.extend({ classNames: ["validator"], classNameBindings: ["isValid", "isInvalid"], - layoutName: "wizard/templates/components/validator", validMessageKey: null, invalidMessageKey: null, isValid: null, diff --git a/assets/javascripts/wizard/controllers/wizard-index.js.es6 b/assets/javascripts/discourse/controllers/custom-wizard-index.js.es6 similarity index 100% rename from assets/javascripts/wizard/controllers/wizard-index.js.es6 rename to assets/javascripts/discourse/controllers/custom-wizard-index.js.es6 diff --git a/assets/javascripts/wizard/controllers/step.js.es6 b/assets/javascripts/discourse/controllers/custom-wizard-step.js.es6 similarity index 85% rename from assets/javascripts/wizard/controllers/step.js.es6 rename to assets/javascripts/discourse/controllers/custom-wizard-step.js.es6 index 4b321173..2dca2e70 100644 --- a/assets/javascripts/wizard/controllers/step.js.es6 +++ b/assets/javascripts/discourse/controllers/custom-wizard-step.js.es6 @@ -15,12 +15,12 @@ export default Controller.extend({ const wizardId = this.get("wizard.id"); window.location.href = getUrl(`/w/${wizardId}/steps/${nextStepId}`); } else { - this.transitionToRoute("step", nextStepId); + this.transitionToRoute("customWizardStep", nextStepId); } }, goBack() { - this.transitionToRoute("step", this.get("step.previous")); + this.transitionToRoute("customWizardStep", this.get("step.previous")); }, showMessage(message) { diff --git a/assets/javascripts/wizard/controllers/wizard.js.es6 b/assets/javascripts/discourse/controllers/custom-wizard.js.es6 similarity index 100% rename from assets/javascripts/wizard/controllers/wizard.js.es6 rename to assets/javascripts/discourse/controllers/custom-wizard.js.es6 diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 deleted file mode 100644 index 90ab5359..00000000 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ /dev/null @@ -1,55 +0,0 @@ -export default { - resource: "admin", - map() { - this.route( - "adminWizards", - { path: "/wizards", resetNamespace: true }, - function () { - this.route( - "adminWizardsWizard", - { path: "/wizard/", resetNamespace: true }, - function () { - this.route("adminWizardsWizardShow", { - path: "/:wizardId/", - resetNamespace: true, - }); - } - ); - - this.route("adminWizardsCustomFields", { - path: "/custom-fields", - resetNamespace: true, - }); - - this.route( - "adminWizardsSubmissions", - { path: "/submissions", resetNamespace: true }, - function () { - this.route("adminWizardsSubmissionsShow", { - path: "/:wizardId/", - resetNamespace: true, - }); - } - ); - - this.route( - "adminWizardsApi", - { path: "/api", resetNamespace: true }, - function () { - this.route("adminWizardsApiShow", { - path: "/:name", - resetNamespace: true, - }); - } - ); - - this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true }); - - this.route("adminWizardsManager", { - path: "/manager", - resetNamespace: true, - }); - } - ); - }, -}; diff --git a/assets/javascripts/discourse/custom-wizard-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-route-map.js.es6 new file mode 100644 index 00000000..e5e3a226 --- /dev/null +++ b/assets/javascripts/discourse/custom-wizard-route-map.js.es6 @@ -0,0 +1,63 @@ +export default function () { + this.route( + "customWizard", + { path: "/w/:wizard_id", resetNamespace: true }, + function () { + this.route("customWizardStep", { + path: "/steps/:step_id", + resetNamespace: true, + }); + } + ); + + this.route( + "adminWizards", + { path: "/wizards", resetNamespace: true }, + function () { + this.route( + "adminWizardsWizard", + { path: "/wizard/", resetNamespace: true }, + function () { + this.route("adminWizardsWizardShow", { + path: "/:wizardId/", + resetNamespace: true, + }); + } + ); + + this.route("adminWizardsCustomFields", { + path: "/custom-fields", + resetNamespace: true, + }); + + this.route( + "adminWizardsSubmissions", + { path: "/submissions", resetNamespace: true }, + function () { + this.route("adminWizardsSubmissionsShow", { + path: "/:wizardId/", + resetNamespace: true, + }); + } + ); + + this.route( + "adminWizardsApi", + { path: "/api", resetNamespace: true }, + function () { + this.route("adminWizardsApiShow", { + path: "/:name", + resetNamespace: true, + }); + } + ); + + this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true }); + + this.route("adminWizardsManager", { + path: "/manager", + resetNamespace: true, + }); + } + ); +} diff --git a/assets/javascripts/wizard/helpers/char-counter.js.es6 b/assets/javascripts/discourse/helpers/char-counter.js.es6 similarity index 100% rename from assets/javascripts/wizard/helpers/char-counter.js.es6 rename to assets/javascripts/discourse/helpers/char-counter.js.es6 diff --git a/assets/javascripts/wizard/helpers/date-node.js.es6 b/assets/javascripts/discourse/helpers/date-node.js.es6 similarity index 100% rename from assets/javascripts/wizard/helpers/date-node.js.es6 rename to assets/javascripts/discourse/helpers/date-node.js.es6 diff --git a/assets/javascripts/wizard/helpers/dir-span.js.es6 b/assets/javascripts/discourse/helpers/dir-span.js.es6 similarity index 100% rename from assets/javascripts/wizard/helpers/dir-span.js.es6 rename to assets/javascripts/discourse/helpers/dir-span.js.es6 diff --git a/assets/javascripts/wizard/helpers/loading-spinner.es6 b/assets/javascripts/discourse/helpers/loading-spinner.es6 similarity index 100% rename from assets/javascripts/wizard/helpers/loading-spinner.es6 rename to assets/javascripts/discourse/helpers/loading-spinner.es6 diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6 index 98bdbfdd..2eaccb17 100644 --- a/assets/javascripts/discourse/lib/wizard.js.es6 +++ b/assets/javascripts/discourse/lib/wizard.js.es6 @@ -1,5 +1,6 @@ import EmberObject from "@ember/object"; import wizardSchema from "./wizard-schema"; +import I18n from "I18n"; function selectKitContent(content) { return content.map((i) => ({ id: i, name: i })); @@ -33,6 +34,10 @@ function camelCase(string) { }); } +function translationOrText(i18nKey, text) { + return I18n.findTranslation(i18nKey) ? I18n.t(i18nKey) : text; +} + const userProperties = [ "name", "username", @@ -121,4 +126,5 @@ export { notificationLevels, wizardFieldList, sentenceCase, + translationOrText, }; diff --git a/assets/javascripts/wizard/mixins/valid-state.js.es6 b/assets/javascripts/discourse/mixins/valid-state.js.es6 similarity index 100% rename from assets/javascripts/wizard/mixins/valid-state.js.es6 rename to assets/javascripts/discourse/mixins/valid-state.js.es6 diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/discourse/models/field.js.es6 similarity index 82% rename from assets/javascripts/wizard/models/field.js.es6 rename to assets/javascripts/discourse/models/field.js.es6 index 9d0be1ff..a03c7c9e 100644 --- a/assets/javascripts/wizard/models/field.js.es6 +++ b/assets/javascripts/discourse/models/field.js.es6 @@ -1,7 +1,7 @@ import EmberObject from "@ember/object"; -import ValidState from "discourse/plugins/discourse-custom-wizard/wizard/mixins/valid-state"; +import ValidState from "discourse/plugins/discourse-custom-wizard/discourse/mixins/valid-state"; import discourseComputed from "discourse-common/utils/decorators"; -import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; +import { translationOrText } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard"; const StandardFieldValidation = [ "text", @@ -34,17 +34,17 @@ export default EmberObject.extend(ValidState, { @discourseComputed("i18nKey", "label") translatedLabel(i18nKey, label) { - return translatedText(`${i18nKey}.label`, label); + return translationOrText(`${i18nKey}.label`, label); }, @discourseComputed("i18nKey", "placeholder") translatedPlaceholder(i18nKey, placeholder) { - return translatedText(`${i18nKey}.placeholder`, placeholder); + return translationOrText(`${i18nKey}.placeholder`, placeholder); }, @discourseComputed("i18nKey", "description") translatedDescription(i18nKey, description) { - return translatedText(`${i18nKey}.description`, description); + return translationOrText(`${i18nKey}.description`, description); }, check() { diff --git a/assets/javascripts/wizard/models/site.js.es6 b/assets/javascripts/discourse/models/site.js.es6 similarity index 100% rename from assets/javascripts/wizard/models/site.js.es6 rename to assets/javascripts/discourse/models/site.js.es6 diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/discourse/models/step.js.es6 similarity index 90% rename from assets/javascripts/wizard/models/step.js.es6 rename to assets/javascripts/discourse/models/step.js.es6 index 87f2d4b6..bc72716f 100644 --- a/assets/javascripts/wizard/models/step.js.es6 +++ b/assets/javascripts/discourse/models/step.js.es6 @@ -1,9 +1,9 @@ import EmberObject from "@ember/object"; -import ValidState from "discourse/plugins/discourse-custom-wizard/wizard/mixins/valid-state"; -import { ajax } from "../lib/ajax"; +import ValidState from "discourse/plugins/discourse-custom-wizard/discourse/mixins/valid-state"; +import { ajax } from "discourse/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; -import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; import { later } from "@ember/runloop"; +import { translationOrText } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard"; export default EmberObject.extend(ValidState, { id: null, @@ -15,12 +15,12 @@ export default EmberObject.extend(ValidState, { @discourseComputed("i18nKey", "title") translatedTitle(i18nKey, title) { - return translatedText(`${i18nKey}.title`, title); + return translationOrText(`${i18nKey}.title`, title); }, @discourseComputed("i18nKey", "description") translatedDescription(i18nKey, description) { - return translatedText(`${i18nKey}.description`, description); + return translationOrText(`${i18nKey}.description`, description); }, @discourseComputed("index") diff --git a/assets/javascripts/wizard/models/wizard.js.es6 b/assets/javascripts/discourse/models/wizard.js.es6 similarity index 98% rename from assets/javascripts/wizard/models/wizard.js.es6 rename to assets/javascripts/discourse/models/wizard.js.es6 index d742c244..1725ae5d 100644 --- a/assets/javascripts/wizard/models/wizard.js.es6 +++ b/assets/javascripts/discourse/models/wizard.js.es6 @@ -1,7 +1,7 @@ import { default as computed } from "discourse-common/utils/decorators"; import getUrl from "discourse-common/lib/get-url"; import Field from "./field"; -import { ajax } from "../lib/ajax"; +import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; import Step from "./step"; import EmberObject from "@ember/object"; diff --git a/assets/javascripts/wizard/routes/wizard-index.js.es6 b/assets/javascripts/discourse/routes/custom-wizard-index.js.es6 similarity index 88% rename from assets/javascripts/wizard/routes/wizard-index.js.es6 rename to assets/javascripts/discourse/routes/custom-wizard-index.js.es6 index 264cb0a2..a7f5b606 100644 --- a/assets/javascripts/wizard/routes/wizard-index.js.es6 +++ b/assets/javascripts/discourse/routes/custom-wizard-index.js.es6 @@ -11,7 +11,7 @@ export default Route.extend({ !wizard.completed && wizard.start ) { - this.replaceWith("step", wizard.start); + this.replaceWith("customWizardStep", wizard.start); } }, @@ -19,10 +19,6 @@ export default Route.extend({ return getCachedWizard(); }, - renderTemplate() { - this.render("wizard/templates/wizard-index"); - }, - setupController(controller, model) { if (model && model.id) { const completed = model.get("completed"); diff --git a/assets/javascripts/wizard/routes/step.js.es6 b/assets/javascripts/discourse/routes/custom-wizard-step.js.es6 similarity index 83% rename from assets/javascripts/wizard/routes/step.js.es6 rename to assets/javascripts/discourse/routes/custom-wizard-step.js.es6 index a076951f..2c18c63d 100644 --- a/assets/javascripts/wizard/routes/step.js.es6 +++ b/assets/javascripts/discourse/routes/custom-wizard-step.js.es6 @@ -1,4 +1,4 @@ -import WizardI18n from "../lib/wizard-i18n"; +import I18n from "I18n"; import { getCachedWizard } from "../models/wizard"; import Route from "@ember/routing/route"; @@ -25,10 +25,6 @@ export default Route.extend({ return model.set("wizardId", this.wizard.id); }, - renderTemplate() { - this.render("wizard/templates/step"); - }, - setupController(controller, model) { let props = { step: model, @@ -38,8 +34,7 @@ export default Route.extend({ if (!model.permitted) { props["stepMessage"] = { state: "not-permitted", - text: - model.permitted_message || WizardI18n("wizard.step_not_permitted"), + text: model.permitted_message || I18n.t("wizard.step_not_permitted"), }; if (model.index > 0) { props["showReset"] = true; diff --git a/assets/javascripts/wizard/routes/wizard.js.es6 b/assets/javascripts/discourse/routes/custom-wizard.js.es6 similarity index 58% rename from assets/javascripts/wizard/routes/wizard.js.es6 rename to assets/javascripts/discourse/routes/custom-wizard.js.es6 index a2c34f13..7e0816a4 100644 --- a/assets/javascripts/wizard/routes/wizard.js.es6 +++ b/assets/javascripts/discourse/routes/custom-wizard.js.es6 @@ -1,8 +1,6 @@ import { findCustomWizard, updateCachedWizard } from "../models/wizard"; -import WizardI18n from "../lib/wizard-i18n"; +import I18n from "I18n"; import Route from "@ember/routing/route"; -import { scheduleOnce } from "@ember/runloop"; -import { getOwner } from "discourse-common/lib/get-owner"; export default Route.extend({ beforeModel(transition) { @@ -16,7 +14,7 @@ export default Route.extend({ }, showDialog(wizardModel) { - const title = WizardI18n("wizard.incomplete_submission.title", { + const title = I18n.t("wizard.incomplete_submission.title", { date: moment(wizardModel.submission_last_updated_at).format( "MMMM Do YYYY" ), @@ -24,14 +22,14 @@ export default Route.extend({ const buttons = [ { - label: WizardI18n("wizard.incomplete_submission.restart"), + label: I18n.t("wizard.incomplete_submission.restart"), class: "btn btn-default", callback: () => { wizardModel.restart(); }, }, { - label: WizardI18n("wizard.incomplete_submission.resume"), + label: I18n.t("wizard.incomplete_submission.resume"), class: "btn btn-primary", }, ]; @@ -47,21 +45,7 @@ export default Route.extend({ updateCachedWizard(model); }, - renderTemplate() { - this.render("wizard/templates/wizard"); - }, - setupController(controller, model) { - const background = model ? model.get("background") : ""; - - scheduleOnce("afterRender", this, function () { - $("body").css("background", background); - - if (model && model.id) { - $(getOwner(this).rootElement).addClass(model.id.dasherize()); - } - }); - controller.setProperties({ customWizard: true, logoUrl: this.siteSettings.logo_small, @@ -77,4 +61,31 @@ export default Route.extend({ this.showDialog(model); } }, + + getWizardBackground() { + const wizard = this.controllerFor("custom-wizard").get("model"); + return wizard ? wizard.get("background") : ""; + }, + + activate() { + if (!document.body.classList.contains("custom-wizard")) { + document.body.classList.add("custom-wizard"); + } + + const background = this.getWizardBackground(); + if (background) { + document.body.style.background = background; + } + }, + + deactivate() { + if (document.body.classList.contains("custom-wizard")) { + document.body.classList.remove("custom-wizard"); + } + + const background = this.getWizardBackground(); + if (background) { + document.body.style.background = ""; + } + }, }); diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs b/assets/javascripts/discourse/templates/components/custom-wizard-composer-editor.hbs similarity index 100% rename from assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs rename to assets/javascripts/discourse/templates/components/custom-wizard-composer-editor.hbs diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-hyperlink.hbs b/assets/javascripts/discourse/templates/components/custom-wizard-composer-hyperlink.hbs similarity index 71% rename from assets/javascripts/wizard/templates/components/wizard-composer-hyperlink.hbs rename to assets/javascripts/discourse/templates/components/custom-wizard-composer-hyperlink.hbs index c4403633..f430fb59 100644 --- a/assets/javascripts/wizard/templates/components/wizard-composer-hyperlink.hbs +++ b/assets/javascripts/discourse/templates/components/custom-wizard-composer-hyperlink.hbs @@ -1,13 +1,13 @@
@@ -34,23 +34,23 @@ {{loading-spinner size="small"}} {{else}} {{#if showQuitButton}} - {{wizard-i18n "wizard.quit"}} + {{i18n "wizard.quit"}} {{/if}} {{#if showBackButton}} - {{wizard-i18n "wizard.back"}} + {{i18n "wizard.back"}} {{/if}} {{/if}} {{#if showNextButton}} {{/if}} {{#if showDoneButton}} {{/if}}
diff --git a/assets/javascripts/wizard/templates/components/wizard-time-input.hbs b/assets/javascripts/discourse/templates/components/custom-wizard-time-input.hbs similarity index 100% rename from assets/javascripts/wizard/templates/components/wizard-time-input.hbs rename to assets/javascripts/discourse/templates/components/custom-wizard-time-input.hbs diff --git a/assets/javascripts/wizard/templates/components/field-validators.hbs b/assets/javascripts/discourse/templates/components/field-validators.hbs similarity index 100% rename from assets/javascripts/wizard/templates/components/field-validators.hbs rename to assets/javascripts/discourse/templates/components/field-validators.hbs diff --git a/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs b/assets/javascripts/discourse/templates/components/similar-topics-validator.hbs similarity index 54% rename from assets/javascripts/wizard/templates/components/similar-topics-validator.hbs rename to assets/javascripts/discourse/templates/components/similar-topics-validator.hbs index 2e92333d..2f760196 100644 --- a/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs +++ b/assets/javascripts/discourse/templates/components/similar-topics-validator.hbs @@ -1,13 +1,13 @@ {{#if showSimilarTopics}} - {{wizard-similar-topics topics=similarTopics}} + {{custom-wizard-similar-topics topics=similarTopics}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/validator.hbs b/assets/javascripts/discourse/templates/components/validator.hbs new file mode 100644 index 00000000..8e08379b --- /dev/null +++ b/assets/javascripts/discourse/templates/components/validator.hbs @@ -0,0 +1,5 @@ +{{#if isValid}} + {{i18n validMessageKey}} +{{else}} + {{i18n invalidMessageKey}} +{{/if}} diff --git a/assets/javascripts/discourse/templates/custom-wizard-index.hbs b/assets/javascripts/discourse/templates/custom-wizard-index.hbs new file mode 100644 index 00000000..d208851d --- /dev/null +++ b/assets/javascripts/discourse/templates/custom-wizard-index.hbs @@ -0,0 +1,3 @@ +{{#if noAccess}} + {{custom-wizard-no-access text=(i18n noAccessI18nKey) wizardId=wizardId reason=noAccessReason}} +{{/if}} diff --git a/assets/javascripts/wizard/templates/step.hbs b/assets/javascripts/discourse/templates/custom-wizard-step.hbs similarity index 56% rename from assets/javascripts/wizard/templates/step.hbs rename to assets/javascripts/discourse/templates/custom-wizard-step.hbs index 5ed14cdf..19357063 100644 --- a/assets/javascripts/wizard/templates/step.hbs +++ b/assets/javascripts/discourse/templates/custom-wizard-step.hbs @@ -5,15 +5,16 @@ {{#if showReset}} - {{wizard-i18n "wizard.reset"}} + {{i18n "wizard.reset"}} {{/if}} {{/if}} {{#if step.permitted}} - {{wizard-step step=step - wizard=wizard - goNext=(action "goNext") - goBack=(action "goBack") - showMessage=(action "showMessage")}} + {{custom-wizard-step + step=step + wizard=wizard + goNext=(action "goNext") + goBack=(action "goBack") + showMessage=(action "showMessage")}} {{/if}} diff --git a/assets/javascripts/wizard/templates/wizard.hbs b/assets/javascripts/discourse/templates/custom-wizard.hbs similarity index 100% rename from assets/javascripts/wizard/templates/wizard.hbs rename to assets/javascripts/discourse/templates/custom-wizard.hbs diff --git a/assets/javascripts/ember_jquery.js b/assets/javascripts/ember_jquery.js deleted file mode 100644 index 7506aa62..00000000 --- a/assets/javascripts/ember_jquery.js +++ /dev/null @@ -1,5 +0,0 @@ -//= require legacy/set-prototype-polyfill -//= require legacy/env -//= require legacy/jquery -//= require legacy/ember_include -//= require legacy/discourse-loader diff --git a/assets/javascripts/legacy/bootbox.js b/assets/javascripts/legacy/bootbox.js deleted file mode 100644 index c2520af8..00000000 --- a/assets/javascripts/legacy/bootbox.js +++ /dev/null @@ -1,717 +0,0 @@ -// discourse-skip-module - -/** - * bootbox.js v3.2.0 - * - * http://bootboxjs.com/license.txt - */ -var bootbox = - window.bootbox || - (function (document, $) { - /*jshint scripturl:true sub:true */ - - var _locale = "en", - _defaultLocale = "en", - _animate = true, - _backdrop = "static", - _defaultHref = "javascript:;", - _classes = "", - _btnClasses = {}, - _icons = {}, - /* last var should always be the public object we'll return */ - that = {}; - - /** - * public API - */ - that.setLocale = function (locale) { - for (var i in _locales) { - if (i == locale) { - _locale = locale; - return; - } - } - throw new Error("Invalid locale: " + locale); - }; - - that.addLocale = function (locale, translations) { - if (typeof _locales[locale] === "undefined") { - _locales[locale] = {}; - } - for (var str in translations) { - _locales[locale][str] = translations[str]; - } - }; - - that.setIcons = function (icons) { - _icons = icons; - if (typeof _icons !== "object" || _icons === null) { - _icons = {}; - } - }; - - that.setBtnClasses = function (btnClasses) { - _btnClasses = btnClasses; - if (typeof _btnClasses !== "object" || _btnClasses === null) { - _btnClasses = {}; - } - }; - - that.alert = function (/*str, label, cb*/) { - var str = "", - label = _translate("OK"), - cb = null; - - switch (arguments.length) { - case 1: - // no callback, default button label - str = arguments[0]; - break; - case 2: - // callback *or* custom button label dependent on type - str = arguments[0]; - if (typeof arguments[1] == "function") { - cb = arguments[1]; - } else { - label = arguments[1]; - } - break; - case 3: - // callback and custom button label - str = arguments[0]; - label = arguments[1]; - cb = arguments[2]; - break; - default: - throw new Error("Incorrect number of arguments: expected 1-3"); - } - - return that.dialog( - str, - { - // only button (ok) - label: label, - icon: _icons.OK, - class: _btnClasses.OK, - callback: cb, - }, - { - // ensure that the escape key works; either invoking the user's - // callback or true to just close the dialog - onEscape: cb || true, - } - ); - }; - - that.confirm = function (/*str, labelCancel, labelOk, cb*/) { - var str = "", - labelCancel = _translate("CANCEL"), - labelOk = _translate("CONFIRM"), - cb = null; - - switch (arguments.length) { - case 1: - str = arguments[0]; - break; - case 2: - str = arguments[0]; - if (typeof arguments[1] == "function") { - cb = arguments[1]; - } else { - labelCancel = arguments[1]; - } - break; - case 3: - str = arguments[0]; - labelCancel = arguments[1]; - if (typeof arguments[2] == "function") { - cb = arguments[2]; - } else { - labelOk = arguments[2]; - } - break; - case 4: - str = arguments[0]; - labelCancel = arguments[1]; - labelOk = arguments[2]; - cb = arguments[3]; - break; - default: - throw new Error("Incorrect number of arguments: expected 1-4"); - } - - var cancelCallback = function () { - if (typeof cb === "function") { - return cb(false); - } - }; - - var confirmCallback = function () { - if (typeof cb === "function") { - return cb(true); - } - }; - - return that.dialog( - str, - [ - { - // first button (cancel) - label: labelCancel, - icon: _icons.CANCEL, - class: _btnClasses.CANCEL, - callback: cancelCallback, - }, - { - // second button (confirm) - label: labelOk, - icon: _icons.CONFIRM, - class: _btnClasses.CONFIRM, - callback: confirmCallback, - }, - ], - { - // escape key bindings - onEscape: cancelCallback, - } - ); - }; - - that.prompt = function (/*str, labelCancel, labelOk, cb, defaultVal*/) { - var str = "", - labelCancel = _translate("CANCEL"), - labelOk = _translate("CONFIRM"), - cb = null, - defaultVal = ""; - - switch (arguments.length) { - case 1: - str = arguments[0]; - break; - case 2: - str = arguments[0]; - if (typeof arguments[1] == "function") { - cb = arguments[1]; - } else { - labelCancel = arguments[1]; - } - break; - case 3: - str = arguments[0]; - labelCancel = arguments[1]; - if (typeof arguments[2] == "function") { - cb = arguments[2]; - } else { - labelOk = arguments[2]; - } - break; - case 4: - str = arguments[0]; - labelCancel = arguments[1]; - labelOk = arguments[2]; - cb = arguments[3]; - break; - case 5: - str = arguments[0]; - labelCancel = arguments[1]; - labelOk = arguments[2]; - cb = arguments[3]; - defaultVal = arguments[4]; - break; - default: - throw new Error("Incorrect number of arguments: expected 1-5"); - } - - var header = str; - - // let's keep a reference to the form object for later - var form = $("
"); - form.append( - "" - ); - - var cancelCallback = function () { - if (typeof cb === "function") { - // yep, native prompts dismiss with null, whereas native - // confirms dismiss with false... - return cb(null); - } - }; - - var confirmCallback = function () { - if (typeof cb === "function") { - return cb(form.find("input[type=text]").val()); - } - }; - - var div = that.dialog( - form, - [ - { - // first button (cancel) - label: labelCancel, - icon: _icons.CANCEL, - class: _btnClasses.CANCEL, - callback: cancelCallback, - }, - { - // second button (confirm) - label: labelOk, - icon: _icons.CONFIRM, - class: _btnClasses.CONFIRM, - callback: confirmCallback, - }, - ], - { - // prompts need a few extra options - header: header, - // explicitly tell dialog NOT to show the dialog... - show: false, - onEscape: cancelCallback, - } - ); - - // ... the reason the prompt needs to be hidden is because we need - // to bind our own "shown" handler, after creating the modal but - // before any show(n) events are triggered - // @see https://github.com/makeusabrew/bootbox/issues/69 - - div.on("shown", function () { - form.find("input[type=text]").focus(); - - // ensure that submitting the form (e.g. with the enter key) - // replicates the behaviour of a normal prompt() - form.on("submit", function (e) { - e.preventDefault(); - div.find(".btn-primary").click(); - }); - }); - - div.modal("show"); - - return div; - }; - - that.dialog = function (str, handlers, options) { - var buttons = "", - callbacks = []; - - if (!options) { - options = {}; - } - - // check for single object and convert to array if necessary - if (typeof handlers === "undefined") { - handlers = []; - } else if (typeof handlers.length == "undefined") { - handlers = [handlers]; - } - - var i = handlers.length; - while (i--) { - var label = null, - href = null, - _class = "btn-default", - icon = "", - callback = null; - - if ( - typeof handlers[i]["label"] == "undefined" && - typeof handlers[i]["class"] == "undefined" && - typeof handlers[i]["callback"] == "undefined" - ) { - // if we've got nothing we expect, check for condensed format - - var propCount = 0, // condensed will only match if this == 1 - property = null; // save the last property we found - - // be nicer to count the properties without this, but don't think it's possible... - for (var j in handlers[i]) { - property = j; - if (++propCount > 1) { - // forget it, too many properties - break; - } - } - - if (propCount == 1 && typeof handlers[i][j] == "function") { - // matches condensed format of label -> function - handlers[i]["label"] = property; - handlers[i]["callback"] = handlers[i][j]; - } - } - - if (typeof handlers[i]["callback"] == "function") { - callback = handlers[i]["callback"]; - } - - if (handlers[i]["class"]) { - _class = handlers[i]["class"]; - } else if (i == handlers.length - 1 && handlers.length <= 2) { - // always add a primary to the main option in a two-button dialog - _class = "btn-primary"; - } - - // See: https://github.com/makeusabrew/bootbox/pull/114 - // Upgrade to official bootbox release when it gets merged. - if (handlers[i]["link"] !== true) { - _class = "btn " + _class; - } - - if (handlers[i]["label"]) { - label = handlers[i]["label"]; - } else { - label = "Option " + (i + 1); - } - - if (handlers[i]["icon"]) { - icon = handlers[i]["icon"]; - } - - if (handlers[i]["href"]) { - href = handlers[i]["href"]; - } else { - href = _defaultHref; - } - - buttons = - buttons + - "" + - icon + - "" + - label + - ""; - - callbacks[i] = callback; - } - - // @see https://github.com/makeusabrew/bootbox/issues/46#issuecomment-8235302 - // and https://github.com/twitter/bootstrap/issues/4474 - // for an explanation of the inline overflow: hidden - // @see https://github.com/twitter/bootstrap/issues/4854 - // for an explanation of tabIndex=-1 - - var parts = [ - ""); - - var div = $(parts.join("\n")); - - // check whether we should fade in/out - var shouldFade = - typeof options.animate === "undefined" ? _animate : options.animate; - - if (shouldFade) { - div.addClass("fade"); - } - - var optionalClasses = - typeof options.classes === "undefined" ? _classes : options.classes; - if (optionalClasses) { - div.addClass(optionalClasses); - } - - // now we've built up the div properly we can inject the content whether it was a string or a jQuery object - div.find(".modal-body").html(str); - - function onCancel(source) { - // for now source is unused, but it will be in future - var hideModal = null; - if (typeof options.onEscape === "function") { - // @see https://github.com/makeusabrew/bootbox/issues/91 - hideModal = options.onEscape(); - } - - if (hideModal !== false) { - div.modal("hide"); - } - } - - // hook into the modal's keyup trigger to check for the escape key - div.on("keyup.dismiss.modal", function (e) { - // any truthy value passed to onEscape will dismiss the dialog - // as long as the onEscape function (if defined) doesn't prevent it - if (e.which === 27 && options.onEscape !== false) { - onCancel("escape"); - } - }); - - // handle close buttons too - div.on("click", "a.close", function (e) { - e.preventDefault(); - onCancel("close"); - }); - - // well, *if* we have a primary - give the first dom element focus - div.on("shown.bs.modal", function () { - div.find("a.btn-primary:first").focus(); - }); - - div.on("hidden.bs.modal", function () { - div.remove(); - }); - - // wire up button handlers - div.on("click", ".modal-footer a", function (e) { - var self = this; - Ember.run(function () { - var handler = $(self).data("handler"), - cb = callbacks[handler], - hideModal = null; - - // sort of @see https://github.com/makeusabrew/bootbox/pull/68 - heavily adapted - // if we've got a custom href attribute, all bets are off - if ( - typeof handler !== "undefined" && - typeof handlers[handler]["href"] !== "undefined" - ) { - return; - } - - e.preventDefault(); - - if (typeof cb === "function") { - hideModal = cb(e); - } - - // the only way hideModal *will* be false is if a callback exists and - // returns it as a value. in those situations, don't hide the dialog - // @see https://github.com/makeusabrew/bootbox/pull/25 - if (hideModal !== false) { - div.modal("hide"); - } - }); - }); - - // stick the modal right at the bottom of the main body out of the way - (that.$body || $("body")).append(div); - - div.modal({ - // unless explicitly overridden take whatever our default backdrop value is - backdrop: - typeof options.backdrop === "undefined" - ? _backdrop - : options.backdrop, - // ignore bootstrap's keyboard options; we'll handle this ourselves (more fine-grained control) - keyboard: false, - // @ see https://github.com/makeusabrew/bootbox/issues/69 - // we *never* want the modal to be shown before we can bind stuff to it - // this method can also take a 'show' option, but we'll only use that - // later if we need to - show: false, - }); - - // @see https://github.com/makeusabrew/bootbox/issues/64 - // @see https://github.com/makeusabrew/bootbox/issues/60 - // ...caused by... - // @see https://github.com/twitter/bootstrap/issues/4781 - div.on("show", function (e) { - $(document).off("focusin.modal"); - }); - - if (typeof options.show === "undefined" || options.show === true) { - div.modal("show"); - } - - return div; - }; - - /** - * #modal is deprecated in v3; it can still be used but no guarantees are - * made - have never been truly convinced of its merit but perhaps just - * needs a tidyup and some TLC - */ - that.modal = function (/*str, label, options*/) { - var str; - var label; - var options; - - var defaultOptions = { - onEscape: null, - keyboard: true, - backdrop: _backdrop, - }; - - switch (arguments.length) { - case 1: - str = arguments[0]; - break; - case 2: - str = arguments[0]; - if (typeof arguments[1] == "object") { - options = arguments[1]; - } else { - label = arguments[1]; - } - break; - case 3: - str = arguments[0]; - label = arguments[1]; - options = arguments[2]; - break; - default: - throw new Error("Incorrect number of arguments: expected 1-3"); - } - - defaultOptions["header"] = label; - - if (typeof options == "object") { - options = $.extend(defaultOptions, options); - } else { - options = defaultOptions; - } - - return that.dialog(str, [], options); - }; - - that.hideAll = function () { - $(".bootbox").modal("hide"); - }; - - that.animate = function (animate) { - _animate = animate; - }; - - that.backdrop = function (backdrop) { - _backdrop = backdrop; - }; - - that.classes = function (classes) { - _classes = classes; - }; - - /** - * private API - */ - - /** - * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are - * unlikely to be required. If this gets too large it can be split out into separate JS files. - */ - var _locales = { - br: { - OK: "OK", - CANCEL: "Cancelar", - CONFIRM: "Sim", - }, - da: { - OK: "OK", - CANCEL: "Annuller", - CONFIRM: "Accepter", - }, - de: { - OK: "OK", - CANCEL: "Abbrechen", - CONFIRM: "Akzeptieren", - }, - en: { - OK: "OK", - CANCEL: "Cancel", - CONFIRM: "OK", - }, - es: { - OK: "OK", - CANCEL: "Cancelar", - CONFIRM: "Aceptar", - }, - fr: { - OK: "OK", - CANCEL: "Annuler", - CONFIRM: "D'accord", - }, - it: { - OK: "OK", - CANCEL: "Annulla", - CONFIRM: "Conferma", - }, - nl: { - OK: "OK", - CANCEL: "Annuleren", - CONFIRM: "Accepteren", - }, - pl: { - OK: "OK", - CANCEL: "Anuluj", - CONFIRM: "Potwierdź", - }, - ru: { - OK: "OK", - CANCEL: "Отмена", - CONFIRM: "Применить", - }, - zh_CN: { - OK: "OK", - CANCEL: "取消", - CONFIRM: "确认", - }, - zh_TW: { - OK: "OK", - CANCEL: "取消", - CONFIRM: "確認", - }, - }; - - function _translate(str, locale) { - // we assume if no target locale is probided then we should take it from current setting - if (typeof locale === "undefined") { - locale = _locale; - } - if (typeof _locales[locale][str] === "string") { - return _locales[locale][str]; - } - - // if we couldn't find a lookup then try and fallback to a default translation - - if (locale != _defaultLocale) { - return _translate(str, _defaultLocale); - } - - // if we can't do anything then bail out with whatever string was passed in - last resort - return str; - } - - return that; - })(document, window.jQuery); - -// @see https://github.com/makeusabrew/bootbox/issues/71 -window.bootbox = bootbox; - -define("bootbox", ["exports"], function (__exports__) { - __exports__.default = window.bootbox; -}); diff --git a/assets/javascripts/legacy/bootstrap-modal.js b/assets/javascripts/legacy/bootstrap-modal.js deleted file mode 100644 index 61c76fbd..00000000 --- a/assets/javascripts/legacy/bootstrap-modal.js +++ /dev/null @@ -1,360 +0,0 @@ -// discourse-skip-module - -/* ======================================================================== - * Bootstrap: modal.js v3.4.1 - * https://getbootstrap.com/docs/3.4/javascript/#modals - * ======================================================================== - * Copyright 2011-2019 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // MODAL CLASS DEFINITION - // ====================== - - var Modal = function (element, options) { - this.options = options - this.$body = $(document.body) - this.$element = $(element) - this.$dialog = this.$element.find('.modal-dialog') - this.$backdrop = null - this.isShown = null - this.originalBodyPad = null - this.scrollbarWidth = 0 - this.ignoreBackdropClick = false - this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom' - - if (this.options.remote) { - this.$element - .find('.modal-content') - .load(this.options.remote, $.proxy(function () { - this.$element.trigger('loaded.bs.modal') - }, this)) - } - } - - Modal.VERSION = '3.4.1' - - Modal.TRANSITION_DURATION = 300 - Modal.BACKDROP_TRANSITION_DURATION = 150 - - Modal.DEFAULTS = { - backdrop: true, - keyboard: true, - show: true - } - - Modal.prototype.toggle = function (_relatedTarget) { - return this.isShown ? this.hide() : this.show(_relatedTarget) - } - - Modal.prototype.show = function (_relatedTarget) { - var that = this - var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) - - this.$element.trigger(e) - - if (this.isShown || e.isDefaultPrevented()) return - - this.isShown = true - - this.checkScrollbar() - this.setScrollbar() - this.$body.addClass('modal-open') - - this.escape() - this.resize() - - this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) - - this.$dialog.on('mousedown.dismiss.bs.modal', function () { - that.$element.one('mouseup.dismiss.bs.modal', function (e) { - if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true - }) - }) - - this.backdrop(function () { - var transition = $.support.transition && that.$element.hasClass('fade') - - if (!that.$element.parent().length) { - that.$element.appendTo(that.$body) // don't move modals dom position - } - - that.$element - .show() - .scrollTop(0) - - that.adjustDialog() - - if (transition) { - that.$element[0].offsetWidth // force reflow - } - - that.$element.addClass('in') - - that.enforceFocus() - - var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) - - transition ? - that.$dialog // wait for modal to slide in - .one('bsTransitionEnd', function () { - that.$element.trigger('focus').trigger(e) - }) - .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - that.$element.trigger('focus').trigger(e) - }) - } - - Modal.prototype.hide = function (e) { - if (e) e.preventDefault() - - e = $.Event('hide.bs.modal') - - this.$element.trigger(e) - - if (!this.isShown || e.isDefaultPrevented()) return - - this.isShown = false - - this.escape() - this.resize() - - $(document).off('focusin.bs.modal') - - this.$element - .removeClass('in') - .off('click.dismiss.bs.modal') - .off('mouseup.dismiss.bs.modal') - - this.$dialog.off('mousedown.dismiss.bs.modal') - - $.support.transition && this.$element.hasClass('fade') ? - this.$element - .one('bsTransitionEnd', $.proxy(this.hideModal, this)) - .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - this.hideModal() - } - - Modal.prototype.enforceFocus = function () { - $(document) - .off('focusin.bs.modal') // guard against infinite focus loop - .on('focusin.bs.modal', $.proxy(function (e) { - if (document !== e.target && - this.$element[0] !== e.target && - !this.$element.has(e.target).length) { - this.$element.trigger('focus') - } - }, this)) - } - - Modal.prototype.escape = function () { - if (this.isShown && this.options.keyboard) { - this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { - e.which == 27 && this.hide() - }, this)) - } else if (!this.isShown) { - this.$element.off('keydown.dismiss.bs.modal') - } - } - - Modal.prototype.resize = function () { - if (this.isShown) { - $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) - } else { - $(window).off('resize.bs.modal') - } - } - - Modal.prototype.hideModal = function () { - var that = this - this.$element.hide() - this.backdrop(function () { - that.$body.removeClass('modal-open') - that.resetAdjustments() - that.resetScrollbar() - that.$element.trigger('hidden.bs.modal') - }) - } - - Modal.prototype.removeBackdrop = function () { - this.$backdrop && this.$backdrop.remove() - this.$backdrop = null - } - - Modal.prototype.backdrop = function (callback) { - var that = this - var animate = this.$element.hasClass('fade') ? 'fade' : '' - - if (this.isShown && this.options.backdrop) { - var doAnimate = $.support.transition && animate - - this.$backdrop = $(document.createElement('div')) - .addClass('modal-backdrop ' + animate) - .appendTo(this.$body) - - this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { - if (this.ignoreBackdropClick) { - this.ignoreBackdropClick = false - return - } - if (e.target !== e.currentTarget) return - this.options.backdrop == 'static' - ? this.$element[0].focus() - : this.hide() - }, this)) - - if (doAnimate) this.$backdrop[0].offsetWidth // force reflow - - this.$backdrop.addClass('in') - - if (!callback) return - - doAnimate ? - this.$backdrop - .one('bsTransitionEnd', callback) - .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callback() - - } else if (!this.isShown && this.$backdrop) { - this.$backdrop.removeClass('in') - - var callbackRemove = function () { - that.removeBackdrop() - callback && callback() - } - $.support.transition && this.$element.hasClass('fade') ? - this.$backdrop - .one('bsTransitionEnd', callbackRemove) - .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callbackRemove() - - } else if (callback) { - callback() - } - } - - // these following methods are used to handle overflowing modals - - Modal.prototype.handleUpdate = function () { - this.adjustDialog() - } - - Modal.prototype.adjustDialog = function () { - var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight - - this.$element.css({ - paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', - paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' - }) - } - - Modal.prototype.resetAdjustments = function () { - this.$element.css({ - paddingLeft: '', - paddingRight: '' - }) - } - - Modal.prototype.checkScrollbar = function () { - var fullWindowWidth = window.innerWidth - if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 - var documentElementRect = document.documentElement.getBoundingClientRect() - fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) - } - this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth - this.scrollbarWidth = this.measureScrollbar() - } - - Modal.prototype.setScrollbar = function () { - var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) - this.originalBodyPad = document.body.style.paddingRight || '' - var scrollbarWidth = this.scrollbarWidth - if (this.bodyIsOverflowing) { - this.$body.css('padding-right', bodyPad + scrollbarWidth) - $(this.fixedContent).each(function (index, element) { - var actualPadding = element.style.paddingRight - var calculatedPadding = $(element).css('padding-right') - $(element) - .data('padding-right', actualPadding) - .css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px') - }) - } - } - - Modal.prototype.resetScrollbar = function () { - this.$body.css('padding-right', this.originalBodyPad) - $(this.fixedContent).each(function (index, element) { - var padding = $(element).data('padding-right') - $(element).removeData('padding-right') - element.style.paddingRight = padding ? padding : '' - }) - } - - Modal.prototype.measureScrollbar = function () { // thx walsh - var scrollDiv = document.createElement('div') - scrollDiv.className = 'modal-scrollbar-measure' - this.$body.append(scrollDiv) - var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth - this.$body[0].removeChild(scrollDiv) - return scrollbarWidth - } - - - // MODAL PLUGIN DEFINITION - // ======================= - - function Plugin(option, _relatedTarget) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.modal') - var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data) $this.data('bs.modal', (data = new Modal(this, options))) - if (typeof option == 'string') data[option](_relatedTarget) - else if (options.show) data.show(_relatedTarget) - }) - } - - var old = $.fn.modal - - $.fn.modal = Plugin - $.fn.modal.Constructor = Modal - - - // MODAL NO CONFLICT - // ================= - - $.fn.modal.noConflict = function () { - $.fn.modal = old - return this - } - - - // MODAL DATA-API - // ============== - - $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { - var $this = $(this) - var href = $this.attr('href') - var target = $this.attr('data-target') || - (href && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 - - var $target = $(document).find(target) - var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) - - if ($this.is('a')) e.preventDefault() - - $target.one('show.bs.modal', function (showEvent) { - if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown - $target.one('hidden.bs.modal', function () { - $this.is(':visible') && $this.trigger('focus') - }) - }) - Plugin.call($target, option, this) - }) - -}(jQuery); diff --git a/assets/javascripts/legacy/caret_position.js b/assets/javascripts/legacy/caret_position.js deleted file mode 100644 index 2ae02794..00000000 --- a/assets/javascripts/legacy/caret_position.js +++ /dev/null @@ -1,164 +0,0 @@ -// discourse-skip-module - -// TODO: This code should be moved to lib, it was heavily modified by us over the years, and mostly written by us -// except for the little snippet from StackOverflow -// -// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea -var clone = null; - -$.fn.caret = function(elem) { - var getCaret = function(el) { - if (el.selectionStart) { - return el.selectionStart; - } - return 0; - }; - return getCaret(elem || this[0]); -}; - -/** - This is a jQuery plugin to retrieve the caret position in a textarea - - @module $.fn.caretPosition -**/ -$.fn.caretPosition = function(options) { - var after, - before, - getStyles, - guard, - html, - important, - insertSpaceAfterBefore, - letter, - makeCursor, - p, - pPos, - pos, - span, - styles, - textarea, - val; - if (clone) { - clone.remove(); - } - span = $("#pos span"); - textarea = $(this); - - getStyles = function(el) { - if (el.currentStyle) { - return el.currentStyle; - } else { - return document.defaultView.getComputedStyle(el, ""); - } - }; - - important = function(prop) { - return styles.getPropertyValue(prop); - }; - - styles = getStyles(textarea[0]); - clone = $("

").appendTo("body"); - p = clone.find("p"); - - var isRTL = $("html").hasClass("rtl"); - clone.css({ - border: "1px solid black", - padding: important("padding"), - resize: important("resize"), - "max-height": textarea.height() + "px", - "overflow-y": "auto", - "word-wrap": "break-word", - position: "absolute", - left: isRTL ? "auto" : "-7000px", - right: isRTL ? "-7000px" : "auto" - }); - - p.css({ - margin: 0, - padding: 0, - "word-wrap": "break-word", - "letter-spacing": important("letter-spacing"), - "font-family": important("font-family"), - "font-size": important("font-size"), - "line-height": important("line-height") - }); - - clone.width(textarea.width()); - clone.height(textarea.height()); - - pos = - options && (options.pos || options.pos === 0) - ? options.pos - : $.caret(textarea[0]); - - val = textarea.val().replace("\r", ""); - if (options && options.key) { - val = val.substring(0, pos) + options.key + val.substring(pos); - } - before = pos - 1; - after = pos; - insertSpaceAfterBefore = false; - - // if before and after are \n insert a space - if (val[before] === "\n" && val[after] === "\n") { - insertSpaceAfterBefore = true; - } - - guard = function(v) { - var buf; - buf = v.replace(//g, ">"); - buf = buf.replace(/[ ]/g, "​ ​"); - return buf.replace(/\n/g, "
"); - }; - - makeCursor = function(pos, klass, color) { - var l; - l = val.substring(pos, pos + 1); - if (l === "\n") return "
"; - return ( - "" + - guard(l) + - "" - ); - }; - - html = ""; - - if (before >= 0) { - html += - guard(val.substring(0, pos - 1)) + - makeCursor(before, "before", "#d0ffff"); - if (insertSpaceAfterBefore) { - html += makeCursor(0, "post-before", "#d0ffff"); - } - } - - if (after >= 0) { - html += makeCursor(after, "after", "#ffd0ff"); - if (after - 1 < val.length) { - html += guard(val.substring(after + 1)); - } - } - - p.html(html); - clone.scrollTop(textarea.scrollTop()); - letter = p.find("span:first"); - pos = letter.offset(); - if (letter.hasClass("before")) { - pos.left = pos.left + letter.width(); - } - - pPos = p.offset(); - var position = { - left: pos.left - pPos.left, - top: pos.top - pPos.top - clone.scrollTop() - }; - - clone.remove(); - return position; -}; diff --git a/assets/javascripts/legacy/discourse-loader.js b/assets/javascripts/legacy/discourse-loader.js deleted file mode 100644 index ee9a2a07..00000000 --- a/assets/javascripts/legacy/discourse-loader.js +++ /dev/null @@ -1,435 +0,0 @@ -// discourse-skip-module - -var define, requirejs; - -(function () { - let JS_MODULES = {}; - let ALIASES = { - "ember-addons/ember-computed-decorators": - "discourse-common/utils/decorators", - "discourse/lib/raw-templates": "discourse/plugins/discourse-custom-wizard/legacy/raw-templates", - "discourse-common/lib/raw-templates": "discourse/plugins/discourse-custom-wizard/legacy/raw-templates", - "preload-store": "discourse/lib/preload-store", - "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures", - }; - let ALIAS_PREPEND = { - fixtures: "discourse/tests/", - helpers: "discourse/tests/", - }; - - // In future versions of ember we don't need this - if (typeof Ember !== "undefined") { - JS_MODULES = { - jquery: { default: $ }, - "@ember/array": { - default: Ember.Array, - A: Ember.A, - isArray: Ember.isArray, - }, - "@ember/array/proxy": { - default: Ember.ArrayProxy, - }, - "@ember/component": { - default: Ember.Component, - }, - "@ember/controller": { - default: Ember.Controller, - inject: Ember.inject.controller, - }, - "@ember/debug": { - assert: Ember.assert, - runInDebug: Ember.runInDebug, - warn: Ember.warn, - }, - "@ember/object": { - action: Ember._action, - default: Ember.Object, - get: Ember.get, - getProperties: Ember.getProperties, - set: Ember.set, - setProperties: Ember.setProperties, - computed: Ember.computed, - defineProperty: Ember.defineProperty, - observer: Ember.observer, - }, - "@ember/object/computed": { - alias: Ember.computed.alias, - and: Ember.computed.and, - bool: Ember.computed.bool, - collect: Ember.computed.collect, - deprecatingAlias: Ember.computed.deprecatingAlias, - empty: Ember.computed.empty, - equal: Ember.computed.equal, - filter: Ember.computed.filter, - filterBy: Ember.computed.filterBy, - gt: Ember.computed.gt, - gte: Ember.computed.gte, - intersect: Ember.computed.intersect, - lt: Ember.computed.lt, - lte: Ember.computed.lte, - map: Ember.computed.map, - mapBy: Ember.computed.mapBy, - match: Ember.computed.match, - max: Ember.computed.max, - min: Ember.computed.min, - none: Ember.computed.none, - not: Ember.computed.not, - notEmpty: Ember.computed.notEmpty, - oneWay: Ember.computed.oneWay, - or: Ember.computed.or, - readOnly: Ember.computed.readOnly, - reads: Ember.computed.reads, - setDiff: Ember.computed.setDiff, - sort: Ember.computed.sort, - sum: Ember.computed.sum, - union: Ember.computed.union, - uniq: Ember.computed.uniq, - uniqBy: Ember.computed.uniqBy, - }, - "@ember/object/mixin": { default: Ember.Mixin }, - "@ember/object/proxy": { default: Ember.ObjectProxy }, - "@ember/object/promise-proxy-mixin": { default: Ember.PromiseProxyMixin }, - "@ember/object/evented": { - default: Ember.Evented, - on: Ember.on, - }, - "@ember/routing/route": { default: Ember.Route }, - "@ember/routing/router": { default: Ember.Router }, - "@ember/runloop": { - bind: Ember.run.bind, - cancel: Ember.run.cancel, - debounce: Ember.testing ? Ember.run : Ember.run.debounce, - later: Ember.run.later, - next: Ember.run.next, - once: Ember.run.once, - run: Ember.run, - schedule: Ember.run.schedule, - scheduleOnce: Ember.run.scheduleOnce, - throttle: Ember.run.throttle, - }, - "@ember/service": { - default: Ember.Service, - inject: Ember.inject.service, - }, - "@ember/utils": { - isBlank: Ember.isBlank, - isEmpty: Ember.isEmpty, - isNone: Ember.isNone, - isPresent: Ember.isPresent, - }, - rsvp: { - asap: Ember.RSVP.asap, - all: Ember.RSVP.all, - allSettled: Ember.RSVP.allSettled, - race: Ember.RSVP.race, - hash: Ember.RSVP.hash, - hashSettled: Ember.RSVP.hashSettled, - rethrow: Ember.RSVP.rethrow, - defer: Ember.RSVP.defer, - denodeify: Ember.RSVP.denodeify, - resolve: Ember.RSVP.resolve, - reject: Ember.RSVP.reject, - map: Ember.RSVP.map, - filter: Ember.RSVP.filter, - default: Ember.RSVP, - Promise: Ember.RSVP.Promise, - EventTarget: Ember.RSVP.EventTarget, - }, - "@ember/string": { - w: Ember.String.w, - dasherize: Ember.String.dasherize, - decamelize: Ember.String.decamelize, - camelize: Ember.String.camelize, - classify: Ember.String.classify, - underscore: Ember.String.underscore, - capitalize: Ember.String.capitalize, - }, - "@ember/template": { - htmlSafe: Ember.String.htmlSafe, - }, - "@ember/application": { - default: Ember.Application, - setOwner: Ember.setOwner, - getOwner: Ember.getOwner, - }, - "@ember/component/helper": { - default: Ember.Helper, - }, - "@ember/component/text-field": { - default: Ember.TextField, - }, - "@ember/component/text-area": { - default: Ember.TextArea, - }, - "@ember/error": { - default: Ember.error, - }, - "@ember/object/internals": { - guidFor: Ember.guidFor, - }, - "@ember/test": { - registerWaiter: Ember.Test && Ember.Test.registerWaiter, - unregisterWaiter: Ember.Test && Ember.Test.unregisterWaiter, - }, - I18n: { - // eslint-disable-next-line - default: I18n, - } - }; - } - - let _isArray; - if (!Array.isArray) { - _isArray = function (x) { - return Object.prototype.toString.call(x) === "[object Array]"; - }; - } else { - _isArray = Array.isArray; - } - - let registry = {}; - let seen = {}; - let FAILED = false; - - let uuid = 0; - - function tryFinally(tryable, finalizer) { - try { - return tryable(); - } finally { - finalizer(); - } - } - - function unsupportedModule(length) { - throw new Error( - "an unsupported module was defined, expected `define(name, deps, module)` instead got: `" + - length + - "` arguments to define`" - ); - } - - function deprecatedModule(depricated, useInstead) { - let warning = "[DEPRECATION] `" + depricated + "` is deprecated."; - if (useInstead) { - warning += " Please use `" + useInstead + "` instead."; - } - // eslint-disable-next-line no-console - console.warn(warning); - } - - let defaultDeps = ["require", "exports", "module"]; - - function Module(name, deps, callback, exports) { - this.id = uuid++; - this.name = name; - this.deps = !deps.length && callback.length ? defaultDeps : deps; - this.exports = exports || {}; - this.callback = callback; - this.state = undefined; - this._require = undefined; - } - - Module.prototype.makeRequire = function () { - let name = transformForAliases(this.name); - - return ( - this._require || - (this._require = function (dep) { - return requirejs(resolve(dep, name)); - }) - ); - }; - - define = function (name, deps, callback) { - if (arguments.length < 2) { - unsupportedModule(arguments.length); - } - - if (!_isArray(deps)) { - callback = deps; - deps = []; - } - - registry[name] = new Module(name, deps, callback); - }; - - // we don't support all of AMD - // define.amd = {}; - // we will support petals... - define.petal = {}; - - function Alias(path) { - this.name = path; - } - - define.alias = function (path) { - return new Alias(path); - }; - - function reify(mod, name, rseen) { - let deps = mod.deps; - let length = deps.length; - let reified = new Array(length); - let dep; - // TODO: new Module - // TODO: seen refactor - let module = {}; - - for (let i = 0, l = length; i < l; i++) { - dep = deps[i]; - if (dep === "exports") { - module.exports = reified[i] = rseen; - } else if (dep === "require") { - reified[i] = mod.makeRequire(); - } else if (dep === "module") { - mod.exports = rseen; - module = reified[i] = mod; - } else { - reified[i] = requireFrom(resolve(dep, name), name); - } - } - - return { - deps: reified, - module, - }; - } - - function requireFrom(name, origin) { - name = transformForAliases(name); - - if (name === "discourse") { - // eslint-disable-next-line no-console - console.log( - "discourse has been moved to `discourse/app` - please update your code" - ); - name = "discourse/app"; - } - - if (name === "discourse/models/input-validation") { - // eslint-disable-next-line no-console - console.log( - "input-validation has been removed and should be replaced with `@ember/object`" - ); - name = "@ember/object"; - } - - let mod = JS_MODULES[name] || registry[name]; - if (!mod) { - throw new Error( - "Could not find module `" + name + "` imported from `" + origin + "`" - ); - } - return requirejs(name); - } - - function missingModule(name) { - throw new Error("Could not find module " + name); - } - - function transformForAliases(name) { - let alias = ALIASES[name]; - if (!alias) { - let segment = name.split("/")[0]; - let prepend = ALIAS_PREPEND[segment]; - if (!prepend) { - return name; - } - alias = prepend + name; - } - deprecatedModule(name, alias); - return alias; - } - - requirejs = require = function (name) { - name = transformForAliases(name); - if (JS_MODULES[name]) { - return JS_MODULES[name]; - } - - let mod = registry[name]; - - if (mod && mod.callback instanceof Alias) { - mod = registry[mod.callback.name]; - } - - if (!mod) { - missingModule(name); - } - - if (mod.state !== FAILED && seen.hasOwnProperty(name)) { - return seen[name]; - } - - let reified; - let module; - let loaded = false; - - seen[name] = {}; // placeholder for run-time cycles - - tryFinally( - function () { - reified = reify(mod, name, seen[name]); - module = mod.callback.apply(this, reified.deps); - loaded = true; - }, - function () { - if (!loaded) { - mod.state = FAILED; - } - } - ); - - let obj; - if (module === undefined && reified.module.exports) { - obj = reified.module.exports; - } else { - obj = seen[name] = module; - } - - if ( - obj !== null && - (typeof obj === "object" || typeof obj === "function") && - obj["default"] === undefined - ) { - obj["default"] = obj; - } - - return (seen[name] = obj); - }; - window.requireModule = requirejs; - - function resolve(child, name) { - if (child.charAt(0) !== ".") { - return child; - } - - let parts = child.split("/"); - let nameParts = name.split("/"); - let parentBase = nameParts.slice(0, -1); - - for (let i = 0, l = parts.length; i < l; i++) { - let part = parts[i]; - - if (part === "..") { - if (parentBase.length === 0) { - throw new Error("Cannot access parent module of root"); - } - parentBase.pop(); - } else if (part === ".") { - continue; - } else { - parentBase.push(part); - } - } - - return parentBase.join("/"); - } - - requirejs.entries = requirejs._eak_seen = registry; - requirejs.clear = function () { - requirejs.entries = requirejs._eak_seen = registry = {}; - seen = {}; - }; -})(); diff --git a/assets/javascripts/legacy/discourse-shims.js b/assets/javascripts/legacy/discourse-shims.js deleted file mode 100644 index a6b25771..00000000 --- a/assets/javascripts/legacy/discourse-shims.js +++ /dev/null @@ -1,69 +0,0 @@ -// discourse-skip-module - -define("message-bus-client", ["exports"], function (__exports__) { - __exports__.default = window.MessageBus; -}); - -define("ember-buffered-proxy/proxy", ["exports"], function (__exports__) { - __exports__.default = window.BufferedProxy; -}); - -define("bootbox", ["exports"], function (__exports__) { - __exports__.default = window.bootbox; -}); - -define("xss", ["exports"], function (__exports__) { - __exports__.default = window.filterXSS; -}); - -define("@discourse/itsatrap", ["exports"], function (__exports__) { - __exports__.default = window.ItsATrap; -}); - -define("@popperjs/core", ["exports"], function (__exports__) { - __exports__.default = window.Popper; - __exports__.createPopper = window.Popper.createPopper; - __exports__.defaultModifiers = window.Popper.defaultModifiers; - __exports__.popperGenerator = window.Popper.popperGenerator; -}); - -define("tippy.js", ["exports"], function (__exports__) { - __exports__.default = window.tippy; -}); - -define("@uppy/core", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Core; - __exports__.BasePlugin = window.Uppy.Core.BasePlugin; -}); - -define("@uppy/aws-s3", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3; -}); - -define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3Multipart; -}); - -define("@uppy/xhr-upload", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.XHRUpload; -}); - -define("@uppy/drop-target", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.DropTarget; -}); - -define("@uppy/utils/lib/delay", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.delay; -}); - -define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.EventTracker; -}); - -define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) { - __exports__.AbortController = - window.Uppy.Utils.AbortControllerLib.AbortController; - __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal; - __exports__.createAbortError = - window.Uppy.Utils.AbortControllerLib.createAbortError; -}); diff --git a/assets/javascripts/legacy/ember_include.js.erb b/assets/javascripts/legacy/ember_include.js.erb deleted file mode 100644 index 0d6ddf43..00000000 --- a/assets/javascripts/legacy/ember_include.js.erb +++ /dev/null @@ -1,9 +0,0 @@ -// discourse-skip-module - -<% - if @force_ember_debug || Rails.env.development? || Rails.env.test? - require_asset("ember.debug.js") - else - require_asset("ember.prod.js") - end -%> diff --git a/assets/javascripts/legacy/env.js b/assets/javascripts/legacy/env.js deleted file mode 100644 index 0ae0e456..00000000 --- a/assets/javascripts/legacy/env.js +++ /dev/null @@ -1,5 +0,0 @@ -// discourse-skip-module - -window.ENV = {}; -window.EmberENV = window.EmberENV || {}; -window.EmberENV.FORCE_JQUERY = true; diff --git a/assets/javascripts/legacy/handlebars.runtime.js b/assets/javascripts/legacy/handlebars.runtime.js deleted file mode 100644 index 145aa7c1..00000000 --- a/assets/javascripts/legacy/handlebars.runtime.js +++ /dev/null @@ -1,1802 +0,0 @@ -// discourse-skip-module - -/**! - - @license - handlebars v4.7.7 - -Copyright (C) 2011-2019 by Yehuda Katz - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - {module.exports = factory();} - else if(typeof define === 'function' && define.amd) - {define([], factory);} - else if(typeof exports === 'object') - {exports["Handlebars"] = factory();} - else - {root["Handlebars"] = factory();} -})(this, function() { -return /******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ let installedModules = {}; - -/******/ // The require function -/******/ function __webpack_require__(moduleId) { - -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) -/******/ {return installedModules[moduleId].exports;} - -/******/ // Create a new module (and put it into the cache) -/******/ let module = installedModules[moduleId] = { -/******/ exports: {}, -/******/ id: moduleId, -/******/ loaded: false -/******/ }; - -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - -/******/ // Flag the module as loaded -/******/ module.loaded = true; - -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } - - -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; - -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; - -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; - -/******/ // Load entry module and return exports -/******/ return __webpack_require__(0); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _interopRequireWildcard = __webpack_require__(1)['default']; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - - let _handlebarsBase = __webpack_require__(3); - - let base = _interopRequireWildcard(_handlebarsBase); - - // Each of these augment the Handlebars object. No need to setup here. - // (This is done to easily share code between commonjs and browse envs) - - let _handlebarsSafeString = __webpack_require__(36); - - let _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString); - - let _handlebarsException = __webpack_require__(5); - - let _handlebarsException2 = _interopRequireDefault(_handlebarsException); - - let _handlebarsUtils = __webpack_require__(4); - - let Utils = _interopRequireWildcard(_handlebarsUtils); - - let _handlebarsRuntime = __webpack_require__(37); - - let runtime = _interopRequireWildcard(_handlebarsRuntime); - - let _handlebarsNoConflict = __webpack_require__(43); - - let _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); - - // For compatibility and usage outside of module systems, make the Handlebars object a namespace - function create() { - let hb = new base.HandlebarsEnvironment(); - - Utils.extend(hb, base); - hb.SafeString = _handlebarsSafeString2['default']; - hb.Exception = _handlebarsException2['default']; - hb.Utils = Utils; - hb.escapeExpression = Utils.escapeExpression; - - hb.VM = runtime; - hb.template = function (spec) { - return runtime.template(spec, hb); - }; - - return hb; - } - - let inst = create(); - inst.create = create; - - _handlebarsNoConflict2['default'](inst); - - inst['default'] = inst; - - exports['default'] = inst; - module.exports = exports['default']; - -/***/ }), -/* 1 */ -/***/ (function(module, exports) { - - "use strict"; - - exports["default"] = function (obj) { - if (obj && obj.__esModule) { - return obj; - } else { - let newObj = {}; - - if (obj != null) { - for (let key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) {newObj[key] = obj[key];} - } - } - - newObj["default"] = obj; - return newObj; - } - }; - - exports.__esModule = true; - -/***/ }), -/* 2 */ -/***/ (function(module, exports) { - - "use strict"; - - exports["default"] = function (obj) { - return obj && obj.__esModule ? obj : { - "default": obj - }; - }; - - exports.__esModule = true; - -/***/ }), -/* 3 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - exports.HandlebarsEnvironment = HandlebarsEnvironment; - - let _utils = __webpack_require__(4); - - let _exception = __webpack_require__(5); - - let _exception2 = _interopRequireDefault(_exception); - - let _helpers = __webpack_require__(9); - - let _decorators = __webpack_require__(29); - - let _logger = __webpack_require__(31); - - let _logger2 = _interopRequireDefault(_logger); - - let _internalProtoAccess = __webpack_require__(32); - - let VERSION = '4.7.7'; - exports.VERSION = VERSION; - let COMPILER_REVISION = 8; - exports.COMPILER_REVISION = COMPILER_REVISION; - let LAST_COMPATIBLE_COMPILER_REVISION = 7; - - exports.LAST_COMPATIBLE_COMPILER_REVISION = LAST_COMPATIBLE_COMPILER_REVISION; - let REVISION_CHANGES = { - 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it - 2: '== 1.0.0-rc.3', - 3: '== 1.0.0-rc.4', - 4: '== 1.x.x', - 5: '== 2.0.0-alpha.x', - 6: '>= 2.0.0-beta.1', - 7: '>= 4.0.0 <4.3.0', - 8: '>= 4.3.0' - }; - - exports.REVISION_CHANGES = REVISION_CHANGES; - let objectType = '[object Object]'; - - function HandlebarsEnvironment(helpers, partials, decorators) { - this.helpers = helpers || {}; - this.partials = partials || {}; - this.decorators = decorators || {}; - - _helpers.registerDefaultHelpers(this); - _decorators.registerDefaultDecorators(this); - } - - HandlebarsEnvironment.prototype = { - constructor: HandlebarsEnvironment, - - logger: _logger2['default'], - log: _logger2['default'].log, - - registerHelper: function registerHelper(name, fn) { - if (_utils.toString.call(name) === objectType) { - if (fn) { - throw new _exception2['default']('Arg not supported with multiple helpers'); - } - _utils.extend(this.helpers, name); - } else { - this.helpers[name] = fn; - } - }, - unregisterHelper: function unregisterHelper(name) { - delete this.helpers[name]; - }, - - registerPartial: function registerPartial(name, partial) { - if (_utils.toString.call(name) === objectType) { - _utils.extend(this.partials, name); - } else { - if (typeof partial === 'undefined') { - throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined'); - } - this.partials[name] = partial; - } - }, - unregisterPartial: function unregisterPartial(name) { - delete this.partials[name]; - }, - - registerDecorator: function registerDecorator(name, fn) { - if (_utils.toString.call(name) === objectType) { - if (fn) { - throw new _exception2['default']('Arg not supported with multiple decorators'); - } - _utils.extend(this.decorators, name); - } else { - this.decorators[name] = fn; - } - }, - unregisterDecorator: function unregisterDecorator(name) { - delete this.decorators[name]; - }, - /** - * Reset the memory of illegal property accesses that have already been logged. - * @deprecated should only be used in handlebars test-cases - */ - resetLoggedPropertyAccesses: function resetLoggedPropertyAccesses() { - _internalProtoAccess.resetLoggedProperties(); - } - }; - - let log = _logger2['default'].log; - - exports.log = log; - exports.createFrame = _utils.createFrame; - exports.logger = _logger2['default']; - -/***/ }), -/* 4 */ -/***/ (function(module, exports) { - - 'use strict'; - - exports.__esModule = true; - exports.extend = extend; - exports.indexOf = indexOf; - exports.escapeExpression = escapeExpression; - exports.isEmpty = isEmpty; - exports.createFrame = createFrame; - exports.blockParams = blockParams; - exports.appendContextPath = appendContextPath; - let escape = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '`': '`', - '=': '=' - }; - - let badChars = /[&<>"'`=]/g, - possible = /[&<>"'`=]/; - - function escapeChar(chr) { - return escape[chr]; - } - - function extend(obj /* , ...source */) { - for (let i = 1; i < arguments.length; i++) { - for (let key in arguments[i]) { - if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { - obj[key] = arguments[i][key]; - } - } - } - - return obj; - } - - let toString = Object.prototype.toString; - - exports.toString = toString; - // Sourced from lodash - // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt - /* eslint-disable func-style */ - let isFunction = function isFunction(value) { - return typeof value === 'function'; - }; - // fallback for older versions of Chrome and Safari - /* istanbul ignore next */ - if (isFunction(/x/)) { - exports.isFunction = isFunction = function (value) { - return typeof value === 'function' && toString.call(value) === '[object Function]'; - }; - } - exports.isFunction = isFunction; - - /* eslint-enable func-style */ - - /* istanbul ignore next */ - let isArray = Array.isArray || function (value) { - return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; - }; - - exports.isArray = isArray; - // Older IE versions do not directly support indexOf so we must implement our own, sadly. - - function indexOf(array, value) { - for (let i = 0, len = array.length; i < len; i++) { - if (array[i] === value) { - return i; - } - } - return -1; - } - - function escapeExpression(string) { - if (typeof string !== 'string') { - // don't escape SafeStrings, since they're already safe - if (string && string.toHTML) { - return string.toHTML(); - } else if (string == null) { - return ''; - } else if (!string) { - return string + ''; - } - - // Force a string conversion as this will be done by the append regardless and - // the regex test will do this transparently behind the scenes, causing issues if - // an object's to string has escaped characters in it. - string = '' + string; - } - - if (!possible.test(string)) { - return string; - } - return string.replace(badChars, escapeChar); - } - - function isEmpty(value) { - if (!value && value !== 0) { - return true; - } else if (isArray(value) && value.length === 0) { - return true; - } else { - return false; - } - } - - function createFrame(object) { - let frame = extend({}, object); - frame._parent = object; - return frame; - } - - function blockParams(params, ids) { - params.path = ids; - return params; - } - - function appendContextPath(contextPath, id) { - return (contextPath ? contextPath + '.' : '') + id; - } - -/***/ }), -/* 5 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _Object$defineProperty = __webpack_require__(6)['default']; - - exports.__esModule = true; - let errorProps = ['description', 'fileName', 'lineNumber', 'endLineNumber', 'message', 'name', 'number', 'stack']; - - function Exception(message, node) { - let loc = node && node.loc, - line = undefined, - endLineNumber = undefined, - column = undefined, - endColumn = undefined; - - if (loc) { - line = loc.start.line; - endLineNumber = loc.end.line; - column = loc.start.column; - endColumn = loc.end.column; - - message += ' - ' + line + ':' + column; - } - - let tmp = Error.prototype.constructor.call(this, message); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (let idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } - - /* istanbul ignore else */ - if (Error.captureStackTrace) { - Error.captureStackTrace(this, Exception); - } - - try { - if (loc) { - this.lineNumber = line; - this.endLineNumber = endLineNumber; - - // Work around issue under safari where we can't directly set the column value - /* istanbul ignore next */ - if (_Object$defineProperty) { - Object.defineProperty(this, 'column', { - value: column, - enumerable: true - }); - Object.defineProperty(this, 'endColumn', { - value: endColumn, - enumerable: true - }); - } else { - this.column = column; - this.endColumn = endColumn; - } - } - } catch (nop) { - /* Ignore if the browser is very particular */ - } - } - - Exception.prototype = new Error(); - - exports['default'] = Exception; - module.exports = exports['default']; - -/***/ }), -/* 6 */ -/***/ (function(module, exports, __webpack_require__) { - - module.exports = { "default": __webpack_require__(7), __esModule: true }; - -/***/ }), -/* 7 */ -/***/ (function(module, exports, __webpack_require__) { - - let $ = __webpack_require__(8); - module.exports = function defineProperty(it, key, desc){ - return $.setDesc(it, key, desc); - }; - -/***/ }), -/* 8 */ -/***/ (function(module, exports) { - - let $Object = Object; - module.exports = { - create: $Object.create, - getProto: $Object.getPrototypeOf, - isEnum: {}.propertyIsEnumerable, - getDesc: $Object.getOwnPropertyDescriptor, - setDesc: $Object.defineProperty, - setDescs: $Object.defineProperties, - getKeys: $Object.keys, - getNames: $Object.getOwnPropertyNames, - getSymbols: $Object.getOwnPropertySymbols, - each: [].forEach - }; - -/***/ }), -/* 9 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - exports.registerDefaultHelpers = registerDefaultHelpers; - exports.moveHelperToHooks = moveHelperToHooks; - - let _helpersBlockHelperMissing = __webpack_require__(10); - - let _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing); - - let _helpersEach = __webpack_require__(11); - - let _helpersEach2 = _interopRequireDefault(_helpersEach); - - let _helpersHelperMissing = __webpack_require__(24); - - let _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing); - - let _helpersIf = __webpack_require__(25); - - let _helpersIf2 = _interopRequireDefault(_helpersIf); - - let _helpersLog = __webpack_require__(26); - - let _helpersLog2 = _interopRequireDefault(_helpersLog); - - let _helpersLookup = __webpack_require__(27); - - let _helpersLookup2 = _interopRequireDefault(_helpersLookup); - - let _helpersWith = __webpack_require__(28); - - let _helpersWith2 = _interopRequireDefault(_helpersWith); - - function registerDefaultHelpers(instance) { - _helpersBlockHelperMissing2['default'](instance); - _helpersEach2['default'](instance); - _helpersHelperMissing2['default'](instance); - _helpersIf2['default'](instance); - _helpersLog2['default'](instance); - _helpersLookup2['default'](instance); - _helpersWith2['default'](instance); - } - - function moveHelperToHooks(instance, helperName, keepHelper) { - if (instance.helpers[helperName]) { - instance.hooks[helperName] = instance.helpers[helperName]; - if (!keepHelper) { - delete instance.helpers[helperName]; - } - } - } - -/***/ }), -/* 10 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - exports.__esModule = true; - - let _utils = __webpack_require__(4); - - exports['default'] = function (instance) { - instance.registerHelper('blockHelperMissing', function (context, options) { - let inverse = options.inverse, - fn = options.fn; - - if (context === true) { - return fn(this); - } else if (context === false || context == null) { - return inverse(this); - } else if (_utils.isArray(context)) { - if (context.length > 0) { - if (options.ids) { - options.ids = [options.name]; - } - - return instance.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - if (options.data && options.ids) { - let data = _utils.createFrame(options.data); - data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name); - options = { data }; - } - - return fn(context, options); - } - }); - }; - - module.exports = exports['default']; - -/***/ }), -/* 11 */ -/***/ (function(module, exports, __webpack_require__) { - - /* WEBPACK VAR INJECTION */(function(global) {'use strict'; - - let _Object$keys = __webpack_require__(12)['default']; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - - let _utils = __webpack_require__(4); - - let _exception = __webpack_require__(5); - - let _exception2 = _interopRequireDefault(_exception); - - exports['default'] = function (instance) { - instance.registerHelper('each', function (context, options) { - if (!options) { - throw new _exception2['default']('Must pass iterator to #each'); - } - - let fn = options.fn, - inverse = options.inverse, - i = 0, - ret = '', - data = undefined, - contextPath = undefined; - - if (options.data && options.ids) { - contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; - } - - if (_utils.isFunction(context)) { - context = context.call(this); - } - - if (options.data) { - data = _utils.createFrame(options.data); - } - - function execIteration(field, index, last) { - if (data) { - data.key = field; - data.index = index; - data.first = index === 0; - data.last = !!last; - - if (contextPath) { - data.contextPath = contextPath + field; - } - } - - ret = ret + fn(context[field], { - data, - blockParams: _utils.blockParams([context[field], field], [contextPath + field, null]) - }); - } - - if (context && typeof context === 'object') { - if (_utils.isArray(context)) { - for (var j = context.length; i < j; i++) { - if (i in context) { - execIteration(i, i, i === context.length - 1); - } - } - } else if (global.Symbol && context[global.Symbol.iterator]) { - let newContext = []; - let iterator = context[global.Symbol.iterator](); - for (let it = iterator.next(); !it.done; it = iterator.next()) { - newContext.push(it.value); - } - context = newContext; - for (var j = context.length; i < j; i++) { - execIteration(i, i, i === context.length - 1); - } - } else { - (function () { - let priorKey = undefined; - - _Object$keys(context).forEach(function (key) { - // We're running the iterations one step out of sync so we can detect - // the last iteration without have to scan the object twice and create - // an itermediate keys array. - if (priorKey !== undefined) { - execIteration(priorKey, i - 1); - } - priorKey = key; - i++; - }); - if (priorKey !== undefined) { - execIteration(priorKey, i - 1, true); - } - })(); - } - } - - if (i === 0) { - ret = inverse(this); - } - - return ret; - }); - }; - - module.exports = exports['default']; - /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; })())); - -/***/ }), -/* 12 */ -/***/ (function(module, exports, __webpack_require__) { - - module.exports = { "default": __webpack_require__(13), __esModule: true }; - -/***/ }), -/* 13 */ -/***/ (function(module, exports, __webpack_require__) { - - __webpack_require__(14); - module.exports = __webpack_require__(20).Object.keys; - -/***/ }), -/* 14 */ -/***/ (function(module, exports, __webpack_require__) { - - // 19.1.2.14 Object.keys(O) - let toObject = __webpack_require__(15); - - __webpack_require__(17)('keys', function($keys){ - return function keys(it){ - return $keys(toObject(it)); - }; - }); - -/***/ }), -/* 15 */ -/***/ (function(module, exports, __webpack_require__) { - - // 7.1.13 ToObject(argument) - let defined = __webpack_require__(16); - module.exports = function(it){ - return Object(defined(it)); - }; - -/***/ }), -/* 16 */ -/***/ (function(module, exports) { - - // 7.2.1 RequireObjectCoercible(argument) - module.exports = function(it){ - if(it == undefined){throw TypeError("Can't call method on " + it);} - return it; - }; - -/***/ }), -/* 17 */ -/***/ (function(module, exports, __webpack_require__) { - - // most Object methods by ES6 should accept primitives - let $export = __webpack_require__(18) - , core = __webpack_require__(20) - , fails = __webpack_require__(23); - module.exports = function(KEY, exec){ - let fn = (core.Object || {})[KEY] || Object[KEY] - , exp = {}; - exp[KEY] = exec(fn); - $export($export.S + $export.F * fails(function(){ fn(1); }), 'Object', exp); - }; - -/***/ }), -/* 18 */ -/***/ (function(module, exports, __webpack_require__) { - - let global = __webpack_require__(19) - , core = __webpack_require__(20) - , ctx = __webpack_require__(21) - , PROTOTYPE = 'prototype'; - - var $export = function(type, name, source){ - let IS_FORCED = type & $export.F - , IS_GLOBAL = type & $export.G - , IS_STATIC = type & $export.S - , IS_PROTO = type & $export.P - , IS_BIND = type & $export.B - , IS_WRAP = type & $export.W - , exports = IS_GLOBAL ? core : core[name] || (core[name] = {}) - , target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE] - , key, own, out; - if(IS_GLOBAL){source = name;} - for(key in source){ - // contains in native - own = !IS_FORCED && target && key in target; - if(own && key in exports){continue;} - // export native or passed - out = own ? target[key] : source[key]; - // prevent global pollution for namespaces - exports[key] = IS_GLOBAL && typeof target[key] !== 'function' ? source[key] - // bind timers to global for call from export context - : IS_BIND && own ? ctx(out, global) - // wrap global constructors for prevent change them in library - : IS_WRAP && target[key] == out ? (function(C){ - let F = function(param){ - return this instanceof C ? new C(param) : C(param); - }; - F[PROTOTYPE] = C[PROTOTYPE]; - return F; - // make static versions for prototype methods - })(out) : IS_PROTO && typeof out === 'function' ? ctx(Function.call, out) : out; - if(IS_PROTO){(exports[PROTOTYPE] || (exports[PROTOTYPE] = {}))[key] = out;} - } - }; - // type bitmap - $export.F = 1; // forced - $export.G = 2; // global - $export.S = 4; // static - $export.P = 8; // proto - $export.B = 16; // bind - $export.W = 32; // wrap - module.exports = $export; - -/***/ }), -/* 19 */ -/***/ (function(module, exports) { - - // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 - let global = module.exports = typeof window !== 'undefined' && window.Math == Math - ? window : typeof self !== 'undefined' && self.Math == Math ? self : Function('return this')(); - if(typeof __g === 'number'){__g = global;} // eslint-disable-line no-undef - -/***/ }), -/* 20 */ -/***/ (function(module, exports) { - - let core = module.exports = {version: '1.2.6'}; - if(typeof __e === 'number'){__e = core;} // eslint-disable-line no-undef - -/***/ }), -/* 21 */ -/***/ (function(module, exports, __webpack_require__) { - - // optional / simple context binding - let aFunction = __webpack_require__(22); - module.exports = function(fn, that, length){ - aFunction(fn); - if(that === undefined){return fn;} - switch(length){ - case 1: return function(a){ - return fn.call(that, a); - }; - case 2: return function(a, b){ - return fn.call(that, a, b); - }; - case 3: return function(a, b, c){ - return fn.call(that, a, b, c); - }; - } - return function(/* ...args */){ - return fn.apply(that, arguments); - }; - }; - -/***/ }), -/* 22 */ -/***/ (function(module, exports) { - - module.exports = function(it){ - if(typeof it !== 'function'){throw TypeError(it + ' is not a function!');} - return it; - }; - -/***/ }), -/* 23 */ -/***/ (function(module, exports) { - - module.exports = function(exec){ - try { - return !!exec(); - } catch(e){ - return true; - } - }; - -/***/ }), -/* 24 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - - let _exception = __webpack_require__(5); - - let _exception2 = _interopRequireDefault(_exception); - - exports['default'] = function (instance) { - instance.registerHelper('helperMissing', function () /* [args, ]options */{ - if (arguments.length === 1) { - // A missing field in a {{foo}} construct. - return undefined; - } else { - // Someone is actually trying to call something, blow up. - throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"'); - } - }); - }; - - module.exports = exports['default']; - -/***/ }), -/* 25 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - - let _utils = __webpack_require__(4); - - let _exception = __webpack_require__(5); - - let _exception2 = _interopRequireDefault(_exception); - - exports['default'] = function (instance) { - instance.registerHelper('if', function (conditional, options) { - if (arguments.length != 2) { - throw new _exception2['default']('#if requires exactly one argument'); - } - if (_utils.isFunction(conditional)) { - conditional = conditional.call(this); - } - - // Default behavior is to render the positive path if the value is truthy and not empty. - // The `includeZero` option may be set to treat the condtional as purely not empty based on the - // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. - if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) { - return options.inverse(this); - } else { - return options.fn(this); - } - }); - - instance.registerHelper('unless', function (conditional, options) { - if (arguments.length != 2) { - throw new _exception2['default']('#unless requires exactly one argument'); - } - return instance.helpers['if'].call(this, conditional, { - fn: options.inverse, - inverse: options.fn, - hash: options.hash - }); - }); - }; - - module.exports = exports['default']; - -/***/ }), -/* 26 */ -/***/ (function(module, exports) { - - 'use strict'; - - exports.__esModule = true; - - exports['default'] = function (instance) { - instance.registerHelper('log', function () /* message, options */{ - let args = [undefined], - options = arguments[arguments.length - 1]; - for (let i = 0; i < arguments.length - 1; i++) { - args.push(arguments[i]); - } - - let level = 1; - if (options.hash.level != null) { - level = options.hash.level; - } else if (options.data && options.data.level != null) { - level = options.data.level; - } - args[0] = level; - - instance.log.apply(instance, args); - }); - }; - - module.exports = exports['default']; - -/***/ }), -/* 27 */ -/***/ (function(module, exports) { - - 'use strict'; - - exports.__esModule = true; - - exports['default'] = function (instance) { - instance.registerHelper('lookup', function (obj, field, options) { - if (!obj) { - // Note for 5.0: Change to "obj == null" in 5.0 - return obj; - } - return options.lookupProperty(obj, field); - }); - }; - - module.exports = exports['default']; - -/***/ }), -/* 28 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - - let _utils = __webpack_require__(4); - - let _exception = __webpack_require__(5); - - let _exception2 = _interopRequireDefault(_exception); - - exports['default'] = function (instance) { - instance.registerHelper('with', function (context, options) { - if (arguments.length != 2) { - throw new _exception2['default']('#with requires exactly one argument'); - } - if (_utils.isFunction(context)) { - context = context.call(this); - } - - let fn = options.fn; - - if (!_utils.isEmpty(context)) { - let data = options.data; - if (options.data && options.ids) { - data = _utils.createFrame(options.data); - data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]); - } - - return fn(context, { - data, - blockParams: _utils.blockParams([context], [data && data.contextPath]) - }); - } else { - return options.inverse(this); - } - }); - }; - - module.exports = exports['default']; - -/***/ }), -/* 29 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - exports.registerDefaultDecorators = registerDefaultDecorators; - - let _decoratorsInline = __webpack_require__(30); - - let _decoratorsInline2 = _interopRequireDefault(_decoratorsInline); - - function registerDefaultDecorators(instance) { - _decoratorsInline2['default'](instance); - } - -/***/ }), -/* 30 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - exports.__esModule = true; - - let _utils = __webpack_require__(4); - - exports['default'] = function (instance) { - instance.registerDecorator('inline', function (fn, props, container, options) { - let ret = fn; - if (!props.partials) { - props.partials = {}; - ret = function (context, options) { - // Create a new partials stack frame prior to exec. - let original = container.partials; - container.partials = _utils.extend({}, original, props.partials); - let ret = fn(context, options); - container.partials = original; - return ret; - }; - } - - props.partials[options.args[0]] = options.fn; - - return ret; - }); - }; - - module.exports = exports['default']; - -/***/ }), -/* 31 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - exports.__esModule = true; - - let _utils = __webpack_require__(4); - - var logger = { - methodMap: ['debug', 'info', 'warn', 'error'], - level: 'info', - - // Maps a given level value to the `methodMap` indexes above. - lookupLevel: function lookupLevel(level) { - if (typeof level === 'string') { - let levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase()); - if (levelMap >= 0) { - level = levelMap; - } else { - level = parseInt(level, 10); - } - } - - return level; - }, - - // Can be overridden in the host environment - log: function log(level) { - level = logger.lookupLevel(level); - - if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { - let method = logger.methodMap[level]; - // eslint-disable-next-line no-console - if (!console[method]) { - method = 'log'; - } - - for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - message[_key - 1] = arguments[_key]; - } - - console[method].apply(console, message); // eslint-disable-line no-console - } - } - }; - - exports['default'] = logger; - module.exports = exports['default']; - -/***/ }), -/* 32 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _Object$create = __webpack_require__(33)['default']; - - let _Object$keys = __webpack_require__(12)['default']; - - let _interopRequireWildcard = __webpack_require__(1)['default']; - - exports.__esModule = true; - exports.createProtoAccessControl = createProtoAccessControl; - exports.resultIsAllowed = resultIsAllowed; - exports.resetLoggedProperties = resetLoggedProperties; - - let _createNewLookupObject = __webpack_require__(35); - - let _logger = __webpack_require__(31); - - let logger = _interopRequireWildcard(_logger); - - let loggedProperties = _Object$create(null); - - function createProtoAccessControl(runtimeOptions) { - let defaultMethodWhiteList = _Object$create(null); - defaultMethodWhiteList['constructor'] = false; - defaultMethodWhiteList['__defineGetter__'] = false; - defaultMethodWhiteList['__defineSetter__'] = false; - defaultMethodWhiteList['__lookupGetter__'] = false; - - let defaultPropertyWhiteList = _Object$create(null); - // eslint-disable-next-line no-proto - defaultPropertyWhiteList['__proto__'] = false; - - return { - properties: { - whitelist: _createNewLookupObject.createNewLookupObject(defaultPropertyWhiteList, runtimeOptions.allowedProtoProperties), - defaultValue: runtimeOptions.allowProtoPropertiesByDefault - }, - methods: { - whitelist: _createNewLookupObject.createNewLookupObject(defaultMethodWhiteList, runtimeOptions.allowedProtoMethods), - defaultValue: runtimeOptions.allowProtoMethodsByDefault - } - }; - } - - function resultIsAllowed(result, protoAccessControl, propertyName) { - if (typeof result === 'function') { - return checkWhiteList(protoAccessControl.methods, propertyName); - } else { - return checkWhiteList(protoAccessControl.properties, propertyName); - } - } - - function checkWhiteList(protoAccessControlForType, propertyName) { - if (protoAccessControlForType.whitelist[propertyName] !== undefined) { - return protoAccessControlForType.whitelist[propertyName] === true; - } - if (protoAccessControlForType.defaultValue !== undefined) { - return protoAccessControlForType.defaultValue; - } - logUnexpecedPropertyAccessOnce(propertyName); - return false; - } - - function logUnexpecedPropertyAccessOnce(propertyName) { - if (loggedProperties[propertyName] !== true) { - loggedProperties[propertyName] = true; - logger.log('error', 'Handlebars: Access has been denied to resolve the property "' + propertyName + '" because it is not an "own property" of its parent.\n' + 'You can add a runtime option to disable the check or this warning:\n' + 'See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details'); - } - } - - function resetLoggedProperties() { - _Object$keys(loggedProperties).forEach(function (propertyName) { - delete loggedProperties[propertyName]; - }); - } - -/***/ }), -/* 33 */ -/***/ (function(module, exports, __webpack_require__) { - - module.exports = { "default": __webpack_require__(34), __esModule: true }; - -/***/ }), -/* 34 */ -/***/ (function(module, exports, __webpack_require__) { - - let $ = __webpack_require__(8); - module.exports = function create(P, D){ - return $.create(P, D); - }; - -/***/ }), -/* 35 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _Object$create = __webpack_require__(33)['default']; - - exports.__esModule = true; - exports.createNewLookupObject = createNewLookupObject; - - let _utils = __webpack_require__(4); - - /** - * Create a new object with "null"-prototype to avoid truthy results on prototype properties. - * The resulting object can be used with "object[property]" to check if a property exists - * @param {...object} sources a varargs parameter of source objects that will be merged - * @returns {object} - */ - - function createNewLookupObject() { - for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) { - sources[_key] = arguments[_key]; - } - - return _utils.extend.apply(undefined, [_Object$create(null)].concat(sources)); - } - -/***/ }), -/* 36 */ -/***/ (function(module, exports) { - - // Build out our basic SafeString type - 'use strict'; - - exports.__esModule = true; - function SafeString(string) { - this.string = string; - } - - SafeString.prototype.toString = SafeString.prototype.toHTML = function () { - return '' + this.string; - }; - - exports['default'] = SafeString; - module.exports = exports['default']; - -/***/ }), -/* 37 */ -/***/ (function(module, exports, __webpack_require__) { - - 'use strict'; - - let _Object$seal = __webpack_require__(38)['default']; - - let _Object$keys = __webpack_require__(12)['default']; - - let _interopRequireWildcard = __webpack_require__(1)['default']; - - let _interopRequireDefault = __webpack_require__(2)['default']; - - exports.__esModule = true; - exports.checkRevision = checkRevision; - exports.template = template; - exports.wrapProgram = wrapProgram; - exports.resolvePartial = resolvePartial; - exports.invokePartial = invokePartial; - exports.noop = noop; - - let _utils = __webpack_require__(4); - - let Utils = _interopRequireWildcard(_utils); - - let _exception = __webpack_require__(5); - - let _exception2 = _interopRequireDefault(_exception); - - let _base = __webpack_require__(3); - - let _helpers = __webpack_require__(9); - - let _internalWrapHelper = __webpack_require__(42); - - let _internalProtoAccess = __webpack_require__(32); - - function checkRevision(compilerInfo) { - let compilerRevision = compilerInfo && compilerInfo[0] || 1, - currentRevision = _base.COMPILER_REVISION; - - if (compilerRevision >= _base.LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= _base.COMPILER_REVISION) { - return; - } - - if (compilerRevision < _base.LAST_COMPATIBLE_COMPILER_REVISION) { - let runtimeVersions = _base.REVISION_CHANGES[currentRevision], - compilerVersions = _base.REVISION_CHANGES[compilerRevision]; - throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); - } else { - // Use the embedded version info since the runtime doesn't know about this revision yet - throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); - } - } - - function template(templateSpec, env) { - /* istanbul ignore next */ - if (!env) { - throw new _exception2['default']('No environment passed to template'); - } - if (!templateSpec || !templateSpec.main) { - throw new _exception2['default']('Unknown template object: ' + typeof templateSpec); - } - - templateSpec.main.decorator = templateSpec.main_d; - - // Note: Using env.VM references rather than local var references throughout this section to allow - // for external users to override these as pseudo-supported APIs. - env.VM.checkRevision(templateSpec.compiler); - - // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0) - let templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7; - - function invokePartialWrapper(partial, context, options) { - if (options.hash) { - context = Utils.extend({}, context, options.hash); - if (options.ids) { - options.ids[0] = true; - } - } - partial = env.VM.resolvePartial.call(this, partial, context, options); - - let extendedOptions = Utils.extend({}, options, { - hooks: this.hooks, - protoAccessControl: this.protoAccessControl - }); - - let result = env.VM.invokePartial.call(this, partial, context, extendedOptions); - - if (result == null && env.compile) { - options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); - result = options.partials[options.name](context, extendedOptions); - } - if (result != null) { - if (options.indent) { - let lines = result.split('\n'); - for (let i = 0, l = lines.length; i < l; i++) { - if (!lines[i] && i + 1 === l) { - break; - } - - lines[i] = options.indent + lines[i]; - } - result = lines.join('\n'); - } - return result; - } else { - throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); - } - } - - // Just add water - var container = { - strict: function strict(obj, name, loc) { - if (!obj || !(name in obj)) { - throw new _exception2['default']('"' + name + '" not defined in ' + obj, { - loc - }); - } - return container.lookupProperty(obj, name); - }, - lookupProperty: function lookupProperty(parent, propertyName) { - let result = parent[propertyName]; - if (result == null) { - return result; - } - if (Object.prototype.hasOwnProperty.call(parent, propertyName)) { - return result; - } - - if (_internalProtoAccess.resultIsAllowed(result, container.protoAccessControl, propertyName)) { - return result; - } - return undefined; - }, - lookup: function lookup(depths, name) { - let len = depths.length; - for (let i = 0; i < len; i++) { - let result = depths[i] && container.lookupProperty(depths[i], name); - if (result != null) { - return depths[i][name]; - } - } - }, - lambda: function lambda(current, context) { - return typeof current === 'function' ? current.call(context) : current; - }, - - escapeExpression: Utils.escapeExpression, - invokePartial: invokePartialWrapper, - - fn: function fn(i) { - let ret = templateSpec[i]; - ret.decorator = templateSpec[i + '_d']; - return ret; - }, - - programs: [], - program: function program(i, data, declaredBlockParams, blockParams, depths) { - let programWrapper = this.programs[i], - fn = this.fn(i); - if (data || depths || blockParams || declaredBlockParams) { - programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); - } else if (!programWrapper) { - programWrapper = this.programs[i] = wrapProgram(this, i, fn); - } - return programWrapper; - }, - - data: function data(value, depth) { - while (value && depth--) { - value = value._parent; - } - return value; - }, - mergeIfNeeded: function mergeIfNeeded(param, common) { - let obj = param || common; - - if (param && common && param !== common) { - obj = Utils.extend({}, common, param); - } - - return obj; - }, - // An empty object to use as replacement for null-contexts - nullContext: _Object$seal({}), - - noop: env.VM.noop, - compilerInfo: templateSpec.compiler - }; - - function ret(context) { - let options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - let data = options.data; - - ret._setup(options); - if (!options.partial && templateSpec.useData) { - data = initData(context, data); - } - let depths = undefined, - blockParams = templateSpec.useBlockParams ? [] : undefined; - if (templateSpec.useDepths) { - if (options.depths) { - depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths; - } else { - depths = [context]; - } - } - - function main(context /*, options*/) { - return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); - } - - main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); - return main(context, options); - } - - ret.isTop = true; - - ret._setup = function (options) { - if (!options.partial) { - let mergedHelpers = Utils.extend({}, env.helpers, options.helpers); - wrapHelpersToPassLookupProperty(mergedHelpers, container); - container.helpers = mergedHelpers; - - if (templateSpec.usePartial) { - // Use mergeIfNeeded here to prevent compiling global partials multiple times - container.partials = container.mergeIfNeeded(options.partials, env.partials); - } - if (templateSpec.usePartial || templateSpec.useDecorators) { - container.decorators = Utils.extend({}, env.decorators, options.decorators); - } - - container.hooks = {}; - container.protoAccessControl = _internalProtoAccess.createProtoAccessControl(options); - - let keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7; - _helpers.moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers); - _helpers.moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers); - } else { - container.protoAccessControl = options.protoAccessControl; // internal option - container.helpers = options.helpers; - container.partials = options.partials; - container.decorators = options.decorators; - container.hooks = options.hooks; - } - }; - - ret._child = function (i, data, blockParams, depths) { - if (templateSpec.useBlockParams && !blockParams) { - throw new _exception2['default']('must pass block params'); - } - if (templateSpec.useDepths && !depths) { - throw new _exception2['default']('must pass parent depths'); - } - - return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); - }; - return ret; - } - - function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { - function prog(context) { - let options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - let currentDepths = depths; - if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) { - currentDepths = [context].concat(depths); - } - - return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths); - } - - prog = executeDecorators(fn, prog, container, depths, data, blockParams); - - prog.program = i; - prog.depth = depths ? depths.length : 0; - prog.blockParams = declaredBlockParams || 0; - return prog; - } - - /** - * This is currently part of the official API, therefore implementation details should not be changed. - */ - - function resolvePartial(partial, context, options) { - if (!partial) { - if (options.name === '@partial-block') { - partial = options.data['partial-block']; - } else { - partial = options.partials[options.name]; - } - } else if (!partial.call && !options.name) { - // This is a dynamic partial that returned a string - options.name = partial; - partial = options.partials[partial]; - } - return partial; - } - - function invokePartial(partial, context, options) { - // Use the current closure context to save the partial-block if this partial - let currentPartialBlock = options.data && options.data['partial-block']; - options.partial = true; - if (options.ids) { - options.data.contextPath = options.ids[0] || options.data.contextPath; - } - - let partialBlock = undefined; - if (options.fn && options.fn !== noop) { - (function () { - options.data = _base.createFrame(options.data); - // Wrapper function to get access to currentPartialBlock from the closure - let fn = options.fn; - partialBlock = options.data['partial-block'] = function partialBlockWrapper(context) { - let options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - - // Restore the partial-block from the closure for the execution of the block - // i.e. the part inside the block of the partial call. - options.data = _base.createFrame(options.data); - options.data['partial-block'] = currentPartialBlock; - return fn(context, options); - }; - if (fn.partials) { - options.partials = Utils.extend({}, options.partials, fn.partials); - } - })(); - } - - if (partial === undefined && partialBlock) { - partial = partialBlock; - } - - if (partial === undefined) { - throw new _exception2['default']('The partial ' + options.name + ' could not be found'); - } else if (partial instanceof Function) { - return partial(context, options); - } - } - - function noop() { - return ''; - } - - function initData(context, data) { - if (!data || !('root' in data)) { - data = data ? _base.createFrame(data) : {}; - data.root = context; - } - return data; - } - - function executeDecorators(fn, prog, container, depths, data, blockParams) { - if (fn.decorator) { - let props = {}; - prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); - Utils.extend(prog, props); - } - return prog; - } - - function wrapHelpersToPassLookupProperty(mergedHelpers, container) { - _Object$keys(mergedHelpers).forEach(function (helperName) { - let helper = mergedHelpers[helperName]; - mergedHelpers[helperName] = passLookupPropertyOption(helper, container); - }); - } - - function passLookupPropertyOption(helper, container) { - let lookupProperty = container.lookupProperty; - return _internalWrapHelper.wrapHelper(helper, function (options) { - return Utils.extend({ lookupProperty }, options); - }); - } - -/***/ }), -/* 38 */ -/***/ (function(module, exports, __webpack_require__) { - - module.exports = { "default": __webpack_require__(39), __esModule: true }; - -/***/ }), -/* 39 */ -/***/ (function(module, exports, __webpack_require__) { - - __webpack_require__(40); - module.exports = __webpack_require__(20).Object.seal; - -/***/ }), -/* 40 */ -/***/ (function(module, exports, __webpack_require__) { - - // 19.1.2.17 Object.seal(O) - let isObject = __webpack_require__(41); - - __webpack_require__(17)('seal', function($seal){ - return function seal(it){ - return $seal && isObject(it) ? $seal(it) : it; - }; - }); - -/***/ }), -/* 41 */ -/***/ (function(module, exports) { - - module.exports = function(it){ - return typeof it === 'object' ? it !== null : typeof it === 'function'; - }; - -/***/ }), -/* 42 */ -/***/ (function(module, exports) { - - 'use strict'; - - exports.__esModule = true; - exports.wrapHelper = wrapHelper; - - function wrapHelper(helper, transformOptionsFn) { - if (typeof helper !== 'function') { - // This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639 - // We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function. - return helper; - } - let wrapper = function wrapper() /* dynamic arguments */{ - let options = arguments[arguments.length - 1]; - arguments[arguments.length - 1] = transformOptionsFn(options); - return helper.apply(this, arguments); - }; - return wrapper; - } - -/***/ }), -/* 43 */ -/***/ (function(module, exports) { - - /* WEBPACK VAR INJECTION */(function(global) {'use strict'; - - exports.__esModule = true; - - exports['default'] = function (Handlebars) { - /* istanbul ignore next */ - let root = typeof global !== 'undefined' ? global : window, - $Handlebars = root.Handlebars; - /* istanbul ignore next */ - Handlebars.noConflict = function () { - if (root.Handlebars === Handlebars) { - root.Handlebars = $Handlebars; - } - return Handlebars; - }; - }; - - module.exports = exports['default']; - /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; })())); - -/***/ }) -/******/ ]); -}); -; diff --git a/assets/javascripts/legacy/itsatrap.js b/assets/javascripts/legacy/itsatrap.js deleted file mode 100644 index ae5ad391..00000000 --- a/assets/javascripts/legacy/itsatrap.js +++ /dev/null @@ -1,1161 +0,0 @@ -// discourse-skip-module - -/*global define:false */ -/** - * Copyright 2012-2017 Craig Campbell - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * ItsATrap is a simple keyboard shortcut library for Javascript with - * no external dependencies - * - * @version 2.0.1 - * @url github.com/discourse/itsatrap - */ -(function(window, document, undefined) { - // Check if itsatrap is used inside browser, if not, return - if (!window) { - return; - } - - /** - * mapping of special keycodes to their corresponding keys - * - * everything in this dictionary cannot use keypress events - * so it has to be here to map to the correct keycodes for - * keyup/keydown events - * - * @type {Object} - */ - let _MAP = { - 8: "backspace", - 9: "tab", - 13: "enter", - 16: "shift", - 17: "ctrl", - 18: "alt", - 20: "capslock", - 27: "esc", - 32: "space", - 33: "pageup", - 34: "pagedown", - 35: "end", - 36: "home", - 37: "left", - 38: "up", - 39: "right", - 40: "down", - 45: "ins", - 46: "del", - 91: "meta", - 93: "meta", - 224: "meta" - }; - - /** - * mapping for special characters so they can support - * - * this dictionary is only used incase you want to bind a - * keyup or keydown event to one of these keys - * - * @type {Object} - */ - let _KEYCODE_MAP = { - 106: "*", - 107: "+", - 109: "-", - 110: ".", - 111: "/", - 186: ";", - 187: "=", - 188: ",", - 189: "-", - 190: ".", - 191: "/", - 192: "`", - 219: "[", - 220: "\\", - 221: "]", - 222: "'" - }; - - /** - * this is a mapping of keys that require shift on a US keypad - * back to the non shift equivelents - * - * this is so you can use keyup events with these keys - * - * note that this will only work reliably on US keyboards - * - * @type {Object} - */ - let _SHIFT_MAP = { - "~": "`", - "!": "1", - "@": "2", - "#": "3", - $: "4", - "%": "5", - "^": "6", - "&": "7", - "*": "8", - "(": "9", - ")": "0", - _: "-", - "+": "=", - ":": ";", - '"': "'", - "<": ",", - ">": ".", - "?": "/", - "|": "\\" - }; - - /** - * this is a list of special strings you can use to map - * to modifier keys when you specify your keyboard shortcuts - * - * @type {Object} - */ - let _SPECIAL_ALIASES = { - option: "alt", - command: "meta", - return: "enter", - escape: "esc", - plus: "+", - mod: /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? "meta" : "ctrl" - }; - - /** - * variable to store the flipped version of _MAP from above - * needed to check if we should use keypress or not when no action - * is specified - * - * @type {Object|undefined} - */ - let _REVERSE_MAP; - - /** - * holds a reference to global bindings - * - * @type {Object|undefined} - */ - let _globalCallbacks = {}; - - /** - * loop through the f keys, f1 to f19 and add them to the map - * programatically - */ - for (var i = 1; i < 20; ++i) { - _MAP[111 + i] = "f" + i; - } - - /** - * loop through to map numbers on the numeric keypad - */ - for (i = 0; i <= 9; ++i) { - // This needs to use a string cause otherwise since 0 is falsey - // itsatrap will never fire for numpad 0 pressed as part of a keydown - // event. - // - // @see https://github.com/ccampbell/itsatrap/pull/258 - _MAP[i + 96] = i.toString(); - } - - /** - * cross browser add event method - * - * @param {Element|HTMLDocument} object - * @param {string} type - * @param {EventListenerOrEventListenerObject} callback - * @returns void - */ - function _addEvent(object, type, callback) { - if (object.addEventListener) { - object.addEventListener(type, callback, false); - return; - } - - object.attachEvent("on" + type, callback); - } - - /** - * cross browser remove event method - * - * @param {Element|HTMLDocument} object - * @param {string} type - * @param {EventListenerOrEventListenerObject} callback - * @returns void - */ - function _removeEvent(object, type, callback) { - if (object.removeEventListener) { - object.removeEventListener(type, callback, false); - return; - } - object.detachEvent("on" + type, callback); - } - - /** - * takes the event and returns the key character - * - * @param {Event} e - * @return {string} - */ - function _characterFromEvent(e) { - // for keypress events we should return the character as is - if (e.type == "keypress") { - let character = String.fromCharCode(e.which); - - // if the shift key is not pressed then it is safe to assume - // that we want the character to be lowercase. this means if - // you accidentally have caps lock on then your key bindings - // will continue to work - // - // the only side effect that might not be desired is if you - // bind something like 'A' cause you want to trigger an - // event when capital A is pressed caps lock will no longer - // trigger the event. shift+a will though. - if (!e.shiftKey) { - character = character.toLowerCase(); - } - - return character; - } - - // for non keypress events the special maps are needed - if (_MAP[e.which]) { - return _MAP[e.which]; - } - - if (_KEYCODE_MAP[e.which]) { - return _KEYCODE_MAP[e.which]; - } - - // if it is not in the special map - - // with keydown and keyup events the character seems to always - // come in as an uppercase character whether you are pressing shift - // or not. we should make sure it is always lowercase for comparisons - return String.fromCharCode(e.which).toLowerCase(); - } - - /** - * checks if two arrays are equal - * - * @param {Array} modifiers1 - * @param {Array} modifiers2 - * @returns {boolean} - */ - function _modifiersMatch(modifiers1, modifiers2) { - return modifiers1.sort().join(",") === modifiers2.sort().join(","); - } - - /** - * takes a key event and figures out what the modifiers are - * - * @param {Event} e - * @returns {Array} - */ - function _eventModifiers(e) { - let modifiers = []; - - if (e.shiftKey) { - modifiers.push("shift"); - } - - if (e.altKey) { - modifiers.push("alt"); - } - - if (e.ctrlKey) { - modifiers.push("ctrl"); - } - - if (e.metaKey) { - modifiers.push("meta"); - } - - return modifiers; - } - - /** - * prevents default for this event - * - * @param {Event} e - * @returns void - */ - function _preventDefault(e) { - if (e.preventDefault) { - e.preventDefault(); - return; - } - - e.returnValue = false; - } - - /** - * stops propogation for this event - * - * @param {Event} e - * @returns void - */ - function _stopPropagation(e) { - if (e.stopPropagation) { - e.stopPropagation(); - return; - } - - e.cancelBubble = true; - } - - /** - * determines if the keycode specified is a modifier key or not - * - * @param {string} key - * @returns {boolean} - */ - function _isModifier(key) { - return key == "shift" || key == "ctrl" || key == "alt" || key == "meta"; - } - - /** - * reverses the map lookup so that we can look for specific keys - * to see what can and can't use keypress - * - * @return {Object} - */ - function _getReverseMap() { - if (!_REVERSE_MAP) { - _REVERSE_MAP = {}; - for (let key in _MAP) { - // pull out the numeric keypad from here cause keypress should - // be able to detect the keys from the character - if (key > 95 && key < 112) { - continue; - } - - if (_MAP.hasOwnProperty(key)) { - _REVERSE_MAP[_MAP[key]] = key; - } - } - } - return _REVERSE_MAP; - } - - /** - * picks the best action based on the key combination - * - * @param {string} key - character for key - * @param {Array} modifiers - * @param {string=} action passed in - */ - function _pickBestAction(key, modifiers, action) { - // if no action was picked in we should try to pick the one - // that we think would work best for this key - if (!action) { - action = _getReverseMap()[key] ? "keydown" : "keypress"; - } - - // modifier keys don't work as expected with keypress, - // switch to keydown - if (action == "keypress" && modifiers.length) { - action = "keydown"; - } - - return action; - } - - /** - * Converts from a string key combination to an array - * - * @param {string} combination like "command+shift+l" - * @return {Array} - */ - function _keysFromString(combination) { - if (combination === "+") { - return ["+"]; - } - - combination = combination.replace(/\+{2}/g, "+plus"); - return combination.split("+"); - } - - /** - * Gets info for a specific key combination - * - * @param {string} combination key combination ("command+s" or "a" or "*") - * @param {string=} action - * @returns {Object} - */ - function _getKeyInfo(combination, action) { - let keys; - let key; - let i; - let modifiers = []; - - // take the keys from this pattern and figure out what the actual - // pattern is all about - keys = _keysFromString(combination); - - for (i = 0; i < keys.length; ++i) { - key = keys[i]; - - // normalize key names - if (_SPECIAL_ALIASES[key]) { - key = _SPECIAL_ALIASES[key]; - } - - // if this is not a keypress event then we should - // be smart about using shift keys - // this will only work for US keyboards however - if (action && action != "keypress" && _SHIFT_MAP[key]) { - key = _SHIFT_MAP[key]; - modifiers.push("shift"); - } - - // if this key is a modifier then add it to the list of modifiers - if (_isModifier(key)) { - modifiers.push(key); - } - } - - // depending on what the key combination is - // we will try to pick the best event for it - action = _pickBestAction(key, modifiers, action); - - return { - key, - modifiers, - action - }; - } - - function _belongsTo(element, ancestor) { - if (element === null || element === document) { - return false; - } - - if (element === ancestor) { - return true; - } - - return _belongsTo(element.parentNode, ancestor); - } - - function ItsATrap(targetElement) { - let self = this; - - targetElement = targetElement || document; - - if (!(self instanceof ItsATrap)) { - return new ItsATrap(targetElement); - } - - /** - * element to attach key events to - * - * @type {Element} - */ - self.target = targetElement; - - /** - * a list of all the callbacks setup via ItsATrap.bind() - * - * @type {Object} - */ - self._callbacks = {}; - - /** - * direct map of string combinations to callbacks used for trigger() - * - * @type {Object} - */ - self._directMap = {}; - - /** - * keeps track of what level each sequence is at since multiple - * sequences can start out with the same sequence - * - * @type {Object} - */ - let _sequenceLevels = {}; - - /** - * variable to store the setTimeout call - * - * @type {null|number} - */ - let _resetTimer; - - /** - * temporary state where we will ignore the next keyup - * - * @type {boolean|string} - */ - let _ignoreNextKeyup = false; - - /** - * temporary state where we will ignore the next keypress - * - * @type {boolean} - */ - let _ignoreNextKeypress = false; - - /** - * are we currently inside of a sequence? - * type of action ("keyup" or "keydown" or "keypress") or false - * - * @type {boolean|string} - */ - let _nextExpectedAction = false; - - /** - * resets all sequence counters except for the ones passed in - * - * @param {Object} doNotReset - * @returns void - */ - function _resetSequences(doNotReset) { - doNotReset = doNotReset || {}; - - let activeSequences = false, - key; - - for (key in _sequenceLevels) { - if (doNotReset[key]) { - activeSequences = true; - continue; - } - _sequenceLevels[key] = 0; - } - - if (!activeSequences) { - _nextExpectedAction = false; - } - } - - /** - * finds all callbacks that match based on the keycode, modifiers, - * and action - * - * @param {string} character - * @param {Array} modifiers - * @param {Event|Object} e - * @param {string=} sequenceName - name of the sequence we are looking for - * @param {string=} combination - * @param {number=} level - * @returns {Array} - */ - function _getMatches( - character, - modifiers, - e, - sequenceName, - combination, - level - ) { - let i; - let callback; - let matches = []; - let action = e.type; - - // if there are no events related to this keycode - if (!self._callbacks[character]) { - return []; - } - - // if a modifier key is coming up on its own we should allow it - if (action == "keyup" && _isModifier(character)) { - modifiers = [character]; - } - - // loop through all callbacks for the key that was pressed - // and see if any of them match - for (i = 0; i < self._callbacks[character].length; ++i) { - callback = self._callbacks[character][i]; - - // if a sequence name is not specified, but this is a sequence at - // the wrong level then move onto the next match - if ( - !sequenceName && - callback.seq && - _sequenceLevels[callback.seq] != callback.level - ) { - continue; - } - - // if the action we are looking for doesn't match the action we got - // then we should keep going - if (action != callback.action) { - continue; - } - - // if this is a keypress event and the meta key and control key - // are not pressed that means that we need to only look at the - // character, otherwise check the modifiers as well - // - // chrome will not fire a keypress if meta or control is down - // safari will fire a keypress if meta or meta+shift is down - // firefox will fire a keypress if meta or control is down - if ( - (action == "keypress" && !e.metaKey && !e.ctrlKey) || - _modifiersMatch(modifiers, callback.modifiers) - ) { - // when you bind a combination or sequence a second time it - // should overwrite the first one. if a sequenceName or - // combination is specified in this call it does just that - // - // @todo make deleting its own method? - let deleteCombo = !sequenceName && callback.combo == combination; - let deleteSequence = - sequenceName && - callback.seq == sequenceName && - callback.level == level; - if (deleteCombo || deleteSequence) { - self._callbacks[character].splice(i, 1); - } - - matches.push(callback); - } - } - - return matches; - } - - /** - * actually calls the callback function - * - * if your callback function returns false this will use the jquery - * convention - prevent default and stop propogation on the event - * - * @param {Function} callback - * @param {Event} e - * @returns void - */ - function _fireCallback(callback, e, combo, sequence) { - // if this event should not happen stop here - if (self.stopCallback(e, e.target || e.srcElement, combo, sequence)) { - return; - } - - if (callback(e, combo) === false) { - _preventDefault(e); - _stopPropagation(e); - } - } - - /** - * handles a character key event - * - * @param {string} character - * @param {Array} modifiers - * @param {Event} e - * @returns void - */ - self._handleKey = function(character, modifiers, e) { - let callbacks = _getMatches(character, modifiers, e); - let i; - let doNotReset = {}; - let maxLevel = 0; - let processedSequenceCallback = false; - - // Calculate the maxLevel for sequences so we can only execute the longest callback sequence - for (i = 0; i < callbacks.length; ++i) { - if (callbacks[i].seq) { - maxLevel = Math.max(maxLevel, callbacks[i].level); - } - } - - // loop through matching callbacks for this key event - for (i = 0; i < callbacks.length; ++i) { - // fire for all sequence callbacks - // this is because if for example you have multiple sequences - // bound such as "g i" and "g t" they both need to fire the - // callback for matching g cause otherwise you can only ever - // match the first one - if (callbacks[i].seq) { - // only fire callbacks for the maxLevel to prevent - // subsequences from also firing - // - // for example 'a option b' should not cause 'option b' to fire - // even though 'option b' is part of the other sequence - // - // any sequences that do not match here will be discarded - // below by the _resetSequences call - if (callbacks[i].level != maxLevel) { - continue; - } - - processedSequenceCallback = true; - - // keep a list of which sequences were matches for later - doNotReset[callbacks[i].seq] = 1; - _fireCallback( - callbacks[i].callback, - e, - callbacks[i].combo, - callbacks[i].seq - ); - continue; - } - - // if there were no sequence matches but we are still here - // that means this is a regular match so we should fire that - if (!processedSequenceCallback) { - _fireCallback(callbacks[i].callback, e, callbacks[i].combo); - } - } - - // if the key you pressed matches the type of sequence without - // being a modifier (ie "keyup" or "keypress") then we should - // reset all sequences that were not matched by this event - // - // this is so, for example, if you have the sequence "h a t" and you - // type "h e a r t" it does not match. in this case the "e" will - // cause the sequence to reset - // - // modifier keys are ignored because you can have a sequence - // that contains modifiers such as "enter ctrl+space" and in most - // cases the modifier key will be pressed before the next key - // - // also if you have a sequence such as "ctrl+b a" then pressing the - // "b" key will trigger a "keypress" and a "keydown" - // - // the "keydown" is expected when there is a modifier, but the - // "keypress" ends up matching the _nextExpectedAction since it occurs - // after and that causes the sequence to reset - // - // we ignore keypresses in a sequence that directly follow a keydown - // for the same character - let ignoreThisKeypress = e.type == "keypress" && _ignoreNextKeypress; - if ( - e.type == _nextExpectedAction && - !_isModifier(character) && - !ignoreThisKeypress - ) { - _resetSequences(doNotReset); - } - - _ignoreNextKeypress = processedSequenceCallback && e.type == "keydown"; - }; - - /** - * handles a keydown event - * - * @param {Event} e - * @returns void - */ - self._handleKeyEvent = function(e) { - // normalize e.which for key events - // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion - if (typeof e.which !== "number") { - e.which = e.keyCode; - } - - let character = _characterFromEvent(e); - - // no character found then stop - if (!character) { - return; - } - - // need to use === for the character check because the character can be 0 - if (e.type == "keyup" && _ignoreNextKeyup === character) { - _ignoreNextKeyup = false; - return; - } - - self.handleKey(character, _eventModifiers(e), e); - }; - - /** - * called to set a 1 second timeout on the specified sequence - * - * this is so after each key press in the sequence you have 1 second - * to press the next key before you have to start over - * - * @returns void - */ - function _resetSequenceTimer() { - clearTimeout(_resetTimer); - _resetTimer = setTimeout(_resetSequences, 1000); - } - - /** - * binds a key sequence to an event - * - * @param {string} combo - combo specified in bind call - * @param {Array} keys - * @param {Function} callback - * @param {string=} action - * @returns void - */ - function _bindSequence(combo, keys, callback, action) { - // start off by adding a sequence level record for this combination - // and setting the level to 0 - _sequenceLevels[combo] = 0; - - /** - * callback to increase the sequence level for this sequence and reset - * all other sequences that were active - * - * @param {string} nextAction - * @returns {Function} - */ - function _increaseSequence(nextAction) { - return function() { - _nextExpectedAction = nextAction; - ++_sequenceLevels[combo]; - _resetSequenceTimer(); - }; - } - - /** - * wraps the specified callback inside of another function in order - * to reset all sequence counters as soon as this sequence is done - * - * @param {Event} e - * @returns void - */ - function _callbackAndReset(e) { - _fireCallback(callback, e, combo); - - // we should ignore the next key up if the action is key down - // or keypress. this is so if you finish a sequence and - // release the key the final key will not trigger a keyup - if (action !== "keyup") { - _ignoreNextKeyup = _characterFromEvent(e); - } - - // weird race condition if a sequence ends with the key - // another sequence begins with - setTimeout(_resetSequences, 10); - } - - // loop through keys one at a time and bind the appropriate callback - // function. for any key leading up to the final one it should - // increase the sequence. after the final, it should reset all sequences - // - // if an action is specified in the original bind call then that will - // be used throughout. otherwise we will pass the action that the - // next key in the sequence should match. this allows a sequence - // to mix and match keypress and keydown events depending on which - // ones are better suited to the key provided - for (let i = 0; i < keys.length; ++i) { - let isFinal = i + 1 === keys.length; - let wrappedCallback = isFinal - ? _callbackAndReset - : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action); - _bindSingle(keys[i], wrappedCallback, action, combo, i); - } - } - - /** - * binds a single keyboard combination - * - * @param {string} combination - * @param {Function} callback - * @param {string=} action - * @param {string=} sequenceName - name of sequence if part of sequence - * @param {number=} level - what part of the sequence the command is - * @returns void - */ - function _bindSingle(combination, callback, action, sequenceName, level) { - // store a direct mapped reference for use with ItsATrap.trigger - self._directMap[combination + ":" + action] = callback; - - // make sure multiple spaces in a row become a single space - combination = combination.replace(/\s+/g, " "); - - let sequence = combination.split(" "); - let info; - - // if this pattern is a sequence of keys then run through this method - // to reprocess each pattern one key at a time - if (sequence.length > 1) { - _bindSequence(combination, sequence, callback, action); - return; - } - - info = _getKeyInfo(combination, action); - - // make sure to initialize array if this is the first time - // a callback is added for this key - self._callbacks[info.key] = self._callbacks[info.key] || []; - - // remove an existing match if there is one - _getMatches( - info.key, - info.modifiers, - { type: info.action }, - sequenceName, - combination, - level - ); - - // add this call back to the array - // if it is a sequence put it at the beginning - // if not put it at the end - // - // this is important because the way these are processed expects - // the sequence ones to come first - self._callbacks[info.key][sequenceName ? "unshift" : "push"]({ - callback, - modifiers: info.modifiers, - action: info.action, - seq: sequenceName, - level, - combo: combination - }); - } - - /** - * binds multiple combinations to the same callback - * - * @param {Array} combinations - * @param {Function} callback - * @param {string|undefined} action - * @returns void - */ - self._bindMultiple = function(combinations, callback, action) { - for (let i = 0; i < combinations.length; ++i) { - _bindSingle(combinations[i], callback, action); - } - }; - - // start! - _addEvent(targetElement, "keypress", self._handleKeyEvent); - _addEvent(targetElement, "keydown", self._handleKeyEvent); - _addEvent(targetElement, "keyup", self._handleKeyEvent); - } - - /** - * binds an event to itsatrap - * - * can be a single key, a combination of keys separated with +, - * an array of keys, or a sequence of keys separated by spaces - * - * be sure to list the modifier keys first to make sure that the - * correct key ends up getting bound (the last key in the pattern) - * - * @param {string|Array} keys - * @param {Function} callback - * @param {string=} action - 'keypress', 'keydown', or 'keyup' - * @returns void - */ - ItsATrap.prototype.bind = function(keys, callback, action) { - let self = this; - keys = keys instanceof Array ? keys : [keys]; - self._bindMultiple.call(self, keys, callback, action); - return self; - }; - - /** - * unbinds an event to itsatrap - * - * the unbinding sets the callback function of the specified key combo - * to an empty function and deletes the corresponding key in the - * _directMap dict. - * - * TODO: actually remove this from the _callbacks dictionary instead - * of binding an empty function - * - * the keycombo+action has to be exactly the same as - * it was defined in the bind method - * - * @param {string|Array} keys - * @param {string} action - * @returns void - */ - ItsATrap.prototype.unbind = function(keys, action) { - let self = this; - return self.bind.call(self, keys, function() {}, action); - }; - - /** - * triggers an event that has already been bound - * - * @param {string} keys - * @param {string=} action - * @returns void - */ - ItsATrap.prototype.trigger = function(keys, action) { - let self = this; - if (self._directMap[keys + ":" + action]) { - self._directMap[keys + ":" + action]({}, keys); - } - return self; - }; - - /** - * resets the library back to its initial state. this is useful - * if you want to clear out the current keyboard shortcuts and bind - * new ones - for example if you switch to another page - * - * @returns void - */ - ItsATrap.prototype.reset = function() { - let self = this; - self._callbacks = {}; - self._directMap = {}; - return self; - }; - - /** - * destroy itsatrap object - * - * - call reset on the itsatrap object ( removing all binding ) - * - remove all javascript event listener from target element or document - * - remove all reference to target - * - * @return void - */ - - ItsATrap.prototype.destroy = function() { - let self = this; - - self.reset(); - - _removeEvent(self.target, "keypress", self._handleKeyEvent); - _removeEvent(self.target, "keydown", self._handleKeyEvent); - _removeEvent(self.target, "keyup", self._handleKeyEvent); - - self.target = undefined; - self._handleKeyEvent = undefined; - }; - - /** - * should we stop this event before firing off callbacks - * - * @param {Event} e - * @param {Element} element - * @return {boolean} - */ - ItsATrap.prototype.stopCallback = function(e, element, combo, sequence) { - let self = this; - - if (self.paused) { - return true; - } - - if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { - return false; - } - - // if the element has the class "itsatrap" then no need to stop - if ((" " + element.className + " ").indexOf(" itsatrap ") > -1) { - return false; - } - - if (_belongsTo(element, self.target)) { - return false; - } - - // Events originating from a shadow DOM are re-targetted and `e.target` is the shadow host, - // not the initial event target in the shadow tree. Note that not all events cross the - // shadow boundary. - // For shadow trees with `mode: 'open'`, the initial event target is the first element in - // the event’s composed path. For shadow trees with `mode: 'closed'`, the initial event - // target cannot be obtained. - if ("composedPath" in e && typeof e.composedPath === "function") { - // For open shadow trees, update `element` so that the following check works. - let initialEventTarget = e.composedPath()[0]; - if (initialEventTarget !== e.target) { - element = initialEventTarget; - } - } - - // stop for input, select, and textarea - return ( - element.tagName == "INPUT" || - element.tagName == "SELECT" || - element.tagName == "TEXTAREA" || - element.isContentEditable - ); - }; - - /** - * exposes _handleKey publicly so it can be overwritten by extensions - */ - ItsATrap.prototype.handleKey = function() { - let self = this; - return self._handleKey.apply(self, arguments); - }; - - /** - * allow custom key mappings - */ - ItsATrap.addKeycodes = function(object) { - for (let key in object) { - if (object.hasOwnProperty(key)) { - _MAP[key] = object[key]; - } - } - _REVERSE_MAP = null; - }; - - /** - * adds a pause and unpause method to ItsATrap - * this allows you to enable or disable keyboard shortcuts - * without having to reset ItsATrap and rebind everything - */ - ItsATrap.prototype.pause = function() { - let self = this; - self.paused = true; - }; - - ItsATrap.prototype.unpause = function() { - let self = this; - self.paused = false; - }; - - /** - * adds a bindGlobal method to ItsATrap that allows you to - * bind specific keyboard shortcuts that will still work - * inside a text input field - * - * usage: - * ItsATrap.bindGlobal('ctrl+s', _saveChanges); - */ - ItsATrap.prototype.bindGlobal = function(keys, callback, action) { - let self = this; - self.bind(keys, callback, action); - - if (keys instanceof Array) { - for (let i = 0; i < keys.length; i++) { - _globalCallbacks[keys[i]] = true; - } - return; - } - - _globalCallbacks[keys] = true; - }; - - // expose itsatrap to the global object - window.ItsATrap = ItsATrap; - - // expose as a common js module - if (typeof module !== "undefined" && module.exports) { - module.exports = ItsATrap; - } - - // expose itsatrap as an AMD module - if (typeof define === "function" && define.amd) { - define(function() { - return ItsATrap; - }); - } -})( - typeof window !== "undefined" ? window : null, - typeof window !== "undefined" ? document : null -); diff --git a/assets/javascripts/legacy/jquery.js b/assets/javascripts/legacy/jquery.js deleted file mode 100644 index 956c6699..00000000 --- a/assets/javascripts/legacy/jquery.js +++ /dev/null @@ -1,10874 +0,0 @@ -// discourse-skip-module - -/*! - * jQuery JavaScript Library v3.5.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-04T22:49Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -let arr = []; - -let getProto = Object.getPrototypeOf; - -let slice = arr.slice; - -let flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -let push = arr.push; - -let indexOf = arr.indexOf; - -let class2type = {}; - -let toString = class2type.toString; - -let hasOwn = class2type.hasOwnProperty; - -let fnToString = hasOwn.toString; - -let ObjectFunctionString = fnToString.call( Object ); - -let support = {}; - -let isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -let isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -let document = window.document; - - - - let preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - let i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.5.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - let ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - let len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - let options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - let proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - let name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - let length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - let ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - let len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - let callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - let length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - let length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -let Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.5 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-03-14 - */ -( function( window ) { -let i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - let i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - let high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - let j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - let m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - let keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - let el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - let arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - let cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - let name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - let name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - let j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - let namespace = elem.namespaceURI, - docElem = ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - let hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - let attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - let elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - let attrId = id.replace( runescape, funescape ); - return function( elem ) { - let node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - let node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - let elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - let input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - let input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - let adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - let compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - let cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - let ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - let fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - let elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - let node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - let excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - let nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - let pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - let result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - let simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - let cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - let args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - let idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - let input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - let elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - let elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - let hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - let nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - let name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - let attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - let i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - let i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - let i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - let i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - let matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - let i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - let dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - let oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - let i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - let i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - let elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - let temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - let checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - let ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - let bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - let elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - let i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - let i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - let val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -let dir = function( elem, dir, until ) { - let matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -let siblings = function( n, elem ) { - let matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -let rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -let rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - let elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - let i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -let rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - let match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -let rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - let targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - let i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - let cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - let parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - let matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -let rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - let object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - let index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - let method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - let fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - let fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - let returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - let maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - let returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - let list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - let - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -let rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -let readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - let i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -let rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -let acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - let value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - let prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - let i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - let cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -let dataPriv = new Data(); - -let dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -let rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - let name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - let i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - let data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - let queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - let queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - let key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - let setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - let queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - let tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -let pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -let rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -let cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -let documentElement = document.documentElement; - - - - let isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -let isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - let adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -let defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - let temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - let display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -let rcheckableType = ( /^(?:checkbox|radio)$/i ); - -let rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -let rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - let fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -let wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - let ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - let i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -let rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - let elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -let - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - let origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - let handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type, - origType, - data, - handler, - guid: handler.guid, - selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - let j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - let i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - let i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - let el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - let el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - let target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - let notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - return result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - let e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - let e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - let e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - let button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - let ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - let handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -let - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - let i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - let nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - let fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - let self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - let node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - let i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - let data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - let target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - let target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - let elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - let elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - let ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - let parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - let elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -let rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -let getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - let view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -let swap = function( elem, options, callback ) { - let ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -let rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - let divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - reliableTrDimensions: function() { - let table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px"; - tr.style.height = "1px"; - trChild.style.height = "9px"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - let width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -let cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - let capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - let final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -let - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - let matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - let i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - let styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - let ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - let ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - let val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - let matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - let i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - let styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - let hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - let eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - let result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -let - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - let which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - let tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - let prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - let index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - let currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - let tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - let index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - let tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - let prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - let opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - let empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - let anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - let stopQueue = function( hooks ) { - let stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - let dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - let index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - let cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - let timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - let timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - let input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -let boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - let ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - let val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - let name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - let getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - let ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -let rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - let ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - let tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - let parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - let parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - let tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - let classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - let classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - let type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - let className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - let className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -let rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - let hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - let val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - let val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - let value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - let optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -let rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - let i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( - dataPriv.get( cur, "events" ) || Object.create( null ) - )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - let e = jQuery.extend( - new jQuery.Event(), - event, - { - type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - let elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - let handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - let doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - let doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -let location = window.location; - -let nonce = { guid: Date.now() }; - -let rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - let xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -let - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - let name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - let prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - let value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - let elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - let type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( _i, elem ) { - let val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -let - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - let dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - let inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - let selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - let dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - let key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - let ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - let conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - let match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - let code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - let finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - let isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script - if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url, - type: method, - dataType: type, - data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - let i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - let wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - let elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - let self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - let htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -let xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - let callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - let i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - let script, callback; - return { - send: function( _, complete ) { - script = jQuery( "