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({
|
||||
useNativePicker: false,
|
||||
classNameBindings: ["fieldClass"],
|
||||
|
||||
@discourseComputed()
|
||||
placeholder() {
|
||||
|
|
|
@ -2,6 +2,8 @@ import DateTimeInput from "discourse/components/date-time-input";
|
|||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default DateTimeInput.extend({
|
||||
classNameBindings: ["fieldClass"],
|
||||
|
||||
@discourseComputed("timeFirst", "tabindex")
|
||||
timeTabindex(timeFirst, tabindex) {
|
||||
return timeFirst ? tabindex : tabindex + 1;
|
||||
|
|
|
@ -2,6 +2,8 @@ import Component from "@ember/component";
|
|||
import { observes } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: ["fieldClass"],
|
||||
|
||||
@observes("time")
|
||||
setValue() {
|
||||
this.set("field.value", this.time.format(this.field.format));
|
||||
|
|
|
@ -4,7 +4,7 @@ import { computed } from "@ember/object";
|
|||
|
||||
export default Component.extend(UppyUploadMixin, {
|
||||
classNames: ["wizard-field-upload"],
|
||||
classNameBindings: ["isImage"],
|
||||
classNameBindings: ["isImage", "fieldClass"],
|
||||
uploading: false,
|
||||
type: computed(function () {
|
||||
return `wizard_${this.field.id}`;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({});
|
||||
export default Component.extend({
|
||||
classNameBindings: ["fieldClass"],
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import CustomWizard, {
|
|||
updateCachedWizard,
|
||||
} from "discourse/plugins/discourse-custom-wizard/discourse/models/custom-wizard";
|
||||
import { alias, not } from "@ember/object/computed";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
|
||||
const alreadyWarned = {};
|
||||
|
||||
|
@ -110,29 +111,22 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
autoFocus() {
|
||||
schedule("afterRender", () => {
|
||||
const $invalid = $(
|
||||
".wizard-field.invalid:nth-of-type(1) .wizard-focusable"
|
||||
);
|
||||
|
||||
if ($invalid.length) {
|
||||
return $invalid.focus();
|
||||
}
|
||||
|
||||
$(".wizard-focusable:first").focus();
|
||||
discourseLater(() => {
|
||||
schedule("afterRender", () => {
|
||||
if ($(".invalid .wizard-focusable").length) {
|
||||
this.animateInvalidFields();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
animateInvalidFields() {
|
||||
schedule("afterRender", () => {
|
||||
let $element = $(
|
||||
".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit"
|
||||
);
|
||||
|
||||
if ($element.length) {
|
||||
let $invalid = $(".invalid .wizard-focusable");
|
||||
if ($invalid.length) {
|
||||
$([document.documentElement, document.body]).animate(
|
||||
{
|
||||
scrollTop: $element.offset().top - 200,
|
||||
scrollTop: $invalid.offset().top - 200,
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
this.setValid(valid);
|
||||
this.setValid(Boolean(valid));
|
||||
|
||||
return valid;
|
||||
},
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{{input
|
||||
type=inputType
|
||||
<Input
|
||||
@type={{this.inputType}}
|
||||
@value={{readonly this.value}}
|
||||
class="date-picker"
|
||||
placeholder=placeholder
|
||||
value=(readonly value)
|
||||
input=(action "onChangeDate")
|
||||
tabindex=tabindex
|
||||
placeholder={{this.placeholder}}
|
||||
tabindex={{this.tabindex}}
|
||||
{{on "input" (action "onChangeDate")}}
|
||||
autocomplete="off"
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="picker-container"></div>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{{custom-wizard-category-selector
|
||||
categories=categories
|
||||
class=fieldClass
|
||||
whitelist=field.content
|
||||
onChange=(action (mut categories))
|
||||
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
|
||||
composer=composer
|
||||
wizard=wizard
|
||||
fieldClass=fieldClass
|
||||
groupsMentioned=(action "groupsMentioned")
|
||||
cannotSeeMention=(action "cannotSeeMention")
|
||||
importQuote=(action "importQuote")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{{custom-wizard-group-selector
|
||||
groups=site.groups
|
||||
class=fieldClass
|
||||
field=field
|
||||
whitelist=field.content
|
||||
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
|
||||
tags=field.value
|
||||
class=fieldClass
|
||||
tabindex=field.tabindex
|
||||
tagGroups=field.tag_groups
|
||||
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 textType}}
|
||||
{{char-counter field.value field.max_length}}
|
||||
{{wizard-char-counter field.value field.max_length}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
|
|
|
@ -716,7 +716,7 @@
|
|||
property="name"
|
||||
onUpdate=(action "mappedFieldUpdated")
|
||||
options=(hash
|
||||
textSelection="key,value"
|
||||
textSelection="key,value,output"
|
||||
wizardFieldSelection=true
|
||||
userFieldSelection="key,value"
|
||||
context="action"
|
||||
|
|
|
@ -32,13 +32,8 @@ body.custom-wizard {
|
|||
}
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
textarea,
|
||||
input[type="text"],
|
||||
input[type="checkbox"],
|
||||
.select-kit {
|
||||
outline: 4px solid var(--danger);
|
||||
}
|
||||
&.invalid .wizard-focusable {
|
||||
outline: 4px solid var(--danger);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ class CustomWizard::Mapper
|
|||
|
||||
def initialize(params)
|
||||
@inputs = params[:inputs] || {}
|
||||
@data = params[:data] || {}
|
||||
@data = params[:data] ? params[:data].with_indifferent_access : {}
|
||||
@user = params[:user]
|
||||
@opts = params[:opts] || {}
|
||||
end
|
||||
|
@ -267,7 +267,12 @@ class CustomWizard::Mapper
|
|||
return nil if data.nil?
|
||||
k = keys.shift
|
||||
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
|
||||
|
||||
def bool(value)
|
||||
|
|
|
@ -84,6 +84,10 @@ class CustomWizard::Submission
|
|||
data
|
||||
end
|
||||
|
||||
def submitted?
|
||||
!!submitted_at
|
||||
end
|
||||
|
||||
def self.get(wizard)
|
||||
data = PluginStore.get("#{wizard.id}_#{KEY}", wizard.actor_id).last
|
||||
new(wizard, data)
|
||||
|
|
|
@ -176,6 +176,7 @@ class CustomWizard::Wizard
|
|||
|
||||
def unfinished?
|
||||
return nil unless actor_id
|
||||
return false if last_submission&.submitted?
|
||||
|
||||
most_recent = CustomWizard::UserHistory.where(
|
||||
actor_id: actor_id,
|
||||
|
@ -194,6 +195,7 @@ class CustomWizard::Wizard
|
|||
|
||||
def completed?
|
||||
return nil unless actor_id
|
||||
return true if last_submission&.submitted?
|
||||
|
||||
history = CustomWizard::UserHistory.where(
|
||||
actor_id: actor_id,
|
||||
|
@ -282,6 +284,10 @@ class CustomWizard::Wizard
|
|||
@submissions ||= CustomWizard::Submission.list(self).submissions
|
||||
end
|
||||
|
||||
def last_submission
|
||||
@last_submission ||= submissions&.last
|
||||
end
|
||||
|
||||
def current_submission
|
||||
@current_submission ||= begin
|
||||
if submissions.present?
|
||||
|
|
|
@ -58,6 +58,11 @@ describe CustomWizard::Mapper do
|
|||
"step_1_field_3" => "Value"
|
||||
}
|
||||
}
|
||||
let(:template_params_object) {
|
||||
{
|
||||
"step_1_field_1": get_wizard_fixture("field/upload")
|
||||
}
|
||||
}
|
||||
|
||||
def create_template_mapper(data, user)
|
||||
CustomWizard::Mapper.new(
|
||||
|
@ -448,6 +453,40 @@ describe CustomWizard::Mapper do
|
|||
expect(result).to eq(template)
|
||||
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
|
||||
it "gives first non empty element from list" do
|
||||
template = <<-LIQUID.strip
|
||||
|
|
|
@ -7,6 +7,7 @@ describe CustomWizard::Wizard do
|
|||
let(:template_json) { get_wizard_fixture("wizard") }
|
||||
let(:permitted_json) { get_wizard_fixture("wizard/permitted") }
|
||||
let(:guests_permitted_json) { get_wizard_fixture("wizard/guests_permitted") }
|
||||
let(:step_json) { get_wizard_fixture("step/step") }
|
||||
|
||||
before do
|
||||
Group.refresh_automatic_group!(:trust_level_3)
|
||||
|
@ -75,6 +76,28 @@ describe CustomWizard::Wizard do
|
|||
expect(@wizard.start).to eq('step_2')
|
||||
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
|
||||
expect(
|
||||
@wizard.create_updater('step_1', step_1_field_1: "Text input")
|
||||
|
|
Laden …
In neuem Issue referenzieren