diff --git a/.discourse-compatibility b/.discourse-compatibility index b2f02cc7..b53ff04d 100644 --- a/.discourse-compatibility +++ b/.discourse-compatibility @@ -1,2 +1,2 @@ -2.7.8: e07a57e398b6b1676ab42a7e34467556fca5416b -2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c \ No newline at end of file +2.7.99: e07a57e398b6b1676ab42a7e34467556fca5416b +2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c diff --git a/assets/javascripts/discourse/models/custom-wizard-manager.js.es6 b/assets/javascripts/discourse/models/custom-wizard-manager.js.es6 index ea7cfd92..f054d0f8 100644 --- a/assets/javascripts/discourse/models/custom-wizard-manager.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-manager.js.es6 @@ -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; diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index cd0b06ae..61507fd9 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -4,7 +4,6 @@ //= 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 +24,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 diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index b60f7551..1356f6ac 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -4,24 +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, throttle } 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, @@ -35,6 +25,7 @@ export default ComposerEditor.extend({ popupMenuOptions: [], draftStatus: "null", replyPlaceholder: alias("field.placeholder"), + uploadingFieldId: null, @on("didInsertElement") _composerEditorInit() { @@ -90,6 +81,50 @@ export default ComposerEditor.extend({ } 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 @@ -105,192 +140,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); @@ -372,7 +221,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(); }, }, }); diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index bb11b655..9c8e4bff 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -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), - }; - - if (this.relativeDate) { - defaultOptions = Object.assign({}, defaultOptions, { - minDate: moment(this.relativeDate).toDate(), - }); - } - - return new Pikaday(Object.assign({}, defaultOptions, this._opts())); - }); + _opts() { + return { + format: this.format || "LL", + }; }, }); diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 3a4a706c..8b9ecb82 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -16,7 +16,7 @@ export default Ember.Component.extend({ "composer", EmberObject.create({ loading: false, - reply: this.get("field.value"), + reply: this.get("field.value") || "", }) ); }, diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 index ca396081..f2095827 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 +++ b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 @@ -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")); + } }); }, }; diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs index c4bf1c74..be98db8e 100644 --- a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs @@ -16,24 +16,10 @@ disabled=disableTextarea outletArgs=(hash composer=composer editorType="composer")}} -{{input - class="wizard-composer-upload hidden-upload-field" - disabled=isUploading + - {{loading-spinner size="small"}}{{wizard-i18n "upload_selector.uploading"}} {{uploadProgress}}% - {{#if isCancellable}} - {{d-icon "times"}} - {{/if}} - -{{/if}} + id={{fileUploadElementId}} + class="wizard-composer-upload" + accept={{allowedFileTypes}} + multiple + > diff --git a/lib/custom_wizard/validators/template.rb b/lib/custom_wizard/validators/template.rb index c90944e9..d4a8367f 100644 --- a/lib/custom_wizard/validators/template.rb +++ b/lib/custom_wizard/validators/template.rb @@ -18,8 +18,8 @@ class CustomWizard::TemplateValidator data[:steps].each do |step| check_required(step, :step) - if data[:fields].present? - data[:fields].each do |field| + if step[:fields].present? + step[:fields].each do |field| check_required(field, :field) end end diff --git a/spec/components/custom_wizard/template_validator_spec.rb b/spec/components/custom_wizard/template_validator_spec.rb index c8ce915a..015228a3 100644 --- a/spec/components/custom_wizard/template_validator_spec.rb +++ b/spec/components/custom_wizard/template_validator_spec.rb @@ -45,4 +45,37 @@ describe CustomWizard::TemplateValidator do CustomWizard::TemplateValidator.new(template).perform ).to eq(false) 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