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

Merge branch 'main' into dependabot/npm_and_yarn/async-2.6.4

Dieser Commit ist enthalten in:
Angus McLeod 2023-12-05 08:20:20 +01:00 committet von GitHub
Commit 80fedfafd1
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
491 geänderte Dateien mit 20921 neuen und 13682 gelöschten Zeilen

Datei anzeigen

@ -1,2 +1,4 @@
3.2.0.beta2: 1ee2f7d8babafe32912372fbbfa50c89f5b09ba9
3.1.999: 1f35b80f85e5fd1efb7f4851f0845700432febdc
2.7.99: e07a57e398b6b1676ab42a7e34467556fca5416b
2.5.1: bb85b3a0d2c0ab6b59bcb405731c39089ec6731c

13
.github/workflows/discourse-plugin.yml gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,13 @@
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,56 +0,0 @@
name: Linting
on:
push:
branches:
- master
- main
- stable
pull_request:
schedule:
- cron: '0 0 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v1
with:
node-version: 14
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
- name: Setup bundler
run: gem install bundler -v 2.1.4 --no-doc
- name: Setup gems
run: bundle install --jobs 4
- name: Yarn install
run: yarn install --dev
- name: ESLint
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts
- name: Prettier
run: |
yarn prettier -v
if [ -d "assets" ]; then \
yarn prettier --list-different "assets/**/*.{scss,js,es6}" ; \
fi
if [ -d "test" ]; then \
yarn prettier --list-different "test/**/*.{js,es6}" ; \
fi
- name: Ember template lint
run: yarn ember-template-lint assets/javascripts
- name: Rubocop
run: bundle exec rubocop .

Datei anzeigen

@ -1,139 +0,0 @@
name: Plugin Tests
on:
push:
branches:
- stable
- master
- main
pull_request:
schedule:
- cron: '0 */12 * * *'
jobs:
build:
name: ${{ matrix.build_type }}
runs-on: ubuntu-latest
timeout-minutes: 60
env:
DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test
PGHOST: localhost
PGUSER: discourse
PGPASSWORD: discourse
strategy:
fail-fast: false
matrix:
build_type: ["backend", "frontend"]
ruby: ["2.7"]
postgres: ["12"]
redis: ["6.x"]
services:
postgres:
image: postgres:${{ matrix.postgres }}
ports:
- 5432:5432
env:
POSTGRES_USER: discourse
POSTGRES_PASSWORD: discourse
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: haya14busa/action-cond@v1
id: discourse_branch
with:
cond: ${{ github.base_ref == 'stable' }}
if_true: "stable"
if_false: "tests-passed"
- uses: actions/checkout@v3
with:
repository: discourse/discourse
ref: ${{ steps.discourse_branch.outputs.value }}
fetch-depth: 1
- name: Install plugin
uses: actions/checkout@v2
with:
path: plugins/${{ github.event.repository.name }}
ref: "${{ github.head_ref }}"
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"
- name: Setup packages
run: |
sudo apt-get update
sudo apt-get -yqq install postgresql-client libpq-dev gifsicle jpegoptim optipng jhead
wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh
- name: Update imagemagick
if: matrix.build_type == 'backend'
run: |
wget https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-imagemagick
chmod +x install-imagemagick
sudo ./install-imagemagick
- name: Setup redis
uses: shogo82148/actions-setup-redis@v1
with:
redis-version: ${{ matrix.redis }}
- name: Setup ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Lint English locale
if: matrix.build_type == 'backend'
run: bundle exec ruby script/i18n_lint.rb "plugins/${{ steps.repo-name.outputs.value }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.os }}-yarn-
- name: Yarn install
run: yarn install --dev
- name: Migrate database
run: |
bin/rake db:create
bin/rake db:migrate
- name: Plugin RSpec with Coverage
if: matrix.build_type == 'backend'
run: |
if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ]
then
cp plugins/${{ steps.repo-name.outputs.value }}/.simplecov .simplecov
export COVERAGE=1
fi
bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}]
- name: Plugin QUnit
if: matrix.build_type == 'frontend'
run: LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 QUNIT_SKIP_CORE=1 bin/rake qunit:test['600000','/w/qunit']
timeout-minutes: 10

3
.gitignore gevendort
Datei anzeigen

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

Datei anzeigen

@ -1,2 +1,11 @@
inherit_gem:
rubocop-discourse: default.yml
RSpec/ContextWording:
Enabled: false
RSpec/DescribeClass:
Enabled: false
Discourse/TimeEqMatcher:
Enabled: false

Datei anzeigen

@ -1,4 +1,4 @@
All code in this repository is Copyright 2018 by Angus McLeod.
All code in this repository is Copyright 2023 by Angus McLeod.
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

Datei anzeigen

@ -2,31 +2,32 @@ GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
parallel (1.20.1)
parser (3.0.1.0)
json (2.6.2)
parallel (1.22.1)
parser (3.1.2.1)
ast (~> 2.4.1)
rainbow (3.0.0)
regexp_parser (2.1.1)
rainbow (3.1.1)
regexp_parser (2.6.0)
rexml (3.2.5)
rubocop (1.12.1)
rubocop (1.36.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.0.0.0)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.2.0, < 2.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.4.1)
parser (>= 2.7.1.5)
rubocop-discourse (2.4.1)
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.2.0)
rubocop (~> 1.0)
rubocop-ast (>= 1.1.0)
rubocop-rspec (2.13.2)
rubocop (~> 1.33)
ruby-progressbar (1.11.0)
unicode-display_width (2.0.0)
unicode-display_width (2.3.0)
PLATFORMS
ruby

Datei anzeigen

