Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-10 04:12:53 +01:00
Merge branch 'master' of https://github.com/angusmcleod/discourse-custom-wizard
Dieser Commit ist enthalten in:
Commit
d7cb20cb3a
17 geänderte Dateien mit 857 neuen und 353 gelöschten Zeilen
|
@ -1,7 +1,5 @@
|
||||||
window.Discourse = {}
|
window.Discourse = {}
|
||||||
window.Wizard = {};
|
window.Wizard = {};
|
||||||
Wizard.SiteSettings = {};
|
Wizard.SiteSettings = {};
|
||||||
Wizard.RAW_TEMPLATES = {};
|
|
||||||
Discourse.__widget_helpers = {};
|
Discourse.__widget_helpers = {};
|
||||||
window.__DISCOURSE_RAW_TEMPLATES = {};
|
|
||||||
Discourse.SiteSettings = Wizard.SiteSettings;
|
Discourse.SiteSettings = Wizard.SiteSettings;
|
|
@ -38,11 +38,15 @@
|
||||||
//= require discourse/app/lib/cookie
|
//= require discourse/app/lib/cookie
|
||||||
//= require discourse/app/lib/public-js-versions
|
//= require discourse/app/lib/public-js-versions
|
||||||
//= require discourse/app/lib/load-oneboxes
|
//= require discourse/app/lib/load-oneboxes
|
||||||
|
//= require discourse/app/lib/highlight-syntax
|
||||||
|
|
||||||
//= require discourse/app/mixins/singleton
|
//= require discourse/app/mixins/singleton
|
||||||
|
//= require discourse/app/mixins/upload
|
||||||
|
|
||||||
//= require discourse/app/adapters/rest
|
//= require discourse/app/adapters/rest
|
||||||
|
|
||||||
|
//= require message-bus
|
||||||
|
|
||||||
//= require discourse/app/models/login-method
|
//= require discourse/app/models/login-method
|
||||||
//= require discourse/app/models/permission-type
|
//= require discourse/app/models/permission-type
|
||||||
//= require discourse/app/models/archetype
|
//= require discourse/app/models/archetype
|
||||||
|
@ -77,6 +81,7 @@
|
||||||
//= require discourse/app/helpers/user-avatar
|
//= require discourse/app/helpers/user-avatar
|
||||||
//= require discourse/app/helpers/format-username
|
//= require discourse/app/helpers/format-username
|
||||||
//= require discourse/app/helpers/share-url
|
//= require discourse/app/helpers/share-url
|
||||||
|
//= require discourse/app/helpers/decorate-username-selector
|
||||||
//= require discourse-common/addon/helpers/component-for-collection
|
//= require discourse-common/addon/helpers/component-for-collection
|
||||||
//= require discourse-common/addon/helpers/component-for-row
|
//= require discourse-common/addon/helpers/component-for-row
|
||||||
//= require discourse-common/addon/lib/raw-templates
|
//= require discourse-common/addon/lib/raw-templates
|
||||||
|
@ -113,6 +118,7 @@
|
||||||
//= require discourse/app/templates/emoji-selector-autocomplete
|
//= require discourse/app/templates/emoji-selector-autocomplete
|
||||||
//= require discourse/app/templates/user-selector-autocomplete
|
//= require discourse/app/templates/user-selector-autocomplete
|
||||||
|
|
||||||
|
//= require discourse/app/initializers/jquery-plugins
|
||||||
//= require discourse/app/pre-initializers/sniff-capabilities
|
//= require discourse/app/pre-initializers/sniff-capabilities
|
||||||
|
|
||||||
//= require ember-addons/decorator-alias
|
//= require ember-addons/decorator-alias
|
||||||
|
@ -126,6 +132,7 @@
|
||||||
//= require template_include.js
|
//= require template_include.js
|
||||||
//= require caret_position.js
|
//= require caret_position.js
|
||||||
//= require popper.js
|
//= require popper.js
|
||||||
|
//= require bootstrap-modal.js
|
||||||
//= require bootbox.js
|
//= require bootbox.js
|
||||||
//= require discourse-shims
|
//= require discourse-shims
|
||||||
|
|
||||||
|
|
|
@ -8,24 +8,30 @@ Discourse.unofficial_plugins.each do |plugin|
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
plugin.each_globbed_asset do |f, is_dir|
|
plugin.each_globbed_asset do |f, is_dir|
|
||||||
files.push(f) if f.include? "raw.hbs"
|
files.push(f) if f.include? "hbr"
|
||||||
end
|
end
|
||||||
|
|
||||||
Dir.glob("#{Rails.root}/app/assets/javascripts/discourse/templates/*.raw.hbs").each do |f|
|
Dir.glob("#{Rails.root}/app/assets/javascripts/discourse/app/templates/*.hbr").each do |f|
|
||||||
files.push(f)
|
files.push(f)
|
||||||
end
|
end
|
||||||
|
|
||||||
files.each do |f|
|
files.each do |f|
|
||||||
name = File.basename(f, ".raw.hbs")
|
name = File.basename(f, ".hbr")
|
||||||
compiled = Barber::Precompiler.new().compile(File.read(f))
|
compiled = Barber::Precompiler.new().compile(File.read(f))
|
||||||
result << "
|
result << "
|
||||||
(function() {
|
(function() {
|
||||||
if ('Wizard' in window) {
|
const { addRawTemplate } = requirejs('discourse-common/lib/raw-templates');
|
||||||
window.__DISCOURSE_RAW_TEMPLATES['javascripts/#{name}'] = requirejs('discourse-common/lib/raw-handlebars').template(#{compiled});
|
addRawTemplate(#{compiled});
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
result << "
|
||||||
|
(function() {
|
||||||
|
const rawTemplates = requirejs('discourse-common/lib/raw-templates');
|
||||||
|
window.__DISCOURSE_RAW_TEMPLATES = rawTemplates.__DISCOURSE_RAW_TEMPLATES;
|
||||||
|
})();
|
||||||
|
"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
result
|
result
|
||||||
|
|
|
@ -1,22 +1,39 @@
|
||||||
import ComposerEditor from 'discourse/components/composer-editor';
|
import ComposerEditor from "discourse/components/composer-editor";
|
||||||
import { default as computed, on } from 'discourse-common/utils/decorators';
|
import { default as computed, on } from "discourse-common/utils/decorators";
|
||||||
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||||
import { throttle } from "@ember/runloop";
|
import { throttle } from "@ember/runloop";
|
||||||
import { scheduleOnce } from "@ember/runloop";
|
import { scheduleOnce, next } from "@ember/runloop";
|
||||||
import { safariHacksDisabled } from "discourse/lib/utilities";
|
import {
|
||||||
|
safariHacksDisabled,
|
||||||
|
caretPosition,
|
||||||
|
inCodeBlock,
|
||||||
|
} from "discourse/lib/utilities";
|
||||||
|
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||||
|
import { getToken } from "wizard/lib/ajax";
|
||||||
|
import {
|
||||||
|
validateUploadedFiles,
|
||||||
|
getUploadMarkdown
|
||||||
|
} from "discourse/lib/uploads";
|
||||||
|
import {
|
||||||
|
cacheShortUploadUrl,
|
||||||
|
} from "pretty-text/upload-short-url";
|
||||||
|
|
||||||
|
const uploadMarkdownResolvers = [];
|
||||||
|
|
||||||
|
const uploadHandlers = [];
|
||||||
export default ComposerEditor.extend({
|
export default ComposerEditor.extend({
|
||||||
classNameBindings: ['fieldClass'],
|
classNameBindings: ["fieldClass"],
|
||||||
allowUpload: false,
|
allowUpload: true,
|
||||||
showLink: false,
|
showLink: false,
|
||||||
|
showHyperlinkBox: false,
|
||||||
topic: null,
|
topic: null,
|
||||||
showToolbar: true,
|
showToolbar: true,
|
||||||
focusTarget: "reply",
|
focusTarget: "reply",
|
||||||
canWhisper: false,
|
canWhisper: false,
|
||||||
lastValidatedAt: 'lastValidatedAt',
|
lastValidatedAt: "lastValidatedAt",
|
||||||
uploadIcon: "upload",
|
uploadIcon: "upload",
|
||||||
popupMenuOptions: [],
|
popupMenuOptions: [],
|
||||||
draftStatus: 'null',
|
draftStatus: "null",
|
||||||
|
|
||||||
@on("didInsertElement")
|
@on("didInsertElement")
|
||||||
_composerEditorInit() {
|
_composerEditorInit() {
|
||||||
|
@ -26,12 +43,15 @@ export default ComposerEditor.extend({
|
||||||
if (this.siteSettings.enable_mentions) {
|
if (this.siteSettings.enable_mentions) {
|
||||||
$input.autocomplete({
|
$input.autocomplete({
|
||||||
template: findRawTemplate("user-selector-autocomplete"),
|
template: findRawTemplate("user-selector-autocomplete"),
|
||||||
dataSource: term => this.userSearchTerm.call(this, term),
|
dataSource: (term) => this.userSearchTerm.call(this, term),
|
||||||
key: "@",
|
key: "@",
|
||||||
transformComplete: v => v.username || v.name,
|
transformComplete: (v) => v.username || v.name,
|
||||||
afterComplete() {
|
afterComplete: (value) => {
|
||||||
|
this.composer.set("reply", value);
|
||||||
scheduleOnce("afterRender", () => $input.blur().focus());
|
scheduleOnce("afterRender", () => $input.blur().focus());
|
||||||
}
|
},
|
||||||
|
triggerRule: (textarea) =>
|
||||||
|
!inCodeBlock(textarea.value, caretPosition(textarea))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,23 +66,247 @@ export default ComposerEditor.extend({
|
||||||
this._bindUploadTarget();
|
this._bindUploadTarget();
|
||||||
},
|
},
|
||||||
|
|
||||||
_bindUploadTarget() {
|
_setUploadPlaceholderSend() {
|
||||||
|
if (!this.composer.get("reply")) {
|
||||||
|
this.composer.set("reply", "");
|
||||||
|
}
|
||||||
|
this._super(...arguments);
|
||||||
},
|
},
|
||||||
|
|
||||||
_unbindUploadTarget() {
|
_bindUploadTarget() {
|
||||||
|
this._super(...arguments);
|
||||||
|
const $element = $(this.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(
|
||||||
|
I18n.t("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.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) {
|
||||||
|
if ($(e.target).hasClass('wizard-composer-hyperlink')) {
|
||||||
|
this.set('showHyperlinkBox', false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
extraButtons(toolbar) {
|
extraButtons(toolbar) {
|
||||||
|
const component = this;
|
||||||
|
|
||||||
if (this.allowUpload && this.uploadIcon && !this.site.mobileView) {
|
if (this.allowUpload && this.uploadIcon && !this.site.mobileView) {
|
||||||
toolbar.addButton({
|
toolbar.addButton({
|
||||||
id: "upload",
|
id: "upload",
|
||||||
group: "insertions",
|
group: "insertions",
|
||||||
icon: this.uploadIcon,
|
icon: this.uploadIcon,
|
||||||
title: "upload",
|
title: "upload",
|
||||||
sendAction: this.showUploadModal
|
sendAction: (event) => component.send("showUploadModal", event),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolbar.addButton({
|
||||||
|
id: "link",
|
||||||
|
group: "insertions",
|
||||||
|
shortcut: "K",
|
||||||
|
trimLeading: true,
|
||||||
|
unshift: true,
|
||||||
|
sendAction: (event) => component.set("showHyperlinkBox", true),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
previewUpdated($preview) {
|
||||||
|
highlightSyntax($preview[0], this.siteSettings, this.session);
|
||||||
|
this._super(...arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
addLink(linkName, linkUrl) {
|
||||||
|
let link = `[${linkName}](${linkUrl})`;
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
classNames: ['wizard-composer-hyperlink'],
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
addLink() {
|
||||||
|
this.addLink(this.linkName, this.linkUrl);
|
||||||
|
},
|
||||||
|
|
||||||
|
hideBox() {
|
||||||
|
this.hideBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
|
@ -5,16 +5,6 @@ export default Ember.Application.extend({
|
||||||
Resolver: buildResolver("wizard"),
|
Resolver: buildResolver("wizard"),
|
||||||
|
|
||||||
start() {
|
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) => {
|
Object.keys(requirejs._eak_seen).forEach((key) => {
|
||||||
if (/\/pre\-initializers\//.test(key)) {
|
if (/\/pre\-initializers\//.test(key)) {
|
||||||
const module = requirejs(key, null, null, true);
|
const module = requirejs(key, null, null, true);
|
||||||
|
@ -31,5 +21,15 @@ export default Ember.Application.extend({
|
||||||
this.initializer(init);
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
178
assets/javascripts/wizard/initializers/custom-wizard-field.js.es6
Normale Datei
178
assets/javascripts/wizard/initializers/custom-wizard-field.js.es6
Normale Datei
|
@ -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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
153
assets/javascripts/wizard/initializers/custom-wizard-step.js.es6
Normale Datei
153
assets/javascripts/wizard/initializers/custom-wizard-step.js.es6
Normale Datei
|
@ -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);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
100
assets/javascripts/wizard/initializers/custom-wizard.js.es6
Normale Datei
100
assets/javascripts/wizard/initializers/custom-wizard.js.es6
Normale Datei
|
@ -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() {},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,302 +0,0 @@
|
||||||
import { default as computed } from 'discourse-common/utils/decorators';
|
|
||||||
import { dasherize } from "@ember/string";
|
|
||||||
|
|
||||||
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 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 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("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);
|
|
||||||
|
|
||||||
let context = {
|
|
||||||
siteSettings: container.lookup("site-settings:main"),
|
|
||||||
currentUser: container.lookup("current-user:main"),
|
|
||||||
site: container.lookup("site:main"),
|
|
||||||
session: container.lookup("session:main"),
|
|
||||||
};
|
|
||||||
createHelperContext(context);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -6,11 +6,33 @@
|
||||||
markdownOptions=markdownOptions
|
markdownOptions=markdownOptions
|
||||||
extraButtons=(action "extraButtons")
|
extraButtons=(action "extraButtons")
|
||||||
importQuote=(action "importQuote")
|
importQuote=(action "importQuote")
|
||||||
showUploadModal=showUploadModal
|
showUploadModal=(action "showUploadModal")
|
||||||
togglePreview=(action "togglePreview")
|
togglePreview=(action "togglePreview")
|
||||||
validation=validation
|
validation=validation
|
||||||
loading=composer.loading
|
loading=composer.loading
|
||||||
showLink=showLink
|
showLink=showLink
|
||||||
composerEvents=true
|
wizardComposerEvents=true
|
||||||
|
fieldId=fieldId
|
||||||
disabled=disableTextarea
|
disabled=disableTextarea
|
||||||
outletArgs=(hash composer=composer editorType="composer")}}
|
outletArgs=(hash composer=composer editorType="composer")}}
|
||||||
|
|
||||||
|
{{input
|
||||||
|
class="wizard-composer-upload hidden-upload-field"
|
||||||
|
disabled=isUploading
|
||||||
|
type="file"
|
||||||
|
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>{{i18n "upload_selector.uploading"}} {{uploadProgress}}%</span>
|
||||||
|
{{#if isCancellable}}
|
||||||
|
<a href id="cancel-file-upload" {{action "cancelUpload"}}>{{d-icon "times"}}</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div class="wizard-composer-hyperlink-contents">
|
||||||
|
<h3>{{i18n "wizard_composer.insert_hyperlink.heading"}}</h3>
|
||||||
|
{{input
|
||||||
|
class="composer-link-name"
|
||||||
|
placeholder=(i18n 'wizard_composer.insert_hyperlink.placeholder.link_name')
|
||||||
|
type="text"
|
||||||
|
value=linkName}}
|
||||||
|
{{input
|
||||||
|
class="composer-link-url"
|
||||||
|
placeholder=(i18n 'wizard_composer.insert_hyperlink.placeholder.link_url')
|
||||||
|
type="text"
|
||||||
|
value=linkUrl}}
|
||||||
|
{{d-button
|
||||||
|
label="wizard_composer.insert_hyperlink.ok"
|
||||||
|
class="add-link btn-primary"
|
||||||
|
click=(action 'addLink')}}
|
||||||
|
{{d-button
|
||||||
|
label="wizard_composer.insert_hyperlink.cancel"
|
||||||
|
class="hide-hyperlink-box btn-danger"
|
||||||
|
click=(action 'hideBox')}}
|
||||||
|
</div>
|
|
@ -1,10 +1,12 @@
|
||||||
{{wizard-composer-editor replyPlaceholder=field.placeholder
|
{{wizard-composer-editor
|
||||||
composer=composer
|
replyPlaceholder=field.placeholder
|
||||||
groupsMentioned=(action "groupsMentioned")
|
fieldId=field.id
|
||||||
cannotSeeMention=(action "cannotSeeMention")
|
composer=composer
|
||||||
importQuote=(action "importQuote")
|
groupsMentioned=(action "groupsMentioned")
|
||||||
togglePreview=(action "togglePreview")
|
cannotSeeMention=(action "cannotSeeMention")
|
||||||
afterRefresh=(action "afterRefresh")}}
|
importQuote=(action "importQuote")
|
||||||
|
togglePreview=(action "togglePreview")
|
||||||
|
afterRefresh=(action "afterRefresh")}}
|
||||||
|
|
||||||
<button class='wizard-btn toggle-preview' {{action 'togglePreview'}}>
|
<button class='wizard-btn toggle-preview' {{action 'togglePreview'}}>
|
||||||
<span class="d-button-label">{{i18n togglePreviewLabel}}</span>
|
<span class="d-button-label">{{i18n togglePreviewLabel}}</span>
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wizard-field-composer .wmd-controls {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.wizard-field-composer.show-preview .d-editor-textarea-wrapper {
|
.wizard-field-composer.show-preview .d-editor-textarea-wrapper {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +59,7 @@
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
border: 1px solid var(--primary-medium);
|
border: 1px solid var(--primary-medium);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
max-width: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.d-editor-textarea-wrapper {
|
.d-editor-textarea-wrapper {
|
||||||
|
@ -182,4 +187,46 @@
|
||||||
|
|
||||||
.d-editor-button-bar .btn {
|
.d-editor-button-bar .btn {
|
||||||
border: none;
|
border: none;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-composer-upload {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-composer-hyperlink {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba($primary, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wizard-composer-hyperlink-contents {
|
||||||
|
background-color: $secondary;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.composer-link-url,
|
||||||
|
.composer-link-name {
|
||||||
|
display: block;
|
||||||
|
min-width: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-hyperlink-box, .add-link {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-uploading {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -40px;
|
||||||
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
@import "common/foundation/colors";
|
@import "common/foundation/colors";
|
||||||
@import "common/foundation/variables";
|
@import "common/foundation/variables";
|
||||||
|
@import "common/base/code_highlighting";
|
||||||
|
@import "common/base/modal";
|
||||||
|
@import "common/components/buttons";
|
||||||
|
@import "common/d-editor";
|
||||||
|
@import "desktop/modal";
|
||||||
|
|
||||||
@import "custom/base";
|
@import "custom/base";
|
||||||
@import "custom/wizard";
|
@import "custom/wizard";
|
||||||
@import "custom/step";
|
@import "custom/step";
|
||||||
|
@ -9,5 +15,3 @@
|
||||||
@import "custom/composer";
|
@import "custom/composer";
|
||||||
@import "custom/events";
|
@import "custom/events";
|
||||||
@import "custom/locations";
|
@import "custom/locations";
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -455,3 +455,10 @@ en:
|
||||||
yourself_confirm:
|
yourself_confirm:
|
||||||
title: "Did you forget to add recipients?"
|
title: "Did you forget to add recipients?"
|
||||||
body: "Right now this message is only being sent to yourself!"
|
body: "Right now this message is only being sent to yourself!"
|
||||||
|
insert_hyperlink:
|
||||||
|
heading: "Insert Hyperlink"
|
||||||
|
ok: "Ok"
|
||||||
|
cancel: "Cancel"
|
||||||
|
placeholder:
|
||||||
|
link_name: "Link Text"
|
||||||
|
link_url: "Link Url"
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
<%= preload_script "ember_jquery" %>
|
<%= preload_script "ember_jquery" %>
|
||||||
<%= preload_script "wizard-vendor" %>
|
<%= preload_script "wizard-vendor" %>
|
||||||
<%= preload_script "wizard-application" %>
|
<%= preload_script "wizard-application" %>
|
||||||
<%= preload_script "wizard-custom-lib" %>
|
<%= preload_script "wizard-custom-globals" %>
|
||||||
|
<%= preload_script "wizard-raw-templates" %>
|
||||||
<%= preload_script "wizard-custom" %>
|
<%= preload_script "wizard-custom" %>
|
||||||
<%= preload_script "wizard-plugin" %>
|
<%= preload_script "wizard-plugin" %>
|
||||||
<%= preload_script "pretty-text-bundle" %>
|
<%= preload_script "pretty-text-bundle" %>
|
||||||
|
@ -23,6 +24,8 @@
|
||||||
|
|
||||||
<%= server_plugin_outlet "custom_wizard" %>
|
<%= server_plugin_outlet "custom_wizard" %>
|
||||||
|
|
||||||
|
<%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
|
||||||
|
|
||||||
<meta name="discourse_theme_ids" content="<%= theme_ids&.join(",") %>">
|
<meta name="discourse_theme_ids" content="<%= theme_ids&.join(",") %>">
|
||||||
<meta name="discourse-base-uri" content="<%= Discourse.base_uri %>">
|
<meta name="discourse-base-uri" content="<%= Discourse.base_uri %>">
|
||||||
|
|
||||||
|
@ -39,7 +42,6 @@
|
||||||
|
|
||||||
<%- if current_user %>
|
<%- if current_user %>
|
||||||
<%= preload_script 'wizard-custom-start' %>
|
<%= preload_script 'wizard-custom-start' %>
|
||||||
<%= preload_script 'wizard-raw-templates' %>
|
|
||||||
<%- else %>
|
<%- else %>
|
||||||
<%= preload_script 'wizard-custom-guest' %>
|
<%= preload_script 'wizard-custom-guest' %>
|
||||||
<%- end %>
|
<%- end %>
|
||||||
|
|
Laden …
In neuem Issue referenzieren