1
0
Fork 0

Merge branch 'main' into subscription_client_update

Dieser Commit ist enthalten in:
Angus McLeod 2023-05-04 17:27:07 +02:00 committet von GitHub
Commit 6be5511d18
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
61 geänderte Dateien mit 739 neuen und 248 gelöschten Zeilen

Datei anzeigen

@ -5,6 +5,8 @@ on:
branches: branches:
- main - main
pull_request: pull_request:
schedule:
- cron: "0 0 * * *"
jobs: jobs:
ci: ci:

Datei anzeigen

@ -10,7 +10,7 @@ class CustomWizard::SubmissionSerializer < ApplicationSerializer
end end
def user def user
::BasicUserSerializer.new(object.wizard.user).as_json ::BasicUserSerializer.new(object.wizard.user, root: false).as_json
end end
def fields def fields

Datei anzeigen

@ -12,6 +12,7 @@ import { alias } from "@ember/object/computed";
import Site from "discourse/models/site"; import Site from "discourse/models/site";
import { uploadIcon } from "discourse/lib/uploads"; import { uploadIcon } from "discourse/lib/uploads";
import { dasherize } from "@ember/string"; import { dasherize } from "@ember/string";
import showModal from "discourse/lib/show-modal";
const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g; const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
@ -19,7 +20,6 @@ export default ComposerEditor.extend({
classNameBindings: ["fieldClass"], classNameBindings: ["fieldClass"],
allowUpload: true, allowUpload: true,
showLink: false, showLink: false,
showHyperlinkBox: false,
topic: null, topic: null,
showToolbar: true, showToolbar: true,
focusTarget: "reply", focusTarget: "reply",
@ -29,6 +29,7 @@ export default ComposerEditor.extend({
draftStatus: "null", draftStatus: "null",
replyPlaceholder: alias("field.translatedPlaceholder"), replyPlaceholder: alias("field.translatedPlaceholder"),
wizardEventFieldId: null, wizardEventFieldId: null,
composerEventPrefix: "wizard-editor",
@on("didInsertElement") @on("didInsertElement")
_composerEditorInit() { _composerEditorInit() {
@ -77,24 +78,13 @@ export default ComposerEditor.extend({
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll); $input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
this._bindUploadTarget(); this._bindUploadTarget();
const wizardEventNames = ["insert-text", "replace-text"]; const field = this.field;
const eventPrefix = this.eventPrefix; this.editorInputClass = `.${dasherize(field.type)}-${dasherize(
this.appEvents.reopen({ field.id
trigger(name, ...args) { )} .d-editor-input`;
let eventParts = name.split(":");
let currentEventPrefix = eventParts[0];
let currentEventName = eventParts[1];
if ( this._uppyInstance.on("file-added", () => {
currentEventPrefix !== "wizard-editor" && this.session.set("wizardEventFieldId", field.id);
wizardEventNames.some((wen) => wen === currentEventName)
) {
let wizardEventName = name.replace(eventPrefix, "wizard-editor");
return this._super(wizardEventName, ...args);
} else {
return this._super(name, ...args);
}
},
}); });
}, },
@ -116,12 +106,6 @@ export default ComposerEditor.extend({
return uploadIcon(false, this.siteSettings); return uploadIcon(false, this.siteSettings);
}, },
click(e) {
if ($(e.target).hasClass("wizard-composer-hyperlink")) {
this.set("showHyperlinkBox", false);
}
},
@bind @bind
_handleImageDeleteButtonClick(event) { _handleImageDeleteButtonClick(event) {
if (!event.target.classList.contains("delete-image-button")) { if (!event.target.classList.contains("delete-image-button")) {
@ -165,7 +149,7 @@ export default ComposerEditor.extend({
shortcut: "K", shortcut: "K",
trimLeading: true, trimLeading: true,
unshift: true, unshift: true,
sendAction: () => component.set("showHyperlinkBox", true), sendAction: (event) => component.send("showLinkModal", event),
}); });
if (this.siteSettings.mentionables_enabled) { if (this.siteSettings.mentionables_enabled) {
@ -206,17 +190,18 @@ export default ComposerEditor.extend({
this._super(...arguments); this._super(...arguments);
}, },
addLink(linkName, linkUrl) { showLinkModal(toolbarEvent) {
let link = `[${linkName}](${linkUrl})`; let linkText = "";
this.appEvents.trigger("wizard-editor:insert-text", { this._lastSel = toolbarEvent.selected;
fieldId: this.field.id,
text: link,
});
this.set("showHyperlinkBox", false);
},
hideBox() { if (this._lastSel) {
this.set("showHyperlinkBox", false); linkText = this._lastSel.value;
}
showModal("insert-hyperlink").setProperties({
linkText,
toolbarEvent,
});
}, },
showUploadModal() { showUploadModal() {

Datei anzeigen

@ -1,15 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
classNames: ["wizard-composer-hyperlink"],
actions: {
addLink() {
this.addLink(this.linkName, this.linkUrl);
},
hideBox() {
this.hideBox();
},
},
});

Datei anzeigen

@ -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() {

Datei anzeigen

@ -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;

Datei anzeigen

@ -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));

Datei anzeigen

@ -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}`;

Datei anzeigen

@ -1,3 +1,5 @@
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({}); export default Component.extend({
classNameBindings: ["fieldClass"],
});

Datei anzeigen

@ -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() {
discourseLater(() => {
schedule("afterRender", () => { schedule("afterRender", () => {
const $invalid = $( if ($(".invalid .wizard-focusable").length) {
".wizard-field.invalid:nth-of-type(1) .wizard-focusable" 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
); );

Datei anzeigen

@ -7,7 +7,7 @@ export default Component.extend(Subscription, {
@discourseComputed("subscribed") @discourseComputed("subscribed")
subscribedIcon(subscribed) { subscribedIcon(subscribed) {
return subscribed ? "check" : "dash"; return subscribed ? "check" : "times";
}, },
@discourseComputed("subscribed") @discourseComputed("subscribed")

Datei anzeigen

@ -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);
});

Datei anzeigen

@ -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);
}
);

Datei anzeigen

@ -35,6 +35,7 @@ function inputTypesContent(options = {}) {
const connectors = { const connectors = {
pair: [ pair: [
"equal", "equal",
"not_equal",
"greater", "greater",
"less", "less",
"greater_or_equal", "greater_or_equal",

Datei anzeigen

@ -4,6 +4,8 @@ import { get, set } from "@ember/object";
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import { deepEqual } from "discourse-common/lib/object"; import { deepEqual } from "discourse-common/lib/object";
const observedCache = [];
export default Mixin.create({ export default Mixin.create({
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
@ -32,7 +34,13 @@ export default Mixin.create({
}; };
listProperties(componentType, opts).forEach((property) => { listProperties(componentType, opts).forEach((property) => {
if (observedCache.includes(property)) {
obj.removeObserver(property, this, this.toggleUndo); obj.removeObserver(property, this, this.toggleUndo);
let index = observedCache.indexOf(property);
if (index !== -1) {
observedCache.splice(index, 1);
}
}
}); });
}, },
@ -45,6 +53,9 @@ export default Mixin.create({
}; };
listProperties(componentType, opts).forEach((property) => { listProperties(componentType, opts).forEach((property) => {
if (observedCache.indexOf(property) === -1) {
observedCache.push(property);
}
obj.addObserver(property, this, this.toggleUndo); obj.addObserver(property, this, this.toggleUndo);
}); });
}, },

Datei anzeigen

@ -72,7 +72,7 @@ export default EmberObject.extend(ValidState, {
valid = true; valid = true;
} }
this.setValid(valid); this.setValid(Boolean(valid));
return valid; return valid;
}, },

Datei anzeigen

@ -1,6 +1,8 @@
import I18n from "I18n"; import I18n from "I18n";
import { getCachedWizard } from "../models/custom-wizard"; import { getCachedWizard } from "../models/custom-wizard";
import Route from "@ember/routing/route"; import Route from "@ember/routing/route";
import { scrollTop } from "discourse/mixins/scroll-top";
import { action } from "@ember/object";
export default Route.extend({ export default Route.extend({
beforeModel() { beforeModel() {
@ -48,4 +50,10 @@ export default Route.extend({
controller.setProperties(props); controller.setProperties(props);
}, },
@action
didTransition() {
scrollTop();
return true;
},
}); });

Datei anzeigen

@ -32,13 +32,19 @@
<div class="metadata"> <div class="metadata">
<div class="title"> <div class="title">
<label>{{i18n "admin.wizard.api.title"}}</label> <label>{{i18n "admin.wizard.api.title"}}</label>
{{input value=api.title placeholder=(i18n "admin.wizard.api.title_placeholder")}} <Input
@value={{this.api.title}}
placeholder={{i18n "admin.wizard.api.title_placeholder"}}
/>
</div> </div>
<div class="name {{nameClass}}"> <div class="name {{nameClass}}">
<label>{{i18n "admin.wizard.api.name"}}</label> <label>{{i18n "admin.wizard.api.name"}}</label>
{{#if api.isNew}} {{#if api.isNew}}
{{input value=api.name placeholder=(i18n "admin.wizard.api.name_placeholder")}} <Input
@value={{this.api.name}}
placeholder={{i18n "admin.wizard.api.name_placeholder"}}
/>
{{else}} {{else}}
<span>{{api.name}}</span> <span>{{api.name}}</span>
{{/if}} {{/if}}
@ -104,7 +110,9 @@
<div class="control-group"> <div class="control-group">
<label>{{i18n "admin.wizard.api.auth.url"}}</label> <label>{{i18n "admin.wizard.api.auth.url"}}</label>
<div class="controls"> <div class="controls">
{{input value=api.authUrl}} <Input
@value={{this.api.authUrl}}
/>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -112,21 +120,27 @@
<div class="control-group"> <div class="control-group">
<label>{{i18n "admin.wizard.api.auth.token_url"}}</label> <label>{{i18n "admin.wizard.api.auth.token_url"}}</label>
<div class="controls"> <div class="controls">
{{input value=api.tokenUrl}} <Input
@value={{this.api.tokenUrl}}
/>
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{i18n "admin.wizard.api.auth.client_id"}}</label> <label>{{i18n "admin.wizard.api.auth.client_id"}}</label>
<div class="controls"> <div class="controls">
{{input value=api.clientId}} <Input
@value={{this.api.clientId}}
/>
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{i18n "admin.wizard.api.auth.client_secret"}}</label> <label>{{i18n "admin.wizard.api.auth.client_secret"}}</label>
<div class="controls"> <div class="controls">
{{input value=api.clientSecret}} <Input
@value={{this.api.clientSecret}}
/>
</div> </div>
</div> </div>
@ -135,8 +149,14 @@
<div class="controls"> <div class="controls">
{{#each api.authParams as |param|}} {{#each api.authParams as |param|}}
<div class="param"> <div class="param">
{{input value=param.key placeholder=(i18n "admin.wizard.key")}} <Input
{{input value=param.value placeholder=(i18n "admin.wizard.value")}} @value={{this.param.key}}
placeholder={{i18n "admin.wizard.key"}}
/>
<Input
@value={{this.param.value}}
placeholder={{i18n "admin.wizard.value"}}
/>
{{d-button action=(action "removeParam") actionParam=param icon="times"}} {{d-button action=(action "removeParam") actionParam=param icon="times"}}
</div> </div>
{{/each}} {{/each}}
@ -149,14 +169,18 @@
<div class="control-group"> <div class="control-group">
<label>{{i18n "admin.wizard.api.auth.username"}}</label> <label>{{i18n "admin.wizard.api.auth.username"}}</label>
<div class="controls"> <div class="controls">
{{input value=api.username}} <Input
@value={{this.api.username}}
/>
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{i18n "admin.wizard.api.auth.password"}}</label> <label>{{i18n "admin.wizard.api.auth.password"}}</label>
<div class="controls"> <div class="controls">
{{input value=api.password}} <Input
@value={{this.api.password}}
/>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -235,11 +259,15 @@
<div class="endpoint"> <div class="endpoint">
<div class="endpoint-"> <div class="endpoint-">
<div class="top"> <div class="top">
{{input value=endpoint.name <Input
placeholder=(i18n "admin.wizard.api.endpoint.name")}} @value={{this.endpoint.name}}
{{input value=endpoint.url placeholder={{i18n "admin.wizard.api.endpoint.name"}}
placeholder=(i18n "admin.wizard.api.endpoint.url") />
class="endpoint-url"}} <Input
@value={{this.endpoint.url}}
placeholder={{i18n "admin.wizard.api.endpoint.url"}}
class="endpoint-url"
/>
{{d-button action=(action "removeEndpoint") {{d-button action=(action "removeEndpoint")
actionParam=endpoint actionParam=endpoint
icon="times" icon="times"

Datei anzeigen

@ -11,11 +11,12 @@
</div> </div>
{{/if}} {{/if}}
{{input <Input
id="custom-wizard-file-upload" id="custom-wizard-file-upload"
type="file" @type="file"
accept="application/json" accept="application/json"
input=(action "setFile")}} {{on "input" (action "setFile")}}
/>
{{d-button {{d-button
id="upload-button" id="upload-button"
label="admin.wizard.manager.upload" label="admin.wizard.manager.upload"
@ -65,16 +66,18 @@
{{/link-to}} {{/link-to}}
</td> </td>
<td class="control-column"> <td class="control-column">
{{input <Input
type="checkbox" @type="checkbox"
class="export" class="export"
change=(action "selectWizard")}} {{on "change" (action "selectWizard")}}
/>
</td> </td>
<td class="control-column"> <td class="control-column">
{{input <Input
type="checkbox" @type="checkbox"
class="destroy" class="destroy"
change=(action "selectWizard")}} {{on "change" (action "selectWizard")}}
/>
</td> </td>
</tr> </tr>
{{/each}} {{/each}}

Datei anzeigen

@ -1,9 +1,10 @@
{{#if wizard}} {{#if wizard}}
<div class="wizard-header large"> <div class="wizard-header large">
{{input <Input
@value={{this.wizard.name}}
name="name" name="name"
value=wizard.name placeholder={{i18n "admin.wizard.name_placeholder"}}
placeholderKey="admin.wizard.name_placeholder"}} />
<div class="wizard-url"> <div class="wizard-url">
{{#if wizard.name}} {{#if wizard.name}}
@ -23,11 +24,12 @@
<label>{{i18n "admin.wizard.background"}}</label> <label>{{i18n "admin.wizard.background"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input <Input
@value={{this.wizard.background}}
name="background" name="background"
value=wizard.background placeholder={{i18n "admin.wizard.background_placeholder"}}
placeholderKey="admin.wizard.background_placeholder" class="small"
class="small"}} />
</div> </div>
</div> </div>
@ -58,7 +60,10 @@
<label>{{i18n "admin.wizard.save_submissions"}}</label> <label>{{i18n "admin.wizard.save_submissions"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=wizard.save_submissions}} <Input
@type="checkbox"
@checked={{this.wizard.save_submissions}}
/>
<span>{{i18n "admin.wizard.save_submissions_label"}}</span> <span>{{i18n "admin.wizard.save_submissions_label"}}</span>
</div> </div>
</div> </div>
@ -68,7 +73,10 @@
<label>{{i18n "admin.wizard.multiple_submissions"}}</label> <label>{{i18n "admin.wizard.multiple_submissions"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=wizard.multiple_submissions}} <Input
@type="checkbox"
@checked={{this.wizard.multiple_submissions}}
/>
<span>{{i18n "admin.wizard.multiple_submissions_label"}}</span> <span>{{i18n "admin.wizard.multiple_submissions_label"}}</span>
</div> </div>
</div> </div>
@ -78,7 +86,10 @@
<label>{{i18n "admin.wizard.after_signup"}}</label> <label>{{i18n "admin.wizard.after_signup"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=wizard.after_signup}} <Input
@type="checkbox"
@checked={{this.wizard.after_signup}}
/>
<span>{{i18n "admin.wizard.after_signup_label"}}</span> <span>{{i18n "admin.wizard.after_signup_label"}}</span>
</div> </div>
</div> </div>
@ -88,7 +99,10 @@
<label>{{i18n "admin.wizard.prompt_completion"}}</label> <label>{{i18n "admin.wizard.prompt_completion"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=wizard.prompt_completion}} <Input
@type="checkbox"
@checked={{this.wizard.prompt_completion}}
/>
<span>{{i18n "admin.wizard.prompt_completion_label"}}</span> <span>{{i18n "admin.wizard.prompt_completion_label"}}</span>
</div> </div>
</div> </div>
@ -98,7 +112,10 @@
<label>{{i18n "admin.wizard.after_time"}}</label> <label>{{i18n "admin.wizard.after_time"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=wizard.after_time}} <Input
@type="checkbox"
@checked={{this.wizard.after_time}}
/>
<span>{{i18n "admin.wizard.after_time_label"}}</span> <span>{{i18n "admin.wizard.after_time_label"}}</span>
{{d-button {{d-button
action=(action "setNextSessionScheduled") action=(action "setNextSessionScheduled")
@ -114,7 +131,10 @@
<label>{{i18n "admin.wizard.required"}}</label> <label>{{i18n "admin.wizard.required"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=wizard.required}} <Input
@type="checkbox"
@checked={{this.wizard.required}}
/>
<span>{{i18n "admin.wizard.required_label"}}</span> <span>{{i18n "admin.wizard.required_label"}}</span>
</div> </div>
</div> </div>
@ -124,7 +144,10 @@
<label>{{i18n "admin.wizard.restart_on_revisit"}}</label> <label>{{i18n "admin.wizard.restart_on_revisit"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=wizard.restart_on_revisit}} <Input
@type="checkbox"
@checked={{this.wizard.restart_on_revisit}}
/>
<span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span> <span>{{i18n "admin.wizard.restart_on_revisit_label"}}</span>
</div> </div>
</div> </div>

Datei anzeigen

@ -20,9 +20,10 @@
)}} )}}
</td> </td>
<td class="input"> <td class="input">
{{input <Input
value=field.name @value={{this.field.name}}
placeholder=(i18n "admin.wizard.custom_field.name.select")}} placeholder={{i18n "admin.wizard.custom_field.name.select"}}
/>
</td> </td>
<td class="multi-select"> <td class="multi-select">
{{multi-select {{multi-select

Datei anzeigen

@ -1,21 +0,0 @@
<div class="wizard-composer-hyperlink-contents">
<h3>{{i18n "composer.link_dialog_title"}}</h3>
{{input
class="composer-link-name"
placeholder=(i18n "composer.link_optional_text")
type="text"
value=linkName}}
{{input
class="composer-link-url"
placeholder=(i18n "composer.link_url_placeholder")
type="text"
value=linkUrl}}
{{d-button
label="wizard_composer.modal_ok"
class="add-link btn-primary"
click=(action "addLink")}}
{{d-button
label="wizard_composer.modal_cancel"
class="hide-hyperlink-box btn-danger"
click=(action "hideBox")}}
</div>

Datei anzeigen

@ -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>

Datei anzeigen

@ -40,7 +40,12 @@
</div> </div>
{{conditional-loading-spinner condition=loading}} {{conditional-loading-spinner condition=loading}}
{{textarea tabindex=tabindex value=value class="d-editor-input" placeholder=placeholder}} <Textarea
tabindex={{this.tabindex}}
@value={{this.value}}
class="d-editor-input"
placeholder={{this.placeholder}}
/>
</div> </div>
{{/if}} {{/if}}
</div> </div>

Datei anzeigen

@ -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

Datei anzeigen

@ -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}}
/>

Datei anzeigen

@ -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")
@ -14,6 +15,6 @@
</button> </button>
{{#if field.char_counter}} {{#if field.char_counter}}
{{char-counter field.value field.max_length}} {{wizard-char-counter field.value field.max_length}}
{{/if}} {{/if}}
</div> </div>

Datei anzeigen

@ -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

Datei anzeigen

@ -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}}
/>

Datei anzeigen

@ -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

Datei anzeigen

@ -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}}
/>

Datei anzeigen

@ -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}}
/>

Datei anzeigen

@ -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}}
/>

Datei anzeigen

@ -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}}

Datei anzeigen

@ -79,7 +79,10 @@
)}} )}}
<div class="setting-gutter"> <div class="setting-gutter">
{{input type="checkbox" checked=action.post_builder}} <Input
@type="checkbox"
@checked={{this.action.post_builder}}
/>
<span>{{i18n "admin.wizard.action.post_builder.checkbox"}}</span> <span>{{i18n "admin.wizard.action.post_builder.checkbox"}}</span>
</div> </div>
</div> </div>
@ -408,7 +411,10 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=action.wizard_user}} <Input
@type="checkbox"
@checked={{this.action.wizard_user}}
/>
</div> </div>
</div> </div>
@ -477,7 +483,10 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=action.wizard_user}} <Input
@type="checkbox"
@checked={{this.action.wizard_user}}
/>
</div> </div>
</div> </div>
@ -716,7 +725,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"
@ -877,7 +886,10 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=action.skip_redirect}} <Input
@type="checkbox"
@checked={{this.action.skip_redirect}}
/>
<span> <span>
{{i18n "admin.wizard.action.skip_redirect.description" type="topic"}} {{i18n "admin.wizard.action.skip_redirect.description" type="topic"}}
@ -891,7 +903,10 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="checkbox" checked=action.suppress_notifications}} <Input
@type="checkbox"
@checked={{this.action.suppress_notifications}}
/>
<span> <span>
{{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}} {{i18n "admin.wizard.action.suppress_notifications.description" type="topic"}}
@ -907,7 +922,7 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input value=action.code}} <Input @value={{this.action.code}} />
</div> </div>
</div> </div>
{{/if}} {{/if}}

Datei anzeigen

@ -11,7 +11,10 @@
<label>{{i18n "admin.wizard.field.label"}}</label> <label>{{i18n "admin.wizard.field.label"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input name="label" value=field.label}} <Input
name="label"
@value={{this.field.label}}
/>
</div> </div>
</div> </div>
@ -22,7 +25,10 @@
<div class="setting-value"> <div class="setting-value">
<span>{{i18n "admin.wizard.field.required_label"}}</span> <span>{{i18n "admin.wizard.field.required_label"}}</span>
{{input type="checkbox" checked=field.required}} <Input
@type="checkbox"
@checked={{this.field.required}}
/>
</div> </div>
</div> </div>
@ -31,7 +37,10 @@
<label>{{i18n "admin.wizard.field.description"}}</label> <label>{{i18n "admin.wizard.field.description"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{textarea name="description" value=field.description}} <Textarea
name="description"
@value={{this.field.description}}
/>
</div> </div>
</div> </div>
@ -78,11 +87,12 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input <Input
type="number" @type="number"
name="min_length" name="min_length"
value=field.min_length @value={{this.field.min_length}}
class="small"}} class="small"
/>
</div> </div>
</div> </div>
@ -92,11 +102,12 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input <Input
type="number" @type="number"
name="max_length" name="max_length"
value=field.max_length @value={{this.field.max_length}}
class="small"}} class="small"
/>
</div> </div>
</div> </div>
@ -107,9 +118,10 @@
<div class="setting-value"> <div class="setting-value">
<span>{{i18n "admin.wizard.field.char_counter_placeholder"}}</span> <span>{{i18n "admin.wizard.field.char_counter_placeholder"}}</span>
{{input <Input
type="checkbox" @type="checkbox"
checked=field.char_counter}} @checked={{this.field.char_counter}}
/>
</div> </div>
</div> </div>
@ -119,10 +131,11 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{textarea <Textarea
name="field_placeholder" name="field_placeholder"
class="medium" class="medium"
value=field.placeholder}} @value={{this.field.placeholder}}
/>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -134,7 +147,11 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{textarea name="preview-template" value=field.preview_template class="preview-template"}} <Textarea
name="preview-template"
class="preview-template"
@value={{this.field.preview_template}}
/>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -146,7 +163,10 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input value=field.file_types class="medium"}} <Input
@value={{this.field.file_types}}
class="medium"
/>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -158,7 +178,11 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="number" value=field.limit class="small"}} <Input
@type="number"
@value={{this.field.limit}}
class="small"
/>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -170,7 +194,10 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input value=field.format class="medium"}} <Input
@value={{this.field.format}}
class="medium"
/>
<label>{{html-safe (i18n "admin.wizard.field.date_time_format.instructions")}}</label> <label>{{html-safe (i18n "admin.wizard.field.date_time_format.instructions")}}</label>
</div> </div>
</div> </div>
@ -229,9 +256,10 @@
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input <Input
type="checkbox" @type="checkbox"
checked=field.can_create_tag}} @checked={{this.field.can_create_tag}}
/>
</div> </div>
</div> </div>
{{/if}} {{/if}}

Datei anzeigen

@ -3,9 +3,10 @@
<label>{{i18n "admin.wizard.step.title"}}</label> <label>{{i18n "admin.wizard.step.title"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input <Input
name="title" name="title"
value=step.title}} @value={{this.step.title}}
/>
</div> </div>
</div> </div>
@ -51,7 +52,10 @@
<div class="setting-label"></div> <div class="setting-label"></div>
<div class="setting-value force-final"> <div class="setting-value force-final">
<h4>{{i18n "admin.wizard.step.force_final.label"}}</h4> <h4>{{i18n "admin.wizard.step.force_final.label"}}</h4>
{{input type="checkbox" checked=step.force_final}} <Input
@type="checkbox"
@checked={{this.step.force_final}}
/>
<span>{{i18n "admin.wizard.step.force_final.description"}}</span> <span>{{i18n "admin.wizard.step.force_final.description"}}</span>
</div> </div>
</div> </div>
@ -77,7 +81,9 @@
<div class="label"> <div class="label">
{{i18n "admin.wizard.step.required_data.not_permitted_message"}} {{i18n "admin.wizard.step.required_data.not_permitted_message"}}
</div> </div>
{{input value=step.required_data_message}} <Input
@value={{this.step.required_data_message}}
/>
</div> </div>
{{/if}} {{/if}}
</div> </div>

Datei anzeigen

@ -21,11 +21,12 @@
<div class="input"> <div class="input">
{{#if showText}} {{#if showText}}
{{input <Input
type="text" @type="text"
value=value @value={{this.value}}
placeholder=(i18n placeholderKey) placeholder={{i18n placeholderKey}}
change=(action "changeInputValue")}} {{on "change" (action "changeInputValue")}}
/>
{{/if}} {{/if}}
{{#if showComboBox}} {{#if showComboBox}}

Datei anzeigen

@ -7,7 +7,10 @@
<li> <li>
<span class="setting-title"> <span class="setting-title">
<h4>{{i18n (concat "admin.wizard.field.validations." type)}}</h4> <h4>{{i18n (concat "admin.wizard.field.validations." type)}}</h4>
{{input type="checkbox" checked=props.status}} <Input
@type="checkbox"
@checked={{this.props.status}}
/>
{{i18n "admin.wizard.field.validations.enabled"}} {{i18n "admin.wizard.field.validations.enabled"}}
</span> </span>
<div class="validation-container"> <div class="validation-container">
@ -27,7 +30,11 @@
<label>{{i18n "admin.wizard.field.validations.max_topic_age"}}</label> <label>{{i18n "admin.wizard.field.validations.max_topic_age"}}</label>
</div> </div>
<div class="setting-value"> <div class="setting-value">
{{input type="number" class="time-n-value" value=props.time_n_value}} <Input
@type="number"
@value={{this.props.time_n_value}}
class="time-n-value"
/>
{{combo-box {{combo-box
value=(readonly props.time_unit) value=(readonly props.time_unit)
content=timeUnits content=timeUnits

Datei anzeigen

@ -32,15 +32,10 @@ body.custom-wizard {
} }
} }
&.invalid { &.invalid .wizard-focusable {
textarea,
input[type="text"],
input[type="checkbox"],
.select-kit {
outline: 4px solid var(--danger); outline: 4px solid var(--danger);
} }
} }
}
.checkbox-field { .checkbox-field {
display: flex; display: flex;

Datei anzeigen

@ -324,6 +324,7 @@ en:
then: "then" then: "then"
set: "set" set: "set"
equal: '=' equal: '='
not_equal: '!='
greater: '>' greater: '>'
less: '<' less: '<'
greater_or_equal: '>=' greater_or_equal: '>='

Datei anzeigen

@ -456,11 +456,16 @@ class CustomWizard::Action
if new_group_params[:usernames].present? if new_group_params[:usernames].present?
user_ids = get_user_ids(new_group_params[:usernames]) user_ids = get_user_ids(new_group_params[:usernames])
if user_ids.count < new_group_params[:usernames].count
log_error("Warning, group creation: some users were not found!")
end
user_ids -= owner_ids if owner_ids user_ids -= owner_ids if owner_ids
user_ids.each { |user_id| group.group_users.build(user_id: user_id) } user_ids.each { |user_id| group.group_users.build(user_id: user_id) }
end end
if group.save
log_success("Group created", group.name) log_success("Group created", group.name)
end
result.output = group.name result.output = group.name
else else

Datei anzeigen

@ -15,13 +15,13 @@ class CustomWizard::Log
@username = attrs['username'] @username = attrs['username']
end end
def self.create(wizard_id, action, username, message) def self.create(wizard_id, action, username, message, date = Time.now)
log_id = SecureRandom.hex(12) log_id = SecureRandom.hex(12)
PluginStore.set('custom_wizard_log', PluginStore.set('custom_wizard_log',
log_id.to_s, log_id.to_s,
{ {
date: Time.now, date: date,
wizard_id: wizard_id, wizard_id: wizard_id,
action: action, action: action,
username: username, username: username,

Datei anzeigen

@ -30,6 +30,7 @@ class CustomWizard::Mapper
OPERATORS = { OPERATORS = {
equal: '==', equal: '==',
not_equal: "!=",
greater: '>', greater: '>',
less: '<', less: '<',
greater_or_equal: '>=', greater_or_equal: '>=',
@ -44,7 +45,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
@ -255,7 +256,7 @@ class CustomWizard::Mapper
end end
end end
if opts[:template] && CustomWizard::Subscription.subscribed? if opts[:template] #&& CustomWizard::Subscription.subscribed?
template = Liquid::Template.parse(string) template = Liquid::Template.parse(string)
string = template.render(data) string = template.render(data)
end end
@ -267,7 +268,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)

Datei anzeigen

@ -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)
@ -120,16 +124,23 @@ class CustomWizard::Submission
end end
def self.list(wizard, order_by: nil, page: nil) def self.list(wizard, order_by: nil, page: nil)
list_actor_id = wizard.actor_id
list_user = wizard.user if wizard.user.present?
params = { plugin_name: "#{wizard.id}_#{KEY}" } params = { plugin_name: "#{wizard.id}_#{KEY}" }
params[:key] = wizard.actor_id if wizard.actor_id params[:key] = list_actor_id if list_actor_id
query = PluginStoreRow.where(params) query = PluginStoreRow.where(params)
result = OpenStruct.new(submissions: [], total: nil) result = OpenStruct.new(submissions: [], total: nil)
query.each do |record| query.each do |record|
if (submission_data = ::JSON.parse(record.value)).any? if (submission_data = ::JSON.parse(record.value)).any?
submission_user = list_user || User.find_by(id: record.key.to_i)
submission_data.each do |data| submission_data.each do |data|
result.submissions.push(new(wizard, data)) _wizard = wizard.clone
_wizard.user = submission_user if submission_user.present?
result.submissions.push(new(_wizard, data))
end end
end end
end end

Datei anzeigen

@ -84,7 +84,7 @@ class CustomWizard::TemplateValidator
def validate_guests(object, type) def validate_guests(object, type)
guests_permitted = @data[:permitted] && @data[:permitted].any? do |m| guests_permitted = @data[:permitted] && @data[:permitted].any? do |m|
m["output"].include?(CustomWizard::Wizard::GUEST_GROUP_ID) m["output"]&.include?(CustomWizard::Wizard::GUEST_GROUP_ID)
end end
return unless guests_permitted return unless guests_permitted

Datei anzeigen

@ -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,
@ -230,6 +232,7 @@ class CustomWizard::Wizard
m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID) m[:type] === 'assignment' && [*m[:result]].include?(GUEST_GROUP_ID)
else else
if m[:type] === 'assignment' if m[:type] === 'assignment'
[*m[:result]].include?(GUEST_GROUP_ID) ||
[*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) || [*m[:result]].include?(Group::AUTO_GROUPS[:everyone]) ||
GroupUser.exists?(group_id: m[:result], user_id: user.id) GroupUser.exists?(group_id: m[:result], user_id: user.id)
elsif m[:type] === 'validation' elsif m[:type] === 'validation'
@ -281,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?

Datei anzeigen

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
# name: discourse-custom-wizard # name: discourse-custom-wizard
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more. # about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
# version: 2.3.0 # version: 2.3.4
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever
# url: https://github.com/paviliondev/discourse-custom-wizard # url: https://github.com/paviliondev/discourse-custom-wizard
# contact_emails: development@pavilion.tech # contact_emails: development@pavilion.tech

Datei anzeigen

@ -2,6 +2,7 @@
describe CustomWizard::Action do describe CustomWizard::Action do
fab!(:user) { Fabricate(:user, name: "Angus", username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) } fab!(:user) { Fabricate(:user, name: "Angus", username: 'angus', email: "angus@email.com", trust_level: TrustLevel[2]) }
fab!(:user1) { Fabricate(:user, name: "Angus One", username: 'angus1', email: "angus_one@email.com", trust_level: TrustLevel[2]) }
fab!(:category) { Fabricate(:category, name: 'cat1', slug: 'cat-slug') } fab!(:category) { Fabricate(:category, name: 'cat1', slug: 'cat-slug') }
fab!(:tag) { Fabricate(:tag, name: 'tag1') } fab!(:tag) { Fabricate(:tag, name: 'tag1') }
fab!(:group) { Fabricate(:group) } fab!(:group) { Fabricate(:group) }
@ -12,6 +13,7 @@ describe CustomWizard::Action do
let(:watch_categories) { get_wizard_fixture("actions/watch_categories") } let(:watch_categories) { get_wizard_fixture("actions/watch_categories") }
let(:watch_tags) { get_wizard_fixture("actions/watch_tags") } let(:watch_tags) { get_wizard_fixture("actions/watch_tags") }
let(:create_group) { get_wizard_fixture("actions/create_group") } let(:create_group) { get_wizard_fixture("actions/create_group") }
let(:create_group_with_nonexistent_user) { get_wizard_fixture("actions/create_group_bad_user") }
let(:add_to_group) { get_wizard_fixture("actions/add_to_group") } let(:add_to_group) { get_wizard_fixture("actions/add_to_group") }
let(:send_message) { get_wizard_fixture("actions/send_message") } let(:send_message) { get_wizard_fixture("actions/send_message") }
let(:send_message_multi) { get_wizard_fixture("actions/send_message_multi") } let(:send_message_multi) { get_wizard_fixture("actions/send_message_multi") }
@ -350,7 +352,25 @@ describe CustomWizard::Action do
wizard = CustomWizard::Builder.new(@template[:id], user).build 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[0].id, step_1_field_1: "Text input").update
group_id = Group.where(name: wizard.current_submission.fields['action_9']).first.id
user_id = User.find_by(username: wizard_template['actions'][4]['usernames'][0]["output"][0]).id
expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true) expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true)
expect(GroupUser.where(group_id: group_id, user_id: user_id).exists?).to eq(true)
end
it '#create_group completes successfully when user included in usernames does not exist but excludes users who do not exist and includes warning in log' do
wizard_template['actions'] << create_group_with_nonexistent_user
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
group_id = Group.where(name: wizard.current_submission.fields['action_9']).first.id
expect(CustomWizard::Log.list_query.all.last.value.include? "some users were not found").to eq(true)
expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true)
expect(GroupUser.where(group_id: group_id).count).to eq(1)
end end
it '#add_to_group' do it '#add_to_group' do

Datei anzeigen

@ -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(
@ -286,6 +291,19 @@ describe CustomWizard::Mapper do
end end
end end
it "handles not equal pairs" do
expect(CustomWizard::Mapper.new(
inputs: inputs['not_equals_pair'],
data: data,
user: user1
).perform).to eq(true)
expect(CustomWizard::Mapper.new(
inputs: inputs['not_equals_pair'],
data: data,
user: user2
).perform).to eq(false)
end
it "handles greater than pairs" do it "handles greater than pairs" do
expect(CustomWizard::Mapper.new( expect(CustomWizard::Mapper.new(
inputs: inputs['greater_than_pair'], inputs: inputs['greater_than_pair'],
@ -373,7 +391,7 @@ describe CustomWizard::Mapper do
expect(result).to eq(template_params["step_1_field_1"]) expect(result).to eq(template_params["step_1_field_1"])
end end
it "requires a subscription" do it "does not require a subscription" do
template = '{{ "w{step_1_field_1}" | size }}' template = '{{ "w{step_1_field_1}" | size }}'
mapper = create_template_mapper(template_params, user1) mapper = create_template_mapper(template_params, user1)
result = mapper.interpolate( result = mapper.interpolate(
@ -383,7 +401,7 @@ describe CustomWizard::Mapper do
wizard: true, wizard: true,
value: true value: true
) )
expect(result).to eq("{{ \"#{template_params["step_1_field_1"]}\" | size }}") expect(result).to eq("5")
end end
context "with a subscription" do context "with a subscription" do
@ -448,6 +466,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

Datei anzeigen

@ -9,6 +9,7 @@ describe CustomWizard::TemplateValidator do
let(:composer_preview) { get_wizard_fixture("field/composer_preview") } let(:composer_preview) { get_wizard_fixture("field/composer_preview") }
let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") } let(:guests_permitted) { get_wizard_fixture("wizard/guests_permitted") }
let(:upload_field) { get_wizard_fixture("field/upload") } let(:upload_field) { get_wizard_fixture("field/upload") }
let(:validation_condition) { get_wizard_fixture("condition/validation_condition") }
let(:valid_liquid_template) { let(:valid_liquid_template) {
<<-LIQUID.strip <<-LIQUID.strip
@ -182,6 +183,13 @@ describe CustomWizard::TemplateValidator do
CustomWizard::TemplateValidator.new(template).perform CustomWizard::TemplateValidator.new(template).perform
).to eq(true) ).to eq(true)
end end
it "validates settings with validation conditions" do
template[:permitted] = validation_condition["condition"]
expect(
CustomWizard::TemplateValidator.new(template).perform
).to eq(true)
end
end end
context "steps" do context "steps" do

Datei anzeigen

@ -6,11 +6,15 @@ describe CustomWizard::Wizard do
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
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(: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)
@permitted_template = template_json.dup @permitted_template = template_json.dup
@permitted_template["permitted"] = permitted_json["permitted"] @permitted_template["permitted"] = permitted_json["permitted"]
@guests_permitted_template = template_json.dup
@guests_permitted_template["permitted"] = guests_permitted_json["permitted"]
@wizard = CustomWizard::Wizard.new(template_json, user) @wizard = CustomWizard::Wizard.new(template_json, user)
end end
@ -72,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")
@ -200,6 +226,30 @@ describe CustomWizard::Wizard do
end end
end end
context "with subscription and guest wizard" do
before do
enable_subscription("standard")
end
it "permits admins" do
expect(
CustomWizard::Wizard.new(@guests_permitted_template, admin_user).permitted?
).to eq(true)
end
it "permits regular users" do
expect(
CustomWizard::Wizard.new(@guests_permitted_template, user).permitted?
).to eq(true)
end
it "permits guests" do
expect(
CustomWizard::Wizard.new(@guests_permitted_template, nil, "guest123").permitted?
).to eq(true)
end
end
context "submissions" do context "submissions" do
before do before do
CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save

Datei anzeigen

@ -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": [
"angus3"
]
}
],
"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"
}
]
}

Datei anzeigen

@ -0,0 +1,17 @@
{
"condition": [
{
"type": "validation",
"pairs": [
{
"index": 0,
"key": "trust_level",
"key_type": "user_field",
"value": "2",
"value_type": "text",
"connector": "greater_or_equal"
}
]
}
]
}

Datei anzeigen

@ -195,6 +195,21 @@
] ]
} }
], ],
"not_equals_pair": [
{
"type": "validation",
"pairs": [
{
"index": 0,
"key": "trust_level",
"key_type": "user_field",
"value": "1",
"value_type": "text",
"connector": "not_equal"
}
]
}
],
"greater_than_pair": [ "greater_than_pair": [
{ {
"type": "validation", "type": "validation",

Datei anzeigen

@ -4,7 +4,7 @@ describe CustomWizard::LogSerializer do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
it 'should return log attributes' do it 'should return log attributes' do
CustomWizard::Log.create('first-test-wizard', 'perform_first_action', 'first_test_user', 'First log message') CustomWizard::Log.create('first-test-wizard', 'perform_first_action', 'first_test_user', 'First log message', 1.day.ago)
CustomWizard::Log.create('second-test-wizard', 'perform_second_action', 'second_test_user', 'Second log message') CustomWizard::Log.create('second-test-wizard', 'perform_second_action', 'second_test_user', 'Second log message')
json_array = ActiveModel::ArraySerializer.new( json_array = ActiveModel::ArraySerializer.new(

Datei anzeigen

@ -3,7 +3,8 @@
require_relative '../../plugin_helper' require_relative '../../plugin_helper'
describe CustomWizard::SubmissionSerializer do describe CustomWizard::SubmissionSerializer do
fab!(:user) { Fabricate(:user) } fab!(:user1) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) }
let(:template_json) { let(:template_json) {
JSON.parse(File.open( JSON.parse(File.open(
@ -13,36 +14,43 @@ describe CustomWizard::SubmissionSerializer do
before do before do
CustomWizard::Template.save(template_json, skip_jobs: true) CustomWizard::Template.save(template_json, skip_jobs: true)
wizard = CustomWizard::Wizard.create(template_json["id"], user)
CustomWizard::Submission.new(wizard, wizard = CustomWizard::Wizard.create(template_json["id"], user1)
step_1_field_1: "I am user submission", CustomWizard::Submission.new(wizard, step_1_field_1: "I am user1 submission", submitted_at: Time.now.iso8601).save
submitted_at: Time.now.iso8601
).save wizard = CustomWizard::Wizard.create(template_json["id"], user2)
@list = CustomWizard::Submission.list(wizard, page: 0) CustomWizard::Submission.new(wizard, step_1_field_1: "I am user2 submission", submitted_at: Time.now.iso8601).save
end end
it 'should return submission attributes' do it 'should return submission attributes' do
wizard = CustomWizard::Wizard.create(template_json["id"])
list = CustomWizard::Submission.list(wizard, page: 0)
json_array = ActiveModel::ArraySerializer.new( json_array = ActiveModel::ArraySerializer.new(
@list.submissions, list.submissions,
each_serializer: described_class each_serializer: described_class
).as_json ).as_json
expect(json_array.length).to eq(1) expect(json_array.length).to eq(2)
expect(json_array[0][:id].present?).to eq(true) 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) expect(json_array[0][:submitted_at].present?).to eq(true)
expect(json_array[0][:user]).to eq(BasicUserSerializer.new(user2, root: false).as_json)
expect(json_array[1][:user]).to eq(BasicUserSerializer.new(user1, root: false).as_json)
end end
it "should return field values, types and labels" do it "should return field values, types and labels" do
wizard = CustomWizard::Wizard.create(template_json["id"])
list = CustomWizard::Submission.list(wizard, page: 0)
json_array = ActiveModel::ArraySerializer.new( json_array = ActiveModel::ArraySerializer.new(
@list.submissions, list.submissions,
each_serializer: described_class each_serializer: described_class
).as_json ).as_json
expect(json_array.length).to eq(1) expect(json_array.length).to eq(2)
expect(json_array[0][:fields].as_json).to eq({ expect(json_array[0][:fields].as_json).to eq({
"step_1_field_1": { "step_1_field_1": {
"value": "I am user submission", "value": "I am user2 submission",
"type": "text", "type": "text",
"label": "Text" "label": "Text"
} }

Datei anzeigen

@ -54,6 +54,70 @@ acceptance("Field | Fields", function (needs) {
"Input in composer" "Input in composer"
); );
}); });
test("Composer - Hyperlink", async function (assert) {
await visit("/w/wizard");
assert.ok(
visible(".wizard-field.composer-field .wizard-field-composer textarea")
);
assert.ok(
exists(".wizard-field.composer-field .d-editor-button-bar button")
);
assert.ok(visible(".wizard-btn.toggle-preview"));
await fillIn(
".wizard-field.composer-field .wizard-field-composer textarea",
"This is a link to "
);
assert.ok(
!exists(".insert-link.modal-body"),
"no hyperlink modal by default"
);
await click(
".wizard-field.composer-field .wizard-field-composer .d-editor button.link"
);
assert.ok(exists(".insert-link.modal-body"), "hyperlink modal visible");
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-primary");
assert.strictEqual(
query(".wizard-field.composer-field .wizard-field-composer textarea")
.value,
"This is a link to [Google](https://google.com)",
"adds link with url and text, prepends 'https://'"
);
assert.ok(
!exists(
".wizard-field.composer-field .wizard-field-composer .insert-link.modal-body"
),
"modal dismissed after submitting link"
);
await fillIn(
".wizard-field.composer-field .wizard-field-composer textarea",
"Reset textarea contents."
);
await click(
".wizard-field.composer-field .wizard-field-composer .d-editor button.link"
);
await fillIn(".modal-body .link-url", "google.com");
await fillIn(".modal-body .link-text", "Google");
await click(".modal-footer button.btn-danger");
assert.strictEqual(
query(".wizard-field.composer-field .wizard-field-composer textarea")
.value,
"Reset textarea contents.",
"does not insert anything after cancelling"
);
assert.ok(
!exists(".insert-link.modal-body"),
"modal dismissed after cancelling"
);
});
test("Text Only", async function (assert) { test("Text Only", async function (assert) {
await visit("/w/wizard"); await visit("/w/wizard");