0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-22 17:30:29 +01:00
Dieser Commit ist enthalten in:
Keegan George 2021-07-19 07:11:47 -07:00
Commit bdc4044581
57 geänderte Dateien mit 858 neuen und 393 gelöschten Zeilen

Datei anzeigen

@ -7,7 +7,7 @@ on:
- main - main
pull_request: pull_request:
schedule: schedule:
- cron: '0 0 * * *' - cron: '0 */12 * * *'
jobs: jobs:
build: build:
@ -53,26 +53,27 @@ jobs:
repository: discourse/discourse repository: discourse/discourse
fetch-depth: 1 fetch-depth: 1
- run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV - name: Fetch Repo Name
shell: bash id: repo-name
run: echo "::set-output name=value::$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')"
- name: Install plugin - name: Install plugin
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: plugins/${{ env.REPOSITORY_NAME }} path: plugins/${{ steps.repo-name.outputs.value }}
fetch-depth: 1 fetch-depth: 1
- name: Check spec existence - name: Check spec existence
id: check_spec id: check_spec
uses: andstor/file-existence-action@v1 uses: andstor/file-existence-action@v1
with: with:
files: "plugins/${{ env.REPOSITORY_NAME }}/spec" files: "plugins/${{ steps.repo-name.outputs.value }}/spec"
- name: Check qunit existence - name: Check qunit existence
id: check_qunit id: check_qunit
uses: andstor/file-existence-action@v1 uses: andstor/file-existence-action@v1
with: with:
files: "plugins/${{ env.REPOSITORY_NAME }}/test/javascripts" files: "plugins/${{ steps.repo-name.outputs.value }}/test/javascripts"
- name: Setup Git - name: Setup Git
run: | run: |
@ -105,7 +106,7 @@ jobs:
- name: Lint English locale - name: Lint English locale
if: matrix.build_type == 'backend' if: matrix.build_type == 'backend'
run: bundle exec ruby script/i18n_lint.rb "plugins/${{ env.REPOSITORY_NAME }}/locales/{client,server}.en.yml" run: bundle exec ruby script/i18n_lint.rb "plugins/${{ steps.repo-name.outputs.value }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory - name: Get yarn cache directory
id: yarn-cache-dir id: yarn-cache-dir
@ -130,9 +131,9 @@ jobs:
- name: Plugin RSpec with Coverage - name: Plugin RSpec with Coverage
if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true'
run: SIMPLECOV=1 bin/rake plugin:spec[${{ env.REPOSITORY_NAME }}] run: SIMPLECOV=1 bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}]
- name: Plugin QUnit - name: Plugin QUnit
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true'
run: bundle exec rake plugin:qunit['${{ env.REPOSITORY_NAME }}','1200000'] run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000']
timeout-minutes: 30 timeout-minutes: 30

Datei anzeigen

@ -25,6 +25,7 @@ export default Component.extend(UndoChanges, {
showContent: or("isCategory", "isTag", "isGroup", "isDropdown"), showContent: or("isCategory", "isTag", "isGroup", "isDropdown"),
showLimit: or("isCategory", "isTag"), showLimit: or("isCategory", "isTag"),
isTextType: or("isText", "isTextarea", "isComposer"), isTextType: or("isText", "isTextarea", "isComposer"),
isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]), categoryPropertyTypes: selectKitContent(["id", "slug"]),
showAdvanced: alias("field.type"), showAdvanced: alias("field.type"),
messageUrl: "https://thepavilion.io/t/2809", messageUrl: "https://thepavilion.io/t/2809",

Datei anzeigen

