1
0
Fork 0

Merge pull request #218 from paviliondev/stable_candidate

Stable candidate
Dieser Commit ist enthalten in:
Angus McLeod 2023-01-31 08:51:54 +01:00 committet von GitHub
Commit 5072d9389a
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
445 geänderte Dateien mit 16539 neuen und 11537 gelöschten Zeilen

Datei anzeigen

@ -3,54 +3,52 @@ name: Linting
on: on:
push: push:
branches: branches:
- master
- main - main
- stable - stable
pull_request: pull_request:
schedule:
- cron: '0 0 * * *' concurrency:
group: plugin-linting-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: 14 node-version: 16
cache: yarn
- name: Yarn install
run: yarn install
- name: Set up ruby - name: Set up ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 2.7 ruby-version: 2.7
bundler-cache: true
- 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 - name: ESLint
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts
- name: Prettier - name: Prettier
if: ${{ always() }}
shell: bash
run: | run: |
yarn prettier -v yarn prettier -v
if [ -d "assets" ]; then \ if [ 0 -lt $(find assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "assets/**/*.{scss,js,es6}" ; \ yarn prettier --list-different "assets/**/*.{scss,js,es6}"
fi fi
if [ -d "test" ]; then \ if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "test/**/*.{js,es6}" ; \ yarn prettier --list-different "test/**/*.{js,es6}"
fi fi
- name: Ember template lint
run: yarn ember-template-lint assets/javascripts
- name: Rubocop - name: Rubocop
run: bundle exec rubocop . if: ${{ always() }}
run: bundle exec rubocop .

Datei anzeigen

@ -36,9 +36,9 @@ jobs:
uses: actions/github-script@v5 uses: actions/github-script@v5
with: with:
script: | script: |
const semver = require('semver'); const semver = require('semver');
const { head_version, base_version } = process.env; const { head_version, base_version } = process.env;
if (semver.lte(head_version, base_version)) { if (semver.lte(head_version, base_version)) {
core.setFailed("Head version is equal to or lower than base version."); core.setFailed("Head version is equal to or lower than base version.");
} }

Datei anzeigen

@ -3,24 +3,25 @@ name: Plugin Tests
on: on:
push: push:
branches: branches:
- stable
- master
- main - main
- stable
pull_request: pull_request:
schedule:
- cron: '0 */12 * * *' concurrency:
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs: jobs:
build: build:
name: ${{ matrix.build_type }} name: ${{ matrix.build_type }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60 container: discourse/discourse_test:slim${{ startsWith(matrix.build_type, 'frontend') && '-browsers' || '' }}
timeout-minutes: 30
env: env:
DISCOURSE_HOSTNAME: www.example.com DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072 RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test RAILS_ENV: test
PGHOST: localhost
PGUSER: discourse PGUSER: discourse
PGPASSWORD: discourse PGPASSWORD: discourse
@ -29,121 +30,107 @@ jobs:
matrix: matrix:
build_type: ["backend", "frontend"] 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: steps:
- uses: haya14busa/action-cond@v1 - uses: actions/checkout@v3
id: discourse_branch
with:
cond: ${{ github.base_ref == 'stable' }}
if_true: "stable"
if_false: "tests-passed"
- uses: actions/checkout@v2
with: with:
repository: discourse/discourse repository: discourse/discourse
ref: ${{ steps.discourse_branch.outputs.value }}
fetch-depth: 1 fetch-depth: 1
- name: Fetch Repo Name
id: repo-name
run: echo "::set-output name=value::$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')"
- name: Install plugin - name: Install plugin
uses: actions/checkout@v2 uses: actions/checkout@v3
with: with:
path: plugins/${{ steps.repo-name.outputs.value }} path: plugins/${{ github.event.repository.name }}
ref: "${{ github.base_ref }}"
fetch-depth: 1 fetch-depth: 1
- name: Check spec existence
id: check_spec
uses: andstor/file-existence-action@v1
with:
files: "plugins/${{ steps.repo-name.outputs.value }}/spec"
- name: Check qunit existence
id: check_qunit
uses: andstor/file-existence-action@v1
with:
files: "plugins/${{ steps.repo-name.outputs.value }}/test/javascripts"
- name: Setup Git - name: Setup Git
run: | run: |
git config --global user.email "ci@ci.invalid" git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI" git config --global user.name "Discourse CI"
- name: Setup packages - name: Start redis
run: | run: |
sudo apt-get update redis-server /etc/redis/redis.conf &
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 - name: Start Postgres
if: matrix.build_type == 'backend'
run: | run: |
wget https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-imagemagick chown -R postgres /var/run/postgresql
chmod +x install-imagemagick sudo -E -u postgres script/start_test_db.rb
sudo ./install-imagemagick sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"
- name: Setup redis - name: Bundler cache
uses: shogo82148/actions-setup-redis@v1 uses: actions/cache@v3
with: with:
redis-version: ${{ matrix.redis }} path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Setup ruby - name: Setup gems
uses: ruby/setup-ruby@v1 run: |
with: gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
ruby-version: ${{ matrix.ruby }} bundle config --local path vendor/bundle
bundler-cache: true bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Lint English locale - name: Lint English locale
if: matrix.build_type == 'backend' if: matrix.build_type == 'backend'
run: bundle exec ruby script/i18n_lint.rb "plugins/${{ steps.repo-name.outputs.value }}/locales/{client,server}.en.yml" run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory - name: Get yarn cache directory
id: yarn-cache-dir id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache - name: Yarn cache
uses: actions/cache@v2 uses: actions/cache@v3
id: yarn-cache id: yarn-cache
with: with:
path: ${{ steps.yarn-cache-dir.outputs.dir }} path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ matrix.os }}-yarn- ${{ runner.os }}-yarn-
- name: Yarn install - name: Yarn install
run: yarn install --dev run: yarn install
- name: Migrate database - name: Fetch app state cache
uses: actions/cache@v3
id: app-cache
with:
path: tmp/app-cache
key: >-
${{ hashFiles('.github/workflows/tests.yml') }}-
${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}-
- name: Restore database from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: psql -f tmp/app-cache/cache.sql postgres
- name: Restore uploads from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads
- name: Create and migrate database
if: steps.app-cache.outputs.cache-hit != 'true'
run: | run: |
bin/rake db:create bin/rake db:create
bin/rake db:migrate bin/rake db:migrate
- name: Plugin RSpec with Coverage - name: Dump database for cache
if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' if: steps.app-cache.outputs.cache-hit != 'true'
run: SIMPLECOV=1 bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}] run: mkdir -p tmp/app-cache && pg_dumpall > tmp/app-cache/cache.sql
- name: Dump uploads for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
- name: Plugin RSpec
if: matrix.build_type == 'backend'
run: bin/rake plugin:spec[${{ github.event.repository.name }}]
- name: Plugin QUnit - name: Plugin QUnit
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true' if: matrix.build_type == 'frontend'
run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000'] run: QUNIT_EMBER_CLI=1 bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000']
timeout-minutes: 30 timeout-minutes: 10

