1
0
Fork 0

Commits vergleichen

..

Keine gemeinsamen Commits. „main“ und „add_date_time_inputs“ haben vollständig unterschiedliche Historien.

631 geänderte Dateien mit 9313 neuen und 76529 gelöschten Zeilen

Datei anzeigen

@ -1,3 +1 @@
3.1.999: 1f35b80f85e5fd1efb7f4851f0845700432febdc 2.5.0: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c
2.7.99: e07a57e398b6b1676ab42a7e34467556fca5416b
2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c

Datei anzeigen

@ -1,3 +0,0 @@
{
"extends": "eslint-config-discourse"
}

Datei anzeigen

@ -1,13 +0,0 @@
name: Discourse Plugin
on:
push:
branches:
- main
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
ci:
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1

Datei anzeigen

@ -1,44 +0,0 @@
name: Metadata
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout head repository
uses: actions/checkout@v2
- name: Store head version
run: |
sed -n -e 's/^.*version: /head_version=/p' plugin.rb >> $GITHUB_ENV
- name: Checkout base repository
uses: actions/checkout@v2
with:
ref: "${{ github.base_ref }}"
- name: Store base version
run: |
sed -n -e 's/^.*version: /base_version=/p' plugin.rb >> $GITHUB_ENV
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 14
- name: Install semver
run: npm install --include=dev
- name: Check version
uses: actions/github-script@v5
with:
script: |
const semver = require('semver');
const { head_version, base_version } = process.env;
if (semver.lte(head_version, base_version)) {
core.setFailed("Head version is equal to or lower than base version.");
}

9
.gitignore gevendort
Datei anzeigen