@ -1,3 +1,31 @@
# discourse-custom-wizard
# Discourse Custom Wizard Plugin
See further: https://thepavilion.io/c/knowledge/discourse/custom-wizard/118
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.
<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://pavilion.tech/products/discourse-custom-wizard-plugin/support/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://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/), 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://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/step-settings)
- [Field Settings](https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/field-settings)
- [Conditional Settings](https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/conditional-settings)
- [Field Interpolation](https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/field-interpolation)
- [Handling Dates and Times](https://coop.pavilion.tech/t/1708)
## Support
- [Report an issue](https://pavilion.tech/products/discourse-custom-wizard-plugin/support/bug-report)
## Statistics
For improved service and development, this plugin collects some generalised quantitative data related to version and usage. No personal or sensitive information is gathered. Please email contact@pavilion.tech if you have any questions or concerns about our data collection.

7
SECURITY.md Normale Datei
Datei anzeigen

@ -0,0 +1,7 @@
# 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

@ -2,9 +2,6 @@
class CustomWizard::AdminController < ::Admin::AdminController
before_action :ensure_admin
def index
end
private
def find_wizard

Datei anzeigen

@ -20,6 +20,10 @@ class CustomWizard::AdminApiController < CustomWizard::AdminController
raise Discourse::InvalidParameters, "An API with that name already exists: '#{current.title || current.name}'"
end
unless subscription.includes?(:api, :all)
raise Discourse::InvalidParameters, "Your subscription doesn't include API features."
end
PluginStoreRow.transaction do
CustomWizard::Api.set(api_params[:name], title: api_params[:title])
@ -130,4 +134,8 @@ class CustomWizard::AdminApiController < CustomWizard::AdminController
@auth_data ||= auth_data
end
def subscription
@subscription ||= CustomWizard::Subscription.new
end
end

Datei anzeigen

@ -1,7 +1,9 @@
# frozen_string_literal: true
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
def index
render_json_dump(custom_field_list)
render_json_dump(
custom_fields: custom_field_list
)
end
def update

Datei anzeigen

@ -1,9 +1,44 @@
# frozen_string_literal: true
class CustomWizard::AdminLogsController < CustomWizard::AdminController
before_action :find_wizard, except: [:index]
def index
render_serialized(
CustomWizard::Log.list(params[:page].to_i, params[:limit].to_i),
CustomWizard::LogSerializer
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

@ -13,12 +13,21 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
def show
render_json_dump(
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false),
submissions: ActiveModel::ArraySerializer.new(ordered_submissions, each_serializer: CustomWizard::SubmissionSerializer)
submissions: ActiveModel::ArraySerializer.new(
submission_list.submissions,
each_serializer: CustomWizard::SubmissionSerializer
),
total: submission_list.total
)
end
def download
send_data ordered_submissions.to_json,
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"
@ -26,7 +35,7 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
protected
def ordered_submissions
CustomWizard::Submission.list(@wizard, order_by: 'id')
def submission_list
CustomWizard::Submission.list(@wizard, page: params[:page].to_i)
end
end

Datei anzeigen

@ -0,0 +1,18 @@
# frozen_string_literal: true
class CustomWizard::SubscriptionController < ::Admin::AdminController
before_action :ensure_admin
def index
if params[:update_from_remote]
subscription = CustomWizard::Subscription.new(true)
else
subscription = CustomWizard::Subscription.new
end
render_json_dump(
subscribed: subscription.subscribed?,
subscription_type: subscription.type,
subscription_attributes: CustomWizard::Subscription.attributes,
)
end
end

Datei anzeigen

@ -88,6 +88,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:title,
:key,
:banner,
:banner_upload_id,
:raw_description,
:required_data_message,
:force_final,
@ -99,6 +100,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:index,
:label,
:image,
:image_upload_id,
:description,
:required,
:key,
@ -112,6 +114,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:property,
:preview_template,
:placeholder,
:can_create_tag,
prefill: mapped_params,
content: mapped_params,
condition: mapped_params,
@ -161,7 +164,9 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
mentionable_level: mapped_params,
messageable_level: mapped_params,
visibility_level: mapped_params,
members_visibility_level: mapped_params
members_visibility_level: mapped_params,
add_event: mapped_params,
add_location: mapped_params
]
)
end

Datei anzeigen

@ -1,6 +1,5 @@
# frozen_string_literal: true
class CustomWizard::StepsController < ::ApplicationController
before_action :ensure_logged_in
class CustomWizard::StepsController < ::CustomWizard::WizardClientController
before_action :ensure_can_update
def update
@ -22,7 +21,7 @@ class CustomWizard::StepsController < ::ApplicationController
if updater.success?
wizard_id = update_params[:wizard_id]
builder = CustomWizard::Builder.new(wizard_id, current_user)
builder = CustomWizard::Builder.new(wizard_id, current_user, guest_id)
@wizard = builder.build(force: true)
current_step = @wizard.find_step(update[:step_id])
@ -85,7 +84,6 @@ class CustomWizard::StepsController < ::ApplicationController
private
def ensure_can_update
@builder = CustomWizard::Builder.new(update_params[:wizard_id], current_user)
raise Discourse::InvalidParameters.new(:wizard_id) if @builder.template.nil?
raise Discourse::InvalidAccess.new if !@builder.wizard || !@builder.wizard.can_access?

Datei anzeigen

@ -1,41 +1,12 @@
# frozen_string_literal: true
class CustomWizard::WizardController < ::ActionController::Base
helper ApplicationHelper
include CurrentUser
include CanonicalURL::ControllerExtensions
include GlobalPath
prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views'))
layout :set_wizard_layout
before_action :preload_wizard_json
before_action :ensure_plugin_enabled
before_action :ensure_logged_in, only: [:skip]
helper_method :wizard_page_title
helper_method :wizard_theme_id
helper_method :wizard_theme_lookup
helper_method :wizard_theme_translations_lookup
def set_wizard_layout
action_name === 'qunit' ? 'qunit' : 'wizard'
end
def index
respond_to do |format|
format.json do
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
format.html do
render "default/empty"
end
end
end
def skip
params.require(:wizard_id)
@ -57,69 +28,12 @@ class CustomWizard::WizardController < ::ActionController::Base
render json: result
end
def qunit
raise Discourse::InvalidAccess.new if Rails.env.production?
respond_to do |format|
format.html do
render "default/empty"
end
end
end
protected
def ensure_logged_in
raise Discourse::NotLoggedIn.new unless current_user.present?
end
def guardian
@guardian ||= Guardian.new(current_user, request)
end
def wizard
@wizard ||= begin
builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user)
return nil unless builder.present?
opts = {}
opts[:reset] = params[:reset]
builder.build(opts, params)
end
end
def wizard_page_title
wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title')
end
def wizard_theme_id
wizard ? wizard.theme_id : nil
end
def wizard_theme_lookup(name)
Theme.lookup_field(wizard_theme_id, view_context.mobile_view? ? :mobile : :desktop, name)
end
def wizard_theme_translations_lookup
Theme.lookup_field(wizard_theme_id, :translations, I18n.locale)
end
def preload_wizard_json
return if request.xhr? || request.format.json?
return if request.method != "GET"
store_preloaded("siteSettings", SiteSetting.client_settings_json)
end
def store_preloaded(key, json)
@preloaded ||= {}
@preloaded[key] = json.gsub("</", "<\\/")
end
private
def ensure_plugin_enabled
unless SiteSetting.custom_wizard_enabled
redirect_to path("/")
return nil unless @builder.present?
@builder.build({ reset: params[:reset] }, params)
end
end
end

Datei anzeigen

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

Datei anzeigen

@ -1,16 +1,35 @@
# frozen_string_literal: true
class CustomWizard::SubmissionSerializer < ApplicationSerializer
attributes :id,
:username,
:fields,
:submitted_at,
:route_to,
:redirect_on_complete,
:redirect_to
:user
def username
object.user.present? ?
object.user.username :
I18n.t('admin.wizard.submission.no_user', user_id: object.user_id)
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

@ -17,6 +17,7 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
:property,
:content,
:tag_groups,
:can_create_tag,
:validations,
:max_length,
:char_counter,
@ -42,13 +43,8 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
object.value
end
def i18n_key
@i18n_key ||= "wizard.step.#{object.step.id}.fields.#{object.id}".underscore
end
def label
return object.label if object.label.present?
I18n.t("#{object.key || i18n_key}.label", default: '')
I18n.t("#{i18n_key}.label", default: object.label, base_url: Discourse.base_url)
end
def include_label?
@ -56,14 +52,21 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
end
def description
return object.description if object.description.present?
I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url)
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
@ -72,15 +75,6 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
object.image.present?
end
def placeholder
return object.placeholder if object.placeholder.present?
I18n.t("#{object.key || i18n_key}.placeholder", default: '')
end
def include_placeholder?
placeholder.present?
end
def file_types
object.file_types
end
@ -105,6 +99,10 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
object.tag_groups
end
def can_create_tag
object.can_create_tag
end
def validations
validations = {}
object.validations&.each do |type, props|
@ -127,4 +125,14 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer
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

@ -9,13 +9,10 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
:completed,
:required,
:permitted,
:uncategorized_category_id,
:categories,
:resume_on_revisit
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects
def completed
object.completed?
@ -46,24 +43,4 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
def include_steps?
!include_completed?
end
def include_categories?
object.needs_categories
end
def include_groups?
object.needs_groups
end
def uncategorized_category_id
SiteSetting.uncategorized_category_id
end
def include_uncategorized_category_id?
object.needs_categories
end
def categories
object.categories.map { |c| c.to_h }
end
end