3
.gitignore gevendort
Datei anzeigen

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

Datei anzeigen

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

7
.simplecov Normale Datei
Datei anzeigen

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

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

Datei anzeigen

@ -1,3 +1,25 @@
# 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">
## 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://discourse.pluginmanager.org/c/discourse-custom-wizard/documentation), or go directly to the relevant section
- [Wizard Administration](https://discourse.pluginmanager.org/t/wizard-administration)
- [Wizard Settings](https://discourse.pluginmanager.org/t/wizard-settings)
- [Step Settings](https://discourse.pluginmanager.org/t/step-settings)
- [Field Settings](https://discourse.pluginmanager.org/t/field-settings)
- [Conditional Settings](https://discourse.pluginmanager.org/t/conditional-settings)
- [Field Interpolation](https://discourse.pluginmanager.org/t/field-interpolation)
- [Wizard Examples and Templates](https://discourse.pluginmanager.org/t/wizard-examples-and-templates)
## Support
- [Report a bug](https://discourse.pluginmanager.org/w/bug-report)

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

@ -3,6 +3,13 @@ class CustomWizard::AdminController < ::Admin::AdminController
before_action :ensure_admin before_action :ensure_admin
def index def index
subcription = CustomWizard::Subscription.new
render_json_dump(
subscribed: subcription.subscribed?,
subscription_type: subcription.type,
subscription_attributes: CustomWizard::Subscription.attributes,
subscription_client_installed: subcription.client_installed?
)
end end
private private

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

Datei anzeigen

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

Datei anzeigen

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

@ -13,12 +13,16 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
def show def show
render_json_dump( render_json_dump(
wizard: CustomWizard::BasicWizardSerializer.new(@wizard, root: false), 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 end
def download def download
send_data ordered_submissions.to_json, send_data submission_list.submissions.to_json,
filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json", filename: "#{Discourse.current_hostname}-wizard-submissions-#{@wizard.name}.json",
content_type: "application/json", content_type: "application/json",
disposition: "attachment" disposition: "attachment"
@ -26,7 +30,7 @@ class CustomWizard::AdminSubmissionsController < CustomWizard::AdminController
protected protected
def ordered_submissions def submission_list
CustomWizard::Submission.list(@wizard, order_by: 'id') CustomWizard::Submission.list(@wizard, page: params[:page].to_i)
end end
end end

Datei anzeigen

@ -88,6 +88,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:title, :title,
:key, :key,
:banner, :banner,
:banner_upload_id,
:raw_description, :raw_description,
:required_data_message, :required_data_message,
:force_final, :force_final,
@ -99,6 +100,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:index, :index,
:label, :label,
:image, :image,
:image_upload_id,
:description, :description,
:required, :required,
:key, :key,
@ -112,6 +114,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
:property, :property,
:preview_template, :preview_template,
:placeholder, :placeholder,
:can_create_tag,
prefill: mapped_params, prefill: mapped_params,
content: mapped_params, content: mapped_params,
condition: mapped_params, condition: mapped_params,
@ -161,7 +164,9 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
mentionable_level: mapped_params, mentionable_level: mapped_params,
messageable_level: mapped_params, messageable_level: mapped_params,
visibility_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 end

Datei anzeigen

@ -0,0 +1,53 @@
# frozen_string_literal: true
class CustomWizard::WizardController < ::ApplicationController
before_action :ensure_plugin_enabled
before_action :ensure_logged_in, only: [:skip]
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
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
private
def ensure_plugin_enabled
unless SiteSetting.custom_wizard_enabled
redirect_to path("/")
end
end
end

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -9,13 +9,10 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
:completed, :completed,
:required, :required,
:permitted, :permitted,
:uncategorized_category_id,
:categories,
:resume_on_revisit :resume_on_revisit
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
has_one :user, serializer: ::BasicUserSerializer, embed: :objects has_one :user, serializer: ::BasicUserSerializer, embed: :objects
has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects
def completed def completed
object.completed? object.completed?
@ -46,30 +43,4 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
def include_steps? def include_steps?
!include_completed? !include_completed?
end 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 do |category|
if category.respond_to?(:to_h)
category.to_h
else
::BasicCategorySerializer.new(category).as_json
end
end
end
end end

Datei anzeigen

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

Datei anzeigen

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

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

Datei anzeigen

@ -3,10 +3,11 @@ import {
observes, observes,
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
import { renderAvatar } from "discourse/helpers/user-avatar"; import { renderAvatar } from "discourse/helpers/user-avatar";
import userSearch from "../lib/user-search"; import userSearch from "discourse/lib/user-search";
import WizardI18n from "../lib/wizard-i18n"; import I18n from "I18n";
import Handlebars from "handlebars"; import Handlebars from "handlebars";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import TextField from "@ember/component/text-field";
const template = function (params) { const template = function (params) {
const options = params.options; const options = params.options;
@ -31,7 +32,7 @@ const template = function (params) {
return new Handlebars.SafeString(html).string; return new Handlebars.SafeString(html).string;
}; };
export default Ember.TextField.extend({ export default TextField.extend({
attributeBindings: ["autofocus", "maxLength"], attributeBindings: ["autofocus", "maxLength"],
autocorrect: false, autocorrect: false,
autocapitalize: false, autocapitalize: false,
@ -40,7 +41,7 @@ export default Ember.TextField.extend({
@computed("placeholderKey") @computed("placeholderKey")
placeholder(placeholderKey) { placeholder(placeholderKey) {
return placeholderKey ? WizardI18n(placeholderKey) : ""; return placeholderKey ? I18n.t(placeholderKey) : "";
}, },
@observes("usernames") @observes("usernames")
@ -55,7 +56,6 @@ export default Ember.TextField.extend({
let self = this, let self = this,
selected = [], selected = [],
groups = [], groups = [],
currentUser = this.currentUser,
includeMentionableGroups = includeMentionableGroups =
this.get("includeMentionableGroups") === "true", this.get("includeMentionableGroups") === "true",
includeMessageableGroups = includeMessageableGroups =
@ -66,13 +66,8 @@ export default Ember.TextField.extend({
function excludedUsernames() { function excludedUsernames() {
// hack works around some issues with allowAny eventing // hack works around some issues with allowAny eventing
const usernames = self.get("single") ? [] : selected; const usernames = self.get("single") ? [] : selected;
if (currentUser && self.get("excludeCurrentUser")) {
return usernames.concat([currentUser.get("username")]);
}
return usernames; return usernames;
} }
$(this.element) $(this.element)
.val(this.get("usernames")) .val(this.get("usernames"))
.autocomplete({ .autocomplete({
@ -84,7 +79,6 @@ export default Ember.TextField.extend({
dataSource(term) { dataSource(term) {
const termRegex = /[^a-zA-Z0-9_\-\.@\+]/; const termRegex = /[^a-zA-Z0-9_\-\.@\+]/;
let results = userSearch({ let results = userSearch({
term: term.replace(termRegex, ""), term: term.replace(termRegex, ""),
topicId: self.get("topicId"), topicId: self.get("topicId"),

Datei anzeigen

@ -1,5 +1,6 @@
import ComposerEditor from "discourse/components/composer-editor"; import ComposerEditor from "discourse/components/composer-editor";
import { import {
bind,
default as discourseComputed, default as discourseComputed,
on, on,
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
@ -8,10 +9,12 @@ import { scheduleOnce } from "@ember/runloop";
import { caretPosition, inCodeBlock } from "discourse/lib/utilities"; import { caretPosition, inCodeBlock } from "discourse/lib/utilities";
import highlightSyntax from "discourse/lib/highlight-syntax"; import highlightSyntax from "discourse/lib/highlight-syntax";
import { alias } from "@ember/object/computed"; import { alias } from "@ember/object/computed";
import Site from "../models/site"; import Site from "discourse/models/site";
import { uploadIcon } from "discourse/lib/uploads"; import { uploadIcon } from "discourse/lib/uploads";
import { dasherize } from "@ember/string"; import { dasherize } from "@ember/string";
const IMAGE_MARKDOWN_REGEX = /!\[(.*?)\|(\d{1,4}x\d{1,4})(,\s*\d{1,3}%)?(.*?)\]\((upload:\/\/.*?)\)(?!(.*`))/g;
export default ComposerEditor.extend({ export default ComposerEditor.extend({
classNameBindings: ["fieldClass"], classNameBindings: ["fieldClass"],
allowUpload: true, allowUpload: true,
@ -24,8 +27,8 @@ export default ComposerEditor.extend({
lastValidatedAt: "lastValidatedAt", lastValidatedAt: "lastValidatedAt",
popupMenuOptions: [], popupMenuOptions: [],
draftStatus: "null", draftStatus: "null",
replyPlaceholder: alias("field.placeholder"), replyPlaceholder: alias("field.translatedPlaceholder"),
uploadingFieldId: null, wizardEventFieldId: null,
@on("didInsertElement") @on("didInsertElement")
_composerEditorInit() { _composerEditorInit() {
@ -34,7 +37,7 @@ export default ComposerEditor.extend({
if (this.siteSettings.enable_mentions) { if (this.siteSettings.enable_mentions) {
$input.autocomplete({ $input.autocomplete({
template: findRawTemplate("user-selector-autocomplete"), template: findRawTemplate("user-selector-autocomplete"),
dataSource: (term) => this.userSearchTerm.call(this, term), dataSource: (term) => this._userSearchTerm.call(this, term),
key: "@", key: "@",
transformComplete: (v) => v.username || v.name, transformComplete: (v) => v.username || v.name,
afterComplete: (value) => { afterComplete: (value) => {
@ -76,7 +79,6 @@ export default ComposerEditor.extend({
const wizardEventNames = ["insert-text", "replace-text"]; const wizardEventNames = ["insert-text", "replace-text"];
const eventPrefix = this.eventPrefix; const eventPrefix = this.eventPrefix;
const session = this.get("session");
this.appEvents.reopen({ this.appEvents.reopen({
trigger(name, ...args) { trigger(name, ...args) {
let eventParts = name.split(":"); let eventParts = name.split(":");
@ -87,26 +89,8 @@ export default ComposerEditor.extend({
currentEventPrefix !== "wizard-editor" && currentEventPrefix !== "wizard-editor" &&
wizardEventNames.some((wen) => wen === currentEventName) wizardEventNames.some((wen) => wen === currentEventName)
) { ) {
let wizardName = name.replace(eventPrefix, "wizard-editor"); let wizardEventName = name.replace(eventPrefix, "wizard-editor");
if (currentEventName === "insert-text") { return this._super(wizardEventName, ...args);
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 { } else {
return this._super(name, ...args); return this._super(name, ...args);
} }
@ -138,11 +122,34 @@ export default ComposerEditor.extend({
} }
}, },
@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: { actions: {
extraButtons(toolbar) { extraButtons(toolbar) {
const component = this; const component = this;
if (this.allowUpload && this.uploadIcon && !this.site.mobileView) { if (this.allowUpload && this.uploadIcon) {
toolbar.addButton({ toolbar.addButton({
id: "upload", id: "upload",
group: "insertions", group: "insertions",
@ -187,14 +194,14 @@ export default ComposerEditor.extend({
} }
}, },
previewUpdated($preview) { previewUpdated(preview) {
highlightSyntax($preview[0], this.siteSettings, this.session); highlightSyntax(preview, this.siteSettings, this.session);
if (this.siteSettings.mentionables_enabled) { if (this.siteSettings.mentionables_enabled) {
const { linkSeenMentionableItems } = requirejs( const { linkSeenMentionableItems } = requirejs(
"discourse/plugins/discourse-mentionables/discourse/lib/mentionable-items-preview-styling" "discourse/plugins/discourse-mentionables/discourse/lib/mentionable-items-preview-styling"
); );
linkSeenMentionableItems($preview, this.siteSettings); linkSeenMentionableItems(preview, this.siteSettings);
} }
this._super(...arguments); this._super(...arguments);
}, },
@ -213,7 +220,7 @@ export default ComposerEditor.extend({
}, },
showUploadModal() { showUploadModal() {
this.session.set("uploadingFieldId", this.field.id); this.session.set("wizardEventFieldId", this.field.id);
document.getElementById(this.fileUploadElementId).click(); document.getElementById(this.fileUploadElementId).click();
}, },
}, },

Datei anzeigen

@ -1,7 +1,8 @@
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
import Category from "discourse/models/category"; import Category from "discourse/models/category";
import Component from "@ember/component";
export default Ember.Component.extend({ export default Component.extend({
didInsertElement() { didInsertElement() {
const property = this.field.property || "id"; const property = this.field.property || "id";
const value = this.field.value; const value = this.field.value;

Datei anzeigen

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

Datei anzeigen

@ -3,8 +3,9 @@ import {
observes, observes,
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import Component from "@ember/component";
export default Ember.Component.extend({ export default Component.extend({
showPreview: false, showPreview: false,
classNameBindings: [ classNameBindings: [
":wizard-field-composer", ":wizard-field-composer",

Datei anzeigen

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

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

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

Datei anzeigen

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

Datei anzeigen

@ -9,6 +9,9 @@ export default Component.extend(UppyUploadMixin, {
type: computed(function () { type: computed(function () {
return `wizard_${this.field.id}`; return `wizard_${this.field.id}`;
}), }),
id: computed(function () {
return `wizard_field_upload_${this.field.id}`;
}),
isImage: computed("field.value.extension", function () { isImage: computed("field.value.extension", function () {
return ( return (
this.field.value && this.field.value &&

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,41 @@
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

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

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

@ -0,0 +1,233 @@
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";
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() {
schedule("afterRender", () => {
const $invalid = $(
".wizard-field.invalid:nth-of-type(1) .wizard-focusable"
);
if ($invalid.length) {
return $invalid.focus();
}
$(".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) {
$([document.documentElement, document.body]).animate(
{
scrollTop: $element.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,10 +1,9 @@
/* eslint no-undef: 0*/
import computed from "discourse-common/utils/decorators"; import computed from "discourse-common/utils/decorators";
import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction";
import WizardI18n from "../lib/wizard-i18n"; import I18n from "I18n";
import TextField from "@ember/component/text-field";
export default Ember.TextField.extend({ export default TextField.extend({
attributeBindings: [ attributeBindings: [
"autocorrect", "autocorrect",
"autocapitalize", "autocapitalize",
@ -15,7 +14,7 @@ export default Ember.TextField.extend({
@computed @computed
dir() { dir() {
if (Wizard.SiteSettings.support_mixed_text_direction) { if (this.siteSettings.support_mixed_text_direction) {
let val = this.value; let val = this.value;
if (val) { if (val) {
return isRTL(val) ? "rtl" : "ltr"; return isRTL(val) ? "rtl" : "ltr";
@ -26,7 +25,7 @@ export default Ember.TextField.extend({
}, },
keyUp() { keyUp() {
if (Wizard.SiteSettings.support_mixed_text_direction) { if (this.siteSettings.support_mixed_text_direction) {
let val = this.value; let val = this.value;
if (isRTL(val)) { if (isRTL(val)) {
this.set("dir", "rtl"); this.set("dir", "rtl");
@ -40,6 +39,6 @@ export default Ember.TextField.extend({
@computed("placeholderKey") @computed("placeholderKey")
placeholder(placeholderKey) { placeholder(placeholderKey) {
return placeholderKey ? WizardI18n(placeholderKey) : ""; return placeholderKey ? I18n.t(placeholderKey) : "";
}, },
}); });

Datei anzeigen

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

Datei anzeigen

@ -1,4 +1,5 @@
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
actions: { actions: {
perform() { perform() {

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 { deepMerge } from "discourse-common/lib/object";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { cancel, later } from "@ember/runloop"; import { cancel, later } from "@ember/runloop";

Datei anzeigen

@ -1,7 +1,6 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { equal } from "@ember/object/computed"; import { equal } from "@ember/object/computed";
import { ajax } from "discourse/lib/ajax"; import { ajax, getToken } from "discourse/lib/ajax";
import { getToken } from "wizard/lib/ajax";
export default Component.extend({ export default Component.extend({
classNames: ["validator"], classNames: ["validator"],
@ -10,7 +9,6 @@ export default Component.extend({
invalidMessageKey: null, invalidMessageKey: null,
isValid: null, isValid: null,
isInvalid: equal("isValid", false), isInvalid: equal("isValid", false),
layoutName: "components/validator", // useful for sharing the template with extending components
init() { init() {
this._super(...arguments); this._super(...arguments);

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 { 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 { notificationLevels, selectKitContent } from "../lib/wizard";
import { computed } from "@ember/object"; import { computed } from "@ember/object";
import wizardSchema from "../lib/wizard-schema";
import UndoChanges from "../mixins/undo-changes"; import UndoChanges from "../mixins/undo-changes";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
@ -16,6 +15,7 @@ export default Component.extend(UndoChanges, {
createTopic: equal("action.type", "create_topic"), createTopic: equal("action.type", "create_topic"),
updateProfile: equal("action.type", "update_profile"), updateProfile: equal("action.type", "update_profile"),
watchCategories: equal("action.type", "watch_categories"), watchCategories: equal("action.type", "watch_categories"),
watchTags: equal("action.type", "watch_tags"),
sendMessage: equal("action.type", "send_message"), sendMessage: equal("action.type", "send_message"),
openComposer: equal("action.type", "open_composer"), openComposer: equal("action.type", "open_composer"),
sendToApi: equal("action.type", "send_to_api"), sendToApi: equal("action.type", "send_to_api"),
@ -25,8 +25,6 @@ export default Component.extend(UndoChanges, {
createGroup: equal("action.type", "create_group"), createGroup: equal("action.type", "create_group"),
apiEmpty: empty("action.api"), apiEmpty: empty("action.api"),
groupPropertyTypes: selectKitContent(["id", "name"]), groupPropertyTypes: selectKitContent(["id", "name"]),
hasAdvanced: or("hasCustomFields", "routeTo"),
showAdvanced: and("hasAdvanced", "action.type"),
hasCustomFields: or( hasCustomFields: or(
"basicTopicFields", "basicTopicFields",
"updateProfile", "updateProfile",
@ -36,22 +34,14 @@ export default Component.extend(UndoChanges, {
basicTopicFields: or("createTopic", "sendMessage", "openComposer"), basicTopicFields: or("createTopic", "sendMessage", "openComposer"),
publicTopicFields: or("createTopic", "openComposer"), publicTopicFields: or("createTopic", "openComposer"),
showPostAdvanced: or("createTopic", "sendMessage"), 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) => { availableNotificationLevels: notificationLevels.map((type) => {
return { return {
id: type, id: type,
name: I18n.t( name: I18n.t(`admin.wizard.action.watch_x.notification_level.${type}`),
`admin.wizard.action.watch_categories.notification_level.${type}`
),
}; };
}), }),
messageUrl: "https://thepavilion.io/t/2810", messageUrl: "https://discourse.pluginmanager.org/t/action-settings",
@discourseComputed("action.type") @discourseComputed("action.type")
messageKey(type) { messageKey(type) {
@ -101,4 +91,14 @@ export default Component.extend(UndoChanges, {
} }
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,5 +1,5 @@
import { default as discourseComputed } from "discourse-common/utils/decorators"; 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 { 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";
@ -27,8 +27,7 @@ export default Component.extend(UndoChanges, {
isTextType: or("isText", "isTextarea", "isComposer"), isTextType: or("isText", "isTextarea", "isComposer"),
isComposerPreview: equal("field.type", "composer_preview"), isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]), categoryPropertyTypes: selectKitContent(["id", "slug"]),
showAdvanced: alias("field.type"), messageUrl: "https://discourse.pluginmanager.org/t/field-settings",
messageUrl: "https://thepavilion.io/t/2809",
@discourseComputed("field.type") @discourseComputed("field.type")
validations(type) { validations(type) {
@ -144,11 +143,17 @@ export default Component.extend(UndoChanges, {
actions: { actions: {
imageUploadDone(upload) { imageUploadDone(upload) {
this.set("field.image", upload.url); this.setProperties({
"field.image": upload.url,
"field.image_upload_id": upload.id,
});
}, },
imageUploadDeleted() { 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: { actions: {
bannerUploadDone(upload) { bannerUploadDone(upload) {
this.set("step.banner", upload.url); this.setProperties({
"step.banner": upload.url,
"step.banner_upload_id": upload.id,
});
}, },
bannerUploadDeleted() { 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: { actions: {
add() { add() {
const items = this.get("items"); const items = this.get("items");
@ -78,16 +89,9 @@ export default Component.extend({
let params = setWizardDefaults({}, itemType); let params = setWizardDefaults({}, itemType);
params.isNew = true; params.isNew = true;
params.index = this.getNextIndex();
let index = 0; let id = `${itemType}_${params.index + 1}`;
if (items.length) {
let last_item = items[items.length - 1];
index = Number(last_item.id.split("_").pop());
}
params.index = index;
let id = `${itemType}_${index + 1}`;
if (itemType === "field") { if (itemType === "field") {
id = `${this.parentId}_${id}`; id = `${this.parentId}_${id}`;
} }

Datei anzeigen

@ -377,7 +377,7 @@ export default Component.extend({
this.changeValue(event.target.value); this.changeValue(event.target.value);
}, },
changeUserValue(previousValue, value) { changeUserValue(value) {
this.changeValue(value); this.changeValue(value);
}, },
}, },

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

@ -0,0 +1,26 @@
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" : "dash";
},
@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

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

@ -0,0 +1,96 @@
import SingleSelectComponent from "select-kit/components/single-select";
import Subscription from "../mixins/subscription";
import wizardSchema 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")
content(feature, attribute) {
return wizardSchema[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

@ -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,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 Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
const excludedUserProperties = [ const excludedUserProperties = ["profile_background", "card_background"];
"avatar",
"profile_background",
"card_background",
];
export default Component.extend({ export default Component.extend({
classNames: "wizard-text-editor", classNames: "wizard-text-editor",
@ -52,12 +48,12 @@ export default Component.extend({
@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: {

Datei anzeigen

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

@ -6,11 +6,11 @@
</label> </label>
<div class="controls"> <div class="controls">
{{combo-box {{combo-box
value=wizardListVal value=wizardListVal
content=wizardList content=wizardList
onChange=(action "changeWizard") onChange=(action "changeWizard")
options=(hash options=(hash
none="admin.wizard.select" none="admin.wizard.select"
)}} )}}
</div> </div>
</section> </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"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default { export default {
setupComponent(attrs, component) { setupComponent(attrs, component) {
CustomWizard.all() CustomWizardAdmin.all()
.then((result) => { .then((result) => {
component.set("wizardList", result); component.set("wizardList", result);
}) })

Datei anzeigen

@ -11,7 +11,7 @@ export default Controller.extend({
queryParams: ["refresh_list"], queryParams: ["refresh_list"],
loadingSubscriptions: false, loadingSubscriptions: false,
notAuthorized: not("api.authorized"), notAuthorized: not("api.authorized"),
endpointMethods: selectKitContent(["GET", "PUT", "POST", "PATCH", "DELETE"]), endpointMethods: selectKitContent(["PUT", "POST", "PATCH", "DELETE"]),
showRemove: not("isNew"), showRemove: not("isNew"),
showRedirectUri: and("threeLeggedOauth", "api.name"), showRedirectUri: and("threeLeggedOauth", "api.name"),
responseIcon: null, responseIcon: null,
@ -88,6 +88,11 @@ export default Controller.extend({
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({});
@ -149,7 +154,6 @@ export default Controller.extend({
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; // eslint-disable-line
let error; let error;
if (!name || !authType) { if (!name || !authType) {
@ -164,11 +168,6 @@ export default Controller.extend({
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;
} }

Datei anzeigen

@ -0,0 +1,14 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, {
actions: {
save() {
this.send("closeModal");
},
resetToDefault() {
this.get("model.reset")();
},
},
});

Datei anzeigen

@ -4,7 +4,7 @@ import CustomWizardCustomField from "../models/custom-wizard-custom-field";
export default Controller.extend({ export default Controller.extend({
messageKey: "create", messageKey: "create",
fieldKeys: ["klass", "type", "name", "serializers"], fieldKeys: ["klass", "type", "name", "serializers"],
documentationUrl: "https://thepavilion.io/t/3572", documentationUrl: "https://discourse.pluginmanager.org/t/custom-fields",
actions: { actions: {
addField() { 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,34 @@
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 Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
export default Controller.extend({ export default Controller.extend({
refreshing: false, documentationUrl: "https://thepavilion.io/t/2818",
hasLogs: notEmpty("logs"),
page: 0,
canLoadMore: true,
logs: [],
loadLogs() { @discourseComputed("wizardId")
if (!this.canLoadMore) { wizardName(wizardId) {
return; 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); return key;
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();
},
}, },
}); });

Datei anzeigen

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

Datei anzeigen

@ -1,6 +1,72 @@
import Controller from "@ember/controller"; 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 { fmt } from "discourse/lib/computed";
import showModal from "discourse/lib/show-modal";
import CustomWizardAdmin from "../models/custom-wizard-admin";
import { formatModel } from "../lib/wizard-submission";
export default Controller.extend({ export default Controller.extend({
downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download"), 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 showModal("admin-wizards-columns", {
model: {
columns: this.get("fields"),
reset: () => {
this.get("fields").forEach((field) => {
field.set("enabled", true);
});
},
},
});
},
},
}); });

Datei anzeigen

@ -0,0 +1,34 @@
import Controller from "@ember/controller";
import { default as discourseComputed } from "discourse-common/utils/decorators";
export default Controller.extend({
documentationUrl: "https://thepavilion.io/t/2818",
@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

@ -36,7 +36,8 @@ export default Controller.extend({
@discourseComputed("wizard.id") @discourseComputed("wizard.id")
wizardUrl(wizardId) { 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") @discourseComputed("wizard.after_time_scheduled")
@ -92,7 +93,11 @@ export default Controller.extend({
wizard wizard
.save(opts) .save(opts)
.then((result) => { .then((result) => {
this.send("afterSave", result.wizard_id); if (result.wizard_id) {
this.send("afterSave", result.wizard_id);
} else if (result.errors) {
this.set("error", result.errors.join(", "));
}
}) })
.catch((result) => { .catch((result) => {
this.set("error", this.getErrorMessage(result)); this.set("error", this.getErrorMessage(result));
@ -118,10 +123,6 @@ export default Controller.extend({
controller.setup(); controller.setup();
}, },
toggleAdvanced() {
this.toggleProperty("wizard.showAdvanced");
},
copyUrl() { copyUrl() {
const $copyRange = $('<p id="copy-range"></p>'); const $copyRange = $('<p id="copy-range"></p>');
$copyRange.html(this.wizardUrl); $copyRange.html(this.wizardUrl);

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