@ -1,8 +1 @@
coverage/* coverage
!coverage/.last_run.json
gems/*
.bundle/
auto_generated
.DS_Store
node_modules/
vendor/*

Datei anzeigen

@ -1 +0,0 @@
{}

Datei anzeigen

@ -1,8 +0,0 @@
inherit_gem:
rubocop-discourse: default.yml
RSpec/ContextWording:
Enabled: false
RSpec/DescribeClass:
Enabled: false

Datei anzeigen

@ -1,7 +0,0 @@
# frozen_string_literal: true
plugin = "discourse-custom-wizard"
SimpleCov.configure do
track_files "plugins/#{plugin}/**/*.rb"
add_filter { |src| !(src.filename =~ /(\/#{plugin}\/app\/|\/#{plugin}\/lib\/)/) }
end

Datei anzeigen

@ -1,4 +0,0 @@
module.exports = {
plugins: ["ember-template-lint-plugin-discourse"],
extends: "discourse:recommended",
};

Datei anzeigen

@ -1,10 +1,12 @@
# We want to use the KVM-based system, so require sudo
sudo: required
services: services:
- docker - docker
before_install: before_install:
- git clone --depth=1 https://github.com/discourse/discourse-plugin-ci - git clone --depth=1 https://github.com/discourse/discourse-plugin-ci
install: true install: true # Prevent travis doing bundle install
script: script:
- discourse-plugin-ci/script.sh - discourse-plugin-ci/script.sh

Datei anzeigen

@ -1,4 +1,4 @@
All code in this repository is Copyright 2023 by Angus McLeod. All code in this repository is Copyright 2018 by Angus McLeod.
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

Datei anzeigen

@ -1,7 +0,0 @@
# frozen_string_literal: true
source 'https://rubygems.org'
group :development do
gem 'rubocop-discourse'
end

Datei anzeigen

@ -1,39 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
json (2.6.2)
parallel (1.22.1)
parser (3.1.2.1)
ast (~> 2.4.1)
rainbow (3.1.1)
regexp_parser (2.6.0)
rexml (3.2.5)
rubocop (1.36.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.20.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.22.0)
parser (>= 3.1.1.0)
rubocop-discourse (3.0)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.13.2)
rubocop (~> 1.33)
ruby-progressbar (1.11.0)
unicode-display_width (2.3.0)
PLATFORMS
ruby
DEPENDENCIES
rubocop-discourse
BUNDLED WITH
2.2.16

Datei anzeigen

@ -1,27 +1,3 @@
# Discourse Custom Wizard Plugin # discourse-custom-wizard
The Custom Wizard Plugin lets you make forms for your Discourse forum. Better user onboarding, structured posting, data enrichment, automated actions and much more for your community. See further: https://meta.discourse.org/t/custom-wizard-plugin/73345
<img src="https://camo.githubusercontent.com/593432f1fc9658ffca104065668cc88fa21dffcd3002cb78ffd50c71f33a2523/68747470733a2f2f706176696c696f6e2d6173736574732e6e7963332e63646e2e6469676974616c6f6365616e7370616365732e636f6d2f706c7567696e732f77697a6172642d7265706f7369746f72792d62616e6e65722e706e67" alt="" data-canonical-src="https://pavilion-assets.nyc3.cdn.digitaloceanspaces.com/plugins/wizard-repository-banner.png" style="max-width: 100%;" width="400">
👋 Looking to report an issue? We're managing issues for this plugin using our [bug report wizard](https://coop.pavilion.tech/w/bug-report).
## Install
If you're not sure how to install a plugin in Discourse, please follow the [plugin installation guide](https://meta.discourse.org/t/install-a-plugin/19157) or contact your Discourse hosting provider.
## Documentation
[Read the full documentation here](https://coop.pavilion.tech/c/82), or go directly to the relevant section
- [Wizard Administration](https://coop.pavilion.tech/t/1602)
- [Wizard Settings](https://coop.pavilion.tech/t/1614)
- [Step Settings](https://coop.pavilion.tech/t/1735)
- [Field Settings](https://coop.pavilion.tech/t/1580)
- [Conditional Settings](https://coop.pavilion.tech/t/1673)
- [Field Interpolation](https://coop.pavilion.tech/t/1557)
- [Handling Dates and Times](https://coop.pavilion.tech/t/1708)
## Support
- [Report an issue](https://coop.pavilion.tech/w/bug-report)

Datei anzeigen

@ -1,7 +0,0 @@
# Security Policy
The security of Discourse plugins are premised on the security of [Discourse](https://github.com/discourse/discourse). Please first consider whether a security issue is best reported and handled by the Discourse team. You can view the Discourse security policy [here](https://github.com/discourse/discourse/security/policy).
## Reporting a Vulnerability
If you find a security vulnerability that is specific to this plugin, please report it to development@pavilion.tech. Security issues always take precedence over all other work. All commits specific to security are prefixed with SECURITY.

Datei anzeigen

@ -1,30 +0,0 @@
# frozen_string_literal: true
class CustomWizard::AdminController < ::Admin::AdminController
before_action :ensure_admin
def index
subcription = CustomWizard::Subscription.new
render_json_dump(
subscribed: subcription.subscribed?,
subscription_type: subcription.type,
subscription_attributes: CustomWizard::Subscription.attributes,
subscription_client_installed: CustomWizard::Subscription.client_installed?
)
end
private
def find_wizard
params.require(:wizard_id)
@wizard = CustomWizard::Wizard.create(params[:wizard_id].underscore)
raise Discourse::InvalidParameters.new(:wizard_id) unless @wizard
end
def custom_field_list
serialize_data(CustomWizard::CustomField.full_list, CustomWizard::CustomFieldSerializer)
end
def render_error(message)
render json: failed_json.merge(error: message)
end
end

Datei anzeigen

@ -1,66 +0,0 @@
# frozen_string_literal: true
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
def index
render_json_dump(
custom_fields: custom_field_list
)
end
def update
errors = []
field_id = nil
field_data = {}
if saved_field = CustomWizard::CustomField.find(field_params[:id].to_i)
CustomWizard::CustomField::ATTRS.each do |attr|
field_data[attr] = saved_field.send(attr)
end
field_id = saved_field.id
end
CustomWizard::CustomField::ATTRS.each do |attr|
field_data[attr] = field_params[attr]
end
field = CustomWizard::CustomField.new(field_id, field_data)
PluginStoreRow.transaction do
unless field.save
field_errors = field.errors.any? ?
field.errors.full_messages.join("\n\n") :
I18n.t("wizard.custom_field.error.save_default", name: field.name)
errors << field_errors
raise ActiveRecord::Rollback.new
end
end
if errors.any?
render json: failed_json.merge(messages: errors)
else
render json: success_json
end
end
def destroy
params.require(:name)
if CustomWizard::CustomField.destroy(params[:name])
render json: success_json
else
render json: failed_json
end
end
private
def field_params
params.required(:custom_field)
.permit(
:id,
:name,
:klass,
:type,
serializers: []
)
end
end

Datei anzeigen

@ -1,44 +0,0 @@
# frozen_string_literal: true
class CustomWizard::AdminLogsController < CustomWizard::AdminController
before_action :find_wizard, except: [:index]
def index
render json: ActiveModel::ArraySerializer.new(
CustomWizard::Wizard.list(current_user),
each_serializer: CustomWizard::BasicWizardSerializer
)
end
def show
render_json_dump(
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false),
logs: ActiveModel::ArraySerializer.new(
log_list.logs,
each_serializer: CustomWizard::LogSerializer
),
total: log_list.total
)
end
protected
def log_list
@log_list ||= begin
list = CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i, params[:wizard_id])
if list.logs.any? && (usernames = list.logs.map(&:username)).present?
user_map = User.where(username: usernames)
.reduce({}) do |result, user|
result[user.username] = user
result
end
list.logs.each do |log_item|
log_item.user = user_map[log_item.username]
end
end
list
end
end
end

Datei anzeigen

@ -1,125 +0,0 @@
# frozen_string_literal: true
class CustomWizard::AdminManagerController < CustomWizard::AdminController
skip_before_action :check_xhr, only: [:export]
before_action :get_wizard_ids, except: [:import]
def export
templates = []
@wizard_ids.each do |wizard_id|
if template = CustomWizard::Template.find(wizard_id)
templates.push(template)
end
end
if templates.empty?
return render_error(I18n.t('wizard.export.error.invalid_wizards'))
end
basename = SiteSetting.title.parameterize || 'discourse'
time = Time.now.to_i
filename = "#{basename}-wizards-#{time}.json"
send_data templates.to_json,
type: "application/json",
disposition: 'attachment',
filename: filename
end
def import
file = File.read(params['file'].tempfile)
if file.nil?
return render_error(I18n.t('wizard.export.error.no_file'))
end
file_size = file.size
max_file_size = 512 * 1024
if max_file_size < file_size
return render_error(I18n.t('wizard.import.error.file_large'))
end
begin
template_json = JSON.parse(file)
rescue JSON::ParserError
return render_error(I18n.t('wizard.import.error.invalid_json'))
end
imported = []
failures = []
templates = template_json.is_a?(Array) ? template_json : [template_json]
templates.each do |raw_template|
template = CustomWizard::Template.new(raw_template)
template.save(skip_jobs: true, create: true)
if template.errors.any?
failures.push(
id: template.data['id'],
messages: template.errors.full_messages.join(', ')
)
else
imported.push(
id: template.data['id'],
name: template.data['name']
)
end
end
render json: success_json.merge(
imported: imported,
failures: failures
)
end
def destroy
destroyed = []
failures = []
@wizard_ids.each do |wizard_id|
template = CustomWizard::Template.find(wizard_id)
if template && CustomWizard::Template.remove(wizard_id)
destroyed.push(
id: wizard_id,
name: template['name']
)
else
failures.push(
id: wizard_id,
messages: I18n.t("wizard.destroy.error.#{template ? 'default' : 'no_template'}")
)
end
end
render json: success_json.merge(
destroyed: destroyed,
failures: failures
)
end
private
def get_wizard_ids
if params['wizard_ids'].blank?
return render_error(I18n.t('wizard.export.error.select_one'))
end
wizard_ids = []
params['wizard_ids'].each do |wizard_id|
begin
wizard_ids.push(wizard_id.underscore)
rescue
#
end
end
if wizard_ids.empty?
return render_error(I18n.t('wizard.export.error.invalid_wizards'))
end
@wizard_ids = wizard_ids
end
end

Datei anzeigen

@ -1,41 +0,0 @@
# frozen_string_literal: true
class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
skip_before_action :preload_json, :check_xhr, only: [:download]
before_action :find_wizard, except: [:index]
def index
render json: ActiveModel::ArraySerializer.new(
CustomWizard::Wizard.list(current_user),
each_serializer: CustomWizard::BasicWizardSerializer
)
end
def show
render_json_dump(
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false),
submissions: ActiveModel::ArraySerializer.new(
submission_list.submissions,
each_serializer: CustomWizard::SubmissionSerializer
),
total: submission_list.total
)
end
def download
content = ActiveModel::ArraySerializer.new(
CustomWizard::Submission.list(@wizard).submissions,
each_serializer: CustomWizard::SubmissionSerializer
)
send_data content.to_json,
filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json",
content_type: "application/json",
disposition: "attachment"
end
protected
def submission_list
CustomWizard::Submission.list(@wizard, page: params[:page].to_i)
end
end

Datei anzeigen

@ -1,18 +0,0 @@
# frozen_string_literal: true
class CustomWizard::RealtimeValidationsController < ::ApplicationController
def validate
klass_str = "CustomWizard::RealtimeValidation::#{validation_params[:type].camelize}"
result = klass_str.constantize.new(current_user).perform(validation_params)
render_serialized(result.items, "#{klass_str}Serializer".constantize, result.serializer_opts)
end
private
def validation_params
params.require(:type)
settings = ::CustomWizard::RealtimeValidation.types[params[:type].to_sym]
params.require(settings[:required_params]) if settings[:required_params].present?
params
end
end

Datei anzeigen

@ -1,113 +0,0 @@
# frozen_string_literal: true
class CustomWizard::StepsController < ::CustomWizard::WizardClientController
before_action :ensure_can_update
def update
update = update_params.to_h
update[:fields] = {}
if params[:fields]
field_ids = @builder.wizard.field_ids
params[:fields].each do |k, v|
update[:fields][k] = v if field_ids.include? k
end
end
@builder.build
updater = @builder.wizard.create_updater(update[:step_id], update[:fields])
updater.update
@result = updater.result
if updater.success?
wizard_id = update_params[:wizard_id]
builder = CustomWizard::Builder.new(wizard_id, current_user, guest_id)
@wizard = builder.build(force: true)
current_step = @wizard.find_step(update[:step_id])
current_submission = @wizard.current_submission
result = {}
if current_step.conditional_final_step && !current_step.last_step
current_step.force_final = true
end
if current_step.final?
builder.template.actions.each do |action_template|
if action_template['run_after'] === 'wizard_completion'
action_result = CustomWizard::Action.new(
action: action_template,
wizard: @wizard,
submission: current_submission
).perform
if action_result.success?
current_submission = action_result.submission
end
end
end
current_submission.save
if redirect = get_redirect
updater.result[:redirect_on_complete] = redirect
end
@wizard.cleanup_on_complete!
result[:final] = true
else
current_submission.save
result[:final] = false
result[:next_step_id] = current_step.next.id
end
result.merge!(updater.result) if updater.result.present?
result[:refresh_required] = true if updater.refresh_required?
result[:wizard] = ::CustomWizard::WizardSerializer.new(
@wizard,
scope: Guardian.new(current_user),
root: false
).as_json
render json: result
else
errors = []
updater.errors.messages.each do |field, msg|
errors << { field: field, description: msg.join(',') }
end
render json: { errors: errors }, status: 422
end
end
private
def ensure_can_update
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?
@step_template = @builder.template.steps.select do |s|
s['id'] == update_params[:step_id]
end.first
raise Discourse::InvalidParameters.new(:step_id) if !@step_template
raise Discourse::InvalidAccess.new if !@builder.check_condition(@step_template)
end
def update_params
@update_params || begin
params.require(:step_id)
params.require(:wizard_id)
params.permit(:wizard_id, :step_id).transform_values { |v| v.underscore }
end
end
def get_redirect
return @result[:redirect_on_next] if @result[:redirect_on_next].present?
submission = @wizard.current_submission
return nil unless submission.present?
## route_to set by actions, redirect_on_complete set by actions, redirect_to set at wizard entry
submission.route_to || submission.redirect_on_complete || submission.redirect_to
end
end

Datei anzeigen

@ -1,39 +0,0 @@
# frozen_string_literal: true
class CustomWizard::WizardController < ::CustomWizard::WizardClientController
def show
if wizard.present?
render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200
else
render json: { error: I18n.t('wizard.none') }
end
end
def skip
params.require(:wizard_id)
if wizard.required && !wizard.completed? && wizard.permitted?
return render json: { error: I18n.t('wizard.no_skip') }
end
result = { success: 'OK' }
if current_user && wizard.can_access?
if redirect_to = wizard.current_submission&.redirect_to
result.merge!(redirect_to: redirect_to)
end
wizard.cleanup_on_skip!
end
render json: result
end
protected
def wizard
@wizard ||= begin
return nil unless @builder.present?
@builder.build({ reset: params[:reset] }, params)
end
end
end

Datei anzeigen

@ -1,23 +0,0 @@
# frozen_string_literal: true
class CustomWizard::WizardClientController < ::ApplicationController
before_action :ensure_plugin_enabled
before_action :set_builder
private
def ensure_plugin_enabled
unless SiteSetting.custom_wizard_enabled
redirect_to path("/")
end
end
def guest_id
return nil if current_user.present?
cookies[:custom_wizard_guest_id] ||= CustomWizard::Wizard.generate_guest_id
cookies[:custom_wizard_guest_id]
end
def set_builder
@builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user, guest_id)
end
end

Datei anzeigen

@ -1,4 +0,0 @@
# frozen_string_literal: true
class CustomWizard::CustomFieldSerializer < ApplicationSerializer
attributes :id, :klass, :name, :type, :serializers
end

Datei anzeigen

@ -1,10 +0,0 @@
# frozen_string_literal: true
class CustomWizard::LogSerializer < ApplicationSerializer
attributes :date,
:action,
:username,
:message
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
end

Datei anzeigen

@ -1,3 +0,0 @@
# frozen_string_literal: true
class ::CustomWizard::RealtimeValidation::SimilarTopicsSerializer < ::SimilarTopicSerializer
end

Datei anzeigen

@ -1,35 +0,0 @@
# frozen_string_literal: true
class CustomWizard::SubmissionSerializer < ApplicationSerializer
attributes :id,
:fields,
:submitted_at,
:user
def include_user?
object.wizard.user.present?
end
def user
::BasicUserSerializer.new(object.wizard.user, root: false).as_json
end
def fields
@fields ||= begin
result = {}
object.wizard.template['steps'].each do |step|
step['fields'].each do |field|
if value = object.fields[field['id']]
result[field['id']] = {
value: value,
type: field['type'],
label: field['label']
}
end
end
end
result
end
end
end

Datei anzeigen

@ -1,138 +0,0 @@
# frozen_string_literal: true
class CustomWizard::FieldSerializer < ::ApplicationSerializer
attributes :id,
:index,
:type,
:required,
:value,
:label,
:placeholder,
:description,
:image,
:file_types,
:format,
:limit,
:property,
:content,
:tag_groups,
:can_create_tag,
:validations,
:max_length,
:char_counter,
:preview_template
def id
object.id
end
def index
object.index
end
def type
object.type
end
def required
object.required
end
def value
object.value
end
def label
I18n.t("#{i18n_key}.label", default: object.label, base_url: Discourse.base_url)
end
def include_label?
label.present?
end
def description
I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url)
end
def include_description?
description.present?
end
def placeholder
I18n.t("#{i18n_key}.placeholder", default: object.placeholder)
end
def include_placeholder?
placeholder.present?
end
def image
object.image
end
def include_image?
object.image.present?
end
def file_types
object.file_types
end
def format
object.format
end
def limit
object.limit
end
def property
object.property
end
def content
object.content
end
def tag_groups
object.tag_groups
end
def can_create_tag
object.can_create_tag
end
def validations
validations = {}
object.validations&.each do |type, props|
next unless props["status"]
validations[props["position"]] ||= {}
validations[props["position"]][type] = props.merge CustomWizard::RealtimeValidation.types[type.to_sym]
end
validations
end
def max_length
object.max_length
end
def char_counter
object.char_counter
end
def preview_template
object.preview_template
end
protected
def i18n_key
@i18n_key ||= "#{object.step.wizard.id}.#{object.step.id}.#{object.id}".underscore
end
def subscribed?
@subscribed ||= CustomWizard::Subscription.subscribed?
end
end

Datei anzeigen

@ -1,83 +0,0 @@
# frozen_string_literal: true
class CustomWizard::StepSerializer < ::ApplicationSerializer
attributes :id,
:index,
:next,
:previous,
:description,
:title,
:banner,
:permitted,
:permitted_message,
:final
has_many :fields, serializer: ::CustomWizard::FieldSerializer, embed: :objects
def id
object.id
end
def index
object.index
end
def next
object.next.id if object.next.present?
end
def include_next?
object.next.present?
end
def previous
object.previous.id if object.previous.present?
end
def include_previous?
object.previous.present?
end
def title
I18n.t("#{i18n_key}.title", default: object.title, base_url: Discourse.base_url)
end
def include_title?
title.present?
end
def description
I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url)
end
def include_description?
description.present?
end
def banner
object.banner
end
def include_banner?
object.banner.present?
end
def permitted
object.permitted
end
def permitted_message
object.permitted_message
end
def final
object.final?
end
protected
def i18n_key
@i18n_key ||= "#{object.wizard.id}.#{object.id}".underscore
end
end

Datei anzeigen

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Custom Wizard QUnit Test Runner</title>
<%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %>
<%= discourse_stylesheet_link_tag :wizard, theme_id: nil %>
<%= discourse_stylesheet_link_tag :wizard_custom %>
<%= preload_script "locales/en" %>
<%= preload_script "ember_jquery" %>
<%= preload_script "wizard-vendor" %>
<%= preload_script "wizard-custom" %>
<%= preload_script "wizard-raw-templates" %>
<%= preload_script "wizard-plugin" %>
<%= preload_script "pretty-text-bundle" %>
<%= preload_script "wizard-qunit" %>
<%= csrf_meta_tags %>
<script src="<%= ExtraLocalesController.url("wizard") %>"></script>
<%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
<meta name="discourse_theme_id" content="">
<meta name="discourse-base-uri" content="<%= Discourse.base_path %>">
</head>
<body class="custom-wizard">
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>

Datei anzeigen

@ -1,126 +0,0 @@
import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed";
import I18n from "I18n";
export default Component.extend({
tagName: "tr",
topicSerializers: ["topic_view", "topic_list_item"],
postSerializers: ["post"],
groupSerializers: ["basic_group"],
categorySerializers: ["basic_category"],
showInputs: or("field.new", "field.edit"),
classNames: ["custom-field-input"],
loading: or("saving", "destroying"),
destroyDisabled: alias("loading"),
closeDisabled: alias("loading"),
isExternal: equal("field.id", "external"),
didInsertElement() {
this.set("originalField", JSON.parse(JSON.stringify(this.field)));
},
@discourseComputed("field.klass")
serializerContent(klass) {
const serializers = this.get(`${klass}Serializers`);
if (serializers) {
return serializers.reduce((result, key) => {
result.push({
id: key,
name: I18n.t(`admin.wizard.custom_field.serializers.${key}`),
});
return result;
}, []);
}
},
@observes("field.klass")
clearSerializersWhenClassChanges() {
this.set("field.serializers", null);
},
compareArrays(array1, array2) {
return (
array1.length === array2.length &&
array1.every((value, index) => {
return value === array2[index];
})
);
},
@discourseComputed(
"saving",
"isExternal",
"field.name",
"field.klass",
"field.type",
"field.serializers"
)
saveDisabled(saving, isExternal) {
if (saving || isExternal) {
return true;
}
const originalField = this.originalField;
if (!originalField) {
return false;
}
return ["name", "klass", "type", "serializers"].every((attr) => {
let current = this.get(attr);
let original = originalField[attr];
if (!current) {
return false;
}
if (attr === "serializers") {
return this.compareArrays(current, original);
} else {
return current === original;
}
});
},
actions: {
edit() {
this.set("field.edit", true);
},
close() {
if (this.field.edit) {
this.set("field.edit", false);
}
},
destroy() {
this.set("destroying", true);
this.removeField(this.field);
},
save() {
this.set("saving", true);
const field = this.field;
let data = {
id: field.id,
klass: field.klass,
type: field.type,
serializers: field.serializers,
name: field.name,
};
this.saveField(data).then((result) => {
this.set("saving", false);
if (result.success) {
this.set("field.edit", false);
} else {
this.set("saveIcon", "times");
}
setTimeout(() => this.set("saveIcon", null), 10000);
});
},
},
});

Datei anzeigen

@ -1,146 +0,0 @@
import {
default as computed,
observes,
} from "discourse-common/utils/decorators";
import { renderAvatar } from "discourse/helpers/user-avatar";
import userSearch from "discourse/lib/user-search";
import I18n from "I18n";
import Handlebars from "handlebars";
import { isEmpty } from "@ember/utils";
import TextField from "discourse/components/text-field";
const template = function (params) {
const options = params.options;
let html = "<div class='autocomplete'>";
if (options.users) {
html += "<ul>";
options.users.forEach((u) => {
html += `<li><a href title="${u.name}">`;
html += renderAvatar(u, { imageSize: "tiny" });
html += `<span class='username'>${u.username}</span>`;
if (u.name) {
html += `<span class='name'>${u.name}</span>`;
}
html += `</a></li>`;
});
html += "</ul>";
}
html += "</div>";
return new Handlebars.SafeString(html).string;
};
export default TextField.extend({
attributeBindings: ["autofocus", "maxLength"],
autocorrect: false,
autocapitalize: false,
name: "user-selector",
id: "custom-member-selector",
@computed("placeholderKey")
placeholder(placeholderKey) {
return placeholderKey ? I18n.t(placeholderKey) : "";
},
@observes("usernames")
_update() {
if (this.get("canReceiveUpdates") === "true") {
this.didInsertElement({ updateData: true });
}
},
didInsertElement(opts) {
this._super();
let self = this,
selected = [],
groups = [],
includeMentionableGroups =
this.get("includeMentionableGroups") === "true",
includeMessageableGroups =
this.get("includeMessageableGroups") === "true",
includeGroups = this.get("includeGroups") === "true",
allowedUsers = this.get("allowedUsers") === "true";
function excludedUsernames() {
// hack works around some issues with allowAny eventing
const usernames = self.get("single") ? [] : selected;
return usernames;
}
$(this.element)
.val(this.get("usernames"))
.autocomplete({
template,
disabled: this.get("disabled"),
single: this.get("single"),
allowAny: this.get("allowAny"),
updateData: opts && opts.updateData ? opts.updateData : false,
dataSource(term) {
const termRegex = /[^a-zA-Z0-9_\-\.@\+]/;
let results = userSearch({
term: term.replace(termRegex, ""),
topicId: self.get("topicId"),
exclude: excludedUsernames(),
includeGroups,
allowedUsers,
includeMentionableGroups,
includeMessageableGroups,
});
return results;
},
transformComplete(v) {
if (v.username || v.name) {
if (!v.username) {
groups.push(v.name);
}
return v.username || v.name;
} else {
let excludes = excludedUsernames();
return v.usernames.filter(function (item) {
return excludes.indexOf(item) === -1;
});
}
},
onChangeItems(items) {
let hasGroups = false;
items = items.map(function (i) {
if (groups.indexOf(i) > -1) {
hasGroups = true;
}
return i.username ? i.username : i;
});
self.set("usernames", items.join(","));
self.set("hasGroups", hasGroups);
selected = items;
if (self.get("onChangeCallback")) {
self.sendAction("onChangeCallback");
}
},
reverseTransform(i) {
return { username: i };
},
});
},
willDestroyElement() {
this._super();
$(this.element).autocomplete("destroy");
},
// THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT
@observes("usernames")
_clearInput: function () {
if (arguments.length > 1) {
if (isEmpty(this.get("usernames"))) {
$(this.element).parent().find("a").click();
}
}
},
});

Datei anzeigen

@ -1,18 +0,0 @@
import CategorySelector from "select-kit/components/category-selector";
import { computed } from "@ember/object";
import { makeArray } from "discourse-common/lib/helpers";
export default CategorySelector.extend({
classNames: ["category-selector", "wizard-category-selector"],
content: computed(
"categories.[]",
"blacklist.[]",
"whitelist.[]",
function () {
return this._super().filter((category) => {
const whitelist = makeArray(this.whitelist);
return !whitelist.length || whitelist.indexOf(category.id) > -1;
});
}
),
});

Datei anzeigen

@ -1,213 +0,0 @@
import ComposerEditor from "discourse/components/composer-editor";
import {
bind,
default as discourseComputed,
on,
} from "discourse-common/utils/decorators";
import { findRawTemplate } from "discourse-common/lib/raw-templates";
import { scheduleOnce } from "@ember/runloop";
import { caretPosition, inCodeBlock } from "discourse/lib/utilities";
import highlightSyntax from "discourse/lib/highlight-syntax";
import { alias } from "@ember/object/computed";
import Site from "discourse/models/site";
import { uploadIcon } from "discourse/lib/uploads";
import { dasherize } from "@ember/string";
import InsertHyperlink from "discourse/components/modal/insert-hyperlink";
import { inject as service } from "@ember/service";
const IMAGE_MARKDOWN_REGEX =
/!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
export default ComposerEditor.extend({
modal: service(),
classNameBindings: ["fieldClass"],
allowUpload: true,
showLink: false,
topic: null,
showToolbar: true,
focusTarget: "reply",
canWhisper: false,
lastValidatedAt: "lastValidatedAt",
popupMenuOptions: [],
draftStatus: "null",
replyPlaceholder: alias("field.translatedPlaceholder"),
wizardEventFieldId: null,
composerEventPrefix: "wizard-editor",
@on("didInsertElement")
_composerEditorInit() {
const $input = $(this.element.querySelector(".d-editor-input"));
if (this.siteSettings.enable_mentions) {
$input.autocomplete({
template: findRawTemplate("user-selector-autocomplete"),
dataSource: (term) => this._userSearchTerm.call(this, term),
key: "@",
transformComplete: (v) => v.username || v.name,
afterComplete: (value) => {
this.composer.set("reply", value);
scheduleOnce("afterRender", () => $input.blur().focus());
},
triggerRule: (textarea) =>
!inCodeBlock(textarea.value, caretPosition(textarea)),
});
}
const siteSettings = this.siteSettings;
if (siteSettings.mentionables_enabled) {
Site.currentProp("mentionable_items", this.wizard.mentionable_items);
const { SEPARATOR } = requirejs(
"discourse/plugins/discourse-mentionables/discourse/lib/discourse-markdown/mentionable-items"
);
const { searchMentionableItem } = requirejs(
"discourse/plugins/discourse-mentionables/discourse/lib/mentionable-item-search"
);
$input.autocomplete({
template: findRawTemplate("javascripts/mentionable-item-autocomplete"),
key: SEPARATOR,
afterComplete: (value) => {
this.composer.set("reply", value);
scheduleOnce("afterRender", () => $input.blur().focus());
},
transformComplete: (item) => item.model.slug,
dataSource: (term) =>
term.match(/\s/) ? null : searchMentionableItem(term, siteSettings),
triggerRule: (textarea) =>
!inCodeBlock(textarea.value, caretPosition(textarea)),
});
}
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
this._bindUploadTarget();
const field = this.field;
this.editorInputClass = `.${dasherize(field.type)}-${dasherize(
field.id
)} .d-editor-input`;
this._uppyInstance.on("file-added", () => {
this.session.set("wizardEventFieldId", field.id);
});
},
@discourseComputed("field.id")
fileUploadElementId(fieldId) {
return `file-uploader-${dasherize(fieldId)}`;
},
@discourseComputed
allowedFileTypes() {
return this.siteSettings.authorized_extensions
.split("|")
.map((ext) => "." + ext)
.join(",");
},
@discourseComputed()
uploadIcon() {
return uploadIcon(false, this.siteSettings);
},
@bind
_handleImageDeleteButtonClick(event) {
if (!event.target.classList.contains("delete-image-button")) {
return;
}
const index = parseInt(
event.target.closest(".button-wrapper").dataset.imageIndex,
10
);
const matchingPlaceholder =
this.get("composer.reply").match(IMAGE_MARKDOWN_REGEX);
this.session.set("wizardEventFieldId", this.field.id);
this.appEvents.trigger(
"composer:replace-text",
matchingPlaceholder[index],
"",
{ regex: IMAGE_MARKDOWN_REGEX, index }
);
},
actions: {
extraButtons(toolbar) {
const component = this;
if (this.allowUpload && this.uploadIcon) {
toolbar.addButton({
id: "upload",
group: "insertions",
icon: this.uploadIcon,
title: "upload",
sendAction: (event) => component.send("showUploadModal", event),
});
}
toolbar.addButton({
id: "link",
group: "insertions",
shortcut: "K",
trimLeading: true,
unshift: true,
sendAction: (event) => component.send("showLinkModal", event),
});
if (this.siteSettings.mentionables_enabled) {
const { SEPARATOR } = requirejs(
"discourse/plugins/discourse-mentionables/discourse/lib/discourse-markdown/mentionable-items"
);
toolbar.addButton({
id: "insert-mentionable",
group: "extras",
icon: this.siteSettings.mentionables_composer_button_icon,
title: "mentionables.composer.insert.title",
perform: () => {
this.appEvents.trigger("wizard-editor:insert-text", {
fieldId: this.field.id,
text: SEPARATOR,
});
const $textarea = $(
document.querySelector(
`.composer-field.${this.field.id} textarea.d-editor-input`
)
);
$textarea.trigger("keyup.autocomplete");
},
});
}
},
previewUpdated(preview) {
highlightSyntax(preview, this.siteSettings, this.session);
if (this.siteSettings.mentionables_enabled) {
const { linkSeenMentionableItems } = requirejs(
"discourse/plugins/discourse-mentionables/discourse/lib/mentionable-items-preview-styling"
);
linkSeenMentionableItems(preview, this.siteSettings);
}
this._super(...arguments);
},
showLinkModal(toolbarEvent) {
let linkText = "";
this._lastSel = toolbarEvent.selected;
if (this._lastSel) {
linkText = this._lastSel.value;
}
this.modal.show(InsertHyperlink, {
model: { linkText, toolbarEvent },
});
},
showUploadModal() {
this.session.set("wizardEventFieldId", this.field.id);
document.getElementById(this.fileUploadElementId).click();
},
},
});

Datei anzeigen

@ -1,17 +0,0 @@
import DateInput from "discourse/components/date-input";
import discourseComputed from "discourse-common/utils/decorators";
export default DateInput.extend({
useNativePicker: false,
classNameBindings: ["fieldClass"],
@discourseComputed()
placeholder() {
return this.format;
},
_opts() {
return {
format: this.format || "LL",
};
},
});

Datei anzeigen

@ -1,16 +0,0 @@
import DateTimeInput from "discourse/components/date-time-input";
import discourseComputed from "discourse-common/utils/decorators";
export default DateTimeInput.extend({
classNameBindings: ["fieldClass"],
@discourseComputed("timeFirst", "tabindex")
timeTabindex(timeFirst, tabindex) {
return timeFirst ? tabindex : tabindex + 1;
},
@discourseComputed("timeFirst", "tabindex")
dateTabindex(timeFirst, tabindex) {
return timeFirst ? tabindex + 1 : tabindex;
},
});

Datei anzeigen

@ -1,42 +0,0 @@
import { observes } from "discourse-common/utils/decorators";
import Category from "discourse/models/category";
import Component from "@ember/component";
export default Component.extend({
didInsertElement() {
const property = this.field.property || "id";
const value = this.field.value;
if (value) {
this.set(
"categories",
[...value].reduce((result, v) => {
let val =
property === "id" ? Category.findById(v) : Category.findBySlug(v);
if (val) {
result.push(val);
}
return result;
}, [])
);
}
},
@observes("categories")
setValue() {
const categories = (this.categories || []).filter((c) => !!c);
const property = this.field.property || "id";
if (categories.length) {
this.set(
"field.value",
categories.reduce((result, c) => {
if (c && c[property]) {
result.push(c[property]);
}
return result;
}, [])
);
}
},
});

Datei anzeigen

@ -1,3 +0,0 @@
import Component from "@ember/component";
export default Component.extend({});

Datei anzeigen

@ -1,49 +0,0 @@
import Component from "@ember/component";
import { loadOneboxes } from "discourse/lib/load-oneboxes";
import { schedule } from "@ember/runloop";
import discourseDebounce from "discourse-common/lib/debounce";
import { resolveAllShortUrls } from "pretty-text/upload-short-url";
import { ajax } from "discourse/lib/ajax";
import { on } from "discourse-common/utils/decorators";
export default Component.extend({
@on("init")
updatePreview() {
if (this.isDestroyed) {
return;
}
schedule("afterRender", () => {
if (this._state !== "inDOM" || !this.element) {
return;
}
const $preview = $(this.element);
if ($preview.length === 0) {
return;
}
this.previewUpdated($preview);
});
},
previewUpdated($preview) {
// Paint oneboxes
const paintFunc = () => {
loadOneboxes(
$preview[0],
ajax,
null,
null,
this.siteSettings.max_oneboxes_per_post,
true // refresh on every load
);
};
discourseDebounce(this, paintFunc, 450);
// Short upload urls need resolution
resolveAllShortUrls(ajax, this.siteSettings, $preview[0]);
},
});

Datei anzeigen

@ -1,48 +0,0 @@
import {
default as computed,
observes,
} from "discourse-common/utils/decorators";
import EmberObject from "@ember/object";
import Component from "@ember/component";
export default Component.extend({
showPreview: false,
classNameBindings: [
":wizard-field-composer",
"showPreview:show-preview:hide-preview",
],
didInsertElement() {
this.set(
"composer",
EmberObject.create({
loading: false,
reply: this.get("field.value") || "",
})
);
},
@observes("composer.reply")
setField() {
this.set("field.value", this.get("composer.reply"));
},
@computed("showPreview")
togglePreviewLabel(showPreview) {
return showPreview
? "wizard_composer.hide_preview"
: "wizard_composer.show_preview";
},
actions: {
togglePreview() {
this.toggleProperty("showPreview");
},
groupsMentioned() {},
afterRefresh() {},
cannotSeeMention() {},
importQuote() {},
showUploadSelector() {},
},
});

Datei anzeigen

@ -1,15 +0,0 @@
import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
@observes("dateTime")
setValue() {
this.set("field.value", this.dateTime.format(this.field.format));
},
actions: {
onChange(value) {
this.set("dateTime", moment(value));
},
},
});

Datei anzeigen

@ -1,15 +0,0 @@
import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
@observes("date")
setValue() {
this.set("field.value", this.date.format(this.field.format));
},
actions: {
onChange(value) {
this.set("date", moment(value));
},
},
});

Datei anzeigen

@ -1,13 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
keyPress(e) {
e.stopPropagation();
},
actions: {
onChangeValue(value) {
this.set("field.value", value);
},
},
});