@ -1,17 +1,19 @@
import CustomWizard from "../models/custom-wizard"; import CustomWizard from "../models/custom-wizard";
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
const excludedMetaFields = ["route_to", "redirect_on_complete", "redirect_to"];
export default DiscourseRoute.extend({ export default DiscourseRoute.extend({
model(params) { model(params) {
return CustomWizard.submissions(params.wizardId); return CustomWizard.submissions(params.wizardId);
}, },
setupController(controller, model) { setupController(controller, model) {
if (model.submissions) { if (model && model.submissions) {
let fields = []; let fields = ["username"];
model.submissions.forEach((s) => { model.submissions.forEach((s) => {
Object.keys(s).forEach((k) => { Object.keys(s.fields).forEach((k) => {
if (fields.indexOf(k) < 0) { if (!excludedMetaFields.includes(k) && fields.indexOf(k) < 0) {
fields.push(k); fields.push(k);
} }
}); });
@ -19,9 +21,13 @@ export default DiscourseRoute.extend({
let submissions = []; let submissions = [];
model.submissions.forEach((s) => { model.submissions.forEach((s) => {
let submission = {}; let submission = {
fields.forEach((f) => { username: s.username,
submission[f] = s[f]; };
Object.keys(s.fields).forEach((f) => {
if (fields.includes(f)) {
submission[f] = s.fields[f];
}
}); });
submissions.push(submission); submissions.push(submission);
}); });

Datei anzeigen

@ -111,6 +111,31 @@
checked=field.char_counter}} checked=field.char_counter}}
</div> </div>
</div> </div>
<div class="setting full">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.field_placeholder"}}</label>
</div>
<div class="setting-value">
{{textarea
name="field_placeholder"
class="medium"
value=field.placeholder}}
</div>
</div>
{{/if}}
{{#if isComposerPreview}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.preview_template"}}</label>
</div>
<div class="setting-value">
{{textarea name="preview-template" value=field.preview_template}}
</div>
</div>
{{/if}} {{/if}}
{{#if isUpload}} {{#if isUpload}}

Datei anzeigen

@ -1,43 +1,4 @@
//= require discourse/app/lib/autocomplete //= require_tree_discourse discourse/app/lib
//= require discourse/app/lib/utilities
//= require discourse/app/lib/offset-calculator
//= require discourse/app/lib/lock-on
//= require discourse/app/lib/text-direction
//= require discourse/app/lib/to-markdown
//= require discourse/app/lib/load-script
//= require discourse/app/lib/url
//= require discourse/app/lib/ajax
//= require discourse/app/lib/ajax-error
//= require discourse/app/lib/page-visible
//= require discourse/app/lib/logout
//= require discourse/app/lib/render-tag
//= require discourse/app/lib/notification-levels
//= require discourse/app/lib/computed
//= require discourse/app/lib/user-search
//= require discourse/app/lib/text
//= require discourse/app/lib/formatter
//= require discourse/app/lib/quote
//= require discourse/app/lib/link-mentions
//= require discourse/app/lib/link-hashtags
//= require discourse/app/lib/category-hashtags
//= require discourse/app/lib/tag-hashtags
//= require discourse/app/lib/uploads
//= require discourse/app/lib/category-tag-search
//= require discourse/app/lib/intercept-click
//= require discourse/app/lib/show-modal
//= require discourse/app/lib/key-value-store
//= require discourse/app/lib/settings
//= require discourse/app/lib/user-presence
//= require discourse/app/lib/hash
//= require discourse/app/lib/bookmark
//= require discourse/app/lib/put-cursor-at-end
//= require discourse/app/lib/safari-hacks
//= require discourse/app/lib/preload-store
//= require discourse/app/lib/topic-fancy-title
//= require discourse/app/lib/cookie
//= require discourse/app/lib/public-js-versions
//= require discourse/app/lib/load-oneboxes
//= require discourse/app/lib/highlight-syntax
//= require discourse/app/mixins/singleton //= require discourse/app/mixins/singleton
//= require discourse/app/mixins/upload //= require discourse/app/mixins/upload
@ -46,35 +7,7 @@
//= require message-bus //= require message-bus
//= require discourse/app/models/login-method //= require_tree_discourse discourse/app/models
//= require discourse/app/models/permission-type
//= require discourse/app/models/archetype
//= require discourse/app/models/rest
//= require discourse/app/models/site
//= require discourse/app/models/category
//= require discourse/app/models/session
//= require discourse/app/models/post-action-type
//= require discourse/app/models/trust-level
//= require discourse/app/models/store
//= require discourse/app/models/result-set
//= require discourse/app/models/bookmark
//= require discourse/app/models/user
//= require discourse/app/models/user-stream
//= require discourse/app/models/user-action
//= require discourse/app/models/user-action-group
//= require discourse/app/models/user-posts-stream
//= require discourse/app/models/badge
//= require discourse/app/models/badge-grouping
//= require discourse/app/models/user-badge
//= require discourse/app/models/topic
//= require discourse/app/models/action-summary
//= require discourse/app/models/user-action-stat
//= require discourse/app/models/user-drafts-stream
//= require discourse/app/models/user-draft
//= require discourse/app/models/composer
//= require discourse/app/models/draft
//= require discourse/app/models/group
//= require discourse/app/models/group-history
//= require discourse/app/helpers/category-link //= require discourse/app/helpers/category-link
//= require discourse/app/helpers/user-avatar //= require discourse/app/helpers/user-avatar

Datei anzeigen

@ -1,3 +1,42 @@
import DateInput from "discourse/components/date-input"; import DateInput from "discourse/components/date-input";
import loadScript from "discourse/lib/load-script";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
/* global Pikaday:true */
export default DateInput.extend(); export default DateInput.extend({
useNativePicker: false,
@discourseComputed()
placeholder() {
return this.format;
},
_loadPikadayPicker(container) {
return loadScript("/javascripts/pikaday.js").then(() => {
let defaultOptions = {
field: this.element.querySelector(".date-picker"),
container: container || this.element.querySelector(".picker-container"),
bound: container === null,
format: this.format,
firstDay: 1,
i18n: {
previousMonth: I18n.t("dates.previous_month"),
nextMonth: I18n.t("dates.next_month"),
months: moment.months(),
weekdays: moment.weekdays(),
weekdaysShort: moment.weekdaysShort(),
},
onSelect: (date) => this._handleSelection(date),
};
if (this.relativeDate) {
defaultOptions = Object.assign({}, defaultOptions, {
minDate: moment(this.relativeDate).toDate(),
});
}
return new Pikaday(Object.assign({}, defaultOptions, this._opts()));
});
},
});

Datei anzeigen

@ -0,0 +1,49 @@
import Component from "@ember/component";
import { loadOneboxes } from "discourse/lib/load-oneboxes";
import { schedule } from "@ember/runloop";
import discourseDebounce from "discourse-common/lib/debounce";
import { resolveAllShortUrls } from "pretty-text/upload-short-url";
import { ajax } from "discourse/lib/ajax";
import { on } from "discourse-common/utils/decorators";
export default Component.extend({
@on("init")
updatePreview() {
if (this.isDestroyed) {
return;
}
schedule("afterRender", () => {
if (this._state !== "inDOM" || !this.element) {
return;
}
const $preview = $(this.element);
if ($preview.length === 0) {
return;
}
this.previewUpdated($preview);
});
},
previewUpdated($preview) {
// Paint oneboxes
const paintFunc = () => {
loadOneboxes(
$preview[0],
ajax,
null,
null,
this.siteSettings.max_oneboxes_per_post,
true // refresh on every load
);
};
discourseDebounce(this, paintFunc, 450);
// Short upload urls need resolution
resolveAllShortUrls(ajax, this.siteSettings, $preview[0]);
},
});

Datei anzeigen

@ -26,7 +26,9 @@ export default {
const setDefaultOwner = requirejs("discourse-common/lib/get-owner") const setDefaultOwner = requirejs("discourse-common/lib/get-owner")
.setDefaultOwner; .setDefaultOwner;
const messageBus = requirejs("message-bus-client").default; const messageBus = requirejs("message-bus-client").default;
const getToken = requirejs("wizard/lib/ajax").getToken;
const setEnvironment = requirejs("discourse-common/config/environment")
.setEnvironment;
const container = app.__container__; const container = app.__container__;
Discourse.Model = EmberObject.extend(); Discourse.Model = EmberObject.extend();
Discourse.__container__ = container; Discourse.__container__ = container;
@ -89,6 +91,7 @@ export default {
const session = container.lookup("session:main"); const session = container.lookup("session:main");
const setupData = document.getElementById("data-discourse-setup").dataset; const setupData = document.getElementById("data-discourse-setup").dataset;
session.set("highlightJsPath", setupData.highlightJsPath); session.set("highlightJsPath", setupData.highlightJsPath);
setEnvironment(setupData.environment);
Router.reopen({ Router.reopen({
rootURL: getUrl("/w/"), rootURL: getUrl("/w/"),
@ -107,5 +110,9 @@ export default {
}, },
model() {}, model() {},
}); });
$.ajaxPrefilter(function (_, __, jqXHR) {
jqXHR.setRequestHeader("X-CSRF-Token", getToken());
});
}, },
}; };

Datei anzeigen

@ -1,7 +1,10 @@
import I18n from "I18n"; import I18n from "I18n";
const getThemeId = () => { const getThemeId = () => {
let themeId = parseInt($("meta[name=discourse_theme_ids]")[0].content, 10); let themeId = parseInt(
document.querySelector("meta[name=discourse_theme_id]").content,
10
);
if (!isNaN(themeId)) { if (!isNaN(themeId)) {
return themeId.toString(); return themeId.toString();

Datei anzeigen

@ -1,7 +1,7 @@
{{d-editor {{d-editor
tabindex=field.tabindex tabindex=field.tabindex
value=composer.reply value=composer.reply
placeholder=replyPlaceholder placeholderTranslated=replyPlaceholder
previewUpdated=(action "previewUpdated") previewUpdated=(action "previewUpdated")
markdownOptions=markdownOptions markdownOptions=markdownOptions
extraButtons=(action "extraButtons") extraButtons=(action "extraButtons")

Datei anzeigen

@ -0,0 +1,5 @@
<div class="wizard-composer-preview d-editor-preview-wrapper">
<div class="d-editor-preview">
{{html-safe field.preview_template}}
</div>
</div>

Datei anzeigen

@ -2,4 +2,5 @@
date=date date=date
onChange=(action "onChange") onChange=(action "onChange")
tabindex=field.tabindex tabindex=field.tabindex
format=field.format
}} }}

Datei anzeigen

@ -15,7 +15,7 @@
} }
.wizard-message { .wizard-message {
background-color: $primary-low; background-color: var(--primary-low);
width: 100%; width: 100%;
padding: 10px; padding: 10px;
box-sizing: border-box; box-sizing: border-box;
@ -37,7 +37,7 @@
} }
a + a { a + a {
border-left: 1px solid $primary-medium; border-left: 1px solid var(--primary-medium);
padding-left: 5px; padding-left: 5px;
margin-left: 5px; margin-left: 5px;
} }
@ -89,7 +89,7 @@
.wizard-settings-parent { .wizard-settings-parent {
padding: 20px; padding: 20px;
border: 1px solid $primary-low; border: 1px solid var(--primary-low);
} }
.wizard-settings-group { .wizard-settings-group {
@ -115,7 +115,7 @@
.wizard-custom-field { .wizard-custom-field {
background: transparent; background: transparent;
background-color: dark-light-diff($primary, $secondary, 96%, -65%); background-color: var(--primary-very-low);
padding: 20px; padding: 20px;
} }
@ -182,7 +182,7 @@
a { a {
padding: 6px 12px; padding: 6px 12px;
font-size: 1rem; font-size: 1rem;
background-color: $primary-low; background-color: var(--primary-low);
} }
button { button {
@ -256,6 +256,10 @@
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 0; margin-bottom: 0;
&.medium {
width: 40%;
}
} }
input[type="number"] { input[type="number"] {
@ -263,7 +267,7 @@
} }
input[disabled] { input[disabled] {
background-color: $primary-low; background-color: var(--primary-low);
cursor: not-allowed; cursor: not-allowed;
} }
@ -434,8 +438,8 @@
display: none; display: none;
margin: 0 0 10px 0; margin: 0 0 10px 0;
padding: 10px; padding: 10px;
background-color: $secondary; background-color: var(--secondary);
border: 1px solid $primary-medium; border: 1px solid var(--primary-medium);
max-width: 100%; max-width: 100%;
&.force-preview { &.force-preview {
@ -454,17 +458,17 @@
.btn { .btn {
margin-right: 10px; margin-right: 10px;
color: $primary; color: var(--primary);
&:hover { &:hover {
color: $secondary; color: var(--secondary);
} }
} }
.wizard-editor-gutter-popover { .wizard-editor-gutter-popover {
position: absolute; position: absolute;
padding: 10px; padding: 10px;
background-color: $secondary; background-color: var(--secondary);
box-shadow: shadow("card"); box-shadow: shadow("card");
z-index: 200; z-index: 200;
top: 40px; top: 40px;
@ -502,7 +506,7 @@
width: 100px; width: 100px;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
background-color: $primary-low; background-color: var(--primary-low);
margin-right: 10px; margin-right: 10px;
margin-left: 3px; margin-left: 3px;
} }
@ -587,8 +591,8 @@
.add-mapper-input .btn, .add-mapper-input .btn,
.btn-after-time, .btn-after-time,
.wizard-editor-gutter .btn { .wizard-editor-gutter .btn {
background-color: $secondary; background-color: var(--secondary);
border: 1px solid $primary-medium; border: 1px solid var(--primary-medium);
} }
.admin-wizards-custom-fields { .admin-wizards-custom-fields {

Datei anzeigen

@ -48,7 +48,7 @@
.wizard-api-authentication { .wizard-api-authentication {
display: flex; display: flex;
background-color: $primary-very-low; background-color: var(--primary-very-low);
padding: 20px; padding: 20px;
margin-bottom: 20px; margin-bottom: 20px;
@ -68,7 +68,7 @@
} }
.status { .status {
border-left: 1px solid $primary; border-left: 1px solid var(--primary);
margin-left: 20px; margin-left: 20px;
padding-left: 20px; padding-left: 20px;
width: 50%; width: 50%;
@ -89,7 +89,7 @@
} }
.wizard-api-endpoints { .wizard-api-endpoints {
background-color: $primary-very-low; background-color: var(--primary-very-low);
padding: 20px; padding: 20px;
margin-bottom: 20px; margin-bottom: 20px;

Datei anzeigen

@ -16,13 +16,13 @@
#import-button:enabled, #import-button:enabled,
#export-button:enabled { #export-button:enabled {
background-color: $tertiary; background-color: var(--tertiary);
color: $secondary; color: var(--secondary);
} }
#destroy-button:enabled { #destroy-button:enabled {
background-color: $danger; background-color: var(--danger);
color: $secondary; color: var(--secondary);
} }
} }
@ -32,13 +32,13 @@
.filename { .filename {
padding: 0 10px; padding: 0 10px;
border: 1px solid $primary; border: 1px solid var(--primary);
display: inline-flex; display: inline-flex;
height: 28px; height: 28px;
line-height: 28px; line-height: 28px;
a { a {
color: $primary; color: var(--primary);
margin-right: 5px; margin-right: 5px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;

Datei anzeigen

@ -22,7 +22,7 @@
width: min-content; width: min-content;
margin-bottom: 10px; margin-bottom: 10px;
height: 20px; height: 20px;
border: 2px solid $primary-low; border: 2px solid var(--primary-low);
} }
} }
@ -33,8 +33,8 @@
position: relative; position: relative;
padding: 25px 7px 7px 7px; padding: 25px 7px 7px 7px;
margin-bottom: 10px; margin-bottom: 10px;
background: rgba($secondary, 0.5); background: rgba(var(--secondary-rgb), 0.5);
border: 2px solid $primary-low; border: 2px solid var(--primary-low);
.d-icon { .d-icon {
text-align: center; text-align: center;
@ -45,7 +45,7 @@
} }
input[disabled] { input[disabled] {
background-color: $primary-low; background-color: var(--primary-low);
border-color: #ddd; border-color: #ddd;
} }
@ -62,10 +62,10 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transform: translateY(-50%); transform: translateY(-50%);
background: $secondary; background: var(--secondary);
border-radius: 50%; border-radius: 50%;
font-size: 0.8em; font-size: 0.8em;
border: 2px solid $primary-low; border: 2px solid var(--primary-low);
} }
&.association, &.association,
@ -89,8 +89,8 @@
&.single { &.single {
height: 28px; height: 28px;
background: $secondary; background: var(--secondary);
border: 1px solid $primary-medium; border: 1px solid var(--primary-medium);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -126,7 +126,7 @@
} }
.type-selector a { .type-selector a {
color: $primary; color: var(--primary);
margin-right: 4px; margin-right: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -150,11 +150,11 @@
box-shadow: shadow("dropdown"); box-shadow: shadow("dropdown");
position: absolute; position: absolute;
display: flex; display: flex;
background: $secondary; background: var(--secondary);
z-index: 200; z-index: 200;
padding: 5px 7px; padding: 5px 7px;
flex-direction: column; flex-direction: column;
border: 1px solid $primary-low; border: 1px solid var(--primary-low);
} }
.value-list .remove-value-btn { .value-list .remove-value-btn {
@ -162,7 +162,7 @@
border: none; border: none;
.d-icon { .d-icon {
color: $primary; color: var(--primary);
} }
} }

Datei anzeigen

@ -31,7 +31,7 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
.extra-info-wrapper & { .extra-info-wrapper & {
color: $header-primary; color: var(--header_primary);
} }
} }
@ -108,7 +108,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
.extra-info-wrapper & { .extra-info-wrapper & {
color: $header-primary; color: var(--header_primary);
} }
} }

Datei anzeigen

@ -199,11 +199,11 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba($primary, 0.8); background-color: rgba(var(--primary-rgb), 0.8);
} }
.wizard-composer-hyperlink-contents { .wizard-composer-hyperlink-contents {
background-color: $secondary; background-color: var(--secondary);
padding: 20px; padding: 20px;
h3 { h3 {
@ -227,7 +227,7 @@
bottom: 1px; bottom: 1px;
right: 1px; right: 1px;
padding: 10px; padding: 10px;
background-color: $secondary; background-color: var(--secondary);
} }
.bottom-bar { .bottom-bar {
@ -239,31 +239,43 @@
// Markdown table styles for wizard composer preview // Markdown table styles for wizard composer preview
.cooked table, .cooked,
.d-editor-preview table { .d-editor-preview {
border-collapse: collapse; a.mention {
display: inline-block; // https://bugzilla.mozilla.org/show_bug.cgi?id=1656119
font-weight: bold;
font-size: 0.93em;
color: var(--primary-high-or-secondary-low);
padding: 0 4px 1px;
background: var(--primary-low);
border-radius: 8px;
}
tr { table {
border-bottom: 1px solid var(--primary-low); border-collapse: collapse;
&.highlighted {
animation: background-fade-highlight 2.5s ease-out; tr {
border-bottom: 1px solid var(--primary-low);
&.highlighted {
animation: background-fade-highlight 2.5s ease-out;
}
} }
}
thead { thead {
th { th {
text-align: left; text-align: left;
padding: 0.5em; padding: 0.5em;
font-weight: bold; font-weight: bold;
color: var(--primary); color: var(--primary);
}
} }
}
tbody { tbody {
border-top: 3px solid var(--primary-low); border-top: 3px solid var(--primary-low);
} }
td { td {
padding: 3px 3px 3px 0.5em; padding: 3px 3px 3px 0.5em;
}
} }
} }

Datei anzeigen

@ -162,4 +162,15 @@
.text-field input { .text-field input {
margin-bottom: 0; margin-bottom: 0;
} }
.text-field,
.textarea-field,
.composer-field {
input[type="text"],
textarea {
&:focus::placeholder {
color: transparent;
}
}
}
} }

Datei anzeigen

@ -2,6 +2,7 @@
@import "common/foundation/variables"; @import "common/foundation/variables";
@import "common/base/code_highlighting"; @import "common/base/code_highlighting";
@import "common/base/modal"; @import "common/base/modal";
@import "common/base/onebox";
@import "common/components/buttons"; @import "common/components/buttons";
@import "common/d-editor"; @import "common/d-editor";
@import "desktop/modal"; @import "desktop/modal";

Datei anzeigen

@ -181,7 +181,9 @@ en:
max_length_placeholder: "Maximum length in characters" max_length_placeholder: "Maximum length in characters"
char_counter: "Character Counter" char_counter: "Character Counter"
char_counter_placeholder: "Display Character Counter" char_counter_placeholder: "Display Character Counter"
field_placeholder: "Field Placeholder"
file_types: "File Types" file_types: "File Types"
preview_template: "Preview Template"
limit: "Limit" limit: "Limit"
property: "Property" property: "Property"
prefill: "Prefill" prefill: "Prefill"
@ -208,6 +210,7 @@ en:
text: "Text" text: "Text"
textarea: Textarea textarea: Textarea
composer: Composer composer: Composer
composer_preview: Composer Preview
text_only: Text Only text_only: Text Only
number: Number number: Number
checkbox: Checkbox checkbox: Checkbox

Datei anzeigen

@ -1,8 +1,8 @@
en: en:
admin: admin:
wizard: wizard:
submissions: submission:
no_user: "deleted (id: %{id})" no_user: "deleted (user_id: %{user_id})"
wizard: wizard:
custom_title: "Wizard" custom_title: "Wizard"

Datei anzeigen

@ -13,34 +13,20 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
def show def show
render_json_dump( render_json_dump(
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false),
submissions: build_submissions.as_json submissions: ActiveModel::ArraySerializer.new(ordered_submissions, each_serializer: CustomWizard::SubmissionSerializer)
) )
end end
def download def download
send_data build_submissions.to_json, send_data ordered_submissions.to_json,
filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json", filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json",
content_type: "application/json", content_type: "application/json",
disposition: "attachment" disposition: "attachment"
end end
private protected
def build_submissions def ordered_submissions
PluginStoreRow.where(plugin_name: "#{@wizard.id}_submissions") CustomWizard::Submission.list(@wizard, order_by: 'id')
.order('id DESC')
.map do |row|
value = ::JSON.parse(row.value)
if user = User.find_by(id: row.key)
username = user.username
else
username = I18n.t('admin.wizard.submissions.no_user', id: row.key)
end
value.map do |v|
{ username: username }.merge!(v.except("redirect_to"))
end
end.flatten
end end
end end

Datei anzeigen

@ -109,6 +109,8 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:format, :format,
:limit, :limit,
:property, :property,
:preview_template,
:placeholder,
prefill: mapped_params, prefill: mapped_params,
content: mapped_params, content: mapped_params,
condition: mapped_params, condition: mapped_params,

Datei anzeigen

@ -8,7 +8,7 @@ class CustomWizard::StepsController < ::ApplicationController
update[:fields] = {} update[:fields] = {}
if params[:fields] if params[:fields]
field_ids = @step_template['fields'].map { |f| f['id'] } field_ids = @builder.wizard.field_ids
params[:fields].each do |k, v| params[:fields].each do |k, v|
update[:fields][k] = v if field_ids.include? k update[:fields][k] = v if field_ids.include? k
end end
@ -28,7 +28,7 @@ class CustomWizard::StepsController < ::ApplicationController
current_step = @wizard.find_step(update[:step_id]) current_step = @wizard.find_step(update[:step_id])
current_submission = @wizard.current_submission current_submission = @wizard.current_submission
result = {} result = {}
@wizard.filter_conditional_fields
if current_step.conditional_final_step && !current_step.last_step if current_step.conditional_final_step && !current_step.last_step
current_step.force_final = true current_step.force_final = true
end end
@ -36,15 +36,19 @@ class CustomWizard::StepsController < ::ApplicationController
if current_step.final? if current_step.final?
builder.template.actions.each do |action_template| builder.template.actions.each do |action_template|
if action_template['run_after'] === 'wizard_completion' if action_template['run_after'] === 'wizard_completion'
CustomWizard::Action.new( action_result = CustomWizard::Action.new(
action: action_template, action: action_template,
wizard: @wizard, wizard: @wizard,
data: current_submission submission: current_submission
).perform ).perform
if action_result.success?
current_submission = action_result.submission
end
end end
end end
@wizard.save_submission(current_submission) current_submission.save
if redirect = get_redirect if redirect = get_redirect
updater.result[:redirect_on_complete] = redirect updater.result[:redirect_on_complete] = redirect
@ -54,6 +58,8 @@ class CustomWizard::StepsController < ::ApplicationController
result[:final] = true result[:final] = true
else else
current_submission.save
result[:final] = false result[:final] = false
result[:next_step_id] = current_step.next.id result[:next_step_id] = current_step.next.id
end end
@ -101,9 +107,9 @@ class CustomWizard::StepsController < ::ApplicationController
def get_redirect def get_redirect
return @result[:redirect_on_next] if @result[:redirect_on_next].present? return @result[:redirect_on_next] if @result[:redirect_on_next].present?
current_submission = @wizard.current_submission submission = @wizard.current_submission
return nil unless current_submission.present? return nil unless submission.present?
## route_to set by actions, redirect_on_complete set by actions, redirect_to set at wizard entry ## route_to set by actions, redirect_on_complete set by actions, redirect_to set at wizard entry
current_submission[:route_to] || current_submission[:redirect_on_complete] || current_submission[:redirect_to] submission.route_to || submission.redirect_on_complete || submission.redirect_to
end end
end end

Datei anzeigen

@ -6,7 +6,7 @@ class CustomWizard::WizardController < ::ApplicationController
before_action :ensure_plugin_enabled before_action :ensure_plugin_enabled
helper_method :wizard_page_title helper_method :wizard_page_title
helper_method :wizard_theme_ids helper_method :wizard_theme_id
helper_method :wizard_theme_lookup helper_method :wizard_theme_lookup
helper_method :wizard_theme_translations_lookup helper_method :wizard_theme_translations_lookup
@ -20,16 +20,16 @@ class CustomWizard::WizardController < ::ApplicationController
wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title')
end end
def wizard_theme_ids def wizard_theme_id
wizard ? [wizard.theme_id] : nil wizard ? wizard.theme_id : nil
end end
def wizard_theme_lookup(name) def wizard_theme_lookup(name)
Theme.lookup_field(wizard_theme_ids, mobile_view? ? :mobile : :desktop, name) Theme.lookup_field(wizard_theme_id, mobile_view? ? :mobile : :desktop, name)
end end
def wizard_theme_translations_lookup def wizard_theme_translations_lookup
Theme.lookup_field(wizard_theme_ids, :translations, I18n.locale) Theme.lookup_field(wizard_theme_id, :translations, I18n.locale)
end end
def index def index
@ -63,8 +63,9 @@ class CustomWizard::WizardController < ::ApplicationController
if user && wizard.can_access? if user && wizard.can_access?
submission = wizard.current_submission submission = wizard.current_submission
if submission && submission['redirect_to']
result.merge!(redirect_to: submission['redirect_to']) if submission.present? && submission.redirect_to
result.merge!(redirect_to: submission.redirect_to)
end end
wizard.final_cleanup! wizard.final_cleanup!

Datei anzeigen

@ -1,5 +1,5 @@
{ {
"result": { "result": {
"line": 90.52 "line": 91.83
} }
} }

Datei anzeigen

@ -5,7 +5,7 @@ module InvitesControllerCustomWizard
wizard_id = @user.custom_fields['redirect_to_wizard'] wizard_id = @user.custom_fields['redirect_to_wizard']
if wizard_id && url != '/' if wizard_id && url != '/'
CustomWizard::Wizard.set_submission_redirect(@user, wizard_id, url) CustomWizard::Wizard.set_wizard_redirect(@user, wizard_id, url)
url = "/w/#{wizard_id.dasherize}" url = "/w/#{wizard_id.dasherize}"
end end
end end

Datei anzeigen

@ -9,7 +9,7 @@ module Jobs
user_ids = [] user_ids = []
User.human_users.each do |user| User.human_users.each do |user|
if CustomWizard::Wizard.set_wizard_redirect(wizard.id, user) if CustomWizard::Wizard.set_user_redirect(wizard.id, user)
user_ids.push(user.id) user_ids.push(user.id)
end end
end end

Datei anzeigen

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::Action class CustomWizard::Action
attr_accessor :data, attr_accessor :submission,
:action, :action,
:user, :user,
:guardian, :guardian,
@ -11,7 +11,7 @@ class CustomWizard::Action
@action = opts[:action] @action = opts[:action]
@user = @wizard.user @user = @wizard.user
@guardian = Guardian.new(@user) @guardian = Guardian.new(@user)
@data = opts[:data] @submission = opts[:submission]
@log = [] @log = []
@result = CustomWizard::ActionResult.new @result = CustomWizard::ActionResult.new
end end
@ -26,14 +26,21 @@ class CustomWizard::Action
end end
if @result.success? && @result.output.present? if @result.success? && @result.output.present?
data[action['id']] = @result.output @submission.fields[action['id']] = @result.output
end end
save_log save_log
@result.submission = @submission
@result
end
def mapper_data
@mapper_data ||= @submission&.fields_and_meta || {}
end end
def mapper def mapper
@mapper ||= CustomWizard::Mapper.new(user: user, data: data) @mapper ||= CustomWizard::Mapper.new(user: user, data: mapper_data)
end end
def create_topic def create_topic
@ -47,7 +54,7 @@ class CustomWizard::Action
messages = creator.errors.full_messages.join(" ") messages = creator.errors.full_messages.join(" ")
log_error("failed to create", messages) log_error("failed to create", messages)
elsif action['skip_redirect'].blank? elsif action['skip_redirect'].blank?
data['redirect_on_complete'] = post.topic.url @submission.redirect_on_complete = post.topic.url
end end
if creator.errors.blank? if creator.errors.blank?
@ -65,7 +72,7 @@ class CustomWizard::Action
if action['required'].present? if action['required'].present?
required = CustomWizard::Mapper.new( required = CustomWizard::Mapper.new(
inputs: action['required'], inputs: action['required'],
data: data, data: mapper_data,
user: user user: user
).perform ).perform
@ -79,7 +86,7 @@ class CustomWizard::Action
targets = CustomWizard::Mapper.new( targets = CustomWizard::Mapper.new(
inputs: action['recipient'], inputs: action['recipient'],
data: data, data: mapper_data,
user: user, user: user,
multiple: true multiple: true
).perform ).perform
@ -115,7 +122,7 @@ class CustomWizard::Action
messages = creator.errors.full_messages.join(" ") messages = creator.errors.full_messages.join(" ")
log_error("failed to create message", messages) log_error("failed to create message", messages)
elsif action['skip_redirect'].blank? elsif action['skip_redirect'].blank?
data['redirect_on_complete'] = post.topic.url @submission.redirect_on_complete = post.topic.url
end end
if creator.errors.blank? if creator.errors.blank?
@ -178,7 +185,7 @@ class CustomWizard::Action
def watch_categories def watch_categories
watched_categories = CustomWizard::Mapper.new( watched_categories = CustomWizard::Mapper.new(
inputs: action['categories'], inputs: action['categories'],
data: data, data: mapper_data,
user: user user: user
).perform ).perform
@ -193,7 +200,7 @@ class CustomWizard::Action
mute_remainder = CustomWizard::Mapper.new( mute_remainder = CustomWizard::Mapper.new(
inputs: action['mute_remainder'], inputs: action['mute_remainder'],
data: data, data: mapper_data,
user: user user: user
).perform ).perform
@ -202,7 +209,7 @@ class CustomWizard::Action
if action['usernames'] if action['usernames']
mapped_users = CustomWizard::Mapper.new( mapped_users = CustomWizard::Mapper.new(
inputs: action['usernames'], inputs: action['usernames'],
data: data, data: mapper_data,
user: user user: user
).perform ).perform
@ -284,7 +291,7 @@ class CustomWizard::Action
end end
route_to = Discourse.base_uri + url route_to = Discourse.base_uri + url
@result.output = data['route_to'] = route_to @result.output = @submission.route_to = route_to
log_success("route: #{route_to}") log_success("route: #{route_to}")
else else
@ -295,7 +302,7 @@ class CustomWizard::Action
def add_to_group def add_to_group
group_map = CustomWizard::Mapper.new( group_map = CustomWizard::Mapper.new(
inputs: action['group'], inputs: action['group'],
data: data, data: mapper_data,
user: user, user: user,
opts: { opts: {
multiple: true multiple: true
@ -345,18 +352,18 @@ class CustomWizard::Action
else else
url = CustomWizard::Mapper.new( url = CustomWizard::Mapper.new(
inputs: url_input, inputs: url_input,
data: data, data: mapper_data,
user: user user: user
).perform ).perform
end end
if action['code'] if action['code']
data[action['code']] = SecureRandom.hex(8) @submission.fields[action['code']] = SecureRandom.hex(8)
url += "&#{action['code']}=#{data[action['code']]}" url += "&#{action['code']}=#{@submission.fields[action['code']]}"
end end
route_to = UrlHelper.encode(url) route_to = UrlHelper.encode(url)
data['route_to'] = route_to @submission.route_to = route_to
log_info("route: #{route_to}") log_info("route: #{route_to}")
end end
@ -416,7 +423,7 @@ class CustomWizard::Action
def action_category def action_category
output = CustomWizard::Mapper.new( output = CustomWizard::Mapper.new(
inputs: action['category'], inputs: action['category'],
data: data, data: mapper_data,
user: user user: user
).perform ).perform
@ -434,7 +441,7 @@ class CustomWizard::Action
def action_tags def action_tags
output = CustomWizard::Mapper.new( output = CustomWizard::Mapper.new(
inputs: action['tags'], inputs: action['tags'],
data: data, data: mapper_data,
user: user, user: user,
).perform ).perform
@ -451,7 +458,7 @@ class CustomWizard::Action
if (custom_fields = action['custom_fields']).present? if (custom_fields = action['custom_fields']).present?
field_map = CustomWizard::Mapper.new( field_map = CustomWizard::Mapper.new(
inputs: custom_fields, inputs: custom_fields,
data: data, data: mapper_data,
user: user user: user
).perform ).perform
registered_fields = CustomWizard::CustomField.full_list registered_fields = CustomWizard::CustomField.full_list
@ -513,7 +520,7 @@ class CustomWizard::Action
params[:title] = CustomWizard::Mapper.new( params[:title] = CustomWizard::Mapper.new(
inputs: action['title'], inputs: action['title'],
data: data, data: mapper_data,
user: user user: user
).perform ).perform
@ -525,7 +532,7 @@ class CustomWizard::Action
wizard: true, wizard: true,
template: true template: true
) : ) :
data[action['post']] @submission.fields[action['post']]
params[:import_mode] = ActiveRecord::Type::Boolean.new.cast(action['suppress_notifications']) params[:import_mode] = ActiveRecord::Type::Boolean.new.cast(action['suppress_notifications'])
@ -548,7 +555,7 @@ class CustomWizard::Action
unless action[field].nil? || action[field] == "" unless action[field].nil? || action[field] == ""
params[field.to_sym] = CustomWizard::Mapper.new( params[field.to_sym] = CustomWizard::Mapper.new(
inputs: action[field], inputs: action[field],
data: data, data: mapper_data,
user: user user: user
).perform ).perform
end end
@ -587,7 +594,7 @@ class CustomWizard::Action
if input.present? if input.present?
value = CustomWizard::Mapper.new( value = CustomWizard::Mapper.new(
inputs: input, inputs: input,
data: data, data: mapper_data,
user: user user: user
).perform ).perform
@ -617,7 +624,7 @@ class CustomWizard::Action
if action[attr].present? if action[attr].present?
value = CustomWizard::Mapper.new( value = CustomWizard::Mapper.new(
inputs: action[attr], inputs: action[attr],
data: data, data: mapper_data,
user: user user: user
).perform ).perform

Datei anzeigen

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::ActionResult class CustomWizard::ActionResult
attr_accessor :success, :handler, :output attr_accessor :success, :handler, :output, :submission
def initialize def initialize
@success = false @success = false

Datei anzeigen

@ -24,7 +24,7 @@ class CustomWizard::Builder
def mapper def mapper
CustomWizard::Mapper.new( CustomWizard::Mapper.new(
user: @wizard.user, user: @wizard.user,
data: @wizard.current_submission data: @wizard.current_submission&.fields_and_meta
) )
end end
@ -47,9 +47,8 @@ class CustomWizard::Builder
step.on_update do |updater| step.on_update do |updater|
@updater = updater @updater = updater
@submission = (@wizard.current_submission || {}) @submission = @wizard.current_submission
.merge(@updater.submission) @submission.fields.merge!(@updater.submission)
.with_indifferent_access
@updater.validate @updater.validate
next if @updater.errors.any? next if @updater.errors.any?
@ -60,11 +59,11 @@ class CustomWizard::Builder
run_step_actions run_step_actions
if @updater.errors.empty? if @updater.errors.empty?
if route_to = @submission['route_to'] route_to = @submission.route_to
@submission.delete('route_to') @submission.route_to = nil
end @submission.save
@wizard.save_submission(@submission) @wizard.update!
@updater.result[:redirect_on_next] = route_to if route_to @updater.result[:redirect_on_next] = route_to if route_to
true true
@ -75,7 +74,7 @@ class CustomWizard::Builder
end end
end end
@wizard.update_step_order! @wizard.update!
@wizard @wizard
end end
@ -92,8 +91,8 @@ class CustomWizard::Builder
params[:value] = prefill_field(field_template, step_template) params[:value] = prefill_field(field_template, step_template)
if !build_opts[:reset] && (submission = @wizard.current_submission) if !build_opts[:reset] && (submission = @wizard.current_submission).present?
params[:value] = submission[field_template['id']] if submission[field_template['id']] params[:value] = submission.fields[field_template['id']] if submission.fields[field_template['id']]
end end
if field_template['type'] === 'group' && params[:value].present? if field_template['type'] === 'group' && params[:value].present?
@ -136,7 +135,7 @@ class CustomWizard::Builder
content = CustomWizard::Mapper.new( content = CustomWizard::Mapper.new(
inputs: content_inputs, inputs: content_inputs,
user: @wizard.user, user: @wizard.user,
data: @wizard.current_submission, data: @wizard.current_submission&.fields_and_meta,
opts: { opts: {
with_type: true with_type: true
} }
@ -171,7 +170,7 @@ class CustomWizard::Builder
index = CustomWizard::Mapper.new( index = CustomWizard::Mapper.new(
inputs: field_template['index'], inputs: field_template['index'],
user: @wizard.user, user: @wizard.user,
data: @wizard.current_submission data: @wizard.current_submission&.fields_and_meta
).perform ).perform
params[:index] = index.to_i unless index.nil? params[:index] = index.to_i unless index.nil?
@ -187,6 +186,28 @@ class CustomWizard::Builder
) )
end end
if field_template['preview_template'].present?
preview_template = mapper.interpolate(
field_template['preview_template'],
user: true,
value: true,
wizard: true,
template: true
)
params[:preview_template] = PrettyText.cook(preview_template)
end
if field_template['placeholder'].present?
params[:placeholder] = mapper.interpolate(
field_template['placeholder'],
user: true,
value: true,
wizard: true,
template: true
)
end
field = step.add_field(params) field = step.add_field(params)
end end
@ -195,7 +216,7 @@ class CustomWizard::Builder
CustomWizard::Mapper.new( CustomWizard::Mapper.new(
inputs: prefill, inputs: prefill,
user: @wizard.user, user: @wizard.user,
data: @wizard.current_submission data: @wizard.current_submission&.fields_and_meta
).perform ).perform
end end
end end
@ -205,7 +226,7 @@ class CustomWizard::Builder
result = CustomWizard::Mapper.new( result = CustomWizard::Mapper.new(
inputs: template['condition'], inputs: template['condition'],
user: @wizard.user, user: @wizard.user,
data: @wizard.current_submission, data: @wizard.current_submission&.fields_and_meta,
opts: { opts: {
multiple: true multiple: true
} }
@ -275,19 +296,20 @@ class CustomWizard::Builder
permitted_data = {} permitted_data = {}
submission_key = nil submission_key = nil
params_key = nil params_key = nil
submission = @wizard.current_submission || {} submission = @wizard.current_submission
permitted_params.each do |pp| permitted_params.each do |pp|
pair = pp['pairs'].first pair = pp['pairs'].first
params_key = pair['key'].to_sym params_key = pair['key'].to_sym
submission_key = pair['value'].to_sym submission_key = pair['value'].to_sym
if submission_key && params_key if submission_key && params_key && params[params_key].present?
submission[submission_key] = params[params_key] submission.permitted_param_keys << submission_key.to_s
submission.fields[submission_key] = params[params_key]
end end
end end
@wizard.save_submission(submission) submission.save
end end
def ensure_required_data(step, step_template) def ensure_required_data(step, step_template)
@ -296,13 +318,13 @@ class CustomWizard::Builder
pair['key'].present? && pair['value'].present? pair['key'].present? && pair['value'].present?
end end
if pairs.any? && !@wizard.current_submission if pairs.any? && !@wizard.current_submission.present?
step.permitted = false step.permitted = false
break break
end end
pairs.each do |pair| pairs.each do |pair|
pair['key'] = @wizard.current_submission[pair['key']] pair['key'] = @wizard.current_submission.fields[pair['key']]
end end
if !mapper.validate_pairs(pairs) if !mapper.validate_pairs(pairs)
@ -326,11 +348,15 @@ class CustomWizard::Builder
if @template.actions.present? if @template.actions.present?
@template.actions.each do |action_template| @template.actions.each do |action_template|
if action_template['run_after'] === updater.step.id if action_template['run_after'] === updater.step.id
CustomWizard::Action.new( result = CustomWizard::Action.new(
action: action_template, action: action_template,
wizard: @wizard, wizard: @wizard,
data: @submission submission: @submission
).perform ).perform
if result.success?
@submission = result.submission
end
end end
end end
end end

Datei anzeigen

@ -0,0 +1,5 @@
# frozen_string_literal: true
module CustomWizard
class SprocketsFileNotFound < StandardError; end
class SprocketsEmptyPath < StandardError; end
end

Datei anzeigen

@ -20,7 +20,9 @@ class CustomWizard::Field
:format, :format,
:limit, :limit,
:property, :property,
:content :content,
:preview_template,
:placeholder
attr_accessor :index, attr_accessor :index,
:step :step
@ -44,6 +46,8 @@ class CustomWizard::Field
@limit = attrs[:limit] @limit = attrs[:limit]
@property = attrs[:property] @property = attrs[:property]
@content = attrs[:content] @content = attrs[:content]
@preview_template = attrs[:preview_template]
@placeholder = attrs[:placeholder]
end end
def label def label
@ -63,20 +67,26 @@ class CustomWizard::Field
max_length: nil, max_length: nil,
prefill: nil, prefill: nil,
char_counter: nil, char_counter: nil,
validations: nil validations: nil,
placeholder: nil
}, },
textarea: { textarea: {
min_length: nil, min_length: nil,
max_length: nil, max_length: nil,
prefill: nil, prefill: nil,
char_counter: nil char_counter: nil,
placeholder: nil
}, },
composer: { composer: {
min_length: nil, min_length: nil,
max_length: nil, max_length: nil,
char_counter: nil char_counter: nil,
placeholder: nil
}, },
text_only: {}, text_only: {},
composer_preview: {
preview_template: nil,
},
date: { date: {
format: "YYYY-MM-DD" format: "YYYY-MM-DD"
}, },

Datei anzeigen

@ -0,0 +1,118 @@
# frozen_string_literal: true
class CustomWizard::Submission
include ActiveModel::SerializerSupport
KEY ||= "submissions"
META ||= %w(submitted_at route_to redirect_on_complete redirect_to)
attr_reader :id,
:user,
:user_id,
:wizard
attr_accessor :fields,
:permitted_param_keys
META.each do |attr|
class_eval { attr_accessor attr }
end
def initialize(wizard, data = {}, user_id = nil)
@wizard = wizard
@user_id = user_id
if user_id
@user = User.find_by(id: user_id)
else
@user = wizard.user
end
data = (data || {}).with_indifferent_access
@id = data['id'] || SecureRandom.hex(12)
non_field_keys = META + ['id']
@fields = data.except(*non_field_keys) || {}
META.each do |attr|
send("#{attr}=", data[attr]) if data[attr]
end
@permitted_param_keys = data['permitted_param_keys'] || []
end
def save
return nil unless wizard.save_submissions
validate
submission_list = self.class.list(wizard, user_id: user.id)
submissions = submission_list.select { |submission| submission.id != self.id }
submissions.push(self)
submission_data = submissions.map { |submission| data_to_save(submission) }
PluginStore.set("#{wizard.id}_#{KEY}", user.id, submission_data)
end
def validate
self.fields = fields.select { |key, value| validate_field_key(key) }
end
def validate_field_key(key)
wizard.field_ids.include?(key) ||
wizard.action_ids.include?(key) ||
permitted_param_keys.include?(key)
end
def fields_and_meta
result = fields
META.each do |attr|
if value = self.send(attr)
result[attr] = value
end
end
result
end
def present?
fields_and_meta.present?
end
def data_to_save(submission)
data = {
id: submission.id
}
data.merge!(submission.fields_and_meta)
if submission.permitted_param_keys.present?
data[:permitted_param_keys] = submission.permitted_param_keys
end
data
end
def self.get(wizard, user_id)
data = PluginStore.get("#{wizard.id}_#{KEY}", user_id).first
new(wizard, data, user_id)
end
def self.list(wizard, user_id: nil, order_by: nil)
params = { plugin_name: "#{wizard.id}_#{KEY}" }
params[:key] = user_id if user_id.present?
query = PluginStoreRow.where(params)
query = query.order("#{order_by} DESC") if order_by.present?
result = []
query.each do |record|
if (submission_data = ::JSON.parse(record.value)).any?
submission_data.each do |data|
result.push(new(wizard, data, record.key))
end
end
end
result
end
end

Datei anzeigen

@ -32,11 +32,11 @@ class ::CustomWizard::UpdateValidator
@updater.errors.add(field_id, I18n.t('wizard.field.required', label: label)) @updater.errors.add(field_id, I18n.t('wizard.field.required', label: label))
end end
if min_length && value.is_a?(String) && value.strip.length < min_length.to_i if min_length.present? && value.is_a?(String) && value.strip.length < min_length.to_i
@updater.errors.add(field_id, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i)) @updater.errors.add(field_id, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i))
end end
if max_length && value.is_a?(String) && value.strip.length > max_length.to_i if max_length.present? && value.is_a?(String) && value.strip.length > max_length.to_i
@updater.errors.add(field_id, I18n.t('wizard.field.too_long', label: label, max: max_length.to_i)) @updater.errors.add(field_id, I18n.t('wizard.field.too_long', label: label, max: max_length.to_i))
end end
@ -52,7 +52,7 @@ class ::CustomWizard::UpdateValidator
@updater.errors.add(field_id, I18n.t('wizard.field.invalid_file', label: label, types: file_types)) @updater.errors.add(field_id, I18n.t('wizard.field.invalid_file', label: label, types: file_types))
end end
if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value) if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value, format)
@updater.errors.add(field_id, I18n.t('wizard.field.invalid_date')) @updater.errors.add(field_id, I18n.t('wizard.field.invalid_date'))
end end
@ -88,13 +88,8 @@ class ::CustomWizard::UpdateValidator
.include?(File.extname(value['original_filename'])[1..-1]) .include?(File.extname(value['original_filename'])[1..-1])
end end
def validate_date(value) def validate_date(value, format)
begin v8.eval("moment('#{value}', '#{format}', true).isValid()")
Date.parse(value)
true
rescue ArgumentError
false
end
end end
def validate_time(value) def validate_time(value)
@ -126,4 +121,12 @@ class ::CustomWizard::UpdateValidator
def standardise_boolean(value) def standardise_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value) ActiveRecord::Type::Boolean.new.cast(value)
end end
def v8
return @ctx if @ctx
@ctx = PrettyText.v8
PrettyText.ctx_load(@ctx, "#{Rails.root}/vendor/assets/javascripts/moment.js")
@ctx
end
end end

Datei anzeigen

@ -26,10 +26,15 @@ class CustomWizard::Wizard
:needs_groups, :needs_groups,
:steps, :steps,
:step_ids, :step_ids,
:field_ids,
:first_step, :first_step,
:start, :start,
:actions, :actions,
:user :action_ids,
:user,
:submissions
attr_reader :all_step_ids
def initialize(attrs = {}, user = nil) def initialize(attrs = {}, user = nil)
@user = user @user = user
@ -58,11 +63,22 @@ class CustomWizard::Wizard
@first_step = nil @first_step = nil
@steps = [] @steps = []
if attrs['steps'].present? if attrs['steps'].present?
@step_ids = attrs['steps'].map { |s| s['id'] } @step_ids = @all_step_ids = attrs['steps'].map { |s| s['id'] }
@field_ids = []
attrs['steps'].each do |step|
if step['fields'].present?
step['fields'].each do |field|
@field_ids << field['id']
end
end
end
end end
@actions = [] @actions = attrs['actions'] || []
@action_ids = @actions.map { |a| a['id'] }
end end
def cast_bool(val) def cast_bool(val)
@ -83,7 +99,19 @@ class CustomWizard::Wizard
step.index = (steps.size == 1 ? 0 : steps.size) if step.index.nil? step.index = (steps.size == 1 ? 0 : steps.size) if step.index.nil?
end end
def update_step_order! def update!
update_step_order
update_step_ids
update_field_ids
update_action_ids
@submissions = nil
@current_submission = nil
true
end
def update_step_order
steps.sort_by!(&:index) steps.sort_by!(&:index)
steps.each_with_index do |step, index| steps.each_with_index do |step, index|
@ -102,7 +130,7 @@ class CustomWizard::Wizard
step.conditional_final_step = true step.conditional_final_step = true
end end
if index === (step_ids.length - 1) if index === (all_step_ids.length - 1)
step.last_step = true step.last_step = true
end end
@ -117,7 +145,7 @@ class CustomWizard::Wizard
acting_user_id: user.id, acting_user_id: user.id,
action: ::UserHistory.actions[:custom_wizard_step], action: ::UserHistory.actions[:custom_wizard_step],
context: id, context: id,
subject: step_ids subject: all_step_ids
).order("created_at").last ).order("created_at").last
last_completed_step.subject last_completed_step.subject
@ -221,70 +249,55 @@ class CustomWizard::Wizard
@groups ||= ::Site.new(Guardian.new(user)).groups @groups ||= ::Site.new(Guardian.new(user)).groups
end end
def update_step_ids
@step_ids = steps.map(&:id)
end
def update_field_ids
@field_ids = steps.map { |step| step.fields.map { |field| field.id } }.flatten
end
def update_action_ids
@action_ids = []
@actions.each do |action|
if action['run_after'].blank? ||
action['run_after'] === 'wizard_completion' ||
step_ids.include?(action['run_after'])
@action_ids << action['id']
end
end
end
def submissions def submissions
return nil unless user.present? return nil unless user.present?
@submissions ||= Array.wrap(PluginStore.get("#{id}_submissions", user.id)) @submissions ||= CustomWizard::Submission.list(self, user_id: user.id)
end end
def current_submission def current_submission
if submissions.present? && submissions.last.present? && !submissions.last.key?("submitted_at") @current_submission ||= begin
submissions.last.with_indifferent_access if submissions.present?
else unsubmitted = submissions.select { |submission| !submission.submitted_at }
nil unsubmitted.present? ? unsubmitted.first : CustomWizard::Submission.new(self)
else
CustomWizard::Submission.new(self)
end
end end
end end
def set_submissions(submissions)
PluginStore.set("#{id}_submissions", user.id, Array.wrap(submissions))
@submissions = nil
end
def save_submission(submission)
return nil unless save_submissions
submissions.pop(1) if unfinished?
submissions.push(submission)
set_submissions(submissions)
end
def filter_conditional_fields
included_fields = steps.map { |s| s.fields.map { |f| f.id } }.flatten
filtered_submision = current_submission&.select do |key, _|
key = key.to_s
included_fields.include?(key) ||
required_fields.include?(key) ||
key.include?("action")
end
save_submission(filtered_submision)
end
def required_fields
%w{
submitted_at
route_to
saved_param
}
end
def final_cleanup! def final_cleanup!
if id == user.custom_fields['redirect_to_wizard'] if id == user.custom_fields['redirect_to_wizard']
user.custom_fields.delete('redirect_to_wizard') user.custom_fields.delete('redirect_to_wizard')
user.save_custom_fields(true) user.save_custom_fields(true)
end end
if submission = current_submission if current_submission.present?
submission['submitted_at'] = Time.now.iso8601 current_submission.submitted_at = Time.now.iso8601
save_submission(submission) current_submission.save
end end
end
def self.submissions(wizard_id, user) update!
new({ id: wizard_id }, user).submissions
end
def self.set_submissions(wizard_id, user, submissions)
new({ id: wizard_id }, user).set_submissions(submissions)
end end
def self.create(wizard_id, user = nil) def self.create(wizard_id, user = nil)
@ -339,11 +352,7 @@ class CustomWizard::Wizard
end end
end end
def self.set_submission_redirect(user, wizard_id, url) def self.set_user_redirect(wizard_id, user)
set_submissions(wizard_id, user, [{ redirect_to: url }])
end
def self.set_wizard_redirect(wizard_id, user)
wizard = self.create(wizard_id, user) wizard = self.create(wizard_id, user)
if wizard.permitted? if wizard.permitted?
@ -353,4 +362,16 @@ class CustomWizard::Wizard
false false
end end
end end
def self.set_wizard_redirect(user, wizard_id, url)
wizard = self.create(wizard_id, user)
if wizard.permitted?
submission = wizard.current_submission
submission.redirect_to = url
submission.save
else
false
end
end
end end

Datei anzeigen

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
# name: discourse-custom-wizard # name: discourse-custom-wizard
# about: Create custom wizards # about: Create custom wizards
# version: 0.7.0 # version: 0.8.0
# authors: Angus McLeod # authors: Angus McLeod
# url: https://github.com/paviliondev/discourse-custom-wizard # url: https://github.com/paviliondev/discourse-custom-wizard
# contact emails: angus@thepavilion.io # contact emails: angus@thepavilion.io
@ -35,6 +35,22 @@ if respond_to?(:register_svg_icon)
register_svg_icon "save" register_svg_icon "save"
end end
class ::Sprockets::DirectiveProcessor
def process_require_tree_discourse_directive(path = ".")
raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "."
discourse_asset_path = "#{Rails.root}/app/assets/javascripts/"
path = File.expand_path(path, discourse_asset_path)
stat = @environment.stat(path)
if stat && stat.directory?
require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
else
raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core"
end
end
end
after_initialize do after_initialize do
%w[ %w[
../lib/custom_wizard/engine.rb ../lib/custom_wizard/engine.rb
@ -66,6 +82,7 @@ after_initialize do
../lib/custom_wizard/log.rb ../lib/custom_wizard/log.rb
../lib/custom_wizard/step_updater.rb ../lib/custom_wizard/step_updater.rb
../lib/custom_wizard/step.rb ../lib/custom_wizard/step.rb
../lib/custom_wizard/submission.rb
../lib/custom_wizard/template.rb ../lib/custom_wizard/template.rb
../lib/custom_wizard/wizard.rb ../lib/custom_wizard/wizard.rb
../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/api.rb
@ -73,6 +90,7 @@ after_initialize do
../lib/custom_wizard/api/endpoint.rb ../lib/custom_wizard/api/endpoint.rb
../lib/custom_wizard/api/log_entry.rb ../lib/custom_wizard/api/log_entry.rb
../lib/custom_wizard/liquid_extensions/first_non_empty.rb ../lib/custom_wizard/liquid_extensions/first_non_empty.rb
../lib/custom_wizard/exceptions/exceptions.rb
../serializers/custom_wizard/api/authorization_serializer.rb ../serializers/custom_wizard/api/authorization_serializer.rb
../serializers/custom_wizard/api/basic_endpoint_serializer.rb ../serializers/custom_wizard/api/basic_endpoint_serializer.rb
../serializers/custom_wizard/api/endpoint_serializer.rb ../serializers/custom_wizard/api/endpoint_serializer.rb
@ -85,6 +103,7 @@ after_initialize do
../serializers/custom_wizard/wizard_step_serializer.rb ../serializers/custom_wizard/wizard_step_serializer.rb
../serializers/custom_wizard/wizard_serializer.rb ../serializers/custom_wizard/wizard_serializer.rb
../serializers/custom_wizard/log_serializer.rb ../serializers/custom_wizard/log_serializer.rb
../serializers/custom_wizard/submission_serializer.rb
../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
../extensions/extra_locales_controller.rb ../extensions/extra_locales_controller.rb
../extensions/invites_controller.rb ../extensions/invites_controller.rb
@ -110,7 +129,7 @@ after_initialize do
if !wizard.completed? if !wizard.completed?
custom_redirect = true custom_redirect = true
CustomWizard::Wizard.set_wizard_redirect(wizard.id, user) CustomWizard::Wizard.set_user_redirect(wizard.id, user)
end end
end end
@ -131,7 +150,7 @@ after_initialize do
on(:user_approved) do |user| on(:user_approved) do |user|
if wizard = CustomWizard::Wizard.after_signup(user) if wizard = CustomWizard::Wizard.after_signup(user)
CustomWizard::Wizard.set_wizard_redirect(wizard.id, user) CustomWizard::Wizard.set_user_redirect(wizard.id, user)
end end
end end
@ -142,7 +161,7 @@ after_initialize do
if request.format === 'text/html' && !@excluded_routes.any? { |str| /#{str}/ =~ url } && wizard_id if request.format === 'text/html' && !@excluded_routes.any? { |str| /#{str}/ =~ url } && wizard_id
if request.referer !~ /\/w\// && request.referer !~ /\/invites\// if request.referer !~ /\/w\// && request.referer !~ /\/invites\//
CustomWizard::Wizard.set_submission_redirect(current_user, wizard_id, request.referer) CustomWizard::Wizard.set_wizard_redirect(current_user, wizard_id, request.referer)
end end
if CustomWizard::Template.exists?(wizard_id) if CustomWizard::Template.exists?(wizard_id)
redirect_to "/w/#{wizard_id.dasherize}" redirect_to "/w/#{wizard_id.dasherize}"

Datei anzeigen

@ -0,0 +1,16 @@
# frozen_string_literal: true
class CustomWizard::SubmissionSerializer < ApplicationSerializer
attributes :id,
:username,
:fields,
:submitted_at,
:route_to,
:redirect_on_complete,
:redirect_to
def username
object.user.present? ?
object.user.username :
I18n.t('admin.wizard.submission.no_user', user_id: object.user_id)
end
end

Datei anzeigen

@ -18,7 +18,8 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
:content, :content,
:validations, :validations,
:max_length, :max_length,
:char_counter :char_counter,
:preview_template
def id def id
object.id object.id
@ -71,6 +72,7 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
end end
def placeholder def placeholder
return object.placeholder if object.placeholder.present?
I18n.t("#{object.key || i18n_key}.placeholder", default: '') I18n.t("#{object.key || i18n_key}.placeholder", default: '')
end end
@ -116,4 +118,8 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
def char_counter def char_counter
object.char_counter object.char_counter
end end
def preview_template
object.preview_template
end
end end

Datei anzeigen

@ -8,11 +8,11 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
:completed, :completed,
:required, :required,
:permitted, :permitted,
:uncategorized_category_id :uncategorized_category_id,
:categories
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
has_one :user, serializer: ::BasicUserSerializer, embed: :objects has_one :user, serializer: ::BasicUserSerializer, embed: :objects
has_many :categories, serializer: ::BasicCategorySerializer, embed: :objects
has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects
def completed def completed
@ -56,4 +56,8 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
def include_uncategorized_category_id? def include_uncategorized_category_id?
object.needs_categories object.needs_categories
end end
def categories
object.categories.map { |c| c.to_h }
end
end end

Datei anzeigen

@ -182,7 +182,7 @@ describe CustomWizard::Action do
updater = wizard.create_updater(wizard.steps[1].id, {}) updater = wizard.create_updater(wizard.steps[1].id, {})
updater.update updater.update
category = Category.find_by(id: wizard.current_submission['action_8']) category = Category.find_by(id: wizard.current_submission.fields['action_8'])
expect(updater.result[:redirect_on_next]).to eq( expect(updater.result[:redirect_on_next]).to eq(
"/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus%40email.com&category_id=#{category.id}&tags=tag1" "/new-topic?title=Title%20of%20the%20composer%20topic&body=I%20am%20interpolating%20some%20user%20fields%20Angus%20angus%20angus%40email.com&category_id=#{category.id}&tags=tag1"
@ -194,11 +194,10 @@ describe CustomWizard::Action do
open_composer['post_template'] = "Body & more body & more body".dup open_composer['post_template'] = "Body & more body & more body".dup
wizard = CustomWizard::Wizard.new(@template, user) wizard = CustomWizard::Wizard.new(@template, user)
action = CustomWizard::Action.new( action = CustomWizard::Action.new(
wizard: wizard, wizard: wizard,
action: open_composer, action: open_composer,
data: {} submission: wizard.current_submission
) )
action.perform action.perform
@ -215,20 +214,20 @@ 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
wizard.create_updater(wizard.steps[1].id, {}).update wizard.create_updater(wizard.steps[1].id, {}).update
expect(Category.where(id: wizard.current_submission['action_8']).exists?).to eq(true) expect(Category.where(id: wizard.current_submission.fields['action_8']).exists?).to eq(true)
end end
it 'creates a group' do it 'creates a group' 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
expect(Group.where(name: wizard.current_submission['action_9']).exists?).to eq(true) expect(Group.where(name: wizard.current_submission.fields['action_9']).exists?).to eq(true)
end end
it 'adds a user to a group' do it 'adds a user to a group' do
wizard = CustomWizard::Builder.new(@template[:id], user).build wizard = CustomWizard::Builder.new(@template[:id], user).build
step_id = wizard.steps[0].id step_id = wizard.steps[0].id
updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update updater = wizard.create_updater(step_id, step_1_field_1: "Text input").update
group = Group.find_by(name: wizard.current_submission['action_9']) group = Group.find_by(name: wizard.current_submission.fields['action_9'])
expect(group.users.first.username).to eq('angus') expect(group.users.first.username).to eq('angus')
end end
@ -237,7 +236,7 @@ describe CustomWizard::Action do
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
wizard.create_updater(wizard.steps[1].id, {}).update wizard.create_updater(wizard.steps[1].id, {}).update
expect(CategoryUser.where( expect(CategoryUser.where(
category_id: wizard.current_submission['action_8'], category_id: wizard.current_submission.fields['action_8'],
user_id: user.id user_id: user.id
).first.notification_level).to eq(2) ).first.notification_level).to eq(2)
expect(CategoryUser.where( expect(CategoryUser.where(

Datei anzeigen

@ -189,7 +189,10 @@ describe CustomWizard::Builder do
context "user has partially completed" do context "user has partially completed" do
before do before do
wizard = CustomWizard::Wizard.new(@template, user) wizard = CustomWizard::Wizard.new(@template, user)
wizard.set_submissions(step_1_field_1: 'I am a user submission') data = {
step_1_field_1: 'I am a user submission'
}
CustomWizard::Submission.new(wizard, data).save
end end
it 'returns saved submissions' do it 'returns saved submissions' do
@ -253,9 +256,9 @@ describe CustomWizard::Builder do
end end
it 'is permitted if required data is present' do it 'is permitted if required data is present' do
CustomWizard::Wizard.set_submissions('super_mega_fun_wizard', user, wizard = CustomWizard::Wizard.create('super_mega_fun_wizard', user)
required_data: "required_value" CustomWizard::Submission.new(wizard, step_1_field_1: "required").save
)
expect( expect(
CustomWizard::Builder.new(@template[:id], user).build CustomWizard::Builder.new(@template[:id], user).build
.steps.first .steps.first
@ -274,7 +277,7 @@ describe CustomWizard::Builder do
wizard = CustomWizard::Builder.new(@template[:id], user).build({}, wizard = CustomWizard::Builder.new(@template[:id], user).build({},
param: 'param_value' param: 'param_value'
) )
expect(wizard.current_submission['saved_param']).to eq('param_value') expect(wizard.current_submission.fields['saved_param']).to eq('param_value')
end end
end end
@ -336,31 +339,27 @@ describe CustomWizard::Builder do
context 'on update' do context 'on update' do
def perform_update(step_id, submission) def perform_update(step_id, submission)
wizard = CustomWizard::Builder.new(@template[:id], user).build updater = @wizard.create_updater(step_id, submission)
updater = wizard.create_updater(step_id, submission)
updater.update updater.update
updater updater
end end
it 'saves submissions' do it 'saves submissions' do
@wizard = CustomWizard::Builder.new(@template[:id], user).build
perform_update('step_1', step_1_field_1: 'Text input') perform_update('step_1', step_1_field_1: 'Text input')
expect( expect(@wizard.current_submission.fields['step_1_field_1']).to eq('Text input')
CustomWizard::Wizard.submissions(@template[:id], user)
.first['step_1_field_1']
).to eq('Text input')
end end
context 'save submissions disabled' do context 'save submissions disabled' do
before do before do
@template[:save_submissions] = false @template[:save_submissions] = false
CustomWizard::Template.save(@template.as_json) CustomWizard::Template.save(@template.as_json)
@wizard = CustomWizard::Builder.new(@template[:id], user).build
end end
it "does not save submissions" do it "does not save submissions" do
perform_update('step_1', step_1_field_1: 'Text input') perform_update('step_1', step_1_field_1: 'Text input')
expect( expect(@wizard.current_submission.present?).to eq(false)
CustomWizard::Wizard.submissions(@template[:id], user).first
).to eq(nil)
end end
end end
end end

Datei anzeigen

@ -0,0 +1,43 @@
# frozen_string_literal: true
require_relative '../../plugin_helper'
describe CustomWizard::Submission do
fab!(:user) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) }
let(:template_json) {
JSON.parse(File.open(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/wizard.json"
).read)
}
before do
CustomWizard::Template.save(template_json, skip_jobs: true)
template_json_2 = template_json.dup
template_json_2["id"] = "super_mega_fun_wizard_2"
CustomWizard::Template.save(template_json_2, skip_jobs: true)
@wizard = CustomWizard::Wizard.create(template_json["id"], user)
@wizard2 = CustomWizard::Wizard.create(template_json["id"], user2)
@wizard3 = CustomWizard::Wizard.create(template_json_2["id"], user)
described_class.new(@wizard, step_1_field_1: "I am a user submission").save
described_class.new(@wizard2, step_1_field_1: "I am another user's submission").save
described_class.new(@wizard3, step_1_field_1: "I am a user submission on another wizard").save
end
it "saves a user's submission" do
expect(
described_class.get(@wizard, user.id).fields["step_1_field_1"]
).to eq("I am a user submission")
end
it "list submissions by wizard" do
expect(described_class.list(@wizard).size).to eq(2)
end
it "list submissions by wizard and user" do
expect(described_class.list(@wizard, user_id: user.id).size).to eq(1)
end
end

Datei anzeigen

@ -132,4 +132,44 @@ describe CustomWizard::UpdateValidator do
updater.errors.messages[:step_2_field_6].first updater.errors.messages[:step_2_field_6].first
).to eq(nil) ).to eq(nil)
end end
it 'validates date fields' do
@template[:steps][1][:fields][0][:format] = "DD-MM-YYYY"
CustomWizard::Template.save(@template)
updater = perform_validation('step_2', step_2_field_1: '13-11-2021')
expect(
updater.errors.messages[:step_2_field_1].first
).to eq(nil)
end
it 'doesn\'t validate date field if the format is not respected' do
@template[:steps][1][:fields][0][:format] = "MM-DD-YYYY"
CustomWizard::Template.save(@template)
updater = perform_validation('step_2', step_2_field_1: '13-11-2021')
expect(
updater.errors.messages[:step_2_field_1].first
).to eq(I18n.t('wizard.field.invalid_date'))
end
it 'validates date time fields' do
@template[:steps][1][:fields][2][:format] = "DD-MM-YYYY HH:mm:ss"
CustomWizard::Template.save(@template)
updater = perform_validation('step_2', step_2_field_3: '13-11-2021 09:15:00')
expect(
updater.errors.messages[:step_2_field_3].first
).to eq(nil)
end
it 'doesn\'t validate date time field if the format is not respected' do
@template[:steps][1][:fields][2][:format] = "MM-DD-YYYY HH:mm:ss"
CustomWizard::Template.save(@template)
updater = perform_validation('step_2', step_2_field_3: '13-11-2021 09:15')
expect(
updater.errors.messages[:step_2_field_3].first
).to eq(I18n.t('wizard.field.invalid_date'))
end
end end

Datei anzeigen

@ -34,7 +34,7 @@ describe CustomWizard::Wizard do
template_json['steps'].each do |step_template| template_json['steps'].each do |step_template|
@wizard.append_step(step_template['id']) @wizard.append_step(step_template['id'])
end end
@wizard.update_step_order! @wizard.update!
end end
def progress_step(step_id, acting_user: user, wizard: @wizard) def progress_step(step_id, acting_user: user, wizard: @wizard)
@ -44,7 +44,7 @@ describe CustomWizard::Wizard do
context: wizard.id, context: wizard.id,
subject: step_id subject: step_id
) )
@wizard.update_step_order! @wizard.update!
end end
it "appends steps" do it "appends steps" do
@ -72,7 +72,7 @@ describe CustomWizard::Wizard do
expect(@wizard.steps.first.index).to eq(2) expect(@wizard.steps.first.index).to eq(2)
expect(@wizard.steps.last.index).to eq(0) expect(@wizard.steps.last.index).to eq(0)
@wizard.update_step_order! @wizard.update!
expect(@wizard.steps.first.id).to eq("step_3") expect(@wizard.steps.first.id).to eq("step_3")
expect(@wizard.steps.last.id).to eq("step_1") expect(@wizard.steps.last.id).to eq("step_1")
@ -199,19 +199,13 @@ describe CustomWizard::Wizard do
end end
it "lists the site categories" do it "lists the site categories" do
Site.clear_cache
expect(@wizard.categories.length).to eq(1) expect(@wizard.categories.length).to eq(1)
end end
context "submissions" do context "submissions" do
before do before do
@wizard.set_submissions(step_1_field_1: 'I am a user submission') CustomWizard::Submission.new(@wizard, step_1_field_1: "I am a user submission").save
end
it "sets the user's submission" do
expect(
PluginStore.get("#{template_json['id']}_submissions", user.id)
.first['step_1_field_1']
).to eq('I am a user submission')
end end
it "lists the user's submissions" do it "lists the user's submissions" do
@ -219,20 +213,10 @@ describe CustomWizard::Wizard do
end end
it "returns the user's current submission" do it "returns the user's current submission" do
expect(@wizard.current_submission['step_1_field_1']).to eq('I am a user submission') expect(@wizard.current_submission.fields["step_1_field_1"]).to eq("I am a user submission")
end end
end end
it "provides class methods to set and list submissions" do
CustomWizard::Wizard.set_submissions(template_json['id'], user,
step_1_field_1: 'I am a user submission'
)
expect(
CustomWizard::Wizard.submissions(template_json['id'], user)
.first['step_1_field_1']
).to eq('I am a user submission')
end
context "class methods" do context "class methods" do
before do before do
CustomWizard::Template.save(@permitted_template, skip_jobs: true) CustomWizard::Template.save(@permitted_template, skip_jobs: true)
@ -273,7 +257,7 @@ describe CustomWizard::Wizard do
it "sets wizard redirects if user is permitted" do it "sets wizard redirects if user is permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true) CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', trusted_user) CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', trusted_user)
expect( expect(
trusted_user.custom_fields['redirect_to_wizard'] trusted_user.custom_fields['redirect_to_wizard']
).to eq("super_mega_fun_wizard") ).to eq("super_mega_fun_wizard")
@ -281,7 +265,7 @@ describe CustomWizard::Wizard do
it "does not set a wizard redirect if user is not permitted" do it "does not set a wizard redirect if user is not permitted" do
CustomWizard::Template.save(@permitted_template, skip_jobs: true) CustomWizard::Template.save(@permitted_template, skip_jobs: true)
CustomWizard::Wizard.set_wizard_redirect('super_mega_fun_wizard', user) CustomWizard::Wizard.set_user_redirect('super_mega_fun_wizard', user)
expect( expect(
trusted_user.custom_fields['redirect_to_wizard'] trusted_user.custom_fields['redirect_to_wizard']
).to eq(nil) ).to eq(nil)

Datei anzeigen

@ -0,0 +1,55 @@
# frozen_string_literal: true
require_relative '../plugin_helper'
describe "Sprockets: require_tree_discourse directive" do
let(:discourse_asset_path) {
"#{Rails.root}/app/assets/javascripts/"
}
let(:fixture_asset_path) {
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/"
}
let(:test_file_contents) {
"console.log('hello')"
}
let(:resolved_file_contents) {
File.read(
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt"
)
}
before do
@env ||= Sprockets::Environment.new
discourse_asset_path = "#{Rails.root}/app/assets/javascripts/"
fixture_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/"
@env.append_path(discourse_asset_path)
@env.append_path(fixture_asset_path)
@env.cache = {}
end
def create_tmp_folder_and_run(path, file_contents, &block)
dir = File.dirname(path)
unless File.directory?(dir)
FileUtils.mkdir_p(dir)
end
File.new(path, 'w')
File.write(path, file_contents)
yield block if block_given?
FileUtils.rm_r(dir)
end
it "includes assets from the discourse core" do
create_tmp_folder_and_run("#{discourse_asset_path}/sptest/test.js", test_file_contents) do
expect(@env.find_asset("require_tree_discourse_test.js").to_s).to eq(resolved_file_contents)
end
end
it "throws ArgumentError if path is empty" do
expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(CustomWizard::SprocketsEmptyPath).with_message("path cannot be empty")
end
it "throws ArgumentError if path is non non-existent" do
expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(CustomWizard::SprocketsFileNotFound)
end
end

Datei anzeigen

@ -0,0 +1 @@
//= require_tree_discourse

Datei anzeigen

@ -0,0 +1 @@
//= require_tree_discourse dummy_path

Datei anzeigen

@ -0,0 +1 @@
//= require_tree_discourse sptest

Datei anzeigen

@ -0,0 +1,3 @@
eval("define(\"sptest/test\", [], function () {\n \"use strict\";\n\n console.log('hello');\n});" + "\n//# sourceURL=sptest/test");
;
eval("" + "\n//# sourceURL=require_tree_discourse_test");

Datei anzeigen

@ -6,9 +6,9 @@
"pairs": [ "pairs": [
{ {
"index": 0, "index": 0,
"key": "required_data", "key": "step_1_field_1",
"key_type": "text", "key_type": "text",
"value": "required_value", "value": "required",
"value_type": "text", "value_type": "text",
"connector": "equal" "connector": "equal"
} }

Datei anzeigen

@ -5,6 +5,7 @@ describe CustomWizard::AdminSubmissionsController do
fab!(:admin_user) { Fabricate(:user, admin: true) } fab!(:admin_user) { Fabricate(:user, admin: true) }
fab!(:user1) { Fabricate(:user) } fab!(:user1) { Fabricate(:user) }
fab!(:user2) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) }
fab!(:user3) { Fabricate(:user) }
let(:template) { let(:template) {
JSON.parse(File.open( JSON.parse(File.open(
@ -12,35 +13,40 @@ describe CustomWizard::AdminSubmissionsController do
).read) ).read)
} }
let(:template_2) {
temp = template.dup
temp["id"] = "super_mega_fun_wizard_2"
temp
}
before do before do
CustomWizard::Template.save(template, skip_jobs: true) CustomWizard::Template.save(template, skip_jobs: true)
CustomWizard::Wizard.set_submissions(template['id'], user1, CustomWizard::Template.save(template_2, skip_jobs: true)
step_1_field_1: "I am a user1's submission"
) wizard1 = CustomWizard::Wizard.create(template["id"], user1)
CustomWizard::Wizard.set_submissions(template['id'], user2, wizard2 = CustomWizard::Wizard.create(template["id"], user2)
step_1_field_1: "I am a user2's submission" wizard3 = CustomWizard::Wizard.create(template_2["id"], user3)
)
CustomWizard::Submission.new(wizard1, step_1_field_1: "I am a user1's submission").save
CustomWizard::Submission.new(wizard2, step_1_field_1: "I am a user2's submission").save
CustomWizard::Submission.new(wizard3, step_1_field_1: "I am a user3's submission").save
sign_in(admin_user) sign_in(admin_user)
end end
it "returns a basic list of wizards" do it "returns a list of wizards" do
get "/admin/wizards/submissions.json" get "/admin/wizards/submissions.json"
expect(response.parsed_body.length).to eq(1) expect(response.parsed_body.length).to eq(2)
expect(response.parsed_body.first['id']).to eq(template['id']) expect(response.parsed_body.first['id']).to eq(template['id'])
end end
it "returns the all user's submissions for a wizard" do it "returns users' submissions for a wizard" do
get "/admin/wizards/submissions/#{template['id']}.json" get "/admin/wizards/submissions/#{template['id']}.json"
expect(response.parsed_body['submissions'].length).to eq(2) expect(response.parsed_body['submissions'].length).to eq(2)
end end
it "returns the all user's submissions for a wizard" do it "downloads submissions" do
get "/admin/wizards/submissions/#{template['id']}.json" get "/admin/wizards/submissions/#{template_2['id']}/download"
expect(response.parsed_body['submissions'].length).to eq(2) expect(response.parsed_body.length).to eq(1)
end
it "downloads all user submissions" do
get "/admin/wizards/submissions/#{template['id']}/download"
expect(response.parsed_body.length).to eq(2)
end end
end end

Datei anzeigen

@ -39,8 +39,8 @@ describe ApplicationController do
it "saves original destination of user" do it "saves original destination of user" do
get '/', headers: { 'REFERER' => "/t/2" } get '/', headers: { 'REFERER' => "/t/2" }
expect( expect(
CustomWizard::Wizard.submissions(@template['id'], user) CustomWizard::Wizard.create(@template['id'], user).submissions
.first['redirect_to'] .first.redirect_to
).to eq("/t/2") ).to eq("/t/2")
end end

Datei anzeigen

@ -68,7 +68,7 @@ describe CustomWizard::StepsController do
wizard_id = response.parsed_body['wizard']['id'] wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user) wizard = CustomWizard::Wizard.create(wizard_id, user)
expect(wizard.submissions.last['step_1_field_1']).to eq("Text input") expect(wizard.current_submission.fields['step_1_field_1']).to eq("Text input")
end end
context "raises an error" do context "raises an error" do
@ -175,8 +175,11 @@ describe CustomWizard::StepsController do
wizard_id = response.parsed_body['wizard']['id'] wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user) wizard = CustomWizard::Wizard.create(wizard_id, user)
group_name = wizard.submissions.last['action_9']
group_name = wizard.submissions.first.fields['action_9']
group = Group.find_by(name: group_name) group = Group.find_by(name: group_name)
expect(group.present?).to eq(true)
expect(group.full_name).to eq("My cool group") expect(group.full_name).to eq("My cool group")
end end
@ -275,7 +278,7 @@ describe CustomWizard::StepsController do
wizard_id = response.parsed_body['wizard']['id'] wizard_id = response.parsed_body['wizard']['id']
wizard = CustomWizard::Wizard.create(wizard_id, user) wizard = CustomWizard::Wizard.create(wizard_id, user)
submission = wizard.submissions.last submission = wizard.current_submission
expect(submission.keys).not_to include("step_2_field_1") expect(submission.fields.keys).not_to include("step_2_field_1")
end end
end end

Datei anzeigen

@ -71,9 +71,8 @@ describe CustomWizard::WizardController do
end end
it 'skip response contains a redirect_to if in users submissions' do it 'skip response contains a redirect_to if in users submissions' do
CustomWizard::Wizard.set_submissions(@template['id'], user, @wizard = CustomWizard::Wizard.create(@template["id"], user)
redirect_to: '/t/2' CustomWizard::Submission.new(@wizard, redirect_to: "/t/2").save
)
put '/w/super-mega-fun-wizard/skip.json' put '/w/super-mega-fun-wizard/skip.json'
expect(response.parsed_body['redirect_to']).to eq('/t/2') expect(response.parsed_body['redirect_to']).to eq('/t/2')
end end

Datei anzeigen

@ -4,8 +4,8 @@
<%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %>
<%= discourse_stylesheet_link_tag :wizard_custom %> <%= discourse_stylesheet_link_tag :wizard_custom %>
<%- if wizard_theme_ids.present? %> <%- if wizard_theme_id.present? %>
<%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme), theme_ids: wizard_theme_ids %> <%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme), theme_id: wizard_theme_id %>
<%- end %> <%- end %>
<%= preload_script "locales/#{I18n.locale}" %> <%= preload_script "locales/#{I18n.locale}" %>
@ -29,7 +29,7 @@
<%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
<meta name="discourse_theme_ids" content="<%= wizard_theme_ids&.join(",") %>"> <meta name="discourse_theme_id" content="<%= wizard_theme_id %>">
<meta name="discourse-base-uri" content="<%= Discourse.base_path %>"> <meta name="discourse-base-uri" content="<%= Discourse.base_path %>">
<%= render partial: "layouts/head" %> <%= render partial: "layouts/head" %>