diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 65df87da..e74cbc77 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -2,7 +2,7 @@ import ComposerEditor from "discourse/components/composer-editor"; import { default as computed, on } from "discourse-common/utils/decorators"; import { findRawTemplate } from "discourse-common/lib/raw-templates"; import { throttle } from "@ember/runloop"; -import { scheduleOnce } from "@ember/runloop"; +import { scheduleOnce, next } from "@ember/runloop"; import { safariHacksDisabled, caretPosition, @@ -10,7 +10,15 @@ import { } from "discourse/lib/utilities"; import highlightSyntax from "discourse/lib/highlight-syntax"; import { getToken } from "wizard/lib/ajax"; -import { validateUploadedFiles } from "discourse/lib/uploads"; +import { + validateUploadedFiles, + getUploadMarkdown +} from "discourse/lib/uploads"; +import { + cacheShortUploadUrl, +} from "pretty-text/upload-short-url"; + +const uploadMarkdownResolvers = []; const uploadHandlers = []; export default ComposerEditor.extend({ @@ -57,10 +65,6 @@ export default ComposerEditor.extend({ this._bindUploadTarget(); }, - - showUploadModal() { - $(".wizard-composer-upload").trigger("click"); - }, _setUploadPlaceholderSend() { if (!this.composer.get("reply")) { @@ -140,11 +144,117 @@ export default ComposerEditor.extend({ 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.fieldId, + 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.fieldId, + 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.fieldId, + 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.fieldId, + oldVal: matchingPlaceholder[index], + newVal: replacement, + options: { + regex: imageScaleRegex, + index + } + } + ); + } + } + + e.preventDefault(); + return; + }); }, click(e) { @@ -155,17 +265,18 @@ export default ComposerEditor.extend({ actions: { extraButtons(toolbar) { + const component = this; + if (this.allowUpload && this.uploadIcon && !this.site.mobileView) { toolbar.addButton({ id: "upload", group: "insertions", icon: this.uploadIcon, title: "upload", - sendAction: this.showUploadModal, + sendAction: (event) => component.send("showUploadModal", event), }); } - const component = this; toolbar.addButton({ id: "link", group: "insertions", @@ -183,12 +294,19 @@ export default ComposerEditor.extend({ addLink(linkName, linkUrl) { let link = `[${linkName}](${linkUrl})`; - this.appEvents.trigger("composer:insert-text", link); + this.appEvents.trigger("wizard-editor:insert-text", { + fieldId: this.fieldId, + text: link + }); this.set("showHyperlinkBox", false); }, hideBox() { this.set("showHyperlinkBox", false); }, + + showUploadModal() { + $(this.element.querySelector(".wizard-composer-upload")).trigger("click"); + } }, }); diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6 index 457c100b..969e969e 100644 --- a/assets/javascripts/wizard/custom-wizard.js.es6 +++ b/assets/javascripts/wizard/custom-wizard.js.es6 @@ -5,16 +5,6 @@ export default Ember.Application.extend({ Resolver: buildResolver("wizard"), start() { - Object.keys(requirejs._eak_seen).forEach(key => { - if (/\/initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - this.initializer(module.default); - } - }); - Object.keys(requirejs._eak_seen).forEach((key) => { if (/\/pre\-initializers\//.test(key)) { const module = requirejs(key, null, null, true); @@ -31,5 +21,15 @@ export default Ember.Application.extend({ this.initializer(init); } }); + + Object.keys(requirejs._eak_seen).forEach(key => { + if (/\/initializers\//.test(key)) { + const module = requirejs(key, null, null, true); + if (!module) { + throw new Error(key + " must export an initializer."); + } + this.initializer(module.default); + } + }); } }); diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 new file mode 100644 index 00000000..75327413 --- /dev/null +++ b/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 @@ -0,0 +1,178 @@ +import { dasherize } from "@ember/string"; + +export default { + name: "custom-wizard-field", + initialize(app) { + if (window.location.pathname.indexOf("/w/") < 0) return; + + const FieldComponent = requirejs("wizard/components/wizard-field").default; + const FieldModel = requirejs("wizard/models/wizard-field").default; + const { cook } = requirejs("discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"); + const DEditor = requirejs("discourse/components/d-editor").default; + const { clipboardHelpers } = requirejs("discourse/lib/utilities"); + const { toMarkdown } = requirejs("discourse/lib/to-markdown"); + + FieldComponent.reopen({ + classNameBindings: ["field.id"], + + cookedDescription: function () { + return cook(this.get("field.description")); + }.property("field.description"), + + inputComponentName: function () { + const type = this.get("field.type"); + const id = this.get("field.id"); + if (["text_only"].includes(type)) return false; + return dasherize(type === "component" ? id : `wizard-field-${type}`); + }.property("field.type", "field.id"), + }); + + const StandardFieldValidation = [ + "text", + "number", + "textarea", + "dropdown", + "tag", + "image", + "user_selector", + "text_only", + "composer", + "category", + "group", + "date", + "time", + "date_time", + ]; + + FieldModel.reopen({ + check() { + if (this.customCheck) { + return this.customCheck(); + } + + let valid = this.valid; + + if (!this.required) { + this.setValid(true); + return true; + } + + const val = this.get("value"); + const type = this.get("type"); + + if (type === "checkbox") { + valid = val; + } else if (type === "upload") { + valid = val && val.id > 0; + } else if (StandardFieldValidation.indexOf(type) > -1) { + valid = val && val.toString().length > 0; + } else if (type === "url") { + valid = true; + } + + this.setValid(valid); + + return valid; + }, + }); + + const isInside = (text, regex) => { + const matches = text.match(regex); + return matches && matches.length % 2; + }; + + DEditor.reopen({ + isComposer: true, + + didInsertElement() { + this._super(); + if (this.wizardComposerEvents) { + this.appEvents.on("wizard-editor:insert-text", this, "_wizardInsertText"); + this.appEvents.on("wizard-editor:replace-text", this, "_wizardReplaceText"); + } + }, + + _wizardInsertText(args = {}) { + if (args.fieldId === this.fieldId) { + this._insertText(args.text, args.options); + } + }, + + _wizardReplaceText(args = {}) { + if (args.fieldId === this.fieldId) { + this._replaceText(args.oldVal, args.newVal, args.opts = {}); + } + }, + + paste(e) { + if (!$(".d-editor-input").is(":focus")) { + return; + } + + const isComposer = this.isComposer; + let { clipboard, canPasteHtml, canUpload } = clipboardHelpers(e, { + siteSettings: this.siteSettings, + canUpload: isComposer, + }); + + let plainText = clipboard.getData("text/plain"); + let html = clipboard.getData("text/html"); + let handled = false; + + const { pre, lineVal } = this._getSelected(null, { lineVal: true }); + const isInlinePasting = pre.match(/[^\n]$/); + const isCodeBlock = isInside(pre, /(^|\n)```/g); + + if ( + plainText && + this.siteSettings.enable_rich_text_paste && + !isInlinePasting && + !isCodeBlock + ) { + plainText = plainText.trim().replace(/\r/g, ""); + const table = this._extractTable(plainText); + if (table) { + this.appEvents.trigger("wizard-editor:insert-text", { + fieldId: this.fieldId, + text: table + }); + handled = true; + } + } + + if (canPasteHtml && plainText) { + if (isInlinePasting) { + canPasteHtml = !( + lineVal.match(/^```/) || + isInside(pre, /`/g) || + lineVal.match(/^ /) + ); + } else { + canPasteHtml = !isCodeBlock; + } + } + + if (canPasteHtml && !handled) { + let markdown = toMarkdown(html); + + if (!plainText || plainText.length < markdown.length) { + if (isInlinePasting) { + markdown = markdown.replace(/^#+/, "").trim(); + markdown = pre.match(/\S$/) ? ` ${markdown}` : markdown; + } + + this.appEvents.trigger("composer:insert-text", { + fieldId: this.fieldId, + text: markdown + }); + handled = true; + } + } + + if (handled || (canUpload && !plainText)) { + e.preventDefault(); + } + }, + }); + } +} \ No newline at end of file diff --git a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 new file mode 100644 index 00000000..c8393c1a --- /dev/null +++ b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 @@ -0,0 +1,153 @@ + +export default { + name: "custom-wizard-step", + initialize(app) { + if (window.location.pathname.indexOf("/w/") < 0) return; + + const StepModel = requirejs("wizard/models/step").default; + const StepComponent = requirejs("wizard/components/wizard-step").default; + const ajax = requirejs("wizard/lib/ajax").ajax; + const cook = requirejs("discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite").cook; + + StepModel.reopen({ + save() { + const wizardId = this.get("wizardId"); + const fields = {}; + + this.get("fields").forEach((f) => { + if (f.type !== "text_only") { + fields[f.id] = f.value; + } + }); + + return ajax({ + url: `/w/${wizardId}/steps/${this.get("id")}`, + type: "PUT", + data: { fields }, + }).catch((response) => { + if ( + response && + response.responseJSON && + response.responseJSON.errors + ) { + let wizardErrors = []; + response.responseJSON.errors.forEach((err) => { + if (err.field === wizardId) { + wizardErrors.push(err.description); + } else if (err.field) { + this.fieldError(err.field, err.description); + } else if (err) { + wizardErrors.push(err); + } + }); + if (wizardErrors.length) { + this.handleWizardError(wizardErrors.join("\n")); + } + this.animateInvalidFields(); + throw response; + } + + if (response && response.responseText) { + const responseText = response.responseText; + const start = responseText.indexOf(">") + 1; + const end = responseText.indexOf("plugins"); + const message = responseText.substring(start, end); + this.handleWizardError(message); + throw message; + } + }); + }, + + handleWizardError(message) { + this.set("message", { + state: "error", + text: message, + }); + Ember.run.later(() => this.set("message", null), 6000); + }, + }); + + StepComponent.reopen({ + classNameBindings: ["step.id"], + + animateInvalidFields() { + Ember.run.scheduleOnce("afterRender", () => { + let $element = $(".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit"); + + if ($element.length) { + $([document.documentElement, document.body]).animate( + { + scrollTop: $element.offset().top - 200, + }, + 400, + function () { + $element.wiggle(2, 100); + } + ); + } + }); + }, + + ensureStartsAtTop: function () { + window.scrollTo(0, 0); + }.observes("step.id"), + + showQuitButton: function () { + const index = this.get("step.index"); + const required = this.get("wizard.required"); + return index === 0 && !required; + }.property("step.index", "wizard.required"), + + cookedTitle: function () { + return cook(this.get("step.title")); + }.property("step.title"), + + cookedDescription: function () { + return cook(this.get("step.description")); + }.property("step.description"), + + bannerImage: function () { + const src = this.get("step.banner"); + if (!src) return; + return getUrl(src); + }.property("step.banner"), + + handleMessage: function () { + const message = this.get("step.message"); + this.sendAction("showMessage", message); + }.observes("step.message"), + + advance() { + this.set("saving", true); + this.get("step") + .save() + .then((response) => { + if (this.get("finalStep")) { + CustomWizard.finished(response); + } else { + this.sendAction("goNext", response); + } + }) + .catch(() => this.animateInvalidFields()) + .finally(() => this.set("saving", false)); + }, + + keyPress(key) {}, + + actions: { + quit() { + this.get("wizard").skip(); + }, + + done() { + this.set("finalStep", true); + this.send("nextStep"); + }, + + showMessage(message) { + this.sendAction("showMessage", message); + }, + }, + }); + } +} \ No newline at end of file diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 new file mode 100644 index 00000000..31f25c9e --- /dev/null +++ b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 @@ -0,0 +1,100 @@ +export default { + name: "custom-routes", + initialize(app) { + if (window.location.pathname.indexOf("/w/") < 0) return; + + const EmberObject = requirejs("@ember/object").default; + const Router = requirejs("wizard/router").default; + const ApplicationRoute = requirejs("wizard/routes/application").default; + const CustomWizard = requirejs("discourse/plugins/discourse-custom-wizard/wizard/models/custom").default; + const getUrl = requirejs("discourse-common/lib/get-url").default; + const Store = requirejs("discourse/models/store").default; + const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers; + const createHelperContext = requirejs("discourse-common/lib/helpers").createHelperContext; + const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default; + const Site = requirejs("discourse/plugins/discourse-custom-wizard/wizard/models/site").default; + const RestAdapter = requirejs("discourse/adapters/rest").default; + const Session = requirejs("discourse/models/session").default; + const setDefaultOwner = requirejs("discourse-common/lib/get-owner").setDefaultOwner; + const messageBus = requirejs("message-bus-client").default; + + const container = app.__container__; + Discourse.Model = EmberObject.extend(); + Discourse.__container__ = container; + setDefaultOwner(container); + registerRawHelpers(RawHandlebars, Handlebars); + + // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill + if (!Object.entries) { + Object.entries = function (obj) { + var ownProps = Object.keys(obj), + i = ownProps.length, + resArray = new Array(i); // preallocate the Array + while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]]; + + return resArray; + }; + } + + Object.keys(Ember.TEMPLATES).forEach((k) => { + if (k.indexOf("select-kit") === 0) { + let template = Ember.TEMPLATES[k]; + define(k, () => template); + } + }); + + const targets = ["controller", "component", "route", "model", "adapter"]; + + const siteSettings = Wizard.SiteSettings; + app.register("site-settings:main", siteSettings, { instantiate: false }); + createHelperContext({ siteSettings }); + targets.forEach((t) => app.inject(t, "siteSettings", "site-settings:main")); + + app.register("message-bus:main", messageBus, { instantiate: false }); + targets.forEach((t) => app.inject(t, "messageBus", "message-bus:main")); + + app.register("service:store", Store); + targets.forEach((t) => app.inject(t, "store", "service:store")); + targets.forEach((t) => app.inject(t, "appEvents", "service:app-events")); + + app.register("adapter:rest", RestAdapter); + + const site = Site.current(); + app.register("site:main", site, { instantiate: false }); + targets.forEach((t) => app.inject(t, "site", "site:main")); + + site.set("can_create_tag", false); + app.register("session:main", Session.current(), { instantiate: false }); + targets.forEach((t) => app.inject(t, "session", "session:main")); + + createHelperContext({ + siteSettings: container.lookup("site-settings:main"), + currentUser: container.lookup("current-user:main"), + site: container.lookup("site:main"), + session: container.lookup("session:main"), + capabilities: container.lookup("capabilities:main"), + }); + + const session = container.lookup("session:main"); + const setupData = document.getElementById("data-discourse-setup").dataset; + session.set("highlightJsPath", setupData.highlightJsPath); + + Router.reopen({ + rootURL: getUrl("/w/") + }); + + Router.map(function () { + this.route("custom", { path: "/:wizard_id" }, function () { + this.route("steps"); + this.route("step", { path: "/steps/:step_id" }); + }); + }); + + ApplicationRoute.reopen({ + redirect() { + this.transitionTo("custom"); + }, + model() {}, + }); + }, +}; diff --git a/assets/javascripts/wizard/initializers/custom.js.es6 b/assets/javascripts/wizard/initializers/custom.js.es6 deleted file mode 100644 index 60f9a0e2..00000000 --- a/assets/javascripts/wizard/initializers/custom.js.es6 +++ /dev/null @@ -1,329 +0,0 @@ -import { default as computed } from "discourse-common/utils/decorators"; -import { dasherize } from "@ember/string"; - -export default { - name: "custom-routes", - after: "sniff-capabilities", - initialize(app) { - if (window.location.pathname.indexOf("/w/") < 0) return; - - const EmberObject = requirejs("@ember/object").default; - const Router = requirejs("wizard/router").default; - const ApplicationRoute = requirejs("wizard/routes/application").default; - const ajax = requirejs("wizard/lib/ajax").ajax; - const StepModel = requirejs("wizard/models/step").default; - const CustomWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).default; - const WizardStep = requirejs("wizard/components/wizard-step").default; - const WizardField = requirejs("wizard/components/wizard-field").default; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const FieldModel = requirejs("wizard/models/wizard-field").default; - const autocomplete = requirejs("discourse/lib/autocomplete").default; - const cook = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ).cook; - const Singleton = requirejs("discourse/mixins/singleton").default; - const Store = requirejs("discourse/models/store").default; - const registerRawHelpers = requirejs( - "discourse-common/lib/raw-handlebars-helpers" - ).registerRawHelpers; - const createHelperContext = requirejs("discourse-common/lib/helpers") - .createHelperContext; - const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") - .default; - const Site = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/site" - ).default; - const RestAdapter = requirejs("discourse/adapters/rest").default; - const Session = requirejs("discourse/models/session").default; - const setDefaultOwner = requirejs("discourse-common/lib/get-owner") - .setDefaultOwner; - const messageBus = requirejs("message-bus-client").default; - const container = app.__container__; - Discourse.Model = EmberObject.extend(); - Discourse.__container__ = container; - setDefaultOwner(container); - registerRawHelpers(RawHandlebars, Handlebars); - - // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill - if (!Object.entries) - Object.entries = function (obj) { - var ownProps = Object.keys(obj), - i = ownProps.length, - resArray = new Array(i); // preallocate the Array - while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]]; - - return resArray; - }; - - $.fn.autocomplete = autocomplete; - - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - const targets = ["controller", "component", "route", "model", "adapter"]; - - const siteSettings = Wizard.SiteSettings; - app.register("site-settings:main", siteSettings, { instantiate: false }); - createHelperContext({ siteSettings }); - targets.forEach((t) => app.inject(t, "siteSettings", "site-settings:main")); - - app.register("message-bus:main", messageBus, { instantiate: false }); - targets.forEach((t) => app.inject(t, "messageBus", "message-bus:main")); - - app.register("service:store", Store); - targets.forEach((t) => app.inject(t, "store", "service:store")); - targets.forEach((t) => app.inject(t, "appEvents", "service:app-events")); - - app.register("adapter:rest", RestAdapter); - - const site = Site.current(); - app.register("site:main", site, { instantiate: false }); - targets.forEach((t) => app.inject(t, "site", "site:main")); - - site.set("can_create_tag", false); - app.register("session:main", Session.current(), { instantiate: false }); - targets.forEach((t) => app.inject(t, "session", "session:main")); - let context = { - siteSettings: container.lookup("site-settings:main"), - currentUser: container.lookup("current-user:main"), - site: container.lookup("site:main"), - session: container.lookup("session:main"), - capabilities: container.lookup("capabilities:main"), - }; - createHelperContext(context); - const session = container.lookup("session:main"); - const setupData = document.getElementById("data-discourse-setup").dataset; - session.set("highlightJsPath", setupData.highlightJsPath); - Router.reopen({ - rootURL: getUrl("/w/"), - }); - - Router.map(function () { - this.route("custom", { path: "/:wizard_id" }, function () { - this.route("steps"); - this.route("step", { path: "/steps/:step_id" }); - }); - }); - - ApplicationRoute.reopen({ - redirect() { - this.transitionTo("custom"); - }, - - model() {}, - }); - - WizardStep.reopen({ - classNameBindings: ["step.id"], - - animateInvalidFields() { - Ember.run.scheduleOnce("afterRender", () => { - let $element = $( - ".invalid input[type=text], .invalid textarea, .invalid input[type=checkbox], .invalid .select-kit" - ); - - if ($element.length) { - $([document.documentElement, document.body]).animate( - { - scrollTop: $element.offset().top - 200, - }, - 400, - function () { - $element.wiggle(2, 100); - } - ); - } - }); - }, - - ensureStartsAtTop: function () { - window.scrollTo(0, 0); - }.observes("step.id"), - - showQuitButton: function () { - const index = this.get("step.index"); - const required = this.get("wizard.required"); - return index === 0 && !required; - }.property("step.index", "wizard.required"), - - cookedTitle: function () { - return cook(this.get("step.title")); - }.property("step.title"), - - cookedDescription: function () { - return cook(this.get("step.description")); - }.property("step.description"), - - bannerImage: function () { - const src = this.get("step.banner"); - if (!src) return; - return getUrl(src); - }.property("step.banner"), - - handleMessage: function () { - const message = this.get("step.message"); - this.sendAction("showMessage", message); - }.observes("step.message"), - - advance() { - this.set("saving", true); - this.get("step") - .save() - .then((response) => { - if (this.get("finalStep")) { - CustomWizard.finished(response); - } else { - this.sendAction("goNext", response); - } - }) - .catch(() => this.animateInvalidFields()) - .finally(() => this.set("saving", false)); - }, - - keyPress(key) {}, - - actions: { - quit() { - this.get("wizard").skip(); - }, - - done() { - this.set("finalStep", true); - this.send("nextStep"); - }, - - showMessage(message) { - this.sendAction("showMessage", message); - }, - }, - }); - - StepModel.reopen({ - save() { - const wizardId = this.get("wizardId"); - const fields = {}; - - this.get("fields").forEach((f) => { - if (f.type !== "text_only") { - fields[f.id] = f.value; - } - }); - - return ajax({ - url: `/w/${wizardId}/steps/${this.get("id")}`, - type: "PUT", - data: { fields }, - }).catch((response) => { - if ( - response && - response.responseJSON && - response.responseJSON.errors - ) { - let wizardErrors = []; - response.responseJSON.errors.forEach((err) => { - if (err.field === wizardId) { - wizardErrors.push(err.description); - } else if (err.field) { - this.fieldError(err.field, err.description); - } else if (err) { - wizardErrors.push(err); - } - }); - if (wizardErrors.length) { - this.handleWizardError(wizardErrors.join("\n")); - } - this.animateInvalidFields(); - throw response; - } - - if (response && response.responseText) { - const responseText = response.responseText; - const start = responseText.indexOf(">") + 1; - const end = responseText.indexOf("plugins"); - const message = responseText.substring(start, end); - this.handleWizardError(message); - throw message; - } - }); - }, - - handleWizardError(message) { - this.set("message", { - state: "error", - text: message, - }); - Ember.run.later(() => this.set("message", null), 6000); - }, - }); - - WizardField.reopen({ - classNameBindings: ["field.id"], - - cookedDescription: function () { - return cook(this.get("field.description")); - }.property("field.description"), - - inputComponentName: function () { - const type = this.get("field.type"); - const id = this.get("field.id"); - if (["text_only"].includes(type)) return false; - return dasherize(type === "component" ? id : `wizard-field-${type}`); - }.property("field.type", "field.id"), - }); - - const StandardFieldValidation = [ - "text", - "number", - "textarea", - "dropdown", - "tag", - "image", - "user_selector", - "text_only", - "composer", - "category", - "group", - "date", - "time", - "date_time", - ]; - - FieldModel.reopen({ - check() { - if (this.customCheck) { - return this.customCheck(); - } - - let valid = this.valid; - - if (!this.required) { - this.setValid(true); - return true; - } - - const val = this.get("value"); - const type = this.get("type"); - - if (type === "checkbox") { - valid = val; - } else if (type === "upload") { - valid = val && val.id > 0; - } else if (StandardFieldValidation.indexOf(type) > -1) { - valid = val && val.toString().length > 0; - } else if (type === "url") { - valid = true; - } - - this.setValid(valid); - - return valid; - }, - }); - }, -}; diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs index 96fa3628..068e6a56 100644 --- a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs @@ -6,18 +6,19 @@ markdownOptions=markdownOptions extraButtons=(action "extraButtons") importQuote=(action "importQuote") - showUploadModal=showUploadModal + showUploadModal=(action "showUploadModal") togglePreview=(action "togglePreview") validation=validation loading=composer.loading showLink=showLink - composerEvents=true + wizardComposerEvents=true + fieldId=fieldId disabled=disableTextarea outletArgs=(hash composer=composer editorType="composer")}} {{input class="wizard-composer-upload hidden-upload-field" - disabled=uploading + disabled=isUploading type="file" multiple=true}} diff --git a/assets/javascripts/wizard/templates/components/wizard-field-composer.hbs b/assets/javascripts/wizard/templates/components/wizard-field-composer.hbs index b35e432b..d2306616 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-composer.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-composer.hbs @@ -1,5 +1,6 @@ {{wizard-composer-editor replyPlaceholder=field.placeholder + fieldId=field.id composer=composer groupsMentioned=(action "groupsMentioned") cannotSeeMention=(action "cannotSeeMention")