Datei anzeigen

@ -1,3 +0,0 @@
import Component from "@ember/component";
export default Component.extend({});

Datei anzeigen

@ -1,3 +0,0 @@
import Component from "@ember/component";
export default Component.extend({});

Datei anzeigen

@ -1,3 +0,0 @@
import Component from "@ember/component";
export default Component.extend({});

Datei anzeigen

@ -1,7 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
keyPress(e) {
e.stopPropagation();
},
});

Datei anzeigen

@ -1,7 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
keyPress(e) {
e.stopPropagation();
},
});

Datei anzeigen

@ -1,23 +0,0 @@
import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
classNameBindings: ["fieldClass"],
@observes("time")
setValue() {
this.set("field.value", this.time.format(this.field.format));
},
actions: {
onChange(value) {
this.set(
"time",
moment({
hours: value.hours,
minutes: value.minutes,
})
);
},
},
});

Datei anzeigen

@ -1,27 +0,0 @@
import UppyUploadMixin from "discourse/mixins/uppy-upload";
import Component from "@ember/component";
import { computed } from "@ember/object";
export default Component.extend(UppyUploadMixin, {
classNames: ["wizard-field-upload"],
classNameBindings: ["isImage", "fieldClass"],
uploading: false,
type: computed(function () {
return `wizard_${this.field.id}`;
}),
id: computed(function () {
return `wizard_field_upload_${this.field.id}`;
}),
isImage: computed("field.value.extension", function () {
return (
this.field.value &&
this.siteSettings.wizard_recognised_image_upload_formats
.split("|")
.includes(this.field.value.extension)
);
}),
uploadDone(upload) {
this.set("field.value", upload);
},
});

Datei anzeigen

@ -1,3 +0,0 @@
import Component from "@ember/component";
export default Component.extend({});

Datei anzeigen

@ -1,5 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
classNameBindings: ["fieldClass"],
});

Datei anzeigen

