Merge branch 'main' into pro-release
Dieser Commit ist enthalten in:
Commit
4605b23585
46 geänderte Dateien mit 5890 neuen und 369 gelöschten Zeilen
|
@ -1,2 +1,2 @@
|
|||
2.7.8: e07a57e398b6b1676ab42a7e34467556fca5416b
|
||||
2.7.99: e07a57e398b6b1676ab42a7e34467556fca5416b
|
||||
2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c
|
3
.github/workflows/plugin-linting.yml
gevendort
3
.github/workflows/plugin-linting.yml
gevendort
|
@ -5,6 +5,7 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- main
|
||||
- stable
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
@ -19,7 +20,7 @@ jobs:
|
|||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
node-version: 14
|
||||
|
||||
- name: Set up ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
|
44
.github/workflows/plugin-metadata.yml
gevendort
Normale Datei
44
.github/workflows/plugin-metadata.yml
gevendort
Normale Datei
|
@ -0,0 +1,44 @@
|
|||
name: Metadata
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout head repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Store head version
|
||||
run: |
|
||||
sed -n -e 's/^.*version: /head_version=/p' plugin.rb >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout base repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: "${{ github.base_ref }}"
|
||||
|
||||
- name: Store base version
|
||||
run: |
|
||||
sed -n -e 's/^.*version: /base_version=/p' plugin.rb >> $GITHUB_ENV
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
|
||||
- name: Install semver
|
||||
run: npm install --include=dev
|
||||
|
||||
- name: Check version
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
script: |
|
||||
const semver = require('semver');
|
||||
const { head_version, base_version } = process.env;
|
||||
|
||||
if (semver.lte(head_version, base_version)) {
|
||||
core.setFailed("Head version is equal to or lower than base version.");
|
||||
}
|
2
.github/workflows/plugin-tests.yml
gevendort
2
.github/workflows/plugin-tests.yml
gevendort
|
@ -3,6 +3,7 @@ name: Plugin Tests
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
|
@ -51,6 +52,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: discourse/discourse
|
||||
ref: "${{ (github.base_ref || github.ref) }}"
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Fetch Repo Name
|
||||
|
|
|
@ -24,6 +24,8 @@ const customFieldActionMap = {
|
|||
user: ["update_profile"],
|
||||
};
|
||||
|
||||
const values = ["present", "true", "false"];
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [":mapper-selector", "activeType"],
|
||||
|
||||
|
@ -60,6 +62,9 @@ export default Component.extend({
|
|||
showCustomField: computed("activeType", function () {
|
||||
return this.showInput("customField");
|
||||
}),
|
||||
showValue: computed("activeType", function () {
|
||||
return this.showInput("value");
|
||||
}),
|
||||
textEnabled: computed("options.textSelection", "inputType", function () {
|
||||
return this.optionEnabled("textSelection");
|
||||
}),
|
||||
|
@ -117,6 +122,9 @@ export default Component.extend({
|
|||
listEnabled: computed("options.listSelection", "inputType", function () {
|
||||
return this.optionEnabled("listSelection");
|
||||
}),
|
||||
valueEnabled: computed("connector", function () {
|
||||
return this.connector === "is";
|
||||
}),
|
||||
|
||||
groups: alias("site.groups"),
|
||||
categories: alias("site.categories"),
|
||||
|
@ -125,7 +133,8 @@ export default Component.extend({
|
|||
"showWizardAction",
|
||||
"showUserField",
|
||||
"showUserFieldOptions",
|
||||
"showCustomField"
|
||||
"showCustomField",
|
||||
"showValue"
|
||||
),
|
||||
showMultiSelect: or("showCategory", "showGroup"),
|
||||
hasTypes: gt("selectorTypes.length", 1),
|
||||
|
@ -157,7 +166,7 @@ export default Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
@discourseComputed("connector")
|
||||
selectorTypes() {
|
||||
return selectionTypes
|
||||
.filter((type) => this[`${type}Enabled`])
|
||||
|
@ -268,6 +277,13 @@ export default Component.extend({
|
|||
}));
|
||||
}
|
||||
|
||||
if (activeType === "value") {
|
||||
content = values.map((value) => ({
|
||||
id: value,
|
||||
name: value,
|
||||
}));
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
|
||||
|
@ -337,7 +353,7 @@ export default Component.extend({
|
|||
resetActiveType() {
|
||||
this.set(
|
||||
"activeType",
|
||||
defaultSelectionType(this.selectorType, this.options)
|
||||
defaultSelectionType(this.selectorType, this.options, this.connector)
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<h3>{{i18n "admin.wizard.category_settings.custom_wizard.title"}}</h3>
|
||||
|
||||
<section class="field new-topic-wizard">
|
||||
<label for="new-topic-wizard">
|
||||
{{i18n "admin.wizard.category_settings.custom_wizard.create_topic_wizard"}}
|
||||
</label>
|
||||
<div class="controls">
|
||||
{{combo-box
|
||||
value=wizardListVal
|
||||
content=wizardList
|
||||
onChange=(action "changeWizard")
|
||||
options=(hash
|
||||
none="admin.wizard.select"
|
||||
)}}
|
||||
</div>
|
||||
</section>
|
|
@ -0,0 +1,24 @@
|
|||
import CustomWizard from "../../models/custom-wizard";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default {
|
||||
setupComponent(attrs, component) {
|
||||
CustomWizard.all()
|
||||
.then((result) => {
|
||||
component.set("wizardList", result);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
|
||||
component.set(
|
||||
"wizardListVal",
|
||||
attrs?.category?.custom_fields?.create_topic_wizard
|
||||
);
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeWizard(wizard) {
|
||||
this.set("wizardListVal", wizard);
|
||||
this.set("category.custom_fields.create_topic_wizard", wizard);
|
||||
},
|
||||
},
|
||||
};
|
|
@ -3,6 +3,7 @@ import { withPluginApi } from "discourse/lib/plugin-api";
|
|||
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { A } from "@ember/array";
|
||||
import getUrl from "discourse-common/lib/get-url";
|
||||
|
||||
export default {
|
||||
name: "custom-wizard-edits",
|
||||
|
@ -63,6 +64,22 @@ export default {
|
|||
this.set("customWizardCriticalNotices", criticalNotices);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
api.modifyClass("component:d-navigation", {
|
||||
pluginId: "custom-wizard",
|
||||
actions: {
|
||||
clickCreateTopicButton() {
|
||||
let createTopicWizard = this.get(
|
||||
"category.custom_fields.create_topic_wizard"
|
||||
);
|
||||
if (createTopicWizard) {
|
||||
window.location.href = getUrl(`/w/${createTopicWizard}`);
|
||||
} else {
|
||||
this._super();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -105,13 +105,18 @@ const selectionTypes = [
|
|||
"tag",
|
||||
"user",
|
||||
"customField",
|
||||
"value",
|
||||
];
|
||||
|
||||
function defaultSelectionType(inputType, options = {}) {
|
||||
function defaultSelectionType(inputType, options = {}, connector = null) {
|
||||
if (options[`${inputType}DefaultSelection`]) {
|
||||
return options[`${inputType}DefaultSelection`];
|
||||
}
|
||||
|
||||
if (connector === "is") {
|
||||
return "value";
|
||||
}
|
||||
|
||||
let type = selectionTypes[0];
|
||||
|
||||
for (let t of selectionTypes) {
|
||||
|
|
|
@ -14,6 +14,7 @@ const wizard = {
|
|||
required: null,
|
||||
prompt_completion: null,
|
||||
restart_on_revisit: null,
|
||||
resume_on_revisit: null,
|
||||
theme_id: null,
|
||||
permitted: null,
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import EmberObject from "@ember/object";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
|
||||
const CustomWizardManager = EmberObject.extend();
|
||||
|
||||
|
@ -17,7 +18,7 @@ CustomWizardManager.reopenClass({
|
|||
},
|
||||
|
||||
export(wizardIds) {
|
||||
let url = `${Discourse.BaseUrl}/${basePath}/export?`;
|
||||
let url = `${getURL()}/${basePath}/export?`;
|
||||
|
||||
wizardIds.forEach((wizardId, index) => {
|
||||
let step = "wizard_ids[]=" + wizardId;
|
||||
|
|
|
@ -40,12 +40,13 @@
|
|||
<label>{{i18n "admin.wizard.field.image"}}</label>
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{image-uploader
|
||||
{{uppy-image-uploader
|
||||
imageUrl=field.image
|
||||
onUploadDone=(action "imageUploadDone")
|
||||
onUploadDeleted=(action "imageUploadDeleted")
|
||||
type="wizard-step"
|
||||
class="no-repeat contain-image"}}
|
||||
class="no-repeat contain-image"
|
||||
id=(concat "wizard-field-" field.id "-image-upload")}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -127,13 +128,13 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if isComposerPreview}}
|
||||
<div class="setting">
|
||||
<div class="setting full">
|
||||
<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}}
|
||||
{{textarea name="preview-template" value=field.preview_template class="preview-template"}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
<label>{{i18n "admin.wizard.step.banner"}}</label>
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{image-uploader
|
||||
{{uppy-image-uploader
|
||||
imageUrl=step.banner
|
||||
onUploadDone=(action "bannerUploadDone")
|
||||
onUploadDeleted=(action "bannerUploadDeleted")
|
||||
type="wizard-banner"
|
||||
class="no-repeat contain-image"}}
|
||||
class="no-repeat contain-image"
|
||||
id=(concat "wizard-step-" step.id "-banner-upload")}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
value=pair.value
|
||||
activeType=pair.value_type
|
||||
options=options
|
||||
onUpdate=onUpdate}}
|
||||
onUpdate=onUpdate
|
||||
connector=pair.connector}}
|
||||
</div>
|
||||
|
||||
{{#if showJoin}}
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
//= require_tree_discourse discourse/app/lib
|
||||
//= require_tree_discourse discourse/app/mixins
|
||||
//
|
||||
|
||||
//= require discourse/app/mixins/singleton
|
||||
//= require discourse/app/mixins/upload
|
||||
//= require discourse/app/mixins/composer-upload
|
||||
//= require discourse/app/mixins/textarea-text-manipulation
|
||||
|
||||
//= require discourse/app/adapters/rest
|
||||
|
||||
|
@ -25,6 +19,7 @@
|
|||
|
||||
//= require discourse/app/services/app-events
|
||||
//= require discourse/app/services/emoji-store
|
||||
//= require discourse/app/services/store
|
||||
|
||||
//= require discourse/app/components/user-selector
|
||||
//= require discourse/app/components/conditional-loading-spinner
|
||||
|
@ -40,6 +35,7 @@
|
|||
//= require discourse/app/components/date-time-input
|
||||
//= require discourse/app/components/text-field
|
||||
//= require discourse/app/components/d-textarea
|
||||
//= require discourse/app/components/popup-input-tip
|
||||
|
||||
//= require discourse/app/templates/components/conditional-loading-spinner
|
||||
//= require discourse/app/templates/components/d-button
|
||||
|
@ -65,6 +61,7 @@
|
|||
//= require markdown-it-bundle
|
||||
//= require lodash.js
|
||||
//= require template_include.js
|
||||
//= require itsatrap.js
|
||||
//= require caret_position.js
|
||||
//= require popper.js
|
||||
//= require uppy.js
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
Discourse.unofficial_plugins.each do |plugin|
|
||||
plugin_name = plugin.metadata.name
|
||||
if require_plugin_assets = CustomWizard::Field.require_assets[plugin_name]
|
||||
plugin.each_globbed_asset do |f, is_dir|
|
||||
next if f.include? "raw.hbs"
|
||||
plugin.each_globbed_asset do |path, is_dir|
|
||||
next if path.include? "raw.hbs"
|
||||
|
||||
if require_plugin_assets.any? { |dir| f.include?(dir) }
|
||||
if require_plugin_assets.any? { |dir| path.include?(dir) }
|
||||
if is_dir
|
||||
depend_on(f)
|
||||
depend_on(path)
|
||||
else
|
||||
require_asset(f)
|
||||
require_asset(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,23 +4,14 @@ import {
|
|||
on,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||
import { next, scheduleOnce, throttle } from "@ember/runloop";
|
||||
import { scheduleOnce } from "@ember/runloop";
|
||||
import { caretPosition, inCodeBlock } from "discourse/lib/utilities";
|
||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||
import { getToken } from "wizard/lib/ajax";
|
||||
import {
|
||||
displayErrorForUpload,
|
||||
getUploadMarkdown,
|
||||
uploadIcon,
|
||||
validateUploadedFiles,
|
||||
} from "discourse/lib/uploads";
|
||||
import { cacheShortUploadUrl } from "pretty-text/upload-short-url";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import WizardI18n from "../lib/wizard-i18n";
|
||||
import Site from "../models/site";
|
||||
import { uploadIcon } from "discourse/lib/uploads";
|
||||
import { dasherize } from "@ember/string";
|
||||
|
||||
const uploadMarkdownResolvers = [];
|
||||
|
||||
const uploadHandlers = [];
|
||||
export default ComposerEditor.extend({
|
||||
classNameBindings: ["fieldClass"],
|
||||
allowUpload: true,
|
||||
|
@ -34,11 +25,11 @@ export default ComposerEditor.extend({
|
|||
popupMenuOptions: [],
|
||||
draftStatus: "null",
|
||||
replyPlaceholder: alias("field.translatedPlaceholder"),
|
||||
uploadingFieldId: null,
|
||||
|
||||
@on("didInsertElement")
|
||||
_composerEditorInit() {
|
||||
const $input = $(this.element.querySelector(".d-editor-input"));
|
||||
const $preview = $(this.element.querySelector(".d-editor-preview-wrapper"));
|
||||
|
||||
if (this.siteSettings.enable_mentions) {
|
||||
$input.autocomplete({
|
||||
|
@ -55,15 +46,77 @@ export default ComposerEditor.extend({
|
|||
});
|
||||
}
|
||||
|
||||
if (this._enableAdvancedEditorPreviewSync()) {
|
||||
this._initInputPreviewSync($input, $preview);
|
||||
} else {
|
||||
$input.on("scroll", () =>
|
||||
throttle(this, this._syncEditorAndPreviewScroll, $input, $preview, 20)
|
||||
const siteSettings = this.siteSettings;
|
||||
if (siteSettings.mentionables_enabled) {
|
||||
Site.currentProp("mentionable_items", this.wizard.mentionable_items);
|
||||
const { SEPARATOR } = requirejs(
|
||||
"discourse/plugins/discourse-mentionables/discourse/lib/discourse-markdown/mentionable-items"
|
||||
);
|
||||
const { searchMentionableItem } = requirejs(
|
||||
"discourse/plugins/discourse-mentionables/discourse/lib/mentionable-item-search"
|
||||
);
|
||||
|
||||
$input.autocomplete({
|
||||
template: findRawTemplate("javascripts/mentionable-item-autocomplete"),
|
||||
key: SEPARATOR,
|
||||
afterComplete: (value) => {
|
||||
this.composer.set("reply", value);
|
||||
scheduleOnce("afterRender", () => $input.blur().focus());
|
||||
},
|
||||
transformComplete: (item) => item.model.slug,
|
||||
dataSource: (term) =>
|
||||
term.match(/\s/) ? null : searchMentionableItem(term, siteSettings),
|
||||
triggerRule: (textarea) =>
|
||||
!inCodeBlock(textarea.value, caretPosition(textarea)),
|
||||
});
|
||||
}
|
||||
|
||||
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
|
||||
this._bindUploadTarget();
|
||||
|
||||
const wizardEventNames = ["insert-text", "replace-text"];
|
||||
const eventPrefix = this.eventPrefix;
|
||||
const session = this.get("session");
|
||||
this.appEvents.reopen({
|
||||
trigger(name, ...args) {
|
||||
let eventParts = name.split(":");
|
||||
let currentEventPrefix = eventParts[0];
|
||||
let currentEventName = eventParts[1];
|
||||
|
||||
if (
|
||||
currentEventPrefix !== "wizard-editor" &&
|
||||
wizardEventNames.some((wen) => wen === currentEventName)
|
||||
) {
|
||||
let wizardName = name.replace(eventPrefix, "wizard-editor");
|
||||
if (currentEventName === "insert-text") {
|
||||
args = {
|
||||
text: args[0],
|
||||
};
|
||||
}
|
||||
if (currentEventName === "replace-text") {
|
||||
args = {
|
||||
oldVal: args[0],
|
||||
newVal: args[1],
|
||||
};
|
||||
}
|
||||
let wizardArgs = Object.assign(
|
||||
{},
|
||||
{
|
||||
fieldId: session.get("uploadingFieldId"),
|
||||
},
|
||||
args
|
||||
);
|
||||
return this._super(wizardName, wizardArgs);
|
||||
} else {
|
||||
return this._super(name, ...args);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("field.id")
|
||||
fileUploadElementId(fieldId) {
|
||||
return `file-uploader-${dasherize(fieldId)}`;
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
|
@ -79,192 +132,6 @@ export default ComposerEditor.extend({
|
|||
return uploadIcon(false, this.siteSettings);
|
||||
},
|
||||
|
||||
_setUploadPlaceholderSend() {
|
||||
if (!this.composer.get("reply")) {
|
||||
this.composer.set("reply", "");
|
||||
}
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
_bindUploadTarget() {
|
||||
this._super(...arguments);
|
||||
const $element = $(this.element);
|
||||
// adding dropZone property post initialization
|
||||
$element.fileupload("option", "dropZone", $element);
|
||||
|
||||
$element.off("fileuploadsubmit");
|
||||
|
||||
$element.on("fileuploadsubmit", (e, data) => {
|
||||
const max = this.siteSettings.simultaneous_uploads;
|
||||
|
||||
// Limit the number of simultaneous uploads
|
||||
if (max > 0 && data.files.length > max) {
|
||||
bootbox.alert(
|
||||
WizardI18n("post.errors.too_many_dragged_and_dropped_files", { max })
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for a matching file upload handler contributed from a plugin
|
||||
const matcher = (handler) => {
|
||||
const ext = handler.extensions.join("|");
|
||||
const regex = new RegExp(`\\.(${ext})$`, "i");
|
||||
return regex.test(data.files[0].name);
|
||||
};
|
||||
|
||||
const matchingHandler = uploadHandlers.find(matcher);
|
||||
if (data.files.length === 1 && matchingHandler) {
|
||||
if (!matchingHandler.method(data.files[0], this)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If no plugin, continue as normal
|
||||
const isPrivateMessage = this.get("composer.privateMessage");
|
||||
|
||||
data.formData = { type: "composer" };
|
||||
data.formData.authenticity_token = getToken();
|
||||
if (isPrivateMessage) {
|
||||
data.formData.for_private_message = true;
|
||||
}
|
||||
if (this._pasted) {
|
||||
data.formData.pasted = true;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
user: this.currentUser,
|
||||
siteSettings: this.siteSettings,
|
||||
isPrivateMessage,
|
||||
allowStaffToUploadAnyFileInPm: this.siteSettings
|
||||
.allow_staff_to_upload_any_file_in_pm,
|
||||
};
|
||||
|
||||
const isUploading = validateUploadedFiles(data.files, opts);
|
||||
|
||||
this.setProperties({ uploadProgress: 0, isUploading });
|
||||
|
||||
return isUploading;
|
||||
});
|
||||
|
||||
$element.on("fileuploadprogressall", (e, data) => {
|
||||
this.set(
|
||||
"uploadProgress",
|
||||
parseInt((data.loaded / data.total) * 100, 10)
|
||||
);
|
||||
});
|
||||
|
||||
$element.on("fileuploadfail", (e, data) => {
|
||||
this._setUploadPlaceholderDone(data);
|
||||
this._resetUpload(true);
|
||||
|
||||
const userCancelled = this._xhr && this._xhr._userCancelled;
|
||||
this._xhr = null;
|
||||
|
||||
if (!userCancelled) {
|
||||
displayErrorForUpload(data, this.siteSettings);
|
||||
}
|
||||
});
|
||||
|
||||
$element.on("fileuploadsend", (e, data) => {
|
||||
this._pasted = false;
|
||||
this._validUploads++;
|
||||
|
||||
this._setUploadPlaceholderSend(data);
|
||||
|
||||
this.appEvents.trigger("wizard-editor:insert-text", {
|
||||
fieldId: this.field.id,
|
||||
text: this.uploadPlaceholder,
|
||||
});
|
||||
|
||||
if (data.xhr && data.originalFiles.length === 1) {
|
||||
this.set("isCancellable", true);
|
||||
this._xhr = data.xhr();
|
||||
}
|
||||
});
|
||||
|
||||
$element.on("fileuploaddone", (e, data) => {
|
||||
let upload = data.result;
|
||||
|
||||
this._setUploadPlaceholderDone(data);
|
||||
|
||||
if (!this._xhr || !this._xhr._userCancelled) {
|
||||
const markdown = uploadMarkdownResolvers.reduce(
|
||||
(md, resolver) => resolver(upload) || md,
|
||||
getUploadMarkdown(upload)
|
||||
);
|
||||
|
||||
cacheShortUploadUrl(upload.short_url, upload);
|
||||
this.appEvents.trigger("wizard-editor:replace-text", {
|
||||
fieldId: this.field.id,
|
||||
oldVal: this.uploadPlaceholder.trim(),
|
||||
newVal: markdown,
|
||||
});
|
||||
this._resetUpload(false);
|
||||
} else {
|
||||
this._resetUpload(true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_resetUpload(removePlaceholder) {
|
||||
next(() => {
|
||||
if (this._validUploads > 0) {
|
||||
this._validUploads--;
|
||||
}
|
||||
if (this._validUploads === 0) {
|
||||
this.setProperties({
|
||||
uploadProgress: 0,
|
||||
isUploading: false,
|
||||
isCancellable: false,
|
||||
});
|
||||
}
|
||||
if (removePlaceholder) {
|
||||
this.appEvents.trigger("wizard-editor:replace-text", {
|
||||
fieldId: this.field.id,
|
||||
oldVal: this.uploadPlaceholder,
|
||||
newVal: "",
|
||||
});
|
||||
}
|
||||
this._resetUploadFilenamePlaceholder();
|
||||
});
|
||||
},
|
||||
|
||||
_registerImageScaleButtonClick($preview) {
|
||||
const imageScaleRegex = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
|
||||
$preview.off("click", ".scale-btn").on("click", ".scale-btn", (e) => {
|
||||
const index = parseInt($(e.target).parent().attr("data-image-index"), 10);
|
||||
|
||||
const scale = e.target.attributes["data-scale"].value;
|
||||
const matchingPlaceholder = this.get("composer.reply").match(
|
||||
imageScaleRegex
|
||||
);
|
||||
|
||||
if (matchingPlaceholder) {
|
||||
const match = matchingPlaceholder[index];
|
||||
|
||||
if (match) {
|
||||
const replacement = match.replace(
|
||||
imageScaleRegex,
|
||||
`![$1|$2, ${scale}%$4]($5)`
|
||||
);
|
||||
|
||||
this.appEvents.trigger("wizard-editor:replace-text", {
|
||||
fieldId: this.field.id,
|
||||
oldVal: matchingPlaceholder[index],
|
||||
newVal: replacement,
|
||||
options: {
|
||||
regex: imageScaleRegex,
|
||||
index,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return;
|
||||
});
|
||||
},
|
||||
|
||||
click(e) {
|
||||
if ($(e.target).hasClass("wizard-composer-hyperlink")) {
|
||||
this.set("showHyperlinkBox", false);
|
||||
|
@ -293,10 +160,42 @@ export default ComposerEditor.extend({
|
|||
unshift: true,
|
||||
sendAction: () => component.set("showHyperlinkBox", true),
|
||||
});
|
||||
|
||||
if (this.siteSettings.mentionables_enabled) {
|
||||
const { SEPARATOR } = requirejs(
|
||||
"discourse/plugins/discourse-mentionables/discourse/lib/discourse-markdown/mentionable-items"
|
||||
);
|
||||
|
||||
toolbar.addButton({
|
||||
id: "insert-mentionable",
|
||||
group: "extras",
|
||||
icon: this.siteSettings.mentionables_composer_button_icon,
|
||||
title: "mentionables.composer.insert.title",
|
||||
perform: () => {
|
||||
this.appEvents.trigger("wizard-editor:insert-text", {
|
||||
fieldId: this.field.id,
|
||||
text: SEPARATOR,
|
||||
});
|
||||
const $textarea = $(
|
||||
document.querySelector(
|
||||
`.composer-field.${this.field.id} textarea.d-editor-input`
|
||||
)
|
||||
);
|
||||
$textarea.trigger("keyup.autocomplete");
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
previewUpdated($preview) {
|
||||
highlightSyntax($preview[0], this.siteSettings, this.session);
|
||||
|
||||
if (this.siteSettings.mentionables_enabled) {
|
||||
const { linkSeenMentionableItems } = requirejs(
|
||||
"discourse/plugins/discourse-mentionables/discourse/lib/mentionable-items-preview-styling"
|
||||
);
|
||||
linkSeenMentionableItems($preview, this.siteSettings);
|
||||
}
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
|
@ -314,7 +213,8 @@ export default ComposerEditor.extend({
|
|||
},
|
||||
|
||||
showUploadModal() {
|
||||
$(this.element.querySelector(".wizard-composer-upload")).trigger("click");
|
||||
this.session.set("uploadingFieldId", this.field.id);
|
||||
document.getElementById(this.fileUploadElementId).click();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
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({
|
||||
useNativePicker: false,
|
||||
|
@ -11,32 +8,9 @@ export default DateInput.extend({
|
|||
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),
|
||||
_opts() {
|
||||
return {
|
||||
format: this.format || "LL",
|
||||
};
|
||||
|
||||
if (this.relativeDate) {
|
||||
defaultOptions = Object.assign({}, defaultOptions, {
|
||||
minDate: moment(this.relativeDate).toDate(),
|
||||
});
|
||||
}
|
||||
|
||||
return new Pikaday(Object.assign({}, defaultOptions, this._opts()));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ export default Ember.Component.extend({
|
|||
"composer",
|
||||
EmberObject.create({
|
||||
loading: false,
|
||||
reply: this.get("field.value"),
|
||||
reply: this.get("field.value") || "",
|
||||
})
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,63 +1,24 @@
|
|||
import getUrl from "discourse-common/lib/get-url";
|
||||
import { getToken } from "wizard/lib/ajax";
|
||||
import WizardI18n from "../lib/wizard-i18n";
|
||||
import UppyUploadMixin from "discourse/mixins/uppy-upload";
|
||||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
export default Component.extend(UppyUploadMixin, {
|
||||
classNames: ["wizard-field-upload"],
|
||||
classNameBindings: ["isImage"],
|
||||
uploading: false,
|
||||
isImage: false,
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
|
||||
const $upload = $(this.element);
|
||||
|
||||
const id = this.get("field.id");
|
||||
|
||||
$upload.fileupload({
|
||||
url: getUrl("/uploads.json"),
|
||||
formData: {
|
||||
synchronous: true,
|
||||
type: `wizard_${id}`,
|
||||
authenticity_token: getToken(),
|
||||
},
|
||||
dataType: "json",
|
||||
dropZone: $upload,
|
||||
});
|
||||
|
||||
$upload.on("fileuploadsubmit", () => this.set("uploading", true));
|
||||
|
||||
$upload.on("fileuploaddone", (e, response) => {
|
||||
this.setProperties({
|
||||
"field.value": response.result,
|
||||
uploading: false,
|
||||
});
|
||||
if (
|
||||
Discourse.SiteSettings.wizard_recognised_image_upload_formats
|
||||
type: computed(function () {
|
||||
return `wizard_${this.field.id}`;
|
||||
}),
|
||||
isImage: computed("field.value.extension", function () {
|
||||
return (
|
||||
this.field.value &&
|
||||
this.siteSettings.wizard_recognised_image_upload_formats
|
||||
.split("|")
|
||||
.includes(response.result.extension)
|
||||
) {
|
||||
this.setProperties({
|
||||
isImage: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
.includes(this.field.value.extension)
|
||||
);
|
||||
}),
|
||||
|
||||
$upload.on("fileuploadfail", (e, response) => {
|
||||
let message = WizardI18n("wizard.upload_error");
|
||||
if (response.jqXHR.responseJSON && response.jqXHR.responseJSON.errors) {
|
||||
message = response.jqXHR.responseJSON.errors.join("\n");
|
||||
}
|
||||
|
||||
window.swal({
|
||||
customClass: "wizard-warning",
|
||||
title: "",
|
||||
text: message,
|
||||
type: "warning",
|
||||
confirmButtonColor: "#6699ff",
|
||||
});
|
||||
this.set("uploading", false);
|
||||
});
|
||||
uploadDone(upload) {
|
||||
this.set("field.value", upload);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ export default {
|
|||
const Router = requirejs("wizard/router").default;
|
||||
const ApplicationRoute = requirejs("wizard/routes/application").default;
|
||||
const getUrl = requirejs("discourse-common/lib/get-url").default;
|
||||
const Store = requirejs("discourse/models/store").default;
|
||||
const Store = requirejs("discourse/services/store").default;
|
||||
const registerRawHelpers = requirejs(
|
||||
"discourse-common/lib/raw-handlebars-helpers"
|
||||
).registerRawHelpers;
|
||||
|
@ -111,8 +111,16 @@ export default {
|
|||
model() {},
|
||||
});
|
||||
|
||||
$.ajaxPrefilter(function (_, __, jqXHR) {
|
||||
jqXHR.setRequestHeader("X-CSRF-Token", getToken());
|
||||
// Add a CSRF token to all AJAX requests
|
||||
let token = getToken();
|
||||
session.set("csrfToken", token);
|
||||
let callbacks = $.Callbacks();
|
||||
$.ajaxPrefilter(callbacks.fire);
|
||||
|
||||
callbacks.add(function (options, originalOptions, xhr) {
|
||||
if (!options.crossDomain) {
|
||||
xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken"));
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,8 +2,10 @@ import { default as computed } from "discourse-common/utils/decorators";
|
|||
import getUrl from "discourse-common/lib/get-url";
|
||||
import WizardField from "wizard/models/wizard-field";
|
||||
import { ajax } from "wizard/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import Step from "wizard/models/step";
|
||||
import EmberObject from "@ember/object";
|
||||
import Site from "./site";
|
||||
|
||||
const CustomWizard = EmberObject.extend({
|
||||
@computed("steps.length")
|
||||
|
@ -15,13 +17,27 @@ const CustomWizard = EmberObject.extend({
|
|||
}
|
||||
CustomWizard.skip(this.id);
|
||||
},
|
||||
|
||||
restart() {
|
||||
CustomWizard.restart(this.id);
|
||||
},
|
||||
});
|
||||
|
||||
CustomWizard.reopenClass({
|
||||
skip(wizardId) {
|
||||
ajax({ url: `/w/${wizardId}/skip`, type: "PUT" }).then((result) => {
|
||||
ajax({ url: `/w/${wizardId}/skip`, type: "PUT" })
|
||||
.then((result) => {
|
||||
CustomWizard.finished(result);
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
restart(wizardId) {
|
||||
ajax({ url: `/w/${wizardId}/skip`, type: "PUT" })
|
||||
.then(() => {
|
||||
window.location.href = `/w/${wizardId}`;
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
finished(result) {
|
||||
|
@ -97,11 +113,11 @@ CustomWizard.reopenClass({
|
|||
}
|
||||
});
|
||||
|
||||
Discourse.Site.currentProp("categoriesList", categories);
|
||||
Discourse.Site.currentProp("sortedCategories", categories);
|
||||
Discourse.Site.currentProp("listByActivity", categories);
|
||||
Discourse.Site.currentProp("categoriesById", categoriesById);
|
||||
Discourse.Site.currentProp(
|
||||
Site.currentProp("categoriesList", categories);
|
||||
Site.currentProp("sortedCategories", categories);
|
||||
Site.currentProp("listByActivity", categories);
|
||||
Site.currentProp("categoriesById", categoriesById);
|
||||
Site.currentProp(
|
||||
"uncategorized_category_id",
|
||||
wizardJson.uncategorized_category_id
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { findCustomWizard, updateCachedWizard } from "../models/custom";
|
||||
import { ajax } from "wizard/lib/ajax";
|
||||
import WizardI18n from "../lib/wizard-i18n";
|
||||
|
||||
export default Ember.Route.extend({
|
||||
beforeModel(transition) {
|
||||
|
@ -12,6 +13,48 @@ export default Ember.Route.extend({
|
|||
return findCustomWizard(params.wizard_id, this.get("queryParams"));
|
||||
},
|
||||
|
||||
renderTemplate() {
|
||||
this.render("custom");
|
||||
const wizardModel = this.modelFor("custom");
|
||||
const stepModel = this.modelFor("custom.step");
|
||||
|
||||
if (
|
||||
wizardModel.resume_on_revisit &&
|
||||
wizardModel.submission_last_updated_at &&
|
||||
stepModel.index > 0
|
||||
) {
|
||||
this.showDialog(wizardModel);
|
||||
}
|
||||
},
|
||||
|
||||
showDialog(wizardModel) {
|
||||
const title = WizardI18n("wizard.incomplete_submission.title", {
|
||||
date: moment(wizardModel.submission_last_updated_at).format(
|
||||
"MMMM Do YYYY"
|
||||
),
|
||||
});
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: WizardI18n("wizard.incomplete_submission.restart"),
|
||||
class: "btn btn-default",
|
||||
callback: () => {
|
||||
wizardModel.restart();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: WizardI18n("wizard.incomplete_submission.resume"),
|
||||
class: "btn btn-primary",
|
||||
},
|
||||
];
|
||||
|
||||
const options = {
|
||||
onEscape: false,
|
||||
};
|
||||
|
||||
bootbox.dialog(title, buttons, options);
|
||||
},
|
||||
|
||||
afterModel(model) {
|
||||
updateCachedWizard(model);
|
||||
|
||||
|
|
|
@ -16,24 +16,10 @@
|
|||
disabled=disableTextarea
|
||||
outletArgs=(hash composer=composer editorType="composer")}}
|
||||
|
||||
{{input
|
||||
class="wizard-composer-upload hidden-upload-field"
|
||||
disabled=isUploading
|
||||
<input
|
||||
type="file"
|
||||
accept=allowedFileTypes
|
||||
multiple=true}}
|
||||
|
||||
{{#if showHyperlinkBox}}
|
||||
{{wizard-composer-hyperlink
|
||||
addLink=(action "addLink")
|
||||
hideBox=(action "hideBox")}}
|
||||
{{/if}}
|
||||
|
||||
{{#if isUploading}}
|
||||
<div id="file-uploading">
|
||||
{{loading-spinner size="small"}}<span>{{wizard-i18n "upload_selector.uploading"}} {{uploadProgress}}%</span>
|
||||
{{#if isCancellable}}
|
||||
<a href id="cancel-file-upload" {{action "cancelUpload"}}>{{d-icon "times"}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
id={{fileUploadElementId}}
|
||||
class="wizard-composer-upload"
|
||||
accept={{allowedFileTypes}}
|
||||
multiple
|
||||
>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{{wizard-composer-editor
|
||||
field=field
|
||||
composer=composer
|
||||
wizard=wizard
|
||||
groupsMentioned=(action "groupsMentioned")
|
||||
cannotSeeMention=(action "cannotSeeMention")
|
||||
importQuote=(action "importQuote")
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{{d-icon "upload"}}
|
||||
{{/if}}
|
||||
|
||||
<input disabled={{uploading}} type="file" accept={{field.file_types}} style="visibility: hidden; position: absolute;" >
|
||||
<input disabled={{uploading}} class="hidden-upload-field" type="file" accept={{field.file_types}} style="visibility: hidden; position: absolute;" >
|
||||
</label>
|
||||
|
||||
{{#if field.value}}
|
||||
|
|
|
@ -365,6 +365,10 @@ $error: #ef1700;
|
|||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-template {
|
||||
min-height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
&.full,
|
||||
|
|
|
@ -81,6 +81,7 @@ img.avatar {
|
|||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
color: var(--primary);
|
||||
|
||||
img {
|
||||
margin-right: 5px;
|
||||
|
@ -98,6 +99,7 @@ img.avatar {
|
|||
}
|
||||
&.selected {
|
||||
background-color: var(--tertiary);
|
||||
color: var(--secondary);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--highlight-low);
|
||||
|
|
|
@ -72,6 +72,10 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 0;
|
||||
overflow: auto;
|
||||
cursor: default;
|
||||
margin-top: unset;
|
||||
padding-top: unset;
|
||||
}
|
||||
|
||||
.d-editor-button-bar {
|
||||
|
@ -107,11 +111,6 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.d-editor-preview-wrapper {
|
||||
overflow: auto;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.d-editor-input,
|
||||
.d-editor-preview {
|
||||
box-sizing: border-box;
|
||||
|
|
32
assets/stylesheets/wizard/custom/mentionables.scss
Normale Datei
32
assets/stylesheets/wizard/custom/mentionables.scss
Normale Datei
|
@ -0,0 +1,32 @@
|
|||
span.mentionable-item {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a.mentionable-item span {
|
||||
background-color: $primary-low;
|
||||
padding: 3px;
|
||||
border-radius: 3px;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.ac-mentionable-item ul li a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ac-mentionable-item-name {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.ac-mentionable-item-image {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
|
@ -28,8 +28,6 @@
|
|||
margin-bottom: 0;
|
||||
|
||||
p {
|
||||
line-height: 1.7;
|
||||
|
||||
img {
|
||||
@extend img.emoji;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@import "common/components/buttons";
|
||||
@import "common/d-editor";
|
||||
@import "desktop/modal";
|
||||
@import "common/input_tip";
|
||||
|
||||
@import "custom/base";
|
||||
@import "custom/wizard";
|
||||
|
@ -18,3 +19,4 @@
|
|||
@import "custom/composer";
|
||||
@import "custom/events";
|
||||
@import "custom/locations";
|
||||
@import "custom/mentionables";
|
||||
|
|
|
@ -35,6 +35,8 @@ en:
|
|||
prompt_completion_label: "Prompt user to complete wizard."
|
||||
restart_on_revisit: "Restart"
|
||||
restart_on_revisit_label: "Clear submissions on each visit."
|
||||
resume_on_revisit: "Resume"
|
||||
resume_on_revisit_label: "Ask the user if they want to resume on each visit."
|
||||
theme_id: "Theme"
|
||||
no_theme: "Select a Theme (optional)"
|
||||
save: "Save Changes"
|
||||
|
@ -59,10 +61,13 @@ en:
|
|||
edit_columns: "Edit Columns"
|
||||
expand_text: "Read More"
|
||||
collapse_text: "Show Less"
|
||||
|
||||
support_button:
|
||||
title: "Request Support"
|
||||
label: "Support"
|
||||
category_settings:
|
||||
custom_wizard:
|
||||
title: "Custom Wizard"
|
||||
create_topic_wizard: "Select a wizard to replace the new topic composer in this category."
|
||||
|
||||
message:
|
||||
wizard:
|
||||
|
@ -146,6 +151,7 @@ en:
|
|||
group: "group"
|
||||
list: "list"
|
||||
custom_field: "custom field"
|
||||
value: "value"
|
||||
|
||||
placeholder:
|
||||
text: "Enter text"
|
||||
|
@ -160,6 +166,7 @@ en:
|
|||
group: "Select group"
|
||||
list: "Enter item"
|
||||
custom_field: "Select field"
|
||||
value: "Select value"
|
||||
|
||||
error:
|
||||
failed: "failed to save wizard"
|
||||
|
@ -199,7 +206,7 @@ en:
|
|||
char_counter_placeholder: "Display Character Counter"
|
||||
field_placeholder: "Placeholder"
|
||||
file_types: "File Types"
|
||||
preview_template: "Preview Template"
|
||||
preview_template: "Template"
|
||||
limit: "Limit"
|
||||
property: "Property"
|
||||
prefill: "Prefill"
|
||||
|
@ -584,6 +591,10 @@ en:
|
|||
requires_login: "You need to be logged in to access this wizard."
|
||||
reset: "Reset this wizard."
|
||||
step_not_permitted: "You're not permitted to view this step."
|
||||
incomplete_submission:
|
||||
title: "Continue editing your draft submission from %{date}?"
|
||||
resume: "Continue"
|
||||
restart: "Start over"
|
||||
x_characters:
|
||||
one: "%{count} Character"
|
||||
other: "%{count} Characters"
|
||||
|
|
|
@ -81,6 +81,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
|||
:required,
|
||||
:prompt_completion,
|
||||
:restart_on_revisit,
|
||||
:resume_on_revisit,
|
||||
:theme_id,
|
||||
permitted: mapped_params,
|
||||
steps: [
|
||||
|
|
|
@ -6,6 +6,7 @@ class CustomWizard::WizardController < ::ApplicationController
|
|||
|
||||
before_action :ensure_plugin_enabled
|
||||
before_action :update_subscription, only: [:index]
|
||||
before_action :ensure_logged_in, only: [:skip]
|
||||
helper_method :wizard_page_title
|
||||
helper_method :wizard_theme_id
|
||||
helper_method :wizard_theme_lookup
|
||||
|
|
|
@ -392,7 +392,6 @@ class CustomWizard::Action
|
|||
user_ids.each { |user_id| group.group_users.build(user_id: user_id) }
|
||||
end
|
||||
|
||||
GroupActionLogger.new(user, group).log_change_group_settings
|
||||
log_success("Group created", group.name)
|
||||
|
||||
result.output = group.name
|
||||
|
|
|
@ -144,7 +144,7 @@ class CustomWizard::Mapper
|
|||
if value == "present"
|
||||
result = key.public_send(operator)
|
||||
elsif ["true", "false"].include?(value)
|
||||
result = key.public_send(operator, ActiveRecord::Type::Boolean.new.cast(value))
|
||||
result = bool(key).public_send(operator, bool(value))
|
||||
end
|
||||
elsif [key, value, operator].all? { |i| !i.nil? }
|
||||
result = key.public_send(operator, value)
|
||||
|
@ -266,4 +266,8 @@ class CustomWizard::Mapper
|
|||
result = data[k]
|
||||
keys.empty? ? result : self.recurse(result, keys)
|
||||
end
|
||||
|
||||
def bool(value)
|
||||
ActiveRecord::Type::Boolean.new.cast(value)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,7 @@ class CustomWizard::Wizard
|
|||
:required,
|
||||
:prompt_completion,
|
||||
:restart_on_revisit,
|
||||
:resume_on_revisit,
|
||||
:permitted,
|
||||
:needs_categories,
|
||||
:needs_groups,
|
||||
|
@ -48,6 +49,7 @@ class CustomWizard::Wizard
|
|||
@multiple_submissions = cast_bool(attrs['multiple_submissions'])
|
||||
@prompt_completion = cast_bool(attrs['prompt_completion'])
|
||||
@restart_on_revisit = cast_bool(attrs['restart_on_revisit'])
|
||||
@resume_on_revisit = cast_bool(attrs['resume_on_revisit'])
|
||||
@after_signup = cast_bool(attrs['after_signup'])
|
||||
@after_time = cast_bool(attrs['after_time'])
|
||||
@after_time_scheduled = attrs['after_time_scheduled']
|
||||
|
|
5363
package-lock.json
generiert
Normale Datei
5363
package-lock.json
generiert
Normale Datei
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
|
@ -5,6 +5,7 @@
|
|||
"author": "Pavilion",
|
||||
"license": "GPL V2",
|
||||
"devDependencies": {
|
||||
"eslint-config-discourse": "^1.1.8"
|
||||
"eslint-config-discourse": "^1.1.8",
|
||||
"semver": "^7.3.5"
|
||||
}
|
||||
}
|
11
plugin.rb
11
plugin.rb
|
@ -1,12 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
# name: discourse-custom-wizard
|
||||
# about: Create custom wizards for topic creation, onboarding, user surveys and much more.
|
||||
# version: 0.8.1
|
||||
# version: 1.16.3
|
||||
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George
|
||||
# contact_emails: support@thepavilion.io
|
||||
# url: https://github.com/paviliondev/discourse-custom-wizard
|
||||
# tests_passed_test_url: https://plugins.discourse.pavilion.tech/w/super-mega-fun-wizard
|
||||
# stable_test_url: https://stable.plugins.discourse.pavilion.tech/w/super-mega-fun-wizard
|
||||
|
||||
gem 'liquid', '5.0.1', require: true
|
||||
register_asset 'stylesheets/admin/admin.scss', :desktop
|
||||
|
@ -138,6 +136,13 @@ after_initialize do
|
|||
load File.expand_path(path, __FILE__)
|
||||
end
|
||||
|
||||
# preloaded category custom fields
|
||||
%w[
|
||||
create_topic_wizard
|
||||
].each do |custom_field|
|
||||
Site.preloaded_category_custom_fields << custom_field
|
||||
end
|
||||
|
||||
Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty)
|
||||
|
||||
add_class_method(:wizard, :user_requires_completion?) do |user|
|
||||
|
|
|
@ -4,13 +4,15 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
|
|||
|
||||
attributes :start,
|
||||
:background,
|
||||
:submission_last_updated_at,
|
||||
:theme_id,
|
||||
:completed,
|
||||
:required,
|
||||
:permitted,
|
||||
:uncategorized_category_id,
|
||||
:categories,
|
||||
:subscribed
|
||||
:subscribed,
|
||||
:resume_on_revisit
|
||||
|
||||
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
|
||||
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
|
||||
|
@ -38,6 +40,10 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
|
|||
include_steps? && object.start.present?
|
||||
end
|
||||
|
||||
def submission_last_updated_at
|
||||
object.current_submission.updated_at
|
||||
end
|
||||
|
||||
def include_steps?
|
||||
!include_completed?
|
||||
end
|
||||
|
|
|
@ -21,6 +21,14 @@ describe CustomWizard::Builder do
|
|||
let(:permitted_param_json) { get_wizard_fixture("step/permitted_params") }
|
||||
let(:user_condition_json) { get_wizard_fixture("condition/user_condition") }
|
||||
|
||||
let(:boolean_field_condition_json) {
|
||||
JSON.parse(
|
||||
File.open(
|
||||
"#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/condition/boolean_field_condition.json"
|
||||
).read
|
||||
)
|
||||
}
|
||||
|
||||
before do
|
||||
Group.refresh_automatic_group!(:trust_level_3)
|
||||
CustomWizard::Template.save(wizard_template, skip_jobs: true)
|
||||
|
@ -297,6 +305,7 @@ describe CustomWizard::Builder do
|
|||
before do
|
||||
enable_subscription("standard")
|
||||
@template[:steps][0][:fields][0][:condition] = user_condition_json['condition']
|
||||
@template[:steps][2][:fields][5][:condition] = boolean_field_condition_json['condition']
|
||||
CustomWizard::Template.save(@template.as_json)
|
||||
end
|
||||
|
||||
|
@ -309,6 +318,16 @@ describe CustomWizard::Builder do
|
|||
wizard = CustomWizard::Builder.new(@template[:id], user).build
|
||||
expect(wizard.steps.first.fields.first.id).to eq(@template[:steps][0][:fields][1]['id'])
|
||||
end
|
||||
|
||||
it "works if a field condition uses 'is true/false'" do
|
||||
builder = CustomWizard::Builder.new(@template[:id], user)
|
||||
wizard = builder.build
|
||||
wizard.create_updater('step_2', step_2_field_5: 'true').update
|
||||
|
||||
builder = CustomWizard::Builder.new(@template[:id], user)
|
||||
wizard = builder.build
|
||||
expect(wizard.steps.last.fields.last.id).to eq(@template[:steps][2][:fields][5]['id'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -106,4 +106,37 @@ describe CustomWizard::TemplateValidator do
|
|||
).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "steps" do
|
||||
CustomWizard::TemplateValidator.required[:step].each do |attribute|
|
||||
it "invalidates if \"#{attribute.to_s}\" is not present" do
|
||||
template[:steps][0][attribute] = nil
|
||||
expect(
|
||||
CustomWizard::TemplateValidator.new(template).perform
|
||||
).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "fields" do
|
||||
CustomWizard::TemplateValidator.required[:field].each do |attribute|
|
||||
it "invalidates if \"#{attribute.to_s}\" is not present" do
|
||||
template[:steps][0][:fields][0][attribute] = nil
|
||||
expect(
|
||||
CustomWizard::TemplateValidator.new(template).perform
|
||||
).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "actions" do
|
||||
CustomWizard::TemplateValidator.required[:action].each do |attribute|
|
||||
it "invalidates if \"#{attribute.to_s}\" is not present" do
|
||||
template[:actions][0][attribute] = nil
|
||||
expect(
|
||||
CustomWizard::TemplateValidator.new(template).perform
|
||||
).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
17
spec/fixtures/condition/boolean_field_condition.json
gevendort
Normale Datei
17
spec/fixtures/condition/boolean_field_condition.json
gevendort
Normale Datei
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"condition": [
|
||||
{
|
||||
"type": "validation",
|
||||
"pairs": [
|
||||
{
|
||||
"index": 0,
|
||||
"key": "step_2_field_5",
|
||||
"key_type": "wizard_field",
|
||||
"value": "true",
|
||||
"value_type": "text",
|
||||
"connector": "is"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
6
spec/fixtures/wizard.json
gevendort
6
spec/fixtures/wizard.json
gevendort
|
@ -157,6 +157,12 @@
|
|||
"label": "User Selector",
|
||||
"description": "",
|
||||
"type": "user_selector"
|
||||
},
|
||||
{
|
||||
"id": "step_3_field_6",
|
||||
"label": "Conditional User Selector",
|
||||
"description": "Shown when checkbox in step_2_field_5 is true",
|
||||
"type": "user_selector"
|
||||
}
|
||||
],
|
||||
"description": "Unfortunately not the edible type :sushi: "
|
||||
|
|
Laden …
In neuem Issue referenzieren