Datei anzeigen

@ -39,13 +39,8 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer
object.previous.present?
end
def i18n_key
@i18n_key ||= "wizard.step.#{object.id}".underscore
end
def title
return PrettyText.cook(object.title) if object.title
PrettyText.cook(I18n.t("#{object.key || i18n_key}.title", default: ''))
I18n.t("#{i18n_key}.title", default: object.title, base_url: Discourse.base_url)
end
def include_title?
@ -53,8 +48,7 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer
end
def description
return object.description if object.description
PrettyText.cook(I18n.t("#{object.key || i18n_key}.description", default: '', base_url: Discourse.base_url))
I18n.t("#{i18n_key}.description", default: object.description, base_url: Discourse.base_url)
end
def include_description?
@ -80,4 +74,10 @@ class CustomWizard::StepSerializer < ::ApplicationSerializer
def final
object.final?
end
protected
def i18n_key
@i18n_key ||= "#{object.wizard.id}.#{object.id}".underscore
end
end

Datei anzeigen

@ -1,62 +0,0 @@
<html>
<head>
<%= discourse_color_scheme_stylesheets %>
<%= discourse_stylesheet_link_tag :wizard, theme_id: nil %>
<%= discourse_stylesheet_link_tag :wizard_custom %>
<%- if wizard_theme_id.present? %>
<%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme), theme_id: wizard_theme_id %>
<%- end %>
<%= preload_script "locales/#{I18n.locale}" %>
<%= 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" %>
<script src="<%= ExtraLocalesController.url("wizard") %>"></script>
<%= csrf_meta_tags %>
<%- unless customization_disabled? %>
<%= wizard_theme_translations_lookup %>
<%= raw wizard_theme_lookup("head_tag") %>
<%- end %>
<%= server_plugin_outlet "custom_wizard" %>
<%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
<meta name="discourse_theme_id" content="<%= wizard_theme_id %>">
<meta name="discourse-base-uri" content="<%= Discourse.base_path %>">
<%= render partial: "layouts/head" %>
<title><%= wizard_page_title %></title>
</head>
<body class='custom-wizard'>
<%- unless customization_disabled? %>
<%= raw wizard_theme_lookup("header") %>
<%- end %>
<div id='custom-wizard-main'></div>
<%- unless customization_disabled? %>
<%= raw wizard_theme_lookup("body_tag") %>
<%- end %>
<%- if current_user %>
<%= preload_script 'wizard-custom-start' %>
<%- else %>
<%= preload_script 'wizard-custom-guest' %>
<%- end %>
<div id="svg-sprites" style="display:none;">
<div class="fontawesome">
<%= raw SvgSprite.bundle %>
</div>
</div>
<div class="hidden" id="data-preloaded-wizard" data-preloaded-wizard="<%= preloaded_json %>"></div>
</body>
</html>

Datei anzeigen