@ -1,41 +0,0 @@
import Component from "@ember/component";
import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators";
import { cookAsync } from "discourse/lib/text";
export default Component.extend({
classNameBindings: [
":wizard-field",
"typeClasses",
"field.invalid",
"field.id",
],
didReceiveAttrs() {
this._super(...arguments);
cookAsync(this.field.translatedDescription).then((cookedDescription) => {
this.set("cookedDescription", cookedDescription);
});
},
@discourseComputed("field.type", "field.id")
typeClasses: (type, id) =>
`${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`,
@discourseComputed("field.id")
fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`,
@discourseComputed("field.type", "field.id")
inputComponentName(type, id) {
if (["text_only"].includes(type)) {
return false;
}
return dasherize(type === "component" ? id : `custom-wizard-field-${type}`);
},
@discourseComputed("field.type")
textType(fieldType) {
return ["text", "textarea"].includes(fieldType);
},
});

Datei anzeigen

@ -1,19 +0,0 @@
import ComboBox from "select-kit/components/combo-box";
import { computed } from "@ember/object";
import { makeArray } from "discourse-common/lib/helpers";
export default ComboBox.extend({
content: computed("groups.[]", "field.content.[]", function () {
const whitelist = makeArray(this.field.content);
return this.groups
.filter((group) => {
return !whitelist.length || whitelist.indexOf(group.id) > -1;
})
.map((g) => {
return {
id: g.id,
name: g.full_name ? g.full_name : g.name,
};
});
}),
});

Datei anzeigen

@ -1,29 +0,0 @@
import CustomWizard from "../models/custom-wizard";
import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
import { dasherize } from "@ember/string";
import getURL from "discourse-common/lib/get-url";
export default Component.extend({
classNameBindings: [":wizard-no-access", "reasonClass"],
@discourseComputed("reason")
reasonClass(reason) {
return dasherize(reason);
},
@discourseComputed
siteName() {
return this.siteSettings.title || "";
},
actions: {
skip() {
if (this.currentUser) {
CustomWizard.skip(this.get("wizardId"));
} else {
window.location = getURL("/");
}
},
},
});

Datei anzeigen

@ -1,38 +0,0 @@
import Component from "@ember/component";
import { bind } from "@ember/runloop";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["wizard-similar-topics"],
showTopics: true,
didInsertElement() {
$(document).on("click", bind(this, this.documentClick));
},
willDestroyElement() {
$(document).off("click", bind(this, this.documentClick));
},
documentClick(e) {
if (this._state === "destroying") {
return;
}
let $target = $(e.target);
if (!$target.hasClass("show-topics")) {
this.set("showTopics", false);
}
},
@observes("topics")
toggleShowWhenTopicsChange() {
this.set("showTopics", true);
},
actions: {
toggleShowTopics() {
this.set("showTopics", true);
},
},
});

Datei anzeigen

@ -1,9 +0,0 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
classNameBindings: [":wizard-step-form", "customStepClass"],
@discourseComputed("step.id")
customStepClass: (stepId) => `wizard-step-${stepId}`,
});

Datei anzeigen

@ -1,227 +0,0 @@
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Component from "@ember/component";
import I18n from "I18n";
import getUrl from "discourse-common/lib/get-url";
import { htmlSafe } from "@ember/template";
import { schedule } from "@ember/runloop";
import { cookAsync } from "discourse/lib/text";
import CustomWizard, {
updateCachedWizard,
} from "discourse/plugins/discourse-custom-wizard/discourse/models/custom-wizard";
import { alias, not } from "@ember/object/computed";
import discourseLater from "discourse-common/lib/later";
const alreadyWarned = {};
export default Component.extend({
classNameBindings: [":wizard-step", "step.id"],
saving: null,
init() {
this._super(...arguments);
this.set("stylingDropdown", {});
},
didReceiveAttrs() {
this._super(...arguments);
cookAsync(this.step.translatedTitle).then((cookedTitle) => {
this.set("cookedTitle", cookedTitle);
});
cookAsync(this.step.translatedDescription).then((cookedDescription) => {
this.set("cookedDescription", cookedDescription);
});
},
didInsertElement() {
this._super(...arguments);
this.autoFocus();
},
@discourseComputed("step.index", "wizard.required")
showQuitButton: (index, required) => index === 0 && !required,
showNextButton: not("step.final"),
showDoneButton: alias("step.final"),
@discourseComputed(
"step.index",
"step.displayIndex",
"wizard.totalSteps",
"wizard.completed"
)
showFinishButton: (index, displayIndex, total, completed) => {
return index !== 0 && displayIndex !== total && completed;
},
@discourseComputed("step.index")
showBackButton: (index) => index > 0,
@discourseComputed("step.banner")
bannerImage(src) {
if (!src) {
return;
}
return getUrl(src);
},
@discourseComputed("step.id")
bannerAndDescriptionClass(id) {
return `wizard-banner-and-description wizard-banner-and-description-${id}`;
},
@discourseComputed("step.fields.[]")
primaryButtonIndex(fields) {
return fields.length + 1;
},
@discourseComputed("step.fields.[]")
secondaryButtonIndex(fields) {
return fields.length + 2;
},
@observes("step.id")
_stepChanged() {
this.set("saving", false);
this.autoFocus();
},
@observes("step.message")
_handleMessage: function () {
const message = this.get("step.message");
this.showMessage(message);
},
@discourseComputed("step.index", "wizard.totalSteps")
barStyle(displayIndex, totalSteps) {
let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1);
if (ratio < 0) {
ratio = 0;
}
if (ratio > 1) {
ratio = 1;
}
return htmlSafe(`width: ${ratio * 200}px`);
},
@discourseComputed("step.fields")
includeSidebar(fields) {
return !!fields.findBy("show_in_sidebar");
},
autoFocus() {
discourseLater(() => {
schedule("afterRender", () => {
if ($(".invalid .wizard-focusable").length) {
this.animateInvalidFields();
}
});
});
},
animateInvalidFields() {
schedule("afterRender", () => {
let $invalid = $(".invalid .wizard-focusable");
if ($invalid.length) {
$([document.documentElement, document.body]).animate(
{
scrollTop: $invalid.offset().top - 200,
},
400
);
}
});
},
advance() {
this.set("saving", true);
this.get("step")
.save()
.then((response) => {
updateCachedWizard(CustomWizard.build(response["wizard"]));
if (response["final"]) {
CustomWizard.finished(response);
} else {
this.goNext(response);
}
})
.catch(() => this.animateInvalidFields())
.finally(() => this.set("saving", false));
},
actions: {
quit() {
this.get("wizard").skip();
},
done() {
this.send("nextStep");
},
showMessage(message) {
this.sendAction(message);
},
stylingDropdownChanged(id, value) {
this.set("stylingDropdown", { id, value });
},
exitEarly() {
const step = this.step;
step.validate();
if (step.get("valid")) {
this.set("saving", true);
step
.save()
.then(() => this.send("quit"))
.finally(() => this.set("saving", false));
} else {
this.autoFocus();
}
},
backStep() {
if (this.saving) {
return;
}
this.goBack();
},
nextStep() {
if (this.saving) {
return;
}
const step = this.step;
const result = step.validate();
if (result.warnings.length) {
const unwarned = result.warnings.filter((w) => !alreadyWarned[w]);
if (unwarned.length) {
unwarned.forEach((w) => (alreadyWarned[w] = true));
return window.bootbox.confirm(
unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"),
I18n.t("no_value"),
I18n.t("yes_value"),
(confirmed) => {
if (confirmed) {
this.advance();
}
}
);
}
}
if (step.get("valid")) {
this.advance();
} else {
this.autoFocus();
}
},
},
});

Datei anzeigen

@ -1,15 +0,0 @@
import TagChooser from "select-kit/components/tag-chooser";
export default TagChooser.extend({
searchTags(url, data, callback) {
if (this.tagGroups) {
let tagGroupsString = this.tagGroups.join(",");
data.filterForInput = {
name: "custom-wizard-tag-chooser",
groups: tagGroupsString,
};
}
return this._super(url, data, callback);
},
});

Datei anzeigen

@ -1,44 +0,0 @@
import computed from "discourse-common/utils/decorators";
import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction";
import I18n from "I18n";
import TextField from "discourse/components/text-field";
export default TextField.extend({
attributeBindings: [
"autocorrect",
"autocapitalize",
"autofocus",
"maxLength",
"dir",
],
@computed
dir() {
if (this.siteSettings.support_mixed_text_direction) {
let val = this.value;
if (val) {
return isRTL(val) ? "rtl" : "ltr";
} else {
return siteDir();
}
}
},
keyUp() {
if (this.siteSettings.support_mixed_text_direction) {
let val = this.value;
if (isRTL(val)) {
this.set("dir", "rtl");
} else if (isLTR(val)) {
this.set("dir", "ltr");
} else {
this.set("dir", siteDir());
}
}
},
@computed("placeholderKey")
placeholder(placeholderKey) {
return placeholderKey ? I18n.t(placeholderKey) : "";
},
});

Datei anzeigen

@ -1,3 +0,0 @@
import TimeInput from "discourse/components/time-input";
export default TimeInput.extend({});

Datei anzeigen

@ -1,9 +0,0 @@
import Component from "@ember/component";
export default Component.extend({
actions: {
perform() {
this.appEvents.trigger("custom-wizard:validate");
},
},
});

Datei anzeigen

@ -1,34 +0,0 @@
<DModal @closeModal={{@closeModal}} @title={{this.title}}>
{{#if loading}}
<LoadingSpinner size="large" />
{{else}}
<div class="edit-directory-columns-container">
{{#each @model.columns as |column|}}
<div class="edit-directory-column">
<div class="left-content">
<label class="column-name">
<Input @type="checkbox" @checked={{column.enabled}} />
{{directory-table-header-title
field=column.label
translated=true
}}
</label>
</div>
</div>
{{/each}}
</div>
{{/if}}
<div class="modal-footer">
<DButton
class="btn-primary"
@label="directory.edit_columns.save"
@action={{action "save"}}
/>
<DButton
class="btn-secondary reset-to-default"
@label="directory.edit_columns.reset_to_default"
@action={{action "resetToDefault"}}
/>
</div>
</DModal>

Datei anzeigen

@ -1,15 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import I18n from "I18n";
export default class AdminWizardsColumnComponent extends Component {
title = I18n.t("admin.wizard.edit_columns");
@action save() {
this.args.closeModal();
}
@action resetToDefault() {
this.args.model.reset();
}
}

Datei anzeigen

@ -1,20 +0,0 @@
<DModal
@closeModal={{@closeModal}}
class="next-session-time-modal"
@title={{this.title}}
>
<DateTimeInput
@date={{this.bufferedDateTime}}
@onChange={{action "dateTimeChanged"}}
@showTime="true"
@clearable="true"
/>
<div class="modal-footer">
<DButton
@action={{action "submit"}}
class="btn-primary"
@label="admin.wizard.after_time_modal.done"
@disabled={{this.submitDisabled}}
/>
</div>
</DModal>

Datei anzeigen

@ -1,30 +0,0 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import I18n from "I18n";
export default class NextSessionScheduledComponent extends Component {
@tracked bufferedDateTime;
title = I18n.t("admin.wizard.after_time_modal.title");
constructor() {
super(...arguments);
this.bufferedDateTime = this.args.model.dateTime
? moment(this.args.model.dateTime)
: moment(Date.now());
}
get submitDisabled() {
return moment().isAfter(this.bufferedDateTime);
}
@action submit() {
const dateTime = this.bufferedDateTime;
this.args.model.update(moment(dateTime).utc().toISOString());
this.args.closeModal();
}
@action dateTimeChanged(dateTime) {
this.bufferedDateTime = dateTime;
}
}

Datei anzeigen

@ -1,160 +0,0 @@
import WizardFieldValidator from "discourse/plugins/discourse-custom-wizard/discourse/components/validator";
import { deepMerge } from "discourse-common/lib/object";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { cancel, later } from "@ember/runloop";
import { A } from "@ember/array";
import EmberObject, { computed } from "@ember/object";
import { and, equal, notEmpty } from "@ember/object/computed";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
import { dasherize } from "@ember/string";
export default WizardFieldValidator.extend({
classNames: ["similar-topics-validator"],
similarTopics: null,
hasInput: notEmpty("field.value"),
hasSimilarTopics: notEmpty("similarTopics"),
hasNotSearched: equal("similarTopics", null),
noSimilarTopics: computed("similarTopics", function () {
return this.similarTopics !== null && this.similarTopics.length === 0;
}),
showSimilarTopics: computed("typing", "hasSimilarTopics", function () {
return this.hasSimilarTopics && !this.typing;
}),
showNoSimilarTopics: computed("typing", "noSimilarTopics", function () {
return this.noSimilarTopics && !this.typing;
}),
hasValidationCategories: notEmpty("validationCategories"),
insufficientCharacters: computed("typing", "field.value", function () {
return this.hasInput && this.field.value.length < 5 && !this.typing;
}),
insufficientCharactersCategories: and(
"insufficientCharacters",
"hasValidationCategories"
),
@discourseComputed("validation.categories")
validationCategories(categoryIds) {
if (categoryIds) {
return categoryIds.map((id) => this.site.categoriesById[id]);
}
return A();
},
@discourseComputed("validationCategories")
catLinks(categories) {
return categories.map((category) => categoryBadgeHTML(category)).join("");
},
@discourseComputed(
"loading",
"showSimilarTopics",
"showNoSimilarTopics",
"insufficientCharacters",
"insufficientCharactersCategories"
)
currentState(
loading,
showSimilarTopics,
showNoSimilarTopics,
insufficientCharacters,
insufficientCharactersCategories
) {
switch (true) {
case loading:
return "loading";
case showSimilarTopics:
return "results";
case showNoSimilarTopics:
return "no_results";
case insufficientCharactersCategories:
return "insufficient_characters_categories";
case insufficientCharacters:
return "insufficient_characters";
default:
return false;
}
},
@discourseComputed("currentState")
currentStateClass(currentState) {
if (currentState) {
return `similar-topics-${dasherize(currentState)}`;
}
return "similar-topics";
},
@discourseComputed("currentState")
currentStateKey(currentState) {
if (currentState) {
return `realtime_validations.similar_topics.${currentState}`;
}
return false;
},
validate() {},
@observes("field.value")
customValidate() {
const field = this.field;
if (!field.value) {
this.set("similarTopics", null);
return;
}
const value = field.value;
this.set("typing", true);
const lastKeyUp = new Date();
this._lastKeyUp = lastKeyUp;
// One second from now, check to see if the last key was hit when
// we recorded it. If it was, the user paused typing.
cancel(this._lastKeyTimeout);
this._lastKeyTimeout = later(() => {
if (lastKeyUp !== this._lastKeyUp) {
return;
}
this.set("typing", false);
if (value && value.length < 5) {
this.set("similarTopics", null);
return;
}
this.updateSimilarTopics();
}, 1000);
},
updateSimilarTopics() {
this.set("similarTopics", null);
this.set("updating", true);
this.backendValidate({
title: this.get("field.value"),
categories: this.get("validation.categories"),
time_unit: this.get("validation.time_unit"),
time_n_value: this.get("validation.time_n_value"),
})
.then((result) => {
const similarTopics = A(
deepMerge(result["topics"], result["similar_topics"])
);
similarTopics.forEach(function (topic, index) {
similarTopics[index] = EmberObject.create(topic);
});
this.set("similarTopics", similarTopics);
})
.finally(() => this.set("updating", false));
},
actions: {
closeMessage() {
this.set("showMessage", false);
},
},
});

Datei anzeigen

@ -1,42 +0,0 @@
import Component from "@ember/component";
import { equal } from "@ember/object/computed";
import { ajax, getToken } from "discourse/lib/ajax";
export default Component.extend({
classNames: ["validator"],
classNameBindings: ["isValid", "isInvalid"],
validMessageKey: null,
invalidMessageKey: null,
isValid: null,
isInvalid: equal("isValid", false),
init() {
this._super(...arguments);
if (this.get("validation.backend")) {
// set a function that can be called as often as it need to
// from the derived component
this.backendValidate = (params) => {
return ajax("/realtime-validations", {
data: {
type: this.get("type"),
authenticity_token: getToken(),
...params,
},
});
};
}
},
didInsertElement() {
this.appEvents.on("custom-wizard:validate", this, this.checkIsValid);
},
willDestroyElement() {
this.appEvents.off("custom-wizard:validate", this, this.checkIsValid);
},
checkIsValid() {
this.set("isValid", this.validate());
},
});

Datei anzeigen

@ -0,0 +1,19 @@
import { default as discourseComputed } from 'discourse-common/utils/decorators';
import Component from '@ember/component';
export default Component.extend({
classNames: 'wizard-advanced-toggle',
@discourseComputed('showAdvanced')
toggleClass(showAdvanced) {
let classes = 'btn'
if (showAdvanced) classes += ' btn-primary';
return classes;
},
actions: {
toggleAdvanced() {
this.toggleProperty('showAdvanced');
}
}
})

Datei anzeigen

@ -1,104 +1,89 @@
import { default as discourseComputed } from "discourse-common/utils/decorators"; import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { empty, equal, or } from "@ember/object/computed"; import { equal, empty, or, and } from "@ember/object/computed";
import { notificationLevels, selectKitContent } from "../lib/wizard"; import { generateName, selectKitContent } from '../lib/wizard';
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import UndoChanges from "../mixins/undo-changes"; import wizardSchema from '../lib/wizard-schema';
import UndoChanges from '../mixins/undo-changes';
import Component from "@ember/component"; import Component from "@ember/component";
import { notificationLevels } from '../lib/wizard';
import I18n from "I18n"; import I18n from "I18n";
export default Component.extend(UndoChanges, { export default Component.extend(UndoChanges, {
componentType: "action", componentType: 'action',
classNameBindings: [":wizard-custom-action", "visible"], classNameBindings: [':wizard-custom-action', 'visible'],
visible: computed("currentActionId", function () { visible: computed('currentActionId', function() { return this.action.id === this.currentActionId }),
return this.action.id === this.currentActionId; createTopic: equal('action.type', 'create_topic'),
}), updateProfile: equal('action.type', 'update_profile'),
createTopic: equal("action.type", "create_topic"), watchCategories: equal('action.type', 'watch_categories'),
updateProfile: equal("action.type", "update_profile"), sendMessage: equal('action.type', 'send_message'),
watchCategories: equal("action.type", "watch_categories"), openComposer: equal('action.type', 'open_composer'),
watchTags: equal("action.type", "watch_tags"), sendToApi: equal('action.type', 'send_to_api'),
sendMessage: equal("action.type", "send_message"), addToGroup: equal('action.type', 'add_to_group'),
openComposer: equal("action.type", "open_composer"), routeTo: equal('action.type', 'route_to'),
sendToApi: equal("action.type", "send_to_api"), createCategory: equal('action.type', 'create_category'),
addToGroup: equal("action.type", "add_to_group"), createGroup: equal('action.type', 'create_group'),
routeTo: equal("action.type", "route_to"), apiEmpty: empty('action.api'),
createCategory: equal("action.type", "create_category"), groupPropertyTypes: selectKitContent(['id', 'name']),
createGroup: equal("action.type", "create_group"), hasAdvanced: or('hasCustomFields', 'routeTo'),
apiEmpty: empty("action.api"), showAdvanced: and('hasAdvanced', 'action.type'),
groupPropertyTypes: selectKitContent(["id", "name"]), hasCustomFields: or('basicTopicFields', 'updateProfile', 'createGroup', 'createCategory'),
hasCustomFields: or( basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
"basicTopicFields", publicTopicFields: or('createTopic', 'openComposer'),
"updateProfile", showSkipRedirect: or('createTopic', 'sendMessage'),
"createGroup", actionTypes: Object.keys(wizardSchema.action.types).map(type => {
"createCategory"
),
basicTopicFields: or("createTopic", "sendMessage", "openComposer"),
publicTopicFields: or("createTopic", "openComposer"),
showPostAdvanced: or("createTopic", "sendMessage"),
availableNotificationLevels: notificationLevels.map((type) => {
return { return {
id: type, id: type,
name: I18n.t(`admin.wizard.action.watch_x.notification_level.${type}`), 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://discourse.pluginmanager.org/t/action-settings", messageUrl: 'https://thepavilion.io/t/2810',
@discourseComputed("action.type") @discourseComputed('action.type')
messageKey(type) { messageKey(type) {
let key = "type"; let key = 'type';
if (type) { if (type) {
key = "edit"; key = 'edit';
} }
return key; return key;
}, },
@discourseComputed("action.type") @discourseComputed('wizard.steps')
customFieldsContext(type) {
return `action.${type}`;
},
@discourseComputed("wizard.steps")
runAfterContent(steps) { runAfterContent(steps) {
let content = steps.map(function (step) { let content = steps.map(function(step) {
return { return {
id: step.id, id: step.id,
name: step.title || step.id, name: step.title || step.id
}; };
}); });
content.unshift({ content.unshift({
id: "wizard_completion", id: 'wizard_completion',
name: I18n.t("admin.wizard.action.run_after.wizard_completion"), name: I18n.t('admin.wizard.action.run_after.wizard_completion')
}); });
return content; return content;
}, },
@discourseComputed("apis") @discourseComputed('apis')
availableApis(apis) { availableApis(apis) {
return apis.map((a) => { return apis.map(a => {
return { return {
id: a.name, id: a.name,
name: a.title, name: a.title
}; };
}); });
}, },
@discourseComputed("apis", "action.api") @discourseComputed('apis', 'action.api')
availableEndpoints(apis, api) { availableEndpoints(apis, api) {
if (!api) { if (!api) return [];
return []; return apis.find(a => a.name === api).endpoints;
} }
return apis.find((a) => a.name === api).endpoints;
},
@discourseComputed("fieldTypes")
hasEventField(fieldTypes) {
return fieldTypes.map((ft) => ft.id).includes("event");
},
@discourseComputed("fieldTypes")
hasLocationField(fieldTypes) {
return fieldTypes.map((ft) => ft.id).includes("location");
},
}); });

Datei anzeigen

@ -1,159 +1,102 @@
import { default as discourseComputed } from "discourse-common/utils/decorators"; import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { equal, or } from "@ember/object/computed"; import { equal, or, alias } from "@ember/object/computed";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import { selectKitContent } from "../lib/wizard"; import { selectKitContent } from '../lib/wizard';
import UndoChanges from "../mixins/undo-changes"; import UndoChanges from '../mixins/undo-changes';
import Component from "@ember/component"; import Component from "@ember/component";
import wizardSchema from "../lib/wizard-schema";
export default Component.extend(UndoChanges, { export default Component.extend(UndoChanges, {
componentType: "field", componentType: 'field',
classNameBindings: [":wizard-custom-field", "visible"], classNameBindings: [':wizard-custom-field', 'visible'],
visible: computed("currentFieldId", function () { visible: computed('currentFieldId', function() { return this.field.id === this.currentFieldId }),
return this.field.id === this.currentFieldId; isDropdown: equal('field.type', 'dropdown'),
}), isUpload: equal('field.type', 'upload'),
isDropdown: equal("field.type", "dropdown"), isCategory: equal('field.type', 'category'),
isUpload: equal("field.type", "upload"), isGroup: equal('field.type', 'group'),
isCategory: equal("field.type", "category"), isTag: equal('field.type', 'tag'),
isGroup: equal("field.type", "group"), isText: equal('field.type', 'text'),
isTag: equal("field.type", "tag"), isTextarea: equal('field.type', 'textarea'),
isText: equal("field.type", "text"), isUrl: equal('field.type', 'url'),
isTextarea: equal("field.type", "textarea"), showPrefill: or('isText', 'isCategory', 'isTag', 'isGroup', 'isDropdown'),
isUrl: equal("field.type", "url"), showContent: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
isComposer: equal("field.type", "composer"), showLimit: or('isCategory', 'isTag'),
showPrefill: or("isText", "isCategory", "isTag", "isGroup", "isDropdown"), showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'),
showContent: or("isCategory", "isTag", "isGroup", "isDropdown"), categoryPropertyTypes: selectKitContent(['id', 'slug']),
showLimit: or("isCategory", "isTag"), showAdvanced: alias('field.type'),
isTextType: or("isText", "isTextarea", "isComposer"), messageUrl: 'https://thepavilion.io/t/2809',
isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]),
messageUrl: "https://discourse.pluginmanager.org/t/field-settings",
@discourseComputed("field.type") @discourseComputed('field.type')
validations(type) {
const applicableToField = [];
for (let validation in wizardSchema.field.validations) {
if (wizardSchema.field.validations[validation]["types"].includes(type)) {
applicableToField.push(validation);
}
}
return applicableToField;
},
@discourseComputed("field.type")
isDateTime(type) { isDateTime(type) {
return ["date_time", "date", "time"].indexOf(type) > -1; return ['date_time', 'date', 'time'].indexOf(type) > -1;
}, },
@discourseComputed("field.type") @discourseComputed('field.type')
messageKey(type) { messageKey(type) {
let key = "type"; let key = 'type';
if (type) { if (type) {
key = "edit"; key = 'edit';
} }
return key; return key;
}, },
setupTypeOutput(fieldType, options) { setupTypeOutput(fieldType, options) {
const selectionType = { const selectionType = {
category: "category", category: 'category',
tag: "tag", tag: 'tag',
group: "group", group: 'group'
}[fieldType]; }[fieldType];
if (selectionType) { if (selectionType) {
options[`${selectionType}Selection`] = "output"; options[`${selectionType}Selection`] = 'output';
options.outputDefaultSelection = selectionType; options.outputDefaultSelection = selectionType;
} }
return options; return options;
}, },
@discourseComputed("field.type") @discourseComputed('field.type')
contentOptions(fieldType) { contentOptions(fieldType) {
let options = { let options = {
wizardFieldSelection: true, wizardFieldSelection: true,
textSelection: "key,value", textSelection: 'key,value',
userFieldSelection: "key,value", userFieldSelection: 'key,value',
context: "field", context: 'field'
}; }
options = this.setupTypeOutput(fieldType, options); options = this.setupTypeOutput(fieldType, options);
if (this.isDropdown) { if (this.isDropdown) {
options.wizardFieldSelection = "key,value"; options.wizardFieldSelection = 'key,value';
options.userFieldOptionsSelection = "output"; options.userFieldOptionsSelection = 'output';
options.textSelection = "key,value"; options.textSelection = 'key,value,output';
options.inputTypes = "association,conditional,assignment"; options.inputTypes = 'conditional,association,assignment';
options.pairConnector = "association"; options.pairConnector = 'association';
options.keyPlaceholder = "admin.wizard.key"; options.keyPlaceholder = 'admin.wizard.key';
options.valuePlaceholder = "admin.wizard.value"; options.valuePlaceholder = 'admin.wizard.value';
} }
return options; return options;
}, },
@discourseComputed("field.type") @discourseComputed('field.type')
prefillOptions(fieldType) { prefillOptions(fieldType) {
let options = { let options = {
wizardFieldSelection: true, wizardFieldSelection: true,
textSelection: true, textSelection: true,
userFieldSelection: "key,value", userFieldSelection: 'key,value',
context: "field", context: 'field'
}; }
return this.setupTypeOutput(fieldType, options); return this.setupTypeOutput(fieldType, options);
}, },
@discourseComputed("step.index")
fieldConditionOptions(stepIndex) {
const options = {
inputTypes: "validation",
context: "field",
textSelection: "value",
userFieldSelection: true,
groupSelection: true,
};
if (stepIndex > 0) {
options.wizardFieldSelection = true;
options.wizardActionSelection = true;
}
return options;
},
@discourseComputed("step.index")
fieldIndexOptions(stepIndex) {
const options = {
context: "field",
userFieldSelection: true,
groupSelection: true,
};
if (stepIndex > 0) {
options.wizardFieldSelection = true;
options.wizardActionSelection = true;
}
return options;
},
actions: { actions: {
imageUploadDone(upload) { imageUploadDone(upload) {
this.setProperties({ this.set("field.image", upload.url);
"field.image": upload.url,
"field.image_upload_id": upload.id,
});
}, },
imageUploadDeleted() { imageUploadDeleted() {
this.setProperties({ this.set("field.image", null);
"field.image": null, }
"field.image_upload_id": null, }
});
},
},
}); });

Datei anzeigen

@ -1,40 +1,16 @@
import Component from "@ember/component"; import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators"; import { default as discourseComputed } from 'discourse-common/utils/decorators';
export default Component.extend({ export default Component.extend({
classNames: "wizard-custom-step", classNames: 'wizard-custom-step',
@discourseComputed("step.index")
stepConditionOptions(stepIndex) {
const options = {
inputTypes: "validation",
context: "step",
textSelection: "value",
userFieldSelection: true,
groupSelection: true,
};
if (stepIndex > 0) {
options["wizardFieldSelection"] = true;
options["wizardActionSelection"] = true;
}
return options;
},
actions: { actions: {
bannerUploadDone(upload) { bannerUploadDone(upload) {
this.setProperties({ this.set("step.banner", upload.url);
"step.banner": upload.url,
"step.banner_upload_id": upload.id,
});
}, },
bannerUploadDeleted() { bannerUploadDeleted() {
this.setProperties({ this.set("step.banner", null);
"step.banner": null, }
"step.banner_upload_id": null, }
});
},
},
}); });

Datei anzeigen

@ -0,0 +1,46 @@
import Component from "@ember/component";
import { A } from "@ember/array";
import I18n from "I18n";
export default Component.extend({
classNames: ['container', 'export'],
selected: A(),
actions: {
checkChanged(event) {
this.set('exportMessage', '');
let selected = this.get('selected');
if (event.target.checked) {
selected.addObject(event.target.id);
} else if (!event.target.checked) {
selected.removeObject(event.target.id);
}
this.set('selected', selected);
},
export() {
const wizards = this.get('selected');
if (!wizards.length) {
this.set('exportMessage', I18n.t("admin.wizard.transfer.export.none_selected"));
} else {
this.set('exportMessage', '');
let url = Discourse.BaseUrl;
let route = '/admin/wizards/transfer/export';
url += route + '?';
wizards.forEach((wizard) => {
let step = 'wizards[]=' + wizard;
step += '&';
url += step;
});
location.href = url;
}
}
}
});

Datei anzeigen

@ -0,0 +1,84 @@
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'],
hasLogs: notEmpty('logs'),
@discourseComputed('successIds', 'failureIds')
logs(successIds, failureIds) {
let logs = [];
if (successIds) {
logs.push(...successIds.map(id => {
return { id, type: 'success' };
}));
}
if (failureIds) {
logs.push(...failureIds.map(id => {
return { id, type: 'failure' };
}));
}
return logs;
},
actions: {
setFilePath(event) {
this.set('importMessage', '');
// 512 kb is the max file size
let maxFileSize = 512 * 1024;
if (event.target.files[0] === undefined) {
this.set('filePath', null);
return;
}
if (maxFileSize < event.target.files[0].size) {
this.setProperties({
importMessage: I18n.t('admin.wizard.transfer.import.file_size_error'),
filePath: null
});
$('#file-url').val('');
} else {
this.set('filePath', event.target.files[0]);
}
},
import() {
const filePath = this.get('filePath');
let $formData = new FormData();
if (filePath) {
$formData.append('file', filePath);
ajax('/admin/wizards/transfer/import', {
type: 'POST',
data: $formData,
processData: false,
contentType: false,
}).then(result => {
if (result.error) {
this.set('importMessage', result.error);
} else {
this.setProperties({
successIds: result.success,
failureIds: result.failed,
fileName: $('#file-url')[0].files[0].name
});
}
this.set('filePath', null);
$('#file-url').val('');
});
} else {
this.set('importMessage', I18n.t("admin.wizard.transfer.import.no_file"));
}
}
}
});

Datei anzeigen

@ -1,47 +1,51 @@
import discourseComputed from "discourse-common/utils/decorators"; import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators';
import { generateName } from "../lib/wizard"; import { generateName } from '../lib/wizard';
import { import { default as wizardSchema, setWizardDefaults } from '../lib/wizard-schema';
setWizardDefaults,
default as wizardSchema,
} from "../lib/wizard-schema";
import { notEmpty } from "@ember/object/computed"; import { notEmpty } from "@ember/object/computed";
import { scheduleOnce, bind } from "@ember/runloop";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import Component from "@ember/component"; import Component from "@ember/component";
import { A } from "@ember/array"; import { A } from "@ember/array";
export default Component.extend({ export default Component.extend({
classNameBindings: [":wizard-links", "itemType"], classNameBindings: [':wizard-links', 'itemType'],
items: A(), items: A(),
anyLinks: notEmpty("links"), anyLinks: notEmpty('links'),
@on('didInsertElement')
@observes('links.[]')
setupSortable() {
scheduleOnce('afterRender', () => (this.applySortable()));
},
applySortable() {
$(this.element).find(".link-list")
.sortable({ tolerance: 'pointer' })
.on('sortupdate', (e, ui) => {
this.updateItemOrder(ui.item.data('id'), ui.item.index());
});
},
updateItemOrder(itemId, newIndex) { updateItemOrder(itemId, newIndex) {
const items = this.items; const items = this.items;
const item = items.findBy("id", itemId); const item = items.findBy('id', itemId);
items.removeObject(item); items.removeObject(item);
item.set("index", newIndex);
items.insertAt(newIndex, item); items.insertAt(newIndex, item);
scheduleOnce('afterRender', this, () => this.applySortable());
}, },
@discourseComputed("itemType") @discourseComputed('itemType')
header: (itemType) => `admin.wizard.${itemType}.header`, header: (itemType) => `admin.wizard.${itemType}.header`,
@discourseComputed( @discourseComputed('current', 'items.@each.id', 'items.@each.type', 'items.@each.label', 'items.@each.title')
"current",
"items.@each.id",
"items.@each.type",
"items.@each.label",
"items.@each.title"
)
links(current, items) { links(current, items) {
if (!items) { if (!items) return;
return;
}
return items.map((item, index) => { return items.map((item) => {
if (item) { if (item) {
let link = { let link = {
id: item.id, id: item.id
}; }
let label = item.label || item.title || item.id; let label = item.label || item.title || item.id;
if (this.generateLabels && item.type) { if (this.generateLabels && item.type) {
@ -50,49 +54,39 @@ export default Component.extend({
link.label = `${label} (${item.id})`; link.label = `${label} (${item.id})`;
let classes = "btn"; let classes = 'btn';
if (current && item.id === current.id) { if (current && item.id === current.id) {
classes += " btn-primary"; classes += ' btn-primary';
} };
link.classes = classes; link.classes = classes;
link.index = index;
if (index === 0) {
link.first = true;
}
if (index === items.length - 1) {
link.last = true;
}
return link; return link;
} }
}); });
}, },
getNextIndex() {
const items = this.items;
if (!items || items.length === 0) {
return 0;
}
const numbers = items
.map((i) => Number(i.id.split("_").pop()))
.sort((a, b) => a - b);
return numbers[numbers.length - 1];
},
actions: { actions: {
add() { add() {
const items = this.get("items"); const items = this.get('items');
const itemType = this.itemType; const itemType = this.itemType;
let params = setWizardDefaults({}, itemType); let params = setWizardDefaults({}, itemType);
params.isNew = true; params.isNew = true;
params.index = this.getNextIndex();
let id = `${itemType}_${params.index + 1}`; let next = 1;
if (itemType === "field") {
if (items.length) {
next = Math.max.apply(Math, items.map((i) => {
let parts = i.id.split('_');
let lastPart = parts[parts.length - 1];
return isNaN(lastPart) ? 0 : lastPart;
})) + 1;
}
let id = `${itemType}_${next}`;
if (itemType === 'field') {
id = `${this.parentId}_${id}`; id = `${this.parentId}_${id}`;
} }
@ -100,27 +94,19 @@ export default Component.extend({
let objectArrays = wizardSchema[itemType].objectArrays; let objectArrays = wizardSchema[itemType].objectArrays;
if (objectArrays) { if (objectArrays) {
Object.keys(objectArrays).forEach((objectType) => { Object.keys(objectArrays).forEach(objectType => {
params[objectArrays[objectType].property] = A(); params[objectArrays[objectType].property] = A();
}); });
} };
const newItem = EmberObject.create(params); const newItem = EmberObject.create(params);
items.pushObject(newItem); items.pushObject(newItem);
this.set("current", newItem); this.set('current', newItem);
},
back(item) {
this.updateItemOrder(item.id, item.index - 1);
},
forward(item) {
this.updateItemOrder(item.id, item.index + 1);
}, },
change(itemId) { change(itemId) {
this.set("current", this.items.findBy("id", itemId)); this.set('current', this.items.findBy('id', itemId));
}, },
remove(itemId) { remove(itemId) {
@ -137,14 +123,14 @@ export default Component.extend({
let nextIndex; let nextIndex;
if (this.current.id === itemId) { if (this.current.id === itemId) {
nextIndex = index < items.length - 2 ? index + 1 : index - 1; nextIndex = index < (items.length-2) ? (index+1) : (index-1);
} }
items.removeObject(item); items.removeObject(item);
if (nextIndex) { if (nextIndex) {
this.set("current", items[nextIndex]); this.set('current', items[nextIndex]);
} }
}, }
}, }
}); });

Datei anzeigen

@ -1,18 +1,15 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { gt } from "@ember/object/computed"; import { gt } from '@ember/object/computed';
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import { defaultConnector } from "../lib/wizard-mapper"; import { defaultConnector } from '../lib/wizard-mapper';
import { later } from "@ember/runloop"; import { later } from "@ember/runloop";
import { observes } from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
export default Component.extend({ export default Component.extend({
classNameBindings: [ classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'],
":mapper-connector", hasMultiple: gt('connectors.length', 1),
":mapper-block", connectorLabel: computed(function() {
"hasMultiple::single",
],
hasMultiple: gt("connectors.length", 1),
connectorLabel: computed(function () {
let key = this.connector; let key = this.connector;
let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`; let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`;
return I18n.t(`admin.wizard.${path}`); return I18n.t(`admin.wizard.${path}`);
@ -22,7 +19,7 @@ export default Component.extend({
if (!this.connector) { if (!this.connector) {
later(() => { later(() => {
this.set( this.set(
"connector", 'connector',
defaultConnector(this.connectorType, this.inputType, this.options) defaultConnector(this.connectorType, this.inputType, this.options)
); );
}); });
@ -31,8 +28,8 @@ export default Component.extend({
actions: { actions: {
changeConnector(value) { changeConnector(value) {
this.set("connector", value); this.set('connector', value);
this.onUpdate("connector", this.connectorType); this.onUpdate('connector', this.connectorType);
}, }
}, }
}); });

Datei anzeigen

@ -1,49 +1,36 @@
import { computed, set } from "@ember/object"; import { computed, set } from "@ember/object";
import { alias, equal, not, or } from "@ember/object/computed"; import { alias, equal, or, not } from "@ember/object/computed";
import { import { newPair, connectorContent, inputTypesContent, defaultSelectionType, defaultConnector } from '../lib/wizard-mapper';
connectorContent,
defaultConnector,
defaultSelectionType,
inputTypesContent,
newPair,
} from "../lib/wizard-mapper";
import Component from "@ember/component"; import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
import { A } from "@ember/array"; import { A } from "@ember/array";
export default Component.extend({ export default Component.extend({
classNameBindings: [":mapper-input", "inputType"], classNameBindings: [':mapper-input', 'inputType'],
inputType: alias("input.type"), inputType: alias('input.type'),
isConditional: equal("inputType", "conditional"), isConditional: equal('inputType', 'conditional'),
isAssignment: equal("inputType", "assignment"), isAssignment: equal('inputType', 'assignment'),
isAssociation: equal("inputType", "association"), isAssociation: equal('inputType', 'association'),
isValidation: equal("inputType", "validation"), isValidation: equal('inputType', 'validation'),
hasOutput: or("isConditional", "isAssignment"), hasOutput: or('isConditional', 'isAssignment'),
hasPairs: or("isConditional", "isAssociation", "isValidation"), hasPairs: or('isConditional', 'isAssociation', 'isValidation'),
canAddPair: not("isAssignment"), canAddPair: not('isAssignment'),
connectors: computed(function () { connectors: computed(function() { return connectorContent('output', this.input.type, this.options) }),
return connectorContent("output", this.input.type, this.options); inputTypes: computed(function() { return inputTypesContent(this.options) }),
}),
inputTypes: computed(function () {
return inputTypesContent(this.options);
}),
@observes("input.type") @observes('input.type')
setupType() { setupType() {
if (this.hasPairs && (!this.input.pairs || this.input.pairs.length < 1)) { if (this.hasPairs && (!this.input.pairs || this.input.pairs.length < 1)) {
this.send("addPair"); this.send('addPair');
} }
if (this.hasOutput) { if (this.hasOutput) {
this.set("input.output", null); this.set('input.output', null);
if (!this.input.outputConnector) { if (!this.input.outputConnector) {
const options = this.options; const options = this.options;
this.set("input.output_type", defaultSelectionType("output", options)); this.set('input.output_type', defaultSelectionType('output', options));
this.set( this.set('input.output_connector', defaultConnector('output', this.inputType, options));
"input.output_connector",
defaultConnector("output", this.inputType, options)
);
} }
} }
}, },
@ -51,18 +38,22 @@ export default Component.extend({
actions: { actions: {
addPair() { addPair() {
if (!this.input.pairs) { if (!this.input.pairs) {
this.set("input.pairs", A()); this.set('input.pairs', A());
} }
const pairs = this.input.pairs; const pairs = this.input.pairs;
const pairCount = pairs.length + 1; const pairCount = pairs.length + 1;
pairs.forEach((p) => set(p, "pairCount", pairCount)); pairs.forEach(p => (set(p, 'pairCount', pairCount)));
pairs.pushObject( pairs.pushObject(
newPair( newPair(
this.input.type, this.input.type,
Object.assign({}, this.options, { index: pairs.length, pairCount }) Object.assign(
{},
this.options,
{ index: pairs.length, pairCount }
)
) )
); );
}, },
@ -71,8 +62,8 @@ export default Component.extend({
const pairs = this.input.pairs; const pairs = this.input.pairs;
const pairCount = pairs.length - 1; const pairCount = pairs.length - 1;
pairs.forEach((p) => set(p, "pairCount", pairCount)); pairs.forEach(p => (set(p, 'pairCount', pairCount)));
pairs.removeObject(pair); pairs.removeObject(pair);
}, }
}, }
}); });

Datei anzeigen

@ -1,16 +1,12 @@
import { connectorContent } from "../lib/wizard-mapper"; import { connectorContent } from '../lib/wizard-mapper';
import { alias, gt } from "@ember/object/computed"; import { gt, or, alias } from "@ember/object/computed";
import { computed } from "@ember/object"; import { computed, observes } from "@ember/object";
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
classNameBindings: [":mapper-pair", "hasConnector::no-connector"], classNameBindings: [':mapper-pair', 'hasConnector::no-connector'],
firstPair: gt("pair.index", 0), firstPair: gt('pair.index', 0),
showRemove: alias("firstPair"), showRemove: alias('firstPair'),
showJoin: computed("pair.pairCount", function () { showJoin: computed('pair.pairCount', function() { return this.pair.index < (this.pair.pairCount - 1) }),
return this.pair.index < this.pair.pairCount - 1; connectors: computed(function() { return connectorContent('pair', this.inputType, this.options) })
}),
connectors: computed(function () {
return connectorContent("pair", this.inputType, this.options);
}),
}); });

Datei anzeigen

@ -1,16 +1,14 @@
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from 'discourse-common/utils/decorators';
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
tagName: "a", tagName: 'a',
classNameBindings: ["active"], classNameBindings: ['active'],
@discourseComputed("item.type", "activeType") @discourseComputed('item.type', 'activeType')
active(type, activeType) { active(type, activeType) { return type === activeType },
return type === activeType;
},
click() { click() {
this.toggle(this.item.type); this.toggle(this.item.type)
}, }
}); })

Datei anzeigen

@ -1,176 +1,46 @@
import { alias, gt, or } from "@ember/object/computed"; import { alias, or, gt } from "@ember/object/computed";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import { import { default as discourseComputed, observes, on } from "discourse-common/utils/decorators";
default as discourseComputed, import { getOwner } from 'discourse-common/lib/get-owner';
observes, import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper';
} from "discourse-common/utils/decorators"; import { snakeCase, generateName, userProperties } from '../lib/wizard';
import { getOwner } from "discourse-common/lib/get-owner";
import { defaultSelectionType, selectionTypes } from "../lib/wizard-mapper";
import {
generateName,
sentenceCase,
snakeCase,
userProperties,
} from "../lib/wizard";
import Component from "@ember/component"; import Component from "@ember/component";
import { bind, later } from "@ember/runloop"; import { bind, later } from "@ember/runloop";
import I18n from "I18n"; import I18n from "I18n";
import Subscription from "../mixins/subscription";
const customFieldActionMap = { export default Component.extend({
topic: ["create_topic", "send_message"], classNameBindings: [':mapper-selector', 'activeType'],
post: ["create_topic", "send_message"],
category: ["create_category"],
group: ["create_group"],
user: ["update_profile"],
};
const values = ["present", "true", "false"]; showText: computed('activeType', function() { return this.showInput('text') }),
showWizardField: computed('activeType', function() { return this.showInput('wizardField') }),
showWizardAction: computed('activeType', function() { return this.showInput('wizardAction') }),
showUserField: computed('activeType', function() { return this.showInput('userField') }),
showUserFieldOptions: computed('activeType', function() { return this.showInput('userFieldOptions') }),
showCategory: computed('activeType', function() { return this.showInput('category') }),
showTag: computed('activeType', function() { return this.showInput('tag') }),
showGroup: computed('activeType', function() { return this.showInput('group') }),
showUser: computed('activeType', function() { return this.showInput('user') }),
showList: computed('activeType', function() { return this.showInput('list') }),
textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }),
wizardFieldEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }),
wizardActionEnabled: computed('options.wizardActionSelection', 'inputType', function() { return this.optionEnabled('wizardActionSelection') }),
userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }),
userFieldOptionsEnabled: computed('options.userFieldOptionsSelection', 'inputType', function() { return this.optionEnabled('userFieldOptionsSelection') }),
categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }),
tagEnabled: computed('options.tagSelection', 'inputType', function() { return this.optionEnabled('tagSelection') }),
groupEnabled: computed('options.groupSelection', 'inputType', function() { return this.optionEnabled('groupSelection') }),
userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }),
listEnabled: computed('options.listSelection', 'inputType', function() { return this.optionEnabled('listSelection') }),
export default Component.extend(Subscription, { groups: alias('site.groups'),
classNameBindings: [":mapper-selector", "activeType"], categories: alias('site.categories'),
showComboBox: or('showWizardField', 'showWizardAction', 'showUserField', 'showUserFieldOptions'),
showText: computed("activeType", function () { showMultiSelect: or('showCategory', 'showGroup'),
return this.showInput("text"); hasTypes: gt('selectorTypes.length', 1),
}),
showWizardField: computed("activeType", function () {
return this.showInput("wizardField");
}),
showWizardAction: computed("activeType", function () {
return this.showInput("wizardAction");
}),
showUserField: computed("activeType", function () {
return this.showInput("userField");
}),
showUserFieldOptions: computed("activeType", function () {
return this.showInput("userFieldOptions");
}),
showCategory: computed("activeType", function () {
return this.showInput("category");
}),
showTag: computed("activeType", function () {
return this.showInput("tag");
}),
showGroup: computed("activeType", function () {
return this.showInput("group");
}),
showUser: computed("activeType", function () {
return this.showInput("user");
}),
showList: computed("activeType", function () {
return this.showInput("list");
}),
showCustomField: computed("activeType", function () {
return this.showInput("customField");
}),
showValue: computed("activeType", function () {
return this.showInput("value");
}),
textEnabled: computed("options.textSelection", "inputType", function () {
return this.optionEnabled("textSelection");
}),
wizardFieldEnabled: computed(
"options.wizardFieldSelection",
"inputType",
function () {
return this.optionEnabled("wizardFieldSelection");
}
),
wizardActionEnabled: computed(
"options.wizardActionSelection",
"inputType",
function () {
return this.optionEnabled("wizardActionSelection");
}
),
customFieldEnabled: computed(
"options.customFieldSelection",
"inputType",
function () {
return this.optionEnabled("customFieldSelection");
}
),
userFieldEnabled: computed(
"options.userFieldSelection",
"inputType",
function () {
return this.optionEnabled("userFieldSelection");
}
),
userFieldOptionsEnabled: computed(
"options.userFieldOptionsSelection",
"inputType",
function () {
return this.optionEnabled("userFieldOptionsSelection");
}
),
categoryEnabled: computed(
"options.categorySelection",
"inputType",
function () {
return this.optionEnabled("categorySelection");
}
),
tagEnabled: computed("options.tagSelection", "inputType", function () {
return this.optionEnabled("tagSelection");
}),
groupEnabled: computed("options.groupSelection", "inputType", function () {
return this.optionEnabled("groupSelection");
}),
guestGroup: computed("options.guestGroup", "inputType", function () {
return this.optionEnabled("guestGroup");
}),
userEnabled: computed("options.userSelection", "inputType", function () {
return this.optionEnabled("userSelection");
}),
listEnabled: computed("options.listSelection", "inputType", function () {
return this.optionEnabled("listSelection");
}),
valueEnabled: computed("connector", function () {
return this.connector === "is";
}),
@discourseComputed("site.groups", "guestGroup", "subscriptionType")
groups(groups, guestGroup, subscriptionType) {
let result = groups;
if (!guestGroup) {
return result;
}
if (["standard", "business"].includes(subscriptionType)) {
let guestIndex;
result.forEach((r, index) => {
if (r.id === 0) {
r.name = I18n.t("admin.wizard.selector.label.users");
guestIndex = index;
}
});
result.splice(guestIndex, 0, {
id: -1,
name: I18n.t("admin.wizard.selector.label.guests"),
});
}
return result;
},
categories: alias("site.categories"),
showComboBox: or(
"showWizardField",
"showWizardAction",
"showUserField",
"showUserFieldOptions",
"showCustomField",
"showValue"
),
showMultiSelect: or("showCategory", "showGroup"),
hasTypes: gt("selectorTypes.length", 1),
showTypes: false, showTypes: false,
didInsertElement() { didInsertElement() {
if ( if (!this.activeType || (this.activeType && !this[`${this.activeType}Enabled`])) {
!this.activeType ||
(this.activeType && !this[`${this.activeType}Enabled`])
) {
later(() => this.resetActiveType()); later(() => this.resetActiveType());
} }
@ -182,49 +52,43 @@ export default Component.extend(Subscription, {
}, },
documentClick(e) { documentClick(e) {
if (this._state === "destroying") { if (this._state == "destroying") return;
return;
}
let $target = $(e.target); let $target = $(e.target);
if (!$target.parents(".type-selector").length && this.showTypes) { if (!$target.parents('.type-selector').length && this.showTypes) {
this.set("showTypes", false); this.set('showTypes', false);
} }
}, },
@discourseComputed("connector") @discourseComputed
selectorTypes() { selectorTypes() {
return selectionTypes return selectionTypes.filter(type => (this[`${type}Enabled`]))
.filter((type) => this[`${type}Enabled`]) .map(type => ({ type, label: this.typeLabel(type) }));
.map((type) => ({ type, label: this.typeLabel(type) }));
}, },
@discourseComputed("activeType") @discourseComputed('activeType')
activeTypeLabel(activeType) { activeTypeLabel(activeType) {
return this.typeLabel(activeType); return this.typeLabel(activeType);
}, },
typeLabel(type) { typeLabel(type) {
return type return type ? I18n.t(`admin.wizard.selector.label.${snakeCase(type)}`) : null;
? I18n.t(`admin.wizard.selector.label.${snakeCase(type)}`)
: null;
}, },
comboBoxAllowAny: or("showWizardField", "showWizardAction"), comboBoxAllowAny: or('showWizardField', 'showWizardAction'),
@discourseComputed @discourseComputed
showController() { showController() {
return getOwner(this).lookup("controller:admin-wizards-wizard-show"); return getOwner(this).lookup('controller:admin-wizards-wizard-show');
}, },
@discourseComputed( @discourseComputed(
"activeType", 'activeType',
"showController.wizardFields.[]", 'showController.wizardFields.[]',
"showController.wizard.actions.[]", 'showController.wizard.actions.[]',
"showController.userFields.[]", 'showController.userFields.[]',
"showController.currentField.id", 'showController.currentField.id',
"showController.currentAction.id", 'showController.currentAction.id'
"showController.customFields"
) )
comboBoxContent( comboBoxContent(
activeType, activeType,
@ -232,115 +96,77 @@ export default Component.extend(Subscription, {
wizardActions, wizardActions,
userFields, userFields,
currentFieldId, currentFieldId,
currentActionId, currentActionId
customFields
) { ) {
let content; let content;
let context;
let contextType;
if (this.options.context) { if (activeType === 'wizardField') {
let contextAttrs = this.options.context.split(".");
context = contextAttrs[0];
contextType = contextAttrs[1];
}
if (activeType === "wizardField") {
content = wizardFields; content = wizardFields;
if (context === "field") { if (this.options.context === 'field') {
content = content.filter((field) => field.id !== currentFieldId); content = content.filter(field => field.id !== currentFieldId);
} }
} }
if (activeType === "wizardAction") { if (activeType === 'wizardAction') {
content = wizardActions.map((a) => ({ content = wizardActions.map(a => ({
id: a.id, id: a.id,
label: `${generateName(a.type)} (${a.id})`, label: `${generateName(a.type)} (${a.id})`,
type: a.type, type: a.type
})); }));
if (context === "action") { if (this.options.context === 'action') {
content = content.filter((a) => a.id !== currentActionId); content = content.filter(a => a.id !== currentActionId);
} }
} }
if (activeType === "userField") { if (activeType === 'userField') {
content = userProperties content = userProperties.map((f) => ({
.map((f) => ({ id: f,
id: f, name: generateName(f)
name: generateName(f), })).concat((userFields || []));
}))
.concat(userFields || []);
if ( if (this.options.context === 'action' &&
context === "action" && this.inputType === 'association' &&
this.inputType === "association" && this.selectorType === 'key') {
this.selectorType === "key"
) { const excludedFields = ['username','email', 'trust_level'];
const excludedFields = ["username", "email", "trust_level"]; content = content.filter(userField => excludedFields.indexOf(userField.id) === -1);
content = content.filter(
(userField) => excludedFields.indexOf(userField.id) === -1
);
} }
} }
if (activeType === "userFieldOptions") { if (activeType === 'userFieldOptions') {
content = userFields; content = userFields;
} }
if (activeType === "customField") {
content = customFields
.filter((f) => {
return (
f.type !== "json" &&
customFieldActionMap[f.klass].includes(contextType)
);
})
.map((f) => ({
id: f.name,
name: `${sentenceCase(f.klass)} ${f.name} (${f.type})`,
}));
}
if (activeType === "value") {
content = values.map((value) => ({
id: value,
name: value,
}));
}
return content; return content;
}, },
@discourseComputed("activeType") @discourseComputed('activeType')
multiSelectContent(activeType) { multiSelectContent(activeType) {
return { return {
category: this.categories, category: this.categories,
group: this.groups, group: this.groups,
list: "", list: ''
}[activeType]; }[activeType];
}, },
@discourseComputed("activeType", "inputType") @discourseComputed('activeType', 'inputType')
placeholderKey(activeType) { placeholderKey(activeType, inputType) {
if ( if (activeType === 'text' && this.options[`${this.selectorType}Placeholder`]) {
activeType === "text" &&
this.options[`${this.selectorType}Placeholder`]
) {
return this.options[`${this.selectorType}Placeholder`]; return this.options[`${this.selectorType}Placeholder`];
} else { } else {
return `admin.wizard.selector.placeholder.${snakeCase(activeType)}`; return `admin.wizard.selector.placeholder.${snakeCase(activeType)}`;
} }
}, },
@discourseComputed("activeType") @discourseComputed('activeType')
multiSelectOptions(activeType) { multiSelectOptions(activeType) {
let result = { let result = {
none: this.placeholderKey, none: this.placeholderKey
}; };
if (activeType === "list") { if (activeType === 'list') {
result.allowAny = true; result.allowAny = true;
} }
@ -349,20 +175,14 @@ export default Component.extend(Subscription, {
optionEnabled(type) { optionEnabled(type) {
const options = this.options; const options = this.options;
if (!options) { if (!options) return false;
return false;
}
const option = options[type]; const option = options[type];
if (option === true) { if (option === true) return true;
return true; if (typeof option !== 'string') return false;
}
if (typeof option !== "string") {
return false;
}
return option.split(",").filter((o) => { return option.split(',').filter(option => {
return [this.selectorType, this.inputType].indexOf(o) !== -1; return [this.selectorType, this.inputType].indexOf(option) !== -1;
}).length; }).length;
}, },
@ -371,28 +191,25 @@ export default Component.extend(Subscription, {
}, },
changeValue(value) { changeValue(value) {
this.set("value", value); this.set('value', value);
this.onUpdate("selector", this.activeType); this.onUpdate('selector', this.activeType);
}, },
@observes("inputType") @observes('inputType')
resetActiveType() { resetActiveType() {
this.set( this.set('activeType', defaultSelectionType(this.selectorType, this.options));
"activeType",
defaultSelectionType(this.selectorType, this.options, this.connector)
);
}, },
actions: { actions: {
toggleType(type) { toggleType(type) {
this.set("activeType", type); this.set('activeType', type);
this.set("showTypes", false); this.set('showTypes', false);
this.set("value", null); this.set('value', null);
this.onUpdate("selector"); this.onUpdate('selector');
}, },
toggleTypes() { toggleTypes() {
this.toggleProperty("showTypes"); this.toggleProperty('showTypes');
}, },
changeValue(value) { changeValue(value) {
@ -403,8 +220,8 @@ export default Component.extend(Subscription, {
this.changeValue(event.target.value); this.changeValue(event.target.value);
}, },
changeUserValue(value) { changeUserValue(previousValue, value) {
this.changeValue(value); this.changeValue(value);
}, }
}, }
}); })

Datei anzeigen

@ -1,71 +1,69 @@
import { newInput, selectionTypes } from "../lib/wizard-mapper"; import { getOwner } from 'discourse-common/lib/get-owner';
import discourseComputed from "discourse-common/utils/decorators"; import { newInput, selectionTypes } from '../lib/wizard-mapper';
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
import { later } from "@ember/runloop"; import { later } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import { A } from "@ember/array"; import { A } from "@ember/array";
export default Component.extend({ export default Component.extend({
classNames: "wizard-mapper", classNames: 'wizard-mapper',
didReceiveAttrs() { didReceiveAttrs() {
if (this.inputs && this.inputs.constructor !== Array) { if (this.inputs && this.inputs.constructor !== Array) {
later(() => this.set("inputs", null)); later(() => this.set('inputs', null));
} }
}, },
@discourseComputed("inputs.@each.type") @discourseComputed('inputs.@each.type')
canAdd(inputs) { canAdd(inputs) {
return ( return !inputs ||
!inputs || inputs.constructor !== Array ||
inputs.constructor !== Array || inputs.every(i => {
inputs.every((i) => { return ['assignment','association'].indexOf(i.type) === -1;
return ["assignment", "association"].indexOf(i.type) === -1; });
})
);
}, },
@discourseComputed("options.@each.inputType") @discourseComputed('options.@each.inputType')
inputOptions(options) { inputOptions(options) {
let result = { let result = {
inputTypes: options.inputTypes || "assignment,conditional", inputTypes: options.inputTypes || 'assignment,conditional',
inputConnector: options.inputConnector || "or", inputConnector: options.inputConnector || 'or',
pairConnector: options.pairConnector || null, pairConnector: options.pairConnector || null,
outputConnector: options.outputConnector || null, outputConnector: options.outputConnector || null,
context: options.context || null, context: options.context || null
guestGroup: options.guestGroup || false, }
};
let inputTypes = ["key", "value", "output"]; let inputTypes = ['key', 'value', 'output'];
inputTypes.forEach((type) => { inputTypes.forEach(type => {
result[`${type}Placeholder`] = options[`${type}Placeholder`] || null; result[`${type}Placeholder`] = options[`${type}Placeholder`] || null;
result[`${type}DefaultSelection`] = result[`${type}DefaultSelection`] = options[`${type}DefaultSelection`] || null;
options[`${type}DefaultSelection`] || null;
}); });
selectionTypes.forEach((type) => { selectionTypes.forEach(type => {
if (options[`${type}Selection`] !== undefined) { if (options[`${type}Selection`] !== undefined) {
result[`${type}Selection`] = options[`${type}Selection`]; result[`${type}Selection`] = options[`${type}Selection`]
} else { } else {
result[`${type}Selection`] = type === "text" ? true : false; result[`${type}Selection`] = type === 'text' ? true : false;
} }
}); });
return result; return result;
}, },
onUpdate() {}, onUpdate() {
},
actions: { actions: {
add() { add() {
if (!this.get("inputs")) { if (!this.get('inputs')) {
this.set("inputs", A()); this.set('inputs', A());
} }
this.get("inputs").pushObject( this.get('inputs').pushObject(
newInput(this.inputOptions, this.inputs.length) newInput(this.inputOptions, this.inputs.length)
); );
this.onUpdate(this.property, "input"); this.onUpdate(this.property, 'input');
}, },
remove(input) { remove(input) {
@ -73,14 +71,14 @@ export default Component.extend({
inputs.removeObject(input); inputs.removeObject(input);
if (inputs.length) { if (inputs.length) {
inputs[0].set("connector", null); inputs[0].set('connector', null);
} }
this.onUpdate(this.property, "input"); this.onUpdate(this.property, 'input');
}, },
inputUpdated(component, type) { inputUpdated(component, type) {
this.onUpdate(this.property, component, type); this.onUpdate(this.property, component, type);
}, }
}, }
}); });

Datei anzeigen

@ -1,33 +1,17 @@
import { default as discourseComputed } from "discourse-common/utils/decorators"; import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { not, notEmpty } from "@ember/object/computed";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
const icons = {
error: "times-circle",
success: "check-circle",
warn: "exclamation-circle",
info: "info-circle",
};
export default Component.extend({ export default Component.extend({
classNameBindings: [":wizard-message", "type", "loading"], classNames: 'wizard-message',
showDocumentation: not("loading"),
showIcon: not("loading"),
hasItems: notEmpty("items"),
@discourseComputed("type") @discourseComputed('key', 'component')
icon(type) { message(key, component) {
return icons[type] || "info-circle"; return I18n.t(`admin.wizard.message.${component}.${key}`);
}, },
@discourseComputed("key", "component", "opts") @discourseComputed('component')
message(key, component, opts) {
return I18n.t(`admin.wizard.message.${component}.${key}`, opts || {});
},
@discourseComputed("component")
documentation(component) { documentation(component) {
return I18n.t(`admin.wizard.message.${component}.documentation`); return I18n.t(`admin.wizard.message.${component}.documentation`);
}, }
}); })

Datei anzeigen

@ -1,58 +0,0 @@
import Component from "@ember/component";
import EmberObject from "@ember/object";
import { cloneJSON } from "discourse-common/lib/object";
import Category from "discourse/models/category";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Component.extend({
classNames: ["realtime-validations", "setting", "full", "subscription"],
@discourseComputed
timeUnits() {
return ["days", "weeks", "months", "years"].map((unit) => {
return {
id: unit,
name: I18n.t(`admin.wizard.field.validations.time_units.${unit}`),
};
});
},
init() {
this._super(...arguments);
if (!this.validations) {
return;
}
if (!this.field.validations) {
const validations = {};
this.validations.forEach((validation) => {
validations[validation] = {};
});
this.set("field.validations", EmberObject.create(validations));
}
const validationBuffer = cloneJSON(this.get("field.validations"));
let bufferCategories;
if (
validationBuffer.similar_topics &&
(bufferCategories = validationBuffer.similar_topics.categories)
) {
const categories = Category.findByIds(bufferCategories);
validationBuffer.similar_topics.categories = categories;
}
this.set("validationBuffer", validationBuffer);
},
actions: {
updateValidationCategories(type, validation, categories) {
this.set(`validationBuffer.${type}.categories`, categories);
this.set(
`field.validations.${type}.categories`,
categories.map((category) => category.id)
);
},
},
});

Datei anzeigen

@ -1,30 +0,0 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import Subscription from "../mixins/subscription";
import DiscourseURL from "discourse/lib/url";
import I18n from "I18n";
export default Component.extend(Subscription, {
tagName: "a",
classNameBindings: [":wizard-subscription-badge", "subscriptionType"],
attributeBindings: ["title"],
@discourseComputed("subscriptionType")
i18nKey(type) {
return `admin.wizard.subscription.type.${type ? type : "none"}`;
},
@discourseComputed("i18nKey")
title(i18nKey) {
return I18n.t(`${i18nKey}.title`);
},
@discourseComputed("i18nKey")
label(i18nKey) {
return I18n.t(`${i18nKey}.label`);
},
click() {
DiscourseURL.routeTo(this.subscriptionLink);
},
});

Datei anzeigen

@ -1,26 +0,0 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import Subscription from "../mixins/subscription";
export default Component.extend(Subscription, {
classNameBindings: [":wizard-subscription-container", "subscribed"],
@discourseComputed("subscribed")
subscribedIcon(subscribed) {
return subscribed ? "check" : "times";
},
@discourseComputed("subscribed")
subscribedLabel(subscribed) {
return `admin.wizard.subscription.${
subscribed ? "subscribed" : "not_subscribed"
}.label`;
},
@discourseComputed("subscribed")
subscribedTitle(subscribed) {
return `admin.wizard.subscription.${
subscribed ? "subscribed" : "not_subscribed"
}.title`;
},
});

Datei anzeigen

@ -1,36 +0,0 @@
import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";
import Subscription from "../mixins/subscription";
import I18n from "I18n";
export default Component.extend(Subscription, {
tagName: "a",
classNameBindings: [":btn", ":btn-pavilion-support", "subscriptionType"],
attributeBindings: ["title"],
@discourseComputed("subscribed")
i18nKey(subscribed) {
return `admin.wizard.subscription.cta.${
subscribed ? "subscribed" : "none"
}`;
},
@discourseComputed("subscribed")
icon(subscribed) {
return subscribed ? "far-life-ring" : "external-link-alt";
},
@discourseComputed("i18nKey")
title(i18nKey) {
return I18n.t(`${i18nKey}.title`);
},
@discourseComputed("i18nKey")
label(i18nKey) {
return I18n.t(`${i18nKey}.label`);
},
click() {
window.open(this.subscriptionCtaLink, "_blank").focus();
},
});

Datei anzeigen

@ -1,96 +0,0 @@
import SingleSelectComponent from "select-kit/components/single-select";
import Subscription from "../mixins/subscription";
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
const nameKey = function (feature, attribute, value) {
if (feature === "action") {
return `admin.wizard.action.${value}.label`;
} else {
return `admin.wizard.${feature}.${attribute}.${value}`;
}
};
export default SingleSelectComponent.extend(Subscription, {
classNames: ["combo-box", "wizard-subscription-selector"],
selectKitOptions: {
autoFilterable: false,
filterable: false,
showFullTitle: true,
headerComponent:
"wizard-subscription-selector/wizard-subscription-selector-header",
caretUpIcon: "caret-up",
caretDownIcon: "caret-down",
},
allowedSubscriptionTypes(feature, attribute, value) {
let attributes = this.subscriptionAttributes[feature];
if (!attributes || !attributes[attribute]) {
return ["none"];
}
let allowedTypes = [];
Object.keys(attributes[attribute]).forEach((subscriptionType) => {
let values = attributes[attribute][subscriptionType];
if (values[0] === "*" || values.includes(value)) {
allowedTypes.push(subscriptionType);
}
});
return allowedTypes;
},
@discourseComputed("feature", "attribute", "wizard.allowGuests")
content(feature, attribute) {
return filterValues(this.wizard, feature, attribute)
.map((value) => {
let allowedSubscriptionTypes = this.allowedSubscriptionTypes(
feature,
attribute,
value
);
let subscriptionRequired =
allowedSubscriptionTypes.length &&
!allowedSubscriptionTypes.includes("none");
let attrs = {
id: value,
name: I18n.t(nameKey(feature, attribute, value)),
subscriptionRequired,
};
if (subscriptionRequired) {
let subscribed = allowedSubscriptionTypes.includes(
this.subscriptionType
);
let selectorKey = subscribed ? "subscribed" : "not_subscribed";
let selectorLabel = `admin.wizard.subscription.${selectorKey}.selector`;
attrs.disabled = !subscribed;
attrs.selectorLabel = selectorLabel;
}
return attrs;
})
.sort(function (a, b) {
if (a.subscriptionType && !b.subscriptionType) {
return 1;
}
if (!a.subscriptionType && b.subscriptionType) {
return -1;
}
if (a.subscriptionType === b.subscriptionType) {
return a.subscriptionType
? a.subscriptionType.localeCompare(b.subscriptionType)
: 0;
} else {
return a.subscriptionType === "standard" ? -1 : 0;
}
});
},
modifyComponentForRow() {
return "wizard-subscription-selector/wizard-subscription-selector-row";
},
});

Datei anzeigen

@ -1,17 +0,0 @@
import SingleSelectHeaderComponent from "select-kit/components/select-kit/single-select-header";
import { computed } from "@ember/object";
import { reads } from "@ember/object/computed";
export default SingleSelectHeaderComponent.extend({
classNames: ["combo-box-header", "wizard-subscription-selector-header"],
caretUpIcon: reads("selectKit.options.caretUpIcon"),
caretDownIcon: reads("selectKit.options.caretDownIcon"),
caretIcon: computed(
"selectKit.isExpanded",
"caretUpIcon",
"caretDownIcon",
function () {
return this.selectKit.isExpanded ? this.caretUpIcon : this.caretDownIcon;
}
),
});

Datei anzeigen

@ -1,20 +0,0 @@
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
import { default as discourseComputed } from "discourse-common/utils/decorators";
export default SelectKitRowComponent.extend({
classNameBindings: ["isDisabled:disabled"],
@discourseComputed("item")
isDisabled() {
return this.item.disabled;
},
click(event) {
event.preventDefault();
event.stopPropagation();
if (!this.item.disabled) {
this.selectKit.select(this.rowValue, this.item);
}
return false;
},
});

Datei anzeigen

@ -1,139 +0,0 @@
import Component from "@ember/component";
import { action } from "@ember/object";
import { equal, notEmpty } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Component.extend({
classNameBindings: ["value.type"],
isText: equal("value.type", "text"),
isComposer: equal("value.type", "composer"),
isDate: equal("value.type", "date"),
isTime: equal("value.type", "time"),
isDateTime: equal("value.type", "date_time"),
isNumber: equal("value.type", "number"),
isCheckbox: equal("value.type", "checkbox"),
isUrl: equal("value.type", "url"),
isUpload: equal("value.type", "upload"),
isDropdown: equal("value.type", "dropdown"),
isTag: equal("value.type", "tag"),
isCategory: equal("value.type", "category"),
isGroup: equal("value.type", "group"),
isUserSelector: equal("value.type", "user_selector"),
isSubmittedAt: equal("field", "submitted_at"),
isComposerPreview: equal("value.type", "composer_preview"),
textState: "text-collapsed",
toggleText: I18n.t("admin.wizard.expand_text"),
@discourseComputed("value", "isUser", "isSubmittedAt")
hasValue(value, isUser, isSubmittedAt) {
if (isUser || isSubmittedAt) {
return value;
}
return value && value.value;
},
@discourseComputed("field", "value.type")
isUser(field, type) {
return field === "username" || field === "user" || type === "user";
},
@discourseComputed("value.type")
isLongtext(type) {
return type === "textarea" || type === "long_text";
},
@discourseComputed("value")
checkboxValue(value) {
const isCheckbox = this.get("isCheckbox");
if (isCheckbox) {
if (value.value.includes("true")) {
return true;
} else if (value.value.includes("false")) {
return false;
}
}
},
@action
expandText() {
const state = this.get("textState");
if (state === "text-collapsed") {
this.set("textState", "text-expanded");
this.set("toggleText", I18n.t("admin.wizard.collapse_text"));
} else if (state === "text-expanded") {
this.set("textState", "text-collapsed");
this.set("toggleText", I18n.t("admin.wizard.expand_text"));
}
},
@discourseComputed("value")
file(value) {
const isUpload = this.get("isUpload");
if (isUpload) {
return value.value;
}
},
@discourseComputed("value")
submittedUsers(value) {
const isUserSelector = this.get("isUserSelector");
const users = [];
if (isUserSelector) {
const userData = value.value;
const usernames = [];
if (userData.indexOf(",")) {
usernames.push(...userData.split(","));
usernames.forEach((u) => {
const user = {
username: u,
url: `/u/${u}`,
};
users.push(user);
});
}
}
return users;
},
@discourseComputed("isUser", "field", "value")
username(isUser, field, value) {
if (isUser) {
return value.username;
}
if (field === "username") {
return value.value;
}
return null;
},
showUsername: notEmpty("username"),
@discourseComputed("username")
userProfileUrl(username) {
if (username) {
return `/u/${username}`;
}
return "/";
},
@discourseComputed("value")
categoryUrl(value) {
const isCategory = this.get("isCategory");
if (isCategory) {
return `/c/${value.value}`;
}
},
@discourseComputed("value")
groupUrl(value) {
const isGroup = this.get("isGroup");
if (isGroup) {
return `/g/${value.value}`;
}
},
});

Datei anzeigen

@ -1,68 +1,64 @@
import discourseComputed from "discourse-common/utils/decorators"; import { default as discourseComputed, on } from 'discourse-common/utils/decorators';
import { notEmpty } from "@ember/object/computed"; import { notEmpty } from "@ember/object/computed";
import { userProperties } from "../lib/wizard"; import { userProperties } from '../lib/wizard';
import { scheduleOnce } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
const excludedUserProperties = ["profile_background", "card_background"];
export default Component.extend({ export default Component.extend({
classNames: "wizard-text-editor", classNames: 'wizard-text-editor',
barEnabled: true, barEnabled: true,
previewEnabled: true, previewEnabled: true,
fieldsEnabled: true, fieldsEnabled: true,
hasWizardFields: notEmpty("wizardFieldList"), hasWizardFields: notEmpty('wizardFieldList'),
hasWizardActions: notEmpty("wizardActionList"), hasWizardActions: notEmpty('wizardActionList'),
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
if (!this.barEnabled) { if (!this.barEnabled) {
scheduleOnce("afterRender", () => { scheduleOnce('afterRender', () => {
$(this.element).find(".d-editor-button-bar").addClass("hidden"); $(this.element).find('.d-editor-button-bar').addClass('hidden');
}); });
} }
}, },
@discourseComputed("forcePreview") @discourseComputed('forcePreview')
previewLabel(forcePreview) { previewLabel(forcePreview) {
return I18n.t("admin.wizard.editor.preview", { return I18n.t("admin.wizard.editor.preview", {
action: I18n.t(`admin.wizard.editor.${forcePreview ? "hide" : "show"}`), action: I18n.t(`admin.wizard.editor.${forcePreview ? 'hide' : 'show'}`)
}); });
}, },
@discourseComputed("showPopover") @discourseComputed('showPopover')
popoverLabel(showPopover) { popoverLabel(showPopover) {
return I18n.t("admin.wizard.editor.popover", { return I18n.t("admin.wizard.editor.popover", {
action: I18n.t(`admin.wizard.editor.${showPopover ? "hide" : "show"}`), action: I18n.t(`admin.wizard.editor.${showPopover ? 'hide' : 'show'}`)
}); });
}, },
@discourseComputed() @discourseComputed()
userPropertyList() { userPropertyList() {
return userProperties return userProperties.map((f) => ` u{${f}}`);
.filter((f) => !excludedUserProperties.includes(f))
.map((f) => ` u{${f}}`);
}, },
@discourseComputed("wizardFields") @discourseComputed('wizardFields')
wizardFieldList(wizardFields) { wizardFieldList(wizardFields) {
return (wizardFields || []).map((f) => ` w{${f.id}}`); return wizardFields.map((f) => ` w{${f.id}}`);
}, },
@discourseComputed("wizardActions") @discourseComputed('wizardActions')
wizardActionList(wizardActions) { wizardActionList(wizardActions) {
return (wizardActions || []).map((a) => ` w{${a.id}}`); return wizardActions.map((a) => ` w{${a.id}}`);
}, },
actions: { actions: {
togglePreview() { togglePreview() {
this.toggleProperty("forcePreview"); this.toggleProperty('forcePreview');
}, },
togglePopover() { togglePopover() {
this.toggleProperty("showPopover"); this.toggleProperty('showPopover');
}, }
}, }
}); });

Datei anzeigen

@ -1,4 +1,4 @@
import ValueList from "admin/components/value-list"; import ValueList from 'admin/components/value-list';
export default ValueList.extend({ export default ValueList.extend({
_saveValues() { _saveValues() {
@ -8,5 +8,5 @@ export default ValueList.extend({
} }
this.onChange(this.collection.join(this.inputDelimiter || "\n")); this.onChange(this.collection.join(this.inputDelimiter || "\n"));
}, }
}); })

Datei anzeigen

@ -1,7 +1,3 @@
{{#if currentUser.admin}} {{#if currentUser.admin}}
{{nav-item route="adminWizards" label="admin.wizard.nav_label"}} {{nav-item route='adminWizards' label='admin.wizard.nav_label'}}
{{#if wizardErrorNotice}}
{{d-icon "exclaimation-circle"}}
{{/if}}
{{/if}} {{/if}}

Datei anzeigen

@ -1,15 +0,0 @@
<h3>{{i18n "admin.wizard.category_settings.custom_wizard.title"}}</h3>
<section class="field new-topic-wizard">
<label for="new-topic-wizard">
{{i18n "admin.wizard.category_settings.custom_wizard.create_topic_wizard"}}
</label>
<div class="controls">
{{combo-box
value=wizardListVal
content=wizardList
onChange=(action "changeWizard")
options=(hash none="admin.wizard.select")
}}
</div>
</section>

Datei anzeigen

@ -1,24 +0,0 @@
import CustomWizardAdmin from "../../models/custom-wizard-admin";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default {
setupComponent(attrs, component) {
CustomWizardAdmin.all()
.then((result) => {
component.set("wizardList", result);
})
.catch(popupAjaxError);
component.set(
"wizardListVal",
attrs?.category?.custom_fields?.create_topic_wizard
);
},
actions: {
changeWizard(wizard) {
this.set("wizardListVal", wizard);
this.set("category.custom_fields.create_topic_wizard", wizard);
},
},
};

Datei anzeigen

@ -1,10 +1,7 @@
{{#each site.complete_custom_wizard as |wizard|}} {{#each site.complete_custom_wizard as |wizard|}}
<div class="row"> <div class='row'>
<div class="alert alert-info alert-wizard"> <div class='alert alert-info alert-wizard'>
<a href={{wizard.url}}>{{i18n <a href="{{wizard.url}}">{{i18n 'wizard.complete_custom' name=wizard.name}}</a>
"wizard.complete_custom"
name=wizard.name
}}</a>
</div> </div>
</div> </div>
{{/each}} {{/each}}

Datei anzeigen

@ -1,7 +1,6 @@
export default { export default {
shouldRender(_, ctx) { shouldRender(_, ctx) {
return ( return ctx.siteSettings.custom_wizard_enabled &&
ctx.siteSettings.custom_wizard_enabled && ctx.site.complete_custom_wizard ctx.site.complete_custom_wizard;
); }
}, }
};

Datei anzeigen

@ -1,130 +1,93 @@
import { ajax } from "discourse/lib/ajax"; import { ajax } from 'discourse/lib/ajax';
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from 'discourse/lib/ajax-error';
import CustomWizardApi from "../models/custom-wizard-api"; import CustomWizardApi from '../models/custom-wizard-api';
import { default as discourseComputed } from "discourse-common/utils/decorators"; import { default as discourseComputed } from 'discourse-common/utils/decorators';
import { and, equal, not } from "@ember/object/computed"; import { not, and, equal } from "@ember/object/computed";
import { selectKitContent } from "../lib/wizard"; import { selectKitContent } from '../lib/wizard';
import { underscore } from "@ember/string";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { inject as service } from "@ember/service";
export default Controller.extend({ export default Controller.extend({
router: service(), queryParams: ['refresh_list'],
queryParams: ["refresh_list"],
loadingSubscriptions: false, loadingSubscriptions: false,
notAuthorized: not("api.authorized"), notAuthorized: not('api.authorized'),
endpointMethods: selectKitContent(["PUT", "POST", "PATCH", "DELETE"]), endpointMethods: selectKitContent(['GET', 'PUT', 'POST', 'PATCH', 'DELETE']),
showRemove: not("isNew"), showRemove: not('isNew'),
showRedirectUri: and("threeLeggedOauth", "api.name"), showRedirectUri: and('threeLeggedOauth', 'api.name'),
responseIcon: null, responseIcon: null,
contentTypes: selectKitContent([ contentTypes: selectKitContent(['application/json', 'application/x-www-form-urlencoded']),
"application/json", successCodes: selectKitContent([100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 303, 304, 305, 306, 307, 308]),
"application/x-www-form-urlencoded",
]),
successCodes: selectKitContent([
100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301,
302, 303, 303, 304, 305, 306, 307, 308,
]),
@discourseComputed( @discourseComputed('saveDisabled', 'api.authType', 'api.authUrl', 'api.tokenUrl', 'api.clientId', 'api.clientSecret', 'threeLeggedOauth')
"saveDisabled", authDisabled(saveDisabled, authType, authUrl, tokenUrl, clientId, clientSecret, threeLeggedOauth) {
"api.authType", if (saveDisabled || !authType || !tokenUrl || !clientId || !clientSecret) return true;
"api.authUrl", if (threeLeggedOauth) return !authUrl;
"api.tokenUrl",
"api.clientId",
"api.clientSecret",
"threeLeggedOauth"
)
authDisabled(
saveDisabled,
authType,
authUrl,
tokenUrl,
clientId,
clientSecret,
threeLeggedOauth
) {
if (saveDisabled || !authType || !tokenUrl || !clientId || !clientSecret) {
return true;
}
if (threeLeggedOauth) {
return !authUrl;
}
return false; return false;
}, },
@discourseComputed("api.name", "api.authType") @discourseComputed('api.name', 'api.authType')
saveDisabled(name, authType) { saveDisabled(name, authType) {
return !name || !authType; return !name || !authType;
}, },
authorizationTypes: selectKitContent(["none", "basic", "oauth_2", "oauth_3"]), authorizationTypes: selectKitContent(['none', 'basic', 'oauth_2', 'oauth_3']),
isBasicAuth: equal("api.authType", "basic"), isBasicAuth: equal('api.authType', 'basic'),
@discourseComputed("api.authType") @discourseComputed('api.authType')
isOauth(authType) { isOauth(authType) {
return authType && authType.indexOf("oauth") > -1; return authType && authType.indexOf('oauth') > -1;
}, },
twoLeggedOauth: equal("api.authType", "oauth_2"), twoLeggedOauth: equal('api.authType', 'oauth_2'),
threeLeggedOauth: equal("api.authType", "oauth_3"), threeLeggedOauth: equal('api.authType', 'oauth_3'),
@discourseComputed("api.isNew")
nameClass(isNew) {
return isNew ? "new" : "saved";
},
actions: { actions: {
addParam() { addParam() {
this.get("api.authParams").pushObject({}); this.get('api.authParams').pushObject({});
}, },
removeParam(param) { removeParam(param) {
this.get("api.authParams").removeObject(param); this.get('api.authParams').removeObject(param);
}, },
addEndpoint() { addEndpoint() {
this.get("api.endpoints").pushObject({}); this.get('api.endpoints').pushObject({});
}, },
removeEndpoint(endpoint) { removeEndpoint(endpoint) {
this.get("api.endpoints").removeObject(endpoint); this.get('api.endpoints').removeObject(endpoint);
}, },
authorize() { authorize() {
const api = this.get("api"); const api = this.get('api');
const { name, authType, authUrl, authParams } = api; const { name, authType, authUrl, authParams } = api;
this.set("authErrorMessage", ""); this.set('authErrorMessage', '');
if (authType === "oauth_2") { if (authType === 'oauth_2') {
this.set("authorizing", true); this.set('authorizing', true);
ajax(`/admin/wizards/apis/${underscore(name)}/authorize`) ajax(`/admin/wizards/apis/${name.underscore()}/authorize`).catch(popupAjaxError)
.catch(popupAjaxError) .then(result => {
.then((result) => {
if (result.success) { if (result.success) {
this.set("api", CustomWizardApi.create(result.api)); this.set('api', CustomWizardApi.create(result.api));
} else if (result.failed && result.message) { } else if (result.failed && result.message) {
this.set("authErrorMessage", result.message); this.set('authErrorMessage', result.message);
} else { } else {
this.set("authErrorMessage", "Authorization Failed"); this.set('authErrorMessage', 'Authorization Failed');
} }
setTimeout(() => { setTimeout(() => {
this.set("authErrorMessage", ""); this.set('authErrorMessage', '');
}, 6000); }, 6000);
}) }).finally(() => this.set('authorizing', false));
.finally(() => this.set("authorizing", false)); } else if (authType === 'oauth_3') {
} else if (authType === "oauth_3") { let query = '?';
let query = "?";
query += `client_id=${api.clientId}`; query += `client_id=${api.clientId}`;
query += `&redirect_uri=${encodeURIComponent(api.redirectUri)}`; query += `&redirect_uri=${encodeURIComponent(api.redirectUri)}`;
query += `&response_type=code`; query += `&response_type=code`;
if (authParams) { if (authParams) {
authParams.forEach((p) => { authParams.forEach(p => {
query += `&${p.key}=${encodeURIComponent(p.value)}`; query += `&${p.key}=${encodeURIComponent(p.value)}`;
}); });
} }
@ -134,129 +97,119 @@ export default Controller.extend({
}, },
save() { save() {
const api = this.get("api"); const api = this.get('api');
const name = api.name; const name = api.name;
const authType = api.authType; const authType = api.authType;
let refreshList = false;
let error; let error;
if (!name || !authType) { if (!name || !authType) return;
return;
}
let data = { let data = {
auth_type: authType, auth_type: authType
}; };
if (api.title) { if (api.title) data['title'] = api.title;
data["title"] = api.title;
const originalTitle = this.get('api.originalTitle');
if (api.get('isNew') || (originalTitle && (api.title !== originalTitle))) {
refreshList = true;
} }
if (api.get("isNew")) { if (api.get('isNew')) {
data["new"] = true; data['new'] = true;
} };
let requiredParams; let requiredParams;
if (authType === "basic") { if (authType === 'basic') {
requiredParams = ["username", "password"]; requiredParams = ['username', 'password'];
} else if (authType === "oauth_2") { } else if (authType === 'oauth_2') {
requiredParams = ["tokenUrl", "clientId", "clientSecret"]; requiredParams = ['tokenUrl', 'clientId', 'clientSecret'];
} else if (authType === "oauth_3") { } else if (authType === 'oauth_3') {
requiredParams = ["authUrl", "tokenUrl", "clientId", "clientSecret"]; requiredParams = ['authUrl', 'tokenUrl', 'clientId', 'clientSecret'];
} }
if (requiredParams) { if (requiredParams) {
for (let rp of requiredParams) { for (let rp of requiredParams) {
if (!api[rp]) { if (!api[rp]) {
let key = rp.replace("auth", ""); let key = rp.replace('auth', '');
error = `${I18n.t( error = `${I18n.t(`admin.wizard.api.auth.${key.underscore()}`)} is required for ${authType}`;
`admin.wizard.api.auth.${underscore(key)}`
)} is required for ${authType}`;
break; break;
} }
data[underscore(rp)] = api[rp]; data[rp.underscore()] = api[rp];
} }
} }
const params = api.authParams; const params = api.authParams;
if (params.length) { if (params.length) {
data["auth_params"] = JSON.stringify(params); data['auth_params'] = JSON.stringify(params);
} }
const endpoints = api.endpoints; const endpoints = api.endpoints;
if (endpoints.length) { if (endpoints.length) {
for (let e of endpoints) { for (let e of endpoints) {
if (!e.name) { if (!e.name) {
error = "Every endpoint must have a name"; error = 'Every endpoint must have a name';
break; break;
} }
} }
data["endpoints"] = JSON.stringify(endpoints); data['endpoints'] = JSON.stringify(endpoints);
} }
if (error) { if (error) {
this.set("error", error); this.set('error', error);
setTimeout(() => { setTimeout(() => {
this.set("error", ""); this.set('error', '');
}, 6000); }, 6000);
return; return;
} }
this.set("updating", true); this.set('updating', true);
ajax(`/admin/wizards/api/${underscore(name)}`, { ajax(`/admin/wizards/api/${name.underscore()}`, {
type: "PUT", type: 'PUT',
data, data
}) }).catch(popupAjaxError)
.catch(popupAjaxError) .then(result => {
.then((result) => {
if (result.success) { if (result.success) {
this.send("afterSave", result.api.name); this.send('afterSave', result.api.name);
} else { } else {
this.set("responseIcon", "times"); this.set('responseIcon', 'times');
} }
}) }).finally(() => this.set('updating', false));
.finally(() => this.set("updating", false));
}, },
remove() { remove() {
const name = this.get("api.name"); const name = this.get('api.name');
if (!name) { if (!name) return;
return;
}
this.set("updating", true); this.set('updating', true);
ajax(`/admin/wizards/api/${underscore(name)}`, { ajax(`/admin/wizards/api/${name.underscore()}`, {
type: "DELETE", type: 'DELETE'
}) }).catch(popupAjaxError)
.catch(popupAjaxError) .then(result => {
.then((result) => {
if (result.success) { if (result.success) {
this.send("afterDestroy"); this.send('afterDestroy');
} }
}) }).finally(() => this.set('updating', false));
.finally(() => this.set("updating", false));
}, },
clearLogs() { clearLogs() {
const name = this.get("api.name"); const name = this.get('api.name');
if (!name) { if (!name) return;
return;
}
ajax(`/admin/wizards/api/${underscore(name)}/logs`, { ajax(`/admin/wizards/api/${name.underscore()}/logs`, {
type: "DELETE", type: 'DELETE'
}) }).catch(popupAjaxError)
.catch(popupAjaxError) .then(result => {
.then((result) => {
if (result.success) { if (result.success) {
this.router.transitionTo("adminWizardsApis").then(() => { this.transitionToRoute('adminWizardsApis').then(() => {
this.send("refreshModel"); this.send('refreshModel');
}); });
} }
}) }).finally(() => this.set('updating', false));
.finally(() => this.set("updating", false)); }
}, }
},
}); });

Datei anzeigen

@ -1,53 +0,0 @@
import Controller from "@ember/controller";
import CustomWizardCustomField from "../models/custom-wizard-custom-field";
export default Controller.extend({
messageKey: "create",
fieldKeys: ["klass", "type", "name", "serializers"],
documentationUrl: "https://discourse.pluginmanager.org/t/custom-fields",
actions: {
addField() {
this.get("customFields").unshiftObject(
CustomWizardCustomField.create({ edit: true })
);
},
saveField(field) {
return CustomWizardCustomField.saveField(field).then((result) => {
if (result.success) {
this.setProperties({
messageKey: "saved",
messageType: "success",
});
} else {
if (result.messages) {
this.setProperties({
messageKey: "error",
messageType: "error",
messageOpts: { messages: result.messages },
});
}
}
setTimeout(
() =>
this.setProperties({
messageKey: "create",
messageType: null,
messageOpts: null,
}),
10000
);
return result;
});
},
removeField(field) {
return CustomWizardCustomField.destroyField(field).then(() => {
this.get("customFields").removeObject(field);
});
},
},
});

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen