0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-09-19 23:31:11 +02:00

Merge branch 'master' into pr/46

Dieser Commit ist enthalten in:
Angus McLeod 2020-07-07 10:31:27 +10:00
Commit 58d0fda5af
38 geänderte Dateien mit 240 neuen und 132 gelöschten Zeilen

Datei anzeigen

@ -5,6 +5,8 @@ import { computed } from "@ember/object";
import wizardSchema from '../lib/wizard-schema';
import UndoChanges from '../mixins/undo-changes';
import Component from "@ember/component";
import { notificationLevels } from '../lib/wizard';
import I18n from "I18n";
export default Component.extend(UndoChanges, {
componentType: 'action',
@ -12,6 +14,7 @@ export default Component.extend(UndoChanges, {
visible: computed('currentActionId', function() { return this.action.id === this.currentActionId }),
createTopic: equal('action.type', 'create_topic'),
updateProfile: equal('action.type', 'update_profile'),
watchCategories: equal('action.type', 'watch_categories'),
sendMessage: equal('action.type', 'send_message'),
openComposer: equal('action.type', 'open_composer'),
sendToApi: equal('action.type', 'send_to_api'),
@ -31,6 +34,12 @@ export default Component.extend(UndoChanges, {
name: I18n.t(`admin.wizard.action.${type}.label`)
};
}),
availableNotificationLevels: notificationLevels.map((type, index) => {
return {
id: type,
name: I18n.t(`admin.wizard.action.watch_categories.notification_level.${type}`)
};
}),
messageUrl: 'https://thepavilion.io/t/2810',

Datei anzeigen

@ -63,7 +63,8 @@ export default Component.extend(UndoChanges, {
if (this.isDropdown) {
options.wizardFieldSelection = 'key,value';
options.userFieldOptionsSelection = 'output';
options.inputTypes = 'association,assignment';
options.textSelection = 'key,value,output';
options.inputTypes = 'conditional,association,assignment';
options.pairConnector = 'association';
options.keyPlaceholder = 'admin.wizard.key';
options.valuePlaceholder = 'admin.wizard.value';

Datei anzeigen

@ -1,5 +1,6 @@
import Component from "@ember/component";
import { A } from "@ember/array";
import I18n from "I18n";
export default Component.extend({
classNames: ['container', 'export'],

Datei anzeigen

@ -2,6 +2,7 @@ import { ajax } from 'discourse/lib/ajax';
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { notEmpty } from "@ember/object/computed";
import Component from "@ember/component";
import I18n from "I18n";
export default Component.extend({
classNames: ['container', 'import'],

Datei anzeigen

@ -4,6 +4,7 @@ import { computed } from "@ember/object";
import { defaultConnector } from '../lib/wizard-mapper';
import { later } from "@ember/runloop";
import { observes } from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Component.extend({
classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'],

Datei anzeigen

@ -6,6 +6,7 @@ import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper';
import { snakeCase, generateName, userProperties } from '../lib/wizard';
import Component from "@ember/component";
import { bind, later } from "@ember/runloop";
import I18n from "I18n";
export default Component.extend({
classNameBindings: [':mapper-selector', 'activeType'],

Datei anzeigen

@ -1,5 +1,6 @@
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import Component from "@ember/component";
import I18n from "I18n";
export default Component.extend({
classNames: 'wizard-message',

Datei anzeigen

@ -3,6 +3,7 @@ import { notEmpty } from "@ember/object/computed";
import { userProperties } from '../lib/wizard';
import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
import I18n from "I18n";
export default Component.extend({
classNames: 'wizard-text-editor',

Datei anzeigen

@ -5,6 +5,7 @@ import { default as discourseComputed } from 'discourse-common/utils/decorators'
import { not, and, equal } from "@ember/object/computed";
import { selectKitContent } from '../lib/wizard';
import Controller from "@ember/controller";
import I18n from "I18n";
export default Controller.extend({
queryParams: ['refresh_list'],

Datei anzeigen

@ -3,8 +3,9 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
import { ajax } from 'discourse/lib/ajax';
import { notEmpty } from "@ember/object/computed";
import CustomWizardLogs from '../models/custom-wizard-logs';
import Controller from "@ember/controller";
export default Ember.Controller.extend({
export default Controller.extend({
refreshing: false,
hasLogs: notEmpty("logs"),
page: 0,

Datei anzeigen

@ -9,6 +9,7 @@ import { scheduleOnce, later } from "@ember/runloop";
import Controller from "@ember/controller";
import copyText from "discourse/lib/copy-text";
import CustomWizard from '../models/custom-wizard';
import I18n from "I18n";
export default Controller.extend({
hasName: notEmpty('wizard.name'),

Datei anzeigen

@ -1,58 +1,27 @@
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { scheduleOnce } from "@ember/runloop";
import Controller from "@ember/controller";
export default Controller.extend({
title: 'admin.wizard.after_time_modal.title',
setup() {
const dateTime = this.get('model.dateTime');
const ROUNDING = 30 * 60 * 1000;
const nextInterval = moment(Math.ceil((+moment()) / ROUNDING) * ROUNDING);
const mDateTime = dateTime ? moment(dateTime) : nextInterval;
const mDateTimeLocal = mDateTime.local();
const date = mDateTimeLocal.format('YYYY-MM-DD');
const time = mDateTimeLocal.format('HH:mm');
this.setProperties({ date, time });
scheduleOnce('afterRender', this, () => {
const $timePicker = $("#time-picker");
$timePicker.timepicker({ timeFormat: 'H:i' });
$timePicker.timepicker('setTime', time);
$timePicker.change(() => this.set('time', $timePicker.val()));
});
this.set('bufferedDateTime', moment(this.model.dateTime));
},
@discourseComputed('date', 'time')
dateTime: function(date, time) {
return moment(date + 'T' + time).format();
},
@discourseComputed('dateTime')
@discourseComputed('bufferedDateTime')
submitDisabled(dateTime) {
return moment().isAfter(dateTime);
},
resetProperties() {
this.setProperties({
date: null,
time: null
});
},
actions: {
clear() {
this.resetProperties();
this.get('model.update')(null);
},
submit() {
const dateTime = this.get('dateTime');
const formatted = moment(dateTime).utc().toISOString();
this.get('model.update')(formatted);
this.resetProperties();
const dateTime = this.get('bufferedDateTime');
this.get('model.update')(moment(dateTime).utc().toISOString());
this.send("closeModal");
},
dateTimeChanged(dateTime) {
this.set('bufferedDateTime', dateTime);
}
}
});

Datei anzeigen

@ -1,5 +1,6 @@
import EmberObject from "@ember/object";
import { A } from "@ember/array";
import I18n from "I18n";
// Inputs

Datei anzeigen

@ -141,6 +141,16 @@ const action = {
profile_updates: null,
custom_fields: null
},
watch_categories: {
categories: null,
notification_level: null,
mute_remainder: null
},
send_to_api: {
api: null,
api_endpoint: null,
api_body: null
},
add_to_group: {
group: null
},
@ -158,7 +168,9 @@ const action = {
'recipient',
'profile_updates',
'group',
'url'
'url',
'categories',
'mute_remainder'
],
advanced: [
'code',

Datei anzeigen

@ -46,7 +46,16 @@ const userProperties = [
'location',
'website',
'bio_raw',
'trust_level'
'trust_level',
'email_level'
];
const notificationLevels = [
'regular',
'watching',
'tracking',
'watching_first_post',
'muted'
];
function listProperties(type, opts={}) {
@ -106,5 +115,6 @@ export {
snakeCase,
userProperties,
listProperties,
notificationLevels,
wizardFieldList
};

Datei anzeigen

@ -1,11 +1,12 @@
import CustomWizardLogs from '../models/custom-wizard-logs';
import DiscourseRoute from "discourse/routes/discourse";
export default Discourse.Route.extend({
export default DiscourseRoute.extend({
model() {
return CustomWizardLogs.list();
},
setupController(controller, model) {
controller.set('logs', model);
}
})
})

Datei anzeigen

@ -1,6 +1,7 @@
import CustomWizard from '../models/custom-wizard';
import { ajax } from 'discourse/lib/ajax';
import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n";
export default DiscourseRoute.extend({
model(params) {

Datei anzeigen

@ -283,6 +283,63 @@
</div>
{{/if}}
{{#if watchCategories}}
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.watch_categories.categories"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.categories
property='categories'
onUpdate=(action 'mappedFieldUpdated')
options=(hash
textSelection='key,value'
wizardFieldSelection=true
userFieldSelection='key,value'
categorySelection='output'
context='action'
)}}
</div>
</div>
<div class="setting full field-mapper-setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.watch_categories.mute_remainder"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=action.mute_remainder
property='mute_remainder'
onUpdate=(action 'mappedFieldUpdated')
options=(hash
context='action'
wizardFieldSelection=true
userFieldSelection='key,value'
)}}
</div>
</div>
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.action.watch_categories.notification_level.label"}}</label>
</div>
<div class="setting-value">
{{combo-box
value=action.notification_level
content=availableNotificationLevels
onChange=(action (mut action.notification_level))
options=(hash
isDisabled=action.custom_title_enabled
none='admin.wizard.action.watch_categories.select_a_notification_level'
)}}
</div>
</div>
{{/if}}
{{#if showAdvanced}}
{{wizard-advanced-toggle showAdvanced=action.showAdvanced}}

Datei anzeigen

@ -1,24 +1,16 @@
{{#d-modal-body class="next-session-time-modal" title=title}}
<div class="date-time-card">
<div class="modal-date-time-set">
<div class="modal-date-area">
<label class="input-group-label">
{{i18n "admin.wizard.after_time_modal.date"}}
</label>
{{date-picker value=date containerId="date-container"}}
</div>
<div class="modal-time-area">
<label class="input-group-label">
{{i18n "admin.wizard.after_time_modal.time"}}
</label>
<input type="text" id="time-picker"/>
</div>
</div>
<div id="date-container"/>
</div>
{{date-time-input
date=bufferedDateTime
onChange=(action "dateTimeChanged")
showTime=true
clearable=true
}}
{{/d-modal-body}}
<div class="modal-footer">
{{d-button action="submit" class="btn-primary" label="admin.wizard.after_time_modal.done" disabled=submitDisabled}}
<a class="clear" {{action 'clear'}}>{{i18n 'admin.wizard.after_time_modal.clear'}}</a>
{{d-button
action="submit"
class="btn-primary"
label="admin.wizard.after_time_modal.done"
disabled=submitDisabled}}
</div>

Datei anzeigen

@ -91,10 +91,12 @@
//= require discourse/app/components/input-tip
//= require discourse/app/components/date-picker
//= require discourse/app/components/text-field
//= require discourse/app/components/d-textarea
//= require discourse/app/templates/components/conditional-loading-spinner
//= require discourse/app/templates/components/d-button
//= require discourse/app/templates/components/d-editor
//= require discourse/app/templates/components/date-picker
//= require discourse/app/templates/components/emoji-picker
//= require discourse/app/templates/components/popup-input-tip
//= require discourse/app/templates/category-tag-autocomplete

Datei anzeigen

@ -1,6 +1,7 @@
import { default as computed, observes } from 'discourse-common/utils/decorators';
import { renderAvatar } from 'discourse/helpers/user-avatar';
import userSearch from '../lib/user-search';
import I18n from "I18n";
const template = function(params) {
const options = params.options;

Datei anzeigen

@ -1,6 +1,6 @@
import ComposerEditor from 'discourse/components/composer-editor';
import { default as computed, on } from 'discourse-common/utils/decorators';
import { findRawTemplate } from "discourse/lib/raw-templates";
import { findRawTemplate } from "discourse-common/lib/raw-templates";
import { throttle } from "@ember/runloop";
import { scheduleOnce } from "@ember/runloop";
import { safariHacksDisabled } from "discourse/lib/utilities";

Datei anzeigen

@ -1,5 +1,6 @@
import getUrl from "discourse-common/lib/get-url";
import { getToken } from "wizard/lib/ajax";
import I18n from "I18n";
export default Ember.Component.extend({
classNames: ["wizard-field-upload"],

Datei anzeigen

@ -2,6 +2,7 @@
import computed from "discourse-common/utils/decorators";
import { siteDir, isRTL, isLTR } from "discourse/lib/text-direction";
import I18n from "I18n";
export default Ember.TextField.extend({
attributeBindings: ['autocorrect', 'autocapitalize', 'autofocus', 'maxLength', 'dir'],

Datei anzeigen

@ -23,7 +23,7 @@ export default {
const Store = requirejs("discourse/models/store").default;
const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers;
const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default;
const Site = requirejs("discourse/models/site").default;
const Site = requirejs("discourse/plugins/discourse-custom-wizard/wizard/models/site").default;
const RestAdapter = requirejs("discourse/adapters/rest").default;
Discourse.Model = EmberObject.extend();

Datei anzeigen

@ -0,0 +1,10 @@
import Site from "discourse/models/site";
export default Site.reopenClass({
// There is no site data actually loaded by the CW yet. This placeholder is
// needed by imported classes
createCurrent() {
const store = Discourse.__container__.lookup("service:store");
return store.createRecord("site", {});
},
})

Datei anzeigen

@ -1,3 +1,5 @@
import I18n from "I18n";
export default Ember.Route.extend({
model(params) {
const appModel = this.modelFor('custom');

Datei anzeigen

@ -0,0 +1,4 @@
{{date-picker
value=field.value
id=field.id
}}

Datei anzeigen

@ -481,54 +481,12 @@
min-height: 150px;
}
.next-session-time-modal {
text-align: center;
.date-time-card {
width: 270px;
padding: 10px 20px;
text-align: left;
}
.modal-date-time-set{
padding-top: 3px;
padding-bottom: 4px;
display: flex;
flex-direction: row;
.modal-date-area{
order: 1;
}
.modal-time-area{
order: 2;
margin-left: 10px;
.modal-time{
width: 127px;
}
}
}
.ui-timepicker-input {
width: 119px;
text-align: center;
}
.date-picker{
width: 121px;
}
.pika-single {
position: relative !important;
.pika-lendar {
border: 1px solid #eee;
padding: 14px;
margin: 0;
float: none;
width: auto;
}
.modal .modal-body.next-session-time-modal {
overflow: visible;
.picker-container {
position: absolute;
top: 30px;
}
}

Datei anzeigen

@ -163,6 +163,7 @@ en:
category: Category
group: Group
user_selector: User Selector
date: Date
connector:
and: "and"
@ -208,6 +209,18 @@ en:
label: "Update Profile"
setting: "Fields"
key: "field"
watch_categories:
label: "Watch Categories"
categories: "Categories"
mute_remainder: "Mute Remainder"
notification_level:
label: "Notification Level"
regular: "Normal"
watching: "Watching"
tracking: "Tracking"
watching_first_post: "Watching First Post"
muted: "Muted"
select_a_notification_level: "Select level"
post_builder:
checkbox: "Post Builder"
label: "Builder"

Datei anzeigen

@ -116,12 +116,18 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:post,
:post_builder,
:post_template,
:notification_level,
:api,
:api_endpoint,
:api_body,
title: mapped_params,
category: mapped_params,
tags: mapped_params,
custom_fields: mapped_params,
required: mapped_params,
recipient: mapped_params,
categories: mapped_params,
mute_remainder: mapped_params,
profile_updates: mapped_params,
group: mapped_params,
url: mapped_params

Datei anzeigen

@ -2,7 +2,7 @@ module Jobs
class SetAfterTimeWizard < ::Jobs::Base
def execute(args)
if SiteSetting.custom_wizard_enabled
wizard = CustomWizard::Wizard.find(args[:wizard_id])
wizard = CustomWizard::Wizard.create(args[:wizard_id])
if wizard && wizard.after_time
user_ids = []

Datei anzeigen

@ -97,19 +97,20 @@ class CustomWizard::Action
end
def update_profile
return unless (profile_updates = action['profile_updates']).length
params = {}
profile_updates.first[:pairs].each do |pair|
if allowed_profile_field?(pair['key'])
key = cast_profile_key(pair['key'])
value = cast_profile_value(mapper.map_field(pair['value'], pair['value_type']), pair['key'])
if user_field?(pair['key'])
params[:custom_fields] ||= {}
params[:custom_fields][key] = value
else
params[key.to_sym] = value
if (profile_updates = action['profile_updates'])
profile_updates.first[:pairs].each do |pair|
if allowed_profile_field?(pair['key'])
key = cast_profile_key(pair['key'])
value = cast_profile_value(mapper.map_field(pair['value'], pair['value_type']), pair['key'])
if user_field?(pair['key'])
params[:custom_fields] ||= {}
params[:custom_fields][key] = value
else
params[key.to_sym] = value
end
end
end
end
@ -133,6 +134,36 @@ class CustomWizard::Action
end
end
def watch_categories
watched_categories = CustomWizard::Mapper.new(
inputs: action['categories'],
data: data,
user: user
).perform
notification_level = action['notification_level']
if notification_level.blank?
log_error("Notifcation Level was not set! Exiting wizard action")
return
end
mute_remainder = CustomWizard::Mapper.new(
inputs: action['mute_remainder'],
data: data,
user: user
).perform
Category.all.each do |category|
if watched_categories.present? && watched_categories.include?(category.id.to_s)
CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[notification_level.to_sym], category.id)
elsif mute_remainder
CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:muted], category.id)
end
end
end
def send_to_api
api_body = nil

Datei anzeigen

@ -258,7 +258,9 @@ class CustomWizard::Builder
}
).perform
if content.present?
if content.present? &&
content[:result].present?
if content[:type] == 'association'
content[:result] = content[:result].map do |item|
{
@ -324,6 +326,10 @@ class CustomWizard::Builder
if type === 'upload' && value.present? && !validate_file_type(value, file_types)
updater.errors.add(id, I18n.t('wizard.field.invalid_file', label: label, types: file_types))
end
if type === 'date' && value.present? && !validate_date(value)
updater.errors.add(id, I18n.t('wizard.field.invalid_date'))
end
CustomWizard::Builder.field_validators.each do |validator|
if type === validator[:type]
@ -337,6 +343,15 @@ class CustomWizard::Builder
.map { |t| t.gsub('.', '') }
.include?(File.extname(value['original_filename'])[1..-1])
end
def validate_date(value)
begin
Date.parse(value)
true
rescue ArgumentError
false
end
end
def is_text_type(field)
['text', 'textarea'].include? field['type']

Datei anzeigen

@ -11,6 +11,7 @@ class CustomWizard::Field
min_length: nil
},
text_only: {},
date: {},
number: {},
checkbox: {},
url: {

Datei anzeigen

@ -1,7 +1,7 @@
class CustomWizard::Mapper
attr_accessor :inputs, :data, :user
USER_FIELDS = ['name', 'username', 'email', 'date_of_birth', 'title', 'locale', 'trust_level']
USER_FIELDS = ['name', 'username', 'email', 'date_of_birth', 'title', 'locale', 'trust_level', 'email_level']
PROFILE_FIELDS = ['location', 'website', 'bio_raw']
def self.user_fields
@ -188,7 +188,7 @@ class CustomWizard::Mapper
def map_user_field_options(value)
if value.include?(User::USER_FIELD_PREFIX)
if field = UserField.find(value.split('_').last)
if field = UserField.find_by(id: value.split('_').last)
field.user_field_options.map(&:value)
end
end

Datei anzeigen

@ -18,6 +18,7 @@ config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard"
if Rails.env.production?
config.assets.precompile += %w{
wizard-preload.js
wizard-custom-guest.js
wizard-custom-lib.js
wizard-custom.js

Datei anzeigen

@ -11,7 +11,8 @@
<%- if theme_ids.present? %>
<%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme) %>
<%- end %>
<%= preload_script "locales/#{I18n.locale}" %>
<%= preload_script "ember_jquery" %>
<%= preload_script "wizard-vendor" %>
<%= preload_script "wizard-application" %>
@ -19,7 +20,6 @@
<%= preload_script "wizard-custom" %>
<%= preload_script "wizard-plugin" %>
<%= preload_script "pretty-text-bundle" %>
<%= preload_script "locales/#{I18n.locale}" %>
<script src="<%= ExtraLocalesController.url("wizard") %>"></script>
<%= csrf_meta_tags %>