@ -3,27 +3,12 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed";
import I18n from "I18n";
const generateContent = function (array, type) {
return array.map((key) => ({
id: key,
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`),
}));
};
export default Component.extend({
tagName: "tr",
topicSerializers: ["topic_view", "topic_list_item"],
postSerializers: ["post"],
groupSerializers: ["basic_group"],
categorySerializers: ["basic_category"],
klassContent: generateContent(
["topic", "post", "group", "category"],
"klass"
),
typeContent: generateContent(
["string", "boolean", "integer", "json"],
"type"
),
showInputs: or("field.new", "field.edit"),
classNames: ["custom-field-input"],
loading: or("saving", "destroying"),
@ -40,9 +25,13 @@ export default Component.extend({
const serializers = this.get(`${klass}Serializers`);
if (serializers) {
return generateContent(serializers, "serializers");
} else {
return [];
return serializers.reduce((result, key) => {
result.push({
id: key,
name: I18n.t(`admin.wizard.custom_field.serializers.${key}`),
});
return result;
}, []);
}
},

Datei anzeigen

@ -3,11 +3,11 @@ import {
observes,
} from "discourse-common/utils/decorators";
import { renderAvatar } from "discourse/helpers/user-avatar";
import userSearch from "../lib/user-search";
import WizardI18n from "../lib/wizard-i18n";
import userSearch from "discourse/lib/user-search";
import I18n from "I18n";
import Handlebars from "handlebars";
import { isEmpty } from "@ember/utils";
import TextField from "@ember/component/text-field";
import TextField from "discourse/components/text-field";
const template = function (params) {
const options = params.options;
@ -41,7 +41,7 @@ export default TextField.extend({
@computed("placeholderKey")
placeholder(placeholderKey) {
return placeholderKey ? WizardI18n(placeholderKey) : "";
return placeholderKey ? I18n.t(placeholderKey) : "";
},
@observes("usernames")

Datei anzeigen

@ -1,5 +1,6 @@
import ComposerEditor from "discourse/components/composer-editor";
import {
bind,
default as discourseComputed,
on,
} from "discourse-common/utils/decorators";
@ -8,16 +9,21 @@ 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 "../models/site";
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({
layoutName: "wizard/templates/components/wizard-composer-editor",
modal: service(),
classNameBindings: ["fieldClass"],
allowUpload: true,
showLink: false,
showHyperlinkBox: false,
topic: null,
showToolbar: true,
focusTarget: "reply",
@ -26,7 +32,8 @@ export default ComposerEditor.extend({
popupMenuOptions: [],
draftStatus: "null",
replyPlaceholder: alias("field.translatedPlaceholder"),
uploadingFieldId: null,
wizardEventFieldId: null,
composerEventPrefix: "wizard-editor",
@on("didInsertElement")
_composerEditorInit() {
@ -35,7 +42,7 @@ export default ComposerEditor.extend({
if (this.siteSettings.enable_mentions) {
$input.autocomplete({
template: findRawTemplate("user-selector-autocomplete"),
dataSource: (term) => this.userSearchTerm.call(this, term),
dataSource: (term) => this._userSearchTerm.call(this, term),
key: "@",
transformComplete: (v) => v.username || v.name,
afterComplete: (value) => {
@ -75,43 +82,13 @@ export default ComposerEditor.extend({
$input.on("scroll", this._throttledSyncEditorAndPreviewScroll);
this._bindUploadTarget();
const wizardEventNames = ["insert-text", "replace-text"];
const eventPrefix = this.eventPrefix;
const session = this.get("session");
this.appEvents.reopen({
trigger(name, ...args) {
let eventParts = name.split(":");
let currentEventPrefix = eventParts[0];
let currentEventName = eventParts[1];
const field = this.field;
this.editorInputClass = `.${dasherize(field.type)}-${dasherize(
field.id
)} .d-editor-input`;
if (
currentEventPrefix !== "wizard-editor" &&
wizardEventNames.some((wen) => wen === currentEventName)
) {
let wizardName = name.replace(eventPrefix, "wizard-editor");
if (currentEventName === "insert-text") {
args = {
text: args[0],
};
}
if (currentEventName === "replace-text") {
args = {
oldVal: args[0],
newVal: args[1],
};
}
let wizardArgs = Object.assign(
{},
{
fieldId: session.get("uploadingFieldId"),
},
args
);
return this._super(wizardName, wizardArgs);
} else {
return this._super(name, ...args);
}
},
this._uppyInstance.on("file-added", () => {
this.session.set("wizardEventFieldId", field.id);
});
},
@ -133,17 +110,33 @@ export default ComposerEditor.extend({
return uploadIcon(false, this.siteSettings);
},
click(e) {
if ($(e.target).hasClass("wizard-composer-hyperlink")) {
this.set("showHyperlinkBox", false);
@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 && !this.site.mobileView) {
if (this.allowUpload && this.uploadIcon) {
toolbar.addButton({
id: "upload",
group: "insertions",
@ -159,7 +152,7 @@ export default ComposerEditor.extend({
shortcut: "K",
trimLeading: true,
unshift: true,
sendAction: () => component.set("showHyperlinkBox", true),
sendAction: (event) => component.send("showLinkModal", event),
});
if (this.siteSettings.mentionables_enabled) {
@ -188,33 +181,32 @@ export default ComposerEditor.extend({
}
},
previewUpdated($preview) {
highlightSyntax($preview[0], this.siteSettings, this.session);
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);
linkSeenMentionableItems(preview, this.siteSettings);
}
this._super(...arguments);
},
addLink(linkName, linkUrl) {
let link = `[${linkName}](${linkUrl})`;
this.appEvents.trigger("wizard-editor:insert-text", {
fieldId: this.field.id,
text: link,
});
this.set("showHyperlinkBox", false);
},
showLinkModal(toolbarEvent) {
let linkText = "";
this._lastSel = toolbarEvent.selected;
hideBox() {
this.set("showHyperlinkBox", false);
if (this._lastSel) {
linkText = this._lastSel.value;
}
this.modal.show(InsertHyperlink, {
model: { linkText, toolbarEvent },
});
},
showUploadModal() {
this.session.set("uploadingFieldId", this.field.id);
this.session.set("wizardEventFieldId", this.field.id);
document.getElementById(this.fileUploadElementId).click();
},
},

Datei anzeigen

@ -3,7 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators";
export default DateInput.extend({
useNativePicker: false,
layoutName: "wizard/templates/components/wizard-date-input",
classNameBindings: ["fieldClass"],
@discourseComputed()
placeholder() {

Datei anzeigen

@ -2,7 +2,7 @@ import DateTimeInput from "discourse/components/date-time-input";
import discourseComputed from "discourse-common/utils/decorators";
export default DateTimeInput.extend({
layoutName: "wizard/templates/components/wizard-date-time-input",
classNameBindings: ["fieldClass"],
@discourseComputed("timeFirst", "tabindex")
timeTabindex(timeFirst, tabindex) {

Datei anzeigen

@ -3,8 +3,6 @@ import Category from "discourse/models/category";
import Component from "@ember/component";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-category",
didInsertElement() {
const property = this.field.property || "id";
const value = this.field.value;

Datei anzeigen

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

Datei anzeigen

@ -7,8 +7,6 @@ import { ajax } from "discourse/lib/ajax";
import { on } from "discourse-common/utils/decorators";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-composer-preview",
@on("init")
updatePreview() {
if (this.isDestroyed) {

Datei anzeigen

@ -6,8 +6,6 @@ import EmberObject from "@ember/object";
import Component from "@ember/component";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-composer",
showPreview: false,
classNameBindings: [
":wizard-field-composer",

Datei anzeigen

@ -2,8 +2,6 @@ import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-date-time",
@observes("dateTime")
setValue() {
this.set("field.value", this.dateTime.format(this.field.format));

Datei anzeigen

@ -2,8 +2,6 @@ import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-date",
@observes("date")
setValue() {
this.set("field.value", this.date.format(this.field.format));

Datei anzeigen

@ -1,8 +1,6 @@
import Component from "@ember/component";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-dropdown",
keyPress(e) {
e.stopPropagation();
},

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,8 +1,6 @@
import Component from "@ember/component";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-text",
keyPress(e) {
e.stopPropagation();
},

Datei anzeigen

@ -1,8 +1,6 @@
import Component from "@ember/component";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-textarea",
keyPress(e) {
e.stopPropagation();
},

Datei anzeigen

@ -2,7 +2,7 @@ import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-time",
classNameBindings: ["fieldClass"],
@observes("time")
setValue() {

Datei anzeigen

@ -3,9 +3,8 @@ import Component from "@ember/component";
import { computed } from "@ember/object";
export default Component.extend(UppyUploadMixin, {
layoutName: "wizard/templates/components/wizard-field-upload",
classNames: ["wizard-field-upload"],
classNameBindings: ["isImage"],
classNameBindings: ["isImage", "fieldClass"],
uploading: false,
type: computed(function () {
return `wizard_${this.field.id}`;

Datei anzeigen

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

Datei anzeigen

@ -1,5 +1,5 @@
import Component from "@ember/component";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field-tag",
classNameBindings: ["fieldClass"],
});

Datei anzeigen

@ -1,10 +1,9 @@
import Component from "@ember/component";
import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators";
import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite";
import { cook } from "discourse/lib/text";
export default Component.extend({
layoutName: "wizard/templates/components/wizard-field",
classNameBindings: [
":wizard-field",
"typeClasses",
@ -12,6 +11,14 @@ export default Component.extend({
"field.id",
],
didReceiveAttrs() {
this._super(...arguments);
cook(this.field.translatedDescription).then((cookedDescription) => {
this.set("cookedDescription", cookedDescription);
});
},
@discourseComputed("field.type", "field.id")
typeClasses: (type, id) =>
`${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`,
@ -24,12 +31,7 @@ export default Component.extend({
if (["text_only"].includes(type)) {
return false;
}
return dasherize(type === "component" ? id : `wizard-field-${type}`);
},
@discourseComputed("field.translatedDescription")
cookedDescription(description) {
return cook(description);
return dasherize(type === "component" ? id : `custom-wizard-field-${type}`);
},
@discourseComputed("field.type")

Datei anzeigen

@ -3,7 +3,6 @@ import { computed } from "@ember/object";
import { makeArray } from "discourse-common/lib/helpers";
export default ComboBox.extend({
layoutName: "wizard/templates/components/wizard-group-selector",
content: computed("groups.[]", "field.content.[]", function () {
const whitelist = makeArray(this.field.content);
return this.groups

Datei anzeigen

@ -1,11 +1,11 @@
import CustomWizard from "../models/wizard";
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"],
layoutName: "wizard/templates/components/wizard-no-access",
@discourseComputed("reason")
reasonClass(reason) {
@ -19,7 +19,11 @@ export default Component.extend({
actions: {
skip() {
if (this.currentUser) {
CustomWizard.skip(this.get("wizardId"));
} else {
window.location = getURL("/");
}
},
},
});

Datei anzeigen

@ -4,7 +4,6 @@ import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["wizard-similar-topics"],
layoutName: "wizard/templates/components/wizard-similar-topics",
showTopics: true,
didInsertElement() {

Datei anzeigen

@ -4,15 +4,16 @@ import I18n from "I18n";
import getUrl from "discourse-common/lib/get-url";
import { htmlSafe } from "@ember/template";
import { schedule } from "@ember/runloop";
import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite";
import { updateCachedWizard } from "discourse/plugins/discourse-custom-wizard/wizard/models/wizard";
import { cook } 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 CustomWizard from "../models/wizard";
import discourseLater from "discourse-common/lib/later";
const alreadyWarned = {};
export default Component.extend({
layoutName: "wizard/templates/components/wizard-step",
classNameBindings: [":wizard-step", "step.id"],
saving: null,
@ -21,6 +22,17 @@ export default Component.extend({
this.set("stylingDropdown", {});
},
didReceiveAttrs() {
this._super(...arguments);
cook(this.step.translatedTitle).then((cookedTitle) => {
this.set("cookedTitle", cookedTitle);
});
cook(this.step.translatedDescription).then((cookedDescription) => {
this.set("cookedDescription", cookedDescription);
});
},
didInsertElement() {
this._super(...arguments);
this.autoFocus();
@ -32,16 +44,6 @@ export default Component.extend({
showNextButton: not("step.final"),
showDoneButton: alias("step.final"),
@discourseComputed("step.translatedTitle")
cookedTitle(title) {
return cook(title);
},
@discourseComputed("step.translatedDescription")
cookedDescription(description) {
return cook(description);
},
@discourseComputed(
"step.index",
"step.displayIndex",
@ -90,16 +92,6 @@ export default Component.extend({
this.showMessage(message);
},
keyPress(event) {
if (event.key === "Enter") {
if (this.showDoneButton) {
this.send("quit");
} else {
this.send("nextStep");
}
}
},
@discourseComputed("step.index", "wizard.totalSteps")
barStyle(displayIndex, totalSteps) {
let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1);
@ -119,34 +111,24 @@ export default Component.extend({
},
autoFocus() {
discourseLater(() => {
schedule("afterRender", () => {
const $invalid = $(
".wizard-field.invalid:nth-of-type(1) .wizard-focusable"
);
if ($invalid.length) {
return $invalid.focus();
if ($(".invalid .wizard-focusable").length) {
this.animateInvalidFields();
}
$(".wizard-focusable:first").focus();
});
});
},
animateInvalidFields() {
schedule("afterRender", () => {
let $element = $(
".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit"
);
if ($element.length) {
let $invalid = $(".invalid .wizard-focusable");
if ($invalid.length) {
$([document.documentElement, document.body]).animate(
{
scrollTop: $element.offset().top - 200,
scrollTop: $invalid.offset().top - 200,
},
400,
function () {
$element.wiggle(2, 100);
}
400
);
}
});

Datei anzeigen

@ -4,7 +4,10 @@ export default TagChooser.extend({
searchTags(url, data, callback) {
if (this.tagGroups) {
let tagGroupsString = this.tagGroups.join(",");
data.tag_groups = tagGroupsString;
data.filterForInput = {
name: "custom-wizard-tag-chooser",
groups: tagGroupsString,
};
}
return this._super(url, data, callback);

Datei anzeigen

@ -1,7 +1,7 @@
import computed from "discourse-common/utils/decorators";
import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction";
import WizardI18n from "../lib/wizard-i18n";
import TextField from "@ember/component/text-field";
import I18n from "I18n";
import TextField from "discourse/components/text-field";
export default TextField.extend({
attributeBindings: [
@ -39,6 +39,6 @@ export default TextField.extend({
@computed("placeholderKey")
placeholder(placeholderKey) {
return placeholderKey ? WizardI18n(placeholderKey) : "";
return placeholderKey ? I18n.t(placeholderKey) : "";
},
});

Datei anzeigen

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

Datei anzeigen

@ -1,8 +1,6 @@
import Component from "@ember/component";
export default Component.extend({
layoutName: "wizard/templates/components/field-validators",
actions: {
perform() {
this.appEvents.trigger("custom-wizard:validate");

Datei anzeigen

@ -0,0 +1,34 @@
<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

@ -0,0 +1,17 @@
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

@ -0,0 +1,20 @@
<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

@ -0,0 +1,32 @@
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,4 +1,4 @@
import WizardFieldValidator from "../../wizard/components/validator";
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";
@ -10,7 +10,6 @@ import { dasherize } from "@ember/string";
export default WizardFieldValidator.extend({
classNames: ["similar-topics-validator"],
layoutName: "wizard/templates/components/similar-topics-validator",
similarTopics: null,
hasInput: notEmpty("field.value"),
hasSimilarTopics: notEmpty("similarTopics"),

Datei anzeigen

@ -1,12 +1,10 @@
import Component from "@ember/component";
import { equal } from "@ember/object/computed";
import { ajax } from "discourse/lib/ajax";
import { getToken } from "wizard/lib/ajax";
import { ajax, getToken } from "discourse/lib/ajax";
export default Component.extend({
classNames: ["validator"],
classNameBindings: ["isValid", "isInvalid"],
layoutName: "wizard/templates/components/validator",
validMessageKey: null,
invalidMessageKey: null,
isValid: null,

Datei anzeigen

@ -1,21 +0,0 @@
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,8 +1,7 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import { and, empty, equal, or } from "@ember/object/computed";
import { empty, equal, or } from "@ember/object/computed";
import { notificationLevels, selectKitContent } from "../lib/wizard";
import { computed } from "@ember/object";
import wizardSchema from "../lib/wizard-schema";
import UndoChanges from "../mixins/undo-changes";
import Component from "@ember/component";
import I18n from "I18n";
@ -16,6 +15,7 @@ export default Component.extend(UndoChanges, {
createTopic: equal("action.type", "create_topic"),
updateProfile: equal("action.type", "update_profile"),
watchCategories: equal("action.type", "watch_categories"),
watchTags: equal("action.type", "watch_tags"),
sendMessage: equal("action.type", "send_message"),
openComposer: equal("action.type", "open_composer"),
sendToApi: equal("action.type", "send_to_api"),
@ -25,8 +25,6 @@ export default Component.extend(UndoChanges, {
createGroup: equal("action.type", "create_group"),
apiEmpty: empty("action.api"),
groupPropertyTypes: selectKitContent(["id", "name"]),
hasAdvanced: or("hasCustomFields", "routeTo"),
showAdvanced: and("hasAdvanced", "action.type"),
hasCustomFields: or(
"basicTopicFields",
"updateProfile",
@ -36,22 +34,15 @@ export default Component.extend(UndoChanges, {
basicTopicFields: or("createTopic", "sendMessage", "openComposer"),
publicTopicFields: or("createTopic", "openComposer"),
showPostAdvanced: or("createTopic", "sendMessage"),
actionTypes: Object.keys(wizardSchema.action.types).map((type) => {
return {
id: type,
name: I18n.t(`admin.wizard.action.${type}.label`),
};
}),
availableNotificationLevels: notificationLevels.map((type) => {
return {
id: type,
name: I18n.t(
`admin.wizard.action.watch_categories.notification_level.${type}`
),
name: I18n.t(`admin.wizard.action.watch_x.notification_level.${type}`),
};
}),
messageUrl: "https://thepavilion.io/t/2810",
messageUrl:
"https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/action-settings",
@discourseComputed("action.type")
messageKey(type) {
@ -101,4 +92,14 @@ export default Component.extend(UndoChanges, {
}
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,5 +1,5 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import { alias, equal, or } from "@ember/object/computed";
import { equal, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import { selectKitContent } from "../lib/wizard";
import UndoChanges from "../mixins/undo-changes";
@ -27,8 +27,8 @@ export default Component.extend(UndoChanges, {
isTextType: or("isText", "isTextarea", "isComposer"),
isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]),
showAdvanced: alias("field.type"),
messageUrl: "https://thepavilion.io/t/2809",
messageUrl:
"https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/field-settings",
@discourseComputed("field.type")
validations(type) {
@ -144,11 +144,17 @@ export default Component.extend(UndoChanges, {
actions: {
imageUploadDone(upload) {
this.set("field.image", upload.url);
this.setProperties({
"field.image": upload.url,
"field.image_upload_id": upload.id,
});
},
imageUploadDeleted() {
this.set("field.image", null);
this.setProperties({
"field.image": null,
"field.image_upload_id": null,
});
},
},
});

Datei anzeigen

@ -24,11 +24,17 @@ export default Component.extend({
actions: {
bannerUploadDone(upload) {
this.set("step.banner", upload.url);
this.setProperties({
"step.banner": upload.url,
"step.banner_upload_id": upload.id,
});
},
bannerUploadDeleted() {
this.set("step.banner", null);
this.setProperties({
"step.banner": null,
"step.banner_upload_id": null,
});
},
},
});

Datei anzeigen

@ -71,6 +71,17 @@ export default Component.extend({
});
},
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: {
add() {
const items = this.get("items");
@ -78,7 +89,7 @@ export default Component.extend({
let params = setWizardDefaults({}, itemType);
params.isNew = true;
params.index = items.length;
params.index = this.getNextIndex();
let id = `${itemType}_${params.index + 1}`;
if (itemType === "field") {

Datei anzeigen

@ -4,7 +4,7 @@ import {
default as discourseComputed,
observes,
} from "discourse-common/utils/decorators";
import { getOwner } from "discourse-common/lib/get-owner";
import { getOwner } from "@ember/application";
import { defaultSelectionType, selectionTypes } from "../lib/wizard-mapper";
import {
generateName,
@ -15,6 +15,7 @@ import {
import Component from "@ember/component";
import { bind, later } from "@ember/runloop";
import I18n from "I18n";
import { inject as service } from "@ember/service";
const customFieldActionMap = {
topic: ["create_topic", "send_message"],
@ -28,6 +29,7 @@ const values = ["present", "true", "false"];
export default Component.extend({
classNameBindings: [":mapper-selector", "activeType"],
subscription: service(),
showText: computed("activeType", function () {
return this.showInput("text");
@ -116,6 +118,9 @@ export default Component.extend({
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");
}),
@ -126,7 +131,33 @@ export default Component.extend({
return this.connector === "is";
}),
groups: alias("site.groups"),
@discourseComputed(
"site.groups",
"guestGroup",
"subscription.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",
@ -377,7 +408,7 @@ export default Component.extend({
this.changeValue(event.target.value);
},
changeUserValue(previousValue, value) {
changeUserValue(value) {
this.changeValue(value);
},
},

Datei anzeigen

@ -32,6 +32,7 @@ export default Component.extend({
pairConnector: options.pairConnector || null,
outputConnector: options.outputConnector || null,
context: options.context || null,
guestGroup: options.guestGroup || false,
};
let inputTypes = ["key", "value", "output"];

Datei anzeigen

@ -6,6 +6,7 @@ import I18n from "I18n";
const icons = {
error: "times-circle",
success: "check-circle",
warn: "exclamation-circle",
info: "info-circle",
};

Datei anzeigen

@ -6,7 +6,8 @@ import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
export default Component.extend({
classNames: ["realtime-validations"],
classNames: ["realtime-validations", "setting", "full", "subscription"],
@discourseComputed
timeUnits() {
return ["days", "weeks", "months", "years"].map((unit) => {

Datei anzeigen

@ -0,0 +1,19 @@
<DButton
@icon={{this.updateIcon}}
@action={{this.update}}
class="btn update"
@disabled={{this.updating}}
@title="admin.wizard.subscription.update.title"
>
{{#if this.updating}}
{{loading-spinner size="small"}}
{{/if}}
</DButton>
<DButton
@action={{this.click}}
class="wizard-subscription-badge {{this.subscription.subscriptionType}}"
@title={{this.title}}
>
{{d-icon "pavilion-logo"}}
<span>{{this.label}}</span>
</DButton>

Datei anzeigen

@ -0,0 +1,46 @@
import { inject as service } from "@ember/service";
import { action, computed } from "@ember/object";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import I18n from "I18n";
export default class WizardSubscriptionBadge extends Component {
@service subscription;
@tracked updating = false;
@tracked updateIcon = "sync";
basePath = "/admin/plugins/subscription-client";
@computed("subscription.subscriptionType")
get i18nKey() {
return `admin.wizard.subscription.type.${
this.subscription.subscriptionType
? this.subscription.subscriptionType
: "none"
}`;
}
@computed("i18nKey")
get title() {
return `${this.i18nKey}.title`;
}
@computed("i18nKey")
get label() {
return I18n.t(`${this.i18nKey}.label`);
}
@action
click() {
window.open(this.subscription.subscriptionCtaLink, "_blank").focus();
}
@action
update() {
this.updating = true;
this.updateIcon = null;
this.subscription.updateSubscriptionStatus().finally(() => {
this.updateIcon = "sync";
this.updating = false;
});
}
}

Datei anzeigen

@ -0,0 +1,17 @@
<div
class="wizard-subscription-container
{{if this.subscription.subscribed 'subscribed'}}"
>
<div class="subscription-header">
<h4>{{i18n "admin.wizard.subscription.title"}}</h4>
<a href={{subscriptionLink}} title={{i18n subscribedTitle}}>
{{d-icon subscribedIcon}}
{{i18n subscribedLabel}}
</a>
</div>
<div class="subscription-settings">
{{yield}}
</div>
</div>

Datei anzeigen

@ -0,0 +1,26 @@
import Component from "@glimmer/component";
import { computed } from "@ember/object";
import { inject as service } from "@ember/service";
export default class WizardSubscriptionContainer extends Component {
@service subscription;
@computed("subscription.subscribed")
get subscribedIcon() {
return this.subscription.subscribed ? "check" : "times";
}
@computed("subscription.subscribed")
get subscribedLabel() {
return `admin.wizard.subscription.${
this.subscription.subscribed ? "subscribed" : "not_subscribed"
}.label`;
}
@computed("subscription.subscribed")
get subscribedTitle() {
return `admin.wizard.subscription.${
this.subscription.subscribed ? "subscribed" : "not_subscribed"
}.title`;
}
}

Datei anzeigen

@ -0,0 +1,96 @@
import SingleSelectComponent from "select-kit/components/single-select";
import { inject as service } from "@ember/service";
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({
classNames: ["combo-box", "wizard-subscription-selector"],
subscription: service(),
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.subscription.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.subscription.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

@ -0,0 +1,17 @@
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

@ -0,0 +1,20 @@
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

@ -0,0 +1,22 @@
<div class="supplier-authorize">
<WizardSubscriptionBadge />
{{#if authorized}}
{{conditional-loading-spinner size="small" condition=unauthorizing}}
<DButton
class="deauthorize"
@title="admin.wizard.subscription.deauthorize.title"
@disabled={{unauthorizing}}
@action={{this.deauthorize}}
>
{{i18n "admin.wizard.subscription.deauthorize.label"}}
</DButton>
{{else}}
<DButton
@icon="id-card"
class="btn-primary"
@label="admin.wizard.subscription.authorize.label"
@title="admin.wizard.subscription.authorize.title"
@action={{this.authorize}}
/>
{{/if}}
</div>

Datei anzeigen

@ -0,0 +1,53 @@
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class WizardSubscriptionStatus extends Component {
@service siteSettings;
@service subscription;
@tracked supplierId = null;
@tracked authorized = false;
@tracked unauthorizing = false;
basePath = "/admin/plugins/subscription-client/suppliers";
constructor() {
super(...arguments);
ajax(`${this.basePath}`)
.then((result) => {
this.supplierId = result.suppliers[0].id;
this.authorized = result.suppliers[0].authorized;
})
.finally(() => {
this.subscription.retrieveSubscriptionStatus();
});
}
@action
authorize() {
window.location.href = `${this.basePath}/authorize?supplier_id=${this.supplierId}&final_landing_path=/admin/wizards/wizard`;
}
@action
deauthorize() {
this.unauthorizing = true;
ajax(`${this.basePath}/authorize`, {
type: "DELETE",
data: {
supplier_id: this.supplierId,
},
})
.then((result) => {
this.supplierId = result.supplier.id;
this.authorized = !(result.supplier.authorized_at === null);
})
.finally(() => {
this.unauthorizing = false;
this.subscription.retrieveSubscriptionStatus();
})
.catch(popupAjaxError);
}
}

Datei anzeigen

@ -0,0 +1,139 @@
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

@ -5,11 +5,7 @@ import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
import I18n from "I18n";
const excludedUserProperties = [
"avatar",
"profile_background",
"card_background",
];
const excludedUserProperties = ["profile_background", "card_background"];
export default Component.extend({
classNames: "wizard-text-editor",
@ -52,12 +48,12 @@ export default Component.extend({
@discourseComputed("wizardFields")
wizardFieldList(wizardFields) {
return wizardFields.map((f) => ` w{${f.id}}`);
return (wizardFields || []).map((f) => ` w{${f.id}}`);
},
@discourseComputed("wizardActions")
wizardActionList(wizardActions) {
return wizardActions.map((a) => ` w{${a.id}}`);
return (wizardActions || []).map((a) => ` w{${a.id}}`);
},
actions: {

Datei anzeigen

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

Datei anzeigen

@ -9,8 +9,7 @@
value=wizardListVal
content=wizardList
onChange=(action "changeWizard")
options=(hash
none="admin.wizard.select"
)}}
options=(hash none="admin.wizard.select")
}}
</div>
</section>

Datei anzeigen

@ -1,9 +1,9 @@
import CustomWizard from "../../models/custom-wizard";
import CustomWizardAdmin from "../../models/custom-wizard-admin";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default {
setupComponent(attrs, component) {
CustomWizard.all()
CustomWizardAdmin.all()
.then((result) => {
component.set("wizardList", result);
})

Datei anzeigen

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

Datei anzeigen

@ -4,14 +4,18 @@ import CustomWizardApi from "../models/custom-wizard-api";
import { default as discourseComputed } from "discourse-common/utils/decorators";
import { and, equal, not } from "@ember/object/computed";
import { selectKitContent } from "../lib/wizard";
import { underscore } from "@ember/string";
import Controller from "@ember/controller";
import I18n from "I18n";
import { inject as service } from "@ember/service";
export default Controller.extend({
router: service(),
queryParams: ["refresh_list"],
loadingSubscriptions: false,
notAuthorized: not("api.authorized"),
endpointMethods: selectKitContent(["GET", "PUT", "POST", "PATCH", "DELETE"]),
endpointMethods: selectKitContent(["PUT", "POST", "PATCH", "DELETE"]),
showRemove: not("isNew"),
showRedirectUri: and("threeLeggedOauth", "api.name"),
responseIcon: null,
@ -20,29 +24,8 @@ export default Controller.extend({
"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,
100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301,
302, 303, 303, 304, 305, 306, 307, 308,
]),
@discourseComputed(
@ -88,6 +71,11 @@ export default Controller.extend({
twoLeggedOauth: equal("api.authType", "oauth_2"),
threeLeggedOauth: equal("api.authType", "oauth_3"),
@discourseComputed("api.isNew")
nameClass(isNew) {
return isNew ? "new" : "saved";
},
actions: {
addParam() {
this.get("api.authParams").pushObject({});
@ -113,7 +101,7 @@ export default Controller.extend({
if (authType === "oauth_2") {
this.set("authorizing", true);
ajax(`/admin/wizards/apis/${name.underscore()}/authorize`)
ajax(`/admin/wizards/apis/${underscore(name)}/authorize`)
.catch(popupAjaxError)
.then((result) => {
if (result.success) {
@ -149,7 +137,6 @@ export default Controller.extend({
const api = this.get("api");
const name = api.name;
const authType = api.authType;
let refreshList = false; // eslint-disable-line
let error;
if (!name || !authType) {
@ -164,11 +151,6 @@ export default Controller.extend({
data["title"] = api.title;
}
const originalTitle = this.get("api.originalTitle");
if (api.get("isNew") || (originalTitle && api.title !== originalTitle)) {
refreshList = true;
}
if (api.get("isNew")) {
data["new"] = true;
}
@ -188,11 +170,11 @@ export default Controller.extend({
if (!api[rp]) {
let key = rp.replace("auth", "");
error = `${I18n.t(
`admin.wizard.api.auth.${key.underscore()}`
`admin.wizard.api.auth.${underscore(key)}`
)} is required for ${authType}`;
break;
}
data[rp.underscore()] = api[rp];
data[underscore(rp)] = api[rp];
}
}
@ -222,7 +204,7 @@ export default Controller.extend({
this.set("updating", true);
ajax(`/admin/wizards/api/${name.underscore()}`, {
ajax(`/admin/wizards/api/${underscore(name)}`, {
type: "PUT",
data,
})
@ -245,7 +227,7 @@ export default Controller.extend({
this.set("updating", true);
ajax(`/admin/wizards/api/${name.underscore()}`, {
ajax(`/admin/wizards/api/${underscore(name)}`, {
type: "DELETE",
})
.catch(popupAjaxError)
@ -263,13 +245,13 @@ export default Controller.extend({
return;
}
ajax(`/admin/wizards/api/${name.underscore()}/logs`, {
ajax(`/admin/wizards/api/${underscore(name)}/logs`, {
type: "DELETE",
})
.catch(popupAjaxError)
.then((result) => {
if (result.success) {
this.transitionToRoute("adminWizardsApis").then(() => {
this.router.transitionTo("adminWizardsApis").then(() => {
this.send("refreshModel");
});
}

Datei anzeigen

@ -4,7 +4,8 @@ import CustomWizardCustomField from "../models/custom-wizard-custom-field";
export default Controller.extend({
messageKey: "create",
fieldKeys: ["klass", "type", "name", "serializers"],
documentationUrl: "https://thepavilion.io/t/3572",
documentationUrl:
"https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/custom-fields",
actions: {
addField() {

Datei anzeigen

@ -0,0 +1,52 @@
import discourseComputed from "discourse-common/utils/decorators";
import { notEmpty } from "@ember/object/computed";
import CustomWizardLogs from "../models/custom-wizard-logs";
import Controller from "@ember/controller";
export default Controller.extend({
refreshing: false,
hasLogs: notEmpty("logs"),
page: 0,
canLoadMore: true,
logs: [],
messageKey: "viewing",
loadLogs() {
if (!this.canLoadMore) {
return;
}
const page = this.get("page");
const wizardId = this.get("wizard.id");
this.set("refreshing", true);
CustomWizardLogs.list(wizardId, page)
.then((result) => {
this.set("logs", this.logs.concat(result.logs));
})
.finally(() => this.set("refreshing", false));
},
@discourseComputed("hasLogs", "refreshing")
noResults(hasLogs, refreshing) {
return !hasLogs && !refreshing;
},
actions: {
loadMore() {
if (!this.loadingMore && this.logs.length < this.total) {
this.set("page", (this.page += 1));
this.loadLogs();
}
},
refresh() {
this.setProperties({
canLoadMore: true,
page: 0,
logs: [],
});
this.loadLogs();
},
},
});

Datei anzeigen

@ -1,50 +1,35 @@
import discourseComputed from "discourse-common/utils/decorators";
import { notEmpty } from "@ember/object/computed";
import CustomWizardLogs from "../models/custom-wizard-logs";
import Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
export default Controller.extend({
refreshing: false,
hasLogs: notEmpty("logs"),
page: 0,
canLoadMore: true,
logs: [],
documentationUrl:
"https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/",
loadLogs() {
if (!this.canLoadMore) {
return;
@discourseComputed("wizardId")
wizardName(wizardId) {
let currentWizard = this.wizardList.find(
(wizard) => wizard.id === wizardId
);
if (currentWizard) {
return currentWizard.name;
}
},
@discourseComputed("wizardName")
messageOpts(wizardName) {
return {
wizardName,
};
},
@discourseComputed("wizardId")
messageKey(wizardId) {
let key = "select";
if (wizardId) {
key = "viewing";
}
this.set("refreshing", true);
CustomWizardLogs.list()
.then((result) => {
if (!result || result.length === 0) {
this.set("canLoadMore", false);
}
this.set("logs", this.logs.concat(result));
})
.finally(() => this.set("refreshing", false));
},
@discourseComputed("hasLogs", "refreshing")
noResults(hasLogs, refreshing) {
return !hasLogs && !refreshing;
},
actions: {
loadMore() {
this.set("page", (this.page += 1));
this.loadLogs();
},
refresh() {
this.setProperties({
canLoadMore: true,
page: 0,
logs: [],
});
this.loadLogs();
},
return key;
},
});

Datei anzeigen

@ -7,7 +7,8 @@ import I18n from "I18n";
import { underscore } from "@ember/string";
export default Controller.extend({
messageUrl: "https://thepavilion.io/t/3652",
messageUrl:
"https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/wizard-manager",
messageKey: "info",
messageIcon: "info-circle",
messageClass: "info",
@ -68,7 +69,7 @@ export default Controller.extend({
file: null,
filename: null,
});
$("#file-upload").val("");
document.getElementById("custom-wizard-file-upload").value = "";
},
@observes("importing", "destroying")
@ -83,7 +84,7 @@ export default Controller.extend({
actions: {
upload() {
$("#file-upload").click();
document.getElementById("custom-wizard-file-upload").click();
},
clearFile() {
@ -102,7 +103,7 @@ export default Controller.extend({
if (maxFileSize < file.size) {
this.setMessage("error", "file_size_error");
this.set("file", null);
$("#file-upload").val("");
document.getElementById("custom-wizard-file-upload").value = "";
} else {
this.setProperties({
file,

Datei anzeigen

@ -1,6 +1,74 @@
import Controller from "@ember/controller";
import { empty } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
import { fmt } from "discourse/lib/computed";
import { inject as service } from "@ember/service";
import AdminWizardsColumnsModal from "../components/modal/admin-wizards-columns";
import CustomWizardAdmin from "../models/custom-wizard-admin";
import { formatModel } from "../lib/wizard-submission";
export default Controller.extend({
modal: service(),
downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"),
noResults: empty("submissions"),
page: 0,
total: 0,
loadMoreSubmissions() {
const page = this.get("page");
const wizardId = this.get("wizard.id");
this.set("loadingMore", true);
CustomWizardAdmin.submissions(wizardId, page)
.then((result) => {
if (result.submissions) {
const { submissions } = formatModel(result);
this.get("submissions").pushObjects(submissions);
}
})
.finally(() => {
this.set("loadingMore", false);
});
},
@discourseComputed("submissions.[]", "fields.@each.enabled")
displaySubmissions(submissions, fields) {
let result = [];
submissions.forEach((submission) => {
let sub = {};
Object.keys(submission).forEach((fieldId) => {
if (fields.some((f) => f.id === fieldId && f.enabled)) {
sub[fieldId] = submission[fieldId];
}
});
result.push(sub);
});
return result;
},
actions: {
loadMore() {
if (!this.loadingMore && this.submissions.length < this.total) {
this.set("page", this.get("page") + 1);
this.loadMoreSubmissions();
}
},
showEditColumnsModal() {
return this.modal.show(AdminWizardsColumnsModal, {
model: {
columns: this.get("fields"),
reset: () => {
this.get("fields").forEach((field) => {
field.set("enabled", true);
});
},
},
});
},
},
});

Datei anzeigen

@ -0,0 +1,35 @@
import Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
export default Controller.extend({
documentationUrl:
"https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/",
@discourseComputed("wizardId")
wizardName(wizardId) {
let currentWizard = this.wizardList.find(
(wizard) => wizard.id === wizardId
);
if (currentWizard) {
return currentWizard.name;
}
},
@discourseComputed("wizardName")
messageOpts(wizardName) {
return {
wizardName,
};
},
@discourseComputed("wizardId")
messageKey(wizardId) {
let key = "select";
if (wizardId) {
key = "viewing";
}
return key;
},
});

Datei anzeigen

@ -3,15 +3,18 @@ import {
observes,
} from "discourse-common/utils/decorators";
import { notEmpty } from "@ember/object/computed";
import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service";
import NextSessionScheduledModal from "../components/modal/next-session-scheduled";
import { generateId, wizardFieldList } from "../lib/wizard";
import { dasherize } from "@ember/string";
import { later, scheduleOnce } from "@ember/runloop";
import Controller from "@ember/controller";
import copyText from "discourse/lib/copy-text";
import I18n from "I18n";
import { filterValues } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard-schema";
export default Controller.extend({
modal: service(),
hasName: notEmpty("wizard.name"),
@observes("currentStep")
@ -36,7 +39,8 @@ export default Controller.extend({
@discourseComputed("wizard.id")
wizardUrl(wizardId) {
return window.location.origin + "/w/" + dasherize(wizardId);
let baseUrl = window.location.href.split("/admin");
return baseUrl[0] + "/w/" + dasherize(wizardId);
},
@discourseComputed("wizard.after_time_scheduled")
@ -58,6 +62,19 @@ export default Controller.extend({
}
return wizardFieldList(steps);
},
@discourseComputed("fieldTypes", "wizard.allowGuests")
filteredFieldTypes(fieldTypes) {
const fieldTypeIds = fieldTypes.map((f) => f.id);
const allowedTypeIds = filterValues(
this.wizard,
"field",
"type",
fieldTypeIds
);
return fieldTypes.filter((f) => allowedTypeIds.includes(f.id));
},
getErrorMessage(result) {
if (result.backend_validation_error) {
return result.backend_validation_error;
@ -92,7 +109,11 @@ export default Controller.extend({
wizard
.save(opts)
.then((result) => {
if (result.wizard_id) {
this.send("afterSave", result.wizard_id);
} else if (result.errors) {
this.set("error", result.errors.join(", "));
}
})
.catch((result) => {
this.set("error", this.getErrorMessage(result));
@ -107,19 +128,13 @@ export default Controller.extend({
},
setNextSessionScheduled() {
let controller = showModal("next-session-scheduled", {
this.modal.show(NextSessionScheduledModal, {
model: {
dateTime: this.wizard.after_time_scheduled,
update: (dateTime) =>
this.set("wizard.after_time_scheduled", dateTime),
},
});
controller.setup();
},
toggleAdvanced() {
this.toggleProperty("wizard.showAdvanced");
},
copyUrl() {

Datei anzeigen

@ -21,5 +21,6 @@ export default Controller.extend({
return key;
},
messageUrl: "https://thepavilion.io/c/knowledge/discourse/custom-wizard",
messageUrl:
"https://pavilion.tech/products/discourse-custom-wizard-plugin/documentation/",
});

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