Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 09:20:29 +01:00
Merge branch 'main' into admin-acceptance-tests
Dieser Commit ist enthalten in:
Commit
0f59c9092f
27 geänderte Dateien mit 176 neuen und 64 gelöschten Zeilen
|
@ -3,6 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default DateInput.extend({
|
export default DateInput.extend({
|
||||||
useNativePicker: false,
|
useNativePicker: false,
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
|
||||||
@discourseComputed()
|
@discourseComputed()
|
||||||
placeholder() {
|
placeholder() {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import DateTimeInput from "discourse/components/date-time-input";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default DateTimeInput.extend({
|
export default DateTimeInput.extend({
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
|
||||||
@discourseComputed("timeFirst", "tabindex")
|
@discourseComputed("timeFirst", "tabindex")
|
||||||
timeTabindex(timeFirst, tabindex) {
|
timeTabindex(timeFirst, tabindex) {
|
||||||
return timeFirst ? tabindex : tabindex + 1;
|
return timeFirst ? tabindex : tabindex + 1;
|
||||||
|
|
|
@ -2,6 +2,8 @@ import Component from "@ember/component";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
|
||||||
@observes("time")
|
@observes("time")
|
||||||
setValue() {
|
setValue() {
|
||||||
this.set("field.value", this.time.format(this.field.format));
|
this.set("field.value", this.time.format(this.field.format));
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { computed } from "@ember/object";
|
||||||
|
|
||||||
export default Component.extend(UppyUploadMixin, {
|
export default Component.extend(UppyUploadMixin, {
|
||||||
classNames: ["wizard-field-upload"],
|
classNames: ["wizard-field-upload"],
|
||||||
classNameBindings: ["isImage"],
|
classNameBindings: ["isImage", "fieldClass"],
|
||||||
uploading: false,
|
uploading: false,
|
||||||
type: computed(function () {
|
type: computed(function () {
|
||||||
return `wizard_${this.field.id}`;
|
return `wizard_${this.field.id}`;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
|
|
||||||
export default Component.extend({});
|
export default Component.extend({
|
||||||
|
classNameBindings: ["fieldClass"],
|
||||||
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import CustomWizard, {
|
||||||
updateCachedWizard,
|
updateCachedWizard,
|
||||||
} from "discourse/plugins/discourse-custom-wizard/discourse/models/custom-wizard";
|
} from "discourse/plugins/discourse-custom-wizard/discourse/models/custom-wizard";
|
||||||
import { alias, not } from "@ember/object/computed";
|
import { alias, not } from "@ember/object/computed";
|
||||||
|
import discourseLater from "discourse-common/lib/later";
|
||||||
|
|
||||||
const alreadyWarned = {};
|
const alreadyWarned = {};
|
||||||
|
|
||||||
|
@ -110,29 +111,22 @@ export default Component.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
autoFocus() {
|
autoFocus() {
|
||||||
schedule("afterRender", () => {
|
discourseLater(() => {
|
||||||
const $invalid = $(
|
schedule("afterRender", () => {
|
||||||
".wizard-field.invalid:nth-of-type(1) .wizard-focusable"
|
if ($(".invalid .wizard-focusable").length) {
|
||||||
);
|
this.animateInvalidFields();
|
||||||
|
}
|
||||||
if ($invalid.length) {
|
});
|
||||||
return $invalid.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(".wizard-focusable:first").focus();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
animateInvalidFields() {
|
animateInvalidFields() {
|
||||||
schedule("afterRender", () => {
|
schedule("afterRender", () => {
|
||||||
let $element = $(
|
let $invalid = $(".invalid .wizard-focusable");
|
||||||
".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit"
|
if ($invalid.length) {
|
||||||
);
|
|
||||||
|
|
||||||
if ($element.length) {
|
|
||||||
$([document.documentElement, document.body]).animate(
|
$([document.documentElement, document.body]).animate(
|
||||||
{
|
{
|
||||||
scrollTop: $element.offset().top - 200,
|
scrollTop: $invalid.offset().top - 200,
|
||||||
},
|
},
|
||||||
400
|
400
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { registerUnbound } from "discourse-common/lib/helpers";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import Handlebars from "handlebars";
|
|
||||||
|
|
||||||
export default registerUnbound("char-counter", function (body, maxLength) {
|
|
||||||
let bodyLength = body ? body.length : 0;
|
|
||||||
let finalString;
|
|
||||||
|
|
||||||
if (maxLength) {
|
|
||||||
let isOverMax = bodyLength > maxLength ? "true" : "false";
|
|
||||||
finalString = `<div class="body-length" data-length=${bodyLength} data-over-max=${isOverMax}>${bodyLength} / ${I18n.t(
|
|
||||||
"wizard.x_characters",
|
|
||||||
{ count: parseInt(maxLength, 10) }
|
|
||||||
)}</div>`;
|
|
||||||
} else {
|
|
||||||
finalString = `<div class="body-length">${I18n.t("wizard.x_characters", {
|
|
||||||
count: parseInt(bodyLength, 10),
|
|
||||||
})}</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Handlebars.SafeString(finalString);
|
|
||||||
});
|
|
25
assets/javascripts/discourse/helpers/wizard-char-counter.js.es6
Normale Datei
25
assets/javascripts/discourse/helpers/wizard-char-counter.js.es6
Normale Datei
|
@ -0,0 +1,25 @@
|
||||||
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import Handlebars from "handlebars";
|
||||||
|
|
||||||
|
export default registerUnbound(
|
||||||
|
"wizard-char-counter",
|
||||||
|
function (body, maxLength) {
|
||||||
|
let bodyLength = body ? body.length : 0;
|
||||||
|
let finalString;
|
||||||
|
|
||||||
|
if (maxLength) {
|
||||||
|
let isOverMax = bodyLength > maxLength ? "true" : "false";
|
||||||
|
finalString = `<div class="body-length" data-length=${bodyLength} data-over-max=${isOverMax}>${bodyLength} / ${I18n.t(
|
||||||
|
"wizard.x_characters",
|
||||||
|
{ count: parseInt(maxLength, 10) }
|
||||||
|
)}</div>`;
|
||||||
|
} else {
|
||||||
|
finalString = `<div class="body-length">${I18n.t("wizard.x_characters", {
|
||||||
|
count: parseInt(bodyLength, 10),
|
||||||
|
})}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Handlebars.SafeString(finalString);
|
||||||
|
}
|
||||||
|
);
|
|
@ -72,7 +72,7 @@ export default EmberObject.extend(ValidState, {
|
||||||
valid = true;
|
valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setValid(valid);
|
this.setValid(Boolean(valid));
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{{input
|
<Input
|
||||||
type=inputType
|
@type={{this.inputType}}
|
||||||
|
@value={{readonly this.value}}
|
||||||
class="date-picker"
|
class="date-picker"
|
||||||
placeholder=placeholder
|
placeholder={{this.placeholder}}
|
||||||
value=(readonly value)
|
tabindex={{this.tabindex}}
|
||||||
input=(action "onChangeDate")
|
{{on "input" (action "onChangeDate")}}
|
||||||
tabindex=tabindex
|
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
}}
|
/>
|
||||||
|
|
||||||
<div class="picker-container"></div>
|
<div class="picker-container"></div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{custom-wizard-category-selector
|
{{custom-wizard-category-selector
|
||||||
categories=categories
|
categories=categories
|
||||||
|
class=fieldClass
|
||||||
whitelist=field.content
|
whitelist=field.content
|
||||||
onChange=(action (mut categories))
|
onChange=(action (mut categories))
|
||||||
tabindex=field.tabindex
|
tabindex=field.tabindex
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
{{input type="checkbox" id=field.id checked=field.value tabindex=field.tabindex}}
|
<Input
|
||||||
|
id={{this.field.id}}
|
||||||
|
@type="checkbox"
|
||||||
|
@checked={{this.field.value}}
|
||||||
|
tabindex={{this.field.tabindex}}
|
||||||
|
class={{this.fieldClass}}
|
||||||
|
/>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
field=field
|
field=field
|
||||||
composer=composer
|
composer=composer
|
||||||
wizard=wizard
|
wizard=wizard
|
||||||
|
fieldClass=fieldClass
|
||||||
groupsMentioned=(action "groupsMentioned")
|
groupsMentioned=(action "groupsMentioned")
|
||||||
cannotSeeMention=(action "cannotSeeMention")
|
cannotSeeMention=(action "cannotSeeMention")
|
||||||
importQuote=(action "importQuote")
|
importQuote=(action "importQuote")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{custom-wizard-group-selector
|
{{custom-wizard-group-selector
|
||||||
groups=site.groups
|
groups=site.groups
|
||||||
|
class=fieldClass
|
||||||
field=field
|
field=field
|
||||||
whitelist=field.content
|
whitelist=field.content
|
||||||
value=field.value
|
value=field.value
|
||||||
|
|
|
@ -1 +1,9 @@
|
||||||
{{input type="number" step="0.01" id=field.id value=field.value tabindex=field.tabindex}}
|
<Input
|
||||||
|
id={{this.field.id}}
|
||||||
|
step="0.01"
|
||||||
|
@type="number"
|
||||||
|
@value={{this.field.value}}
|
||||||
|
tabindex={{this.field.tabindex}}
|
||||||
|
class={{this.fieldClass}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{custom-wizard-tag-chooser
|
{{custom-wizard-tag-chooser
|
||||||
tags=field.value
|
tags=field.value
|
||||||
|
class=fieldClass
|
||||||
tabindex=field.tabindex
|
tabindex=field.tabindex
|
||||||
tagGroups=field.tag_groups
|
tagGroups=field.tag_groups
|
||||||
everyTag=true
|
everyTag=true
|
||||||
|
|
|
@ -1 +1,8 @@
|
||||||
{{input id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex autocomplete=autocomplete}}
|
<Input
|
||||||
|
id={{this.field.id}}
|
||||||
|
@value={{this.field.value}}
|
||||||
|
tabindex={{this.field.tabindex}}
|
||||||
|
class={{this.fieldClass}}
|
||||||
|
placeholder={{this.field.translatedPlaceholder}}
|
||||||
|
autocomplete={{this.autocomplete}}
|
||||||
|
/>
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
{{textarea id=field.id value=field.value class=fieldClass placeholder=field.translatedPlaceholder tabindex=field.tabindex}}
|
<Textarea
|
||||||
|
id={{this.field.id}}
|
||||||
|
@value={{this.field.value}}
|
||||||
|
tabindex={{this.field.tabindex}}
|
||||||
|
class={{this.fieldClass}}
|
||||||
|
placeholder={{this.field.translatedPlaceholder}}
|
||||||
|
/>
|
||||||
|
|
|
@ -1 +1,6 @@
|
||||||
{{input type="text" id=field.id value=field.value tabindex=field.tabindex}}
|
<Input
|
||||||
|
id={{this.field.id}}
|
||||||
|
@value={{this.field.value}}
|
||||||
|
tabindex={{this.field.tabindex}}
|
||||||
|
class={{this.fieldClass}}
|
||||||
|
/>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
{{#if field.char_counter}}
|
{{#if field.char_counter}}
|
||||||
{{#if textType}}
|
{{#if textType}}
|
||||||
{{char-counter field.value field.max_length}}
|
{{wizard-char-counter field.value field.max_length}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
|
|
@ -716,7 +716,7 @@
|
||||||
property="name"
|
property="name"
|
||||||
onUpdate=(action "mappedFieldUpdated")
|
onUpdate=(action "mappedFieldUpdated")
|
||||||
options=(hash
|
options=(hash
|
||||||
textSelection="key,value"
|
textSelection="key,value,output"
|
||||||
wizardFieldSelection=true
|
wizardFieldSelection=true
|
||||||
userFieldSelection="key,value"
|
userFieldSelection="key,value"
|
||||||
context="action"
|
context="action"
|
||||||
|
|
|
@ -32,13 +32,8 @@ body.custom-wizard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.invalid {
|
&.invalid .wizard-focusable {
|
||||||
textarea,
|
outline: 4px solid var(--danger);
|
||||||
input[type="text"],
|
|
||||||
input[type="checkbox"],
|
|
||||||
.select-kit {
|
|
||||||
outline: 4px solid var(--danger);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class CustomWizard::Mapper
|
||||||
|
|
||||||
def initialize(params)
|
def initialize(params)
|
||||||
@inputs = params[:inputs] || {}
|
@inputs = params[:inputs] || {}
|
||||||
@data = params[:data] || {}
|
@data = params[:data] ? params[:data].with_indifferent_access : {}
|
||||||
@user = params[:user]
|
@user = params[:user]
|
||||||
@opts = params[:opts] || {}
|
@opts = params[:opts] || {}
|
||||||
end
|
end
|
||||||
|
@ -267,7 +267,12 @@ class CustomWizard::Mapper
|
||||||
return nil if data.nil?
|
return nil if data.nil?
|
||||||
k = keys.shift
|
k = keys.shift
|
||||||
result = data[k]
|
result = data[k]
|
||||||
keys.empty? ? result : self.recurse(result, keys)
|
|
||||||
|
if keys.empty?
|
||||||
|
result.is_a?(Hash) ? "" : result
|
||||||
|
else
|
||||||
|
self.recurse(result, keys)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def bool(value)
|
def bool(value)
|
||||||
|
|
|
@ -84,6 +84,10 @@ class CustomWizard::Submission
|
||||||
data
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def submitted?
|
||||||
|
!!submitted_at
|
||||||
|
end
|
||||||
|
|
||||||
def self.get(wizard)
|
def self.get(wizard)
|
||||||
data = PluginStore.get("#{wizard.id}_#{KEY}", wizard.actor_id).last
|
data = PluginStore.get("#{wizard.id}_#{KEY}", wizard.actor_id).last
|
||||||
new(wizard, data)
|
new(wizard, data)
|
||||||
|
|
|
@ -176,6 +176,7 @@ class CustomWizard::Wizard
|
||||||
|
|
||||||
def unfinished?
|
def unfinished?
|
||||||
return nil unless actor_id
|
return nil unless actor_id
|
||||||
|
return false if last_submission&.submitted?
|
||||||
|
|
||||||
most_recent = CustomWizard::UserHistory.where(
|
most_recent = CustomWizard::UserHistory.where(
|
||||||
actor_id: actor_id,
|
actor_id: actor_id,
|
||||||
|
@ -194,6 +195,7 @@ class CustomWizard::Wizard
|
||||||
|
|
||||||
def completed?
|
def completed?
|
||||||
return nil unless actor_id
|
return nil unless actor_id
|
||||||
|
return true if last_submission&.submitted?
|
||||||
|
|
||||||
history = CustomWizard::UserHistory.where(
|
history = CustomWizard::UserHistory.where(
|
||||||
actor_id: actor_id,
|
actor_id: actor_id,
|
||||||
|
@ -282,6 +284,10 @@ class CustomWizard::Wizard
|
||||||
@submissions ||= CustomWizard::Submission.list(self).submissions
|
@submissions ||= CustomWizard::Submission.list(self).submissions
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def last_submission
|
||||||
|
@last_submission ||= submissions&.last
|
||||||
|
end
|
||||||
|
|
||||||
def current_submission
|
def current_submission
|
||||||
@current_submission ||= begin
|
@current_submission ||= begin
|
||||||
if submissions.present?
|
if submissions.present?
|
||||||
|
|
|
@ -58,6 +58,11 @@ describe CustomWizard::Mapper do
|
||||||
"step_1_field_3" => "Value"
|
"step_1_field_3" => "Value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let(:template_params_object) {
|
||||||
|
{
|
||||||
|
"step_1_field_1": get_wizard_fixture("field/upload")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def create_template_mapper(data, user)
|
def create_template_mapper(data, user)
|
||||||
CustomWizard::Mapper.new(
|
CustomWizard::Mapper.new(
|
||||||
|
@ -448,6 +453,40 @@ describe CustomWizard::Mapper do
|
||||||
expect(result).to eq(template)
|
expect(result).to eq(template)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "handles correct object variable references" do
|
||||||
|
template = <<-LIQUID.strip
|
||||||
|
{%- if "w{step_1_field_1.id}" == "step_2_field_7" -%}
|
||||||
|
Correct
|
||||||
|
{%- else -%}
|
||||||
|
Incorrect
|
||||||
|
{%-endif-%}
|
||||||
|
LIQUID
|
||||||
|
mapper = create_template_mapper(template_params_object, user1)
|
||||||
|
result = mapper.interpolate(
|
||||||
|
template.dup,
|
||||||
|
template: true,
|
||||||
|
wizard: true
|
||||||
|
)
|
||||||
|
expect(result).to eq("Correct")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles incorrect object variable references" do
|
||||||
|
template = <<-LIQUID.strip
|
||||||
|
{%- if "w{step_1_field_1}" == "step_2_field_7" -%}
|
||||||
|
Correct
|
||||||
|
{%- else -%}
|
||||||
|
Incorrect
|
||||||
|
{%-endif-%}
|
||||||
|
LIQUID
|
||||||
|
mapper = create_template_mapper(template_params_object, user1)
|
||||||
|
result = mapper.interpolate(
|
||||||
|
template.dup,
|
||||||
|
template: true,
|
||||||
|
wizard: true
|
||||||
|
)
|
||||||
|
expect(result).to eq("Incorrect")
|
||||||
|
end
|
||||||
|
|
||||||
context "custom filter: 'first_non_empty'" do
|
context "custom filter: 'first_non_empty'" do
|
||||||
it "gives first non empty element from list" do
|
it "gives first non empty element from list" do
|
||||||
template = <<-LIQUID.strip
|
template = <<-LIQUID.strip
|
||||||
|
|
|
@ -7,6 +7,7 @@ describe CustomWizard::Wizard do
|
||||||
let(:template_json) { get_wizard_fixture("wizard") }
|
let(:template_json) { get_wizard_fixture("wizard") }
|
||||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||||
let(:guests_permitted_json) { get_wizard_fixture("wizard/guests_permitted") }
|
let(:guests_permitted_json) { get_wizard_fixture("wizard/guests_permitted") }
|
||||||
|
let(:step_json) { get_wizard_fixture("step/step") }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Group.refresh_automatic_group!(:trust_level_3)
|
Group.refresh_automatic_group!(:trust_level_3)
|
||||||
|
@ -75,6 +76,28 @@ describe CustomWizard::Wizard do
|
||||||
expect(@wizard.start).to eq('step_2')
|
expect(@wizard.start).to eq('step_2')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "determines the user's current step if steps are added" do
|
||||||
|
append_steps
|
||||||
|
progress_step('step_1')
|
||||||
|
progress_step('step_2')
|
||||||
|
progress_step("step_3")
|
||||||
|
|
||||||
|
fourth_step = step_json.dup
|
||||||
|
fourth_step['id'] = "step_4"
|
||||||
|
template = template_json.dup
|
||||||
|
template['steps'] << fourth_step
|
||||||
|
|
||||||
|
CustomWizard::Template.save(template, skip_jobs: true)
|
||||||
|
|
||||||
|
wizard = CustomWizard::Wizard.new(template, user)
|
||||||
|
template['steps'].each do |step_template|
|
||||||
|
wizard.append_step(step_template['id'])
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(wizard.steps.size).to eq(4)
|
||||||
|
expect(wizard.start).to eq(nil)
|
||||||
|
end
|
||||||
|
|
||||||
it "creates a step updater" do
|
it "creates a step updater" do
|
||||||
expect(
|
expect(
|
||||||
@wizard.create_updater('step_1', step_1_field_1: "Text input")
|
@wizard.create_updater('step_1', step_1_field_1: "Text input")
|
||||||
|
|
Laden …
In neuem Issue referenzieren