1
0
Fork 0

Commits vergleichen

..

23 Commits

Autor SHA1 Nachricht Datum
Keegan George
dc087fef47 DEV: Remove padding from new line 2021-08-05 10:28:38 -07:00
Keegan George
21da4f14f3 DEV: Remove name attribute in localizations 2021-07-21 13:41:50 -07:00
Keegan George
2979033350 DEV: Add new line at end of file 2021-07-21 13:38:59 -07:00
Keegan George
a08f074dd9 DEV: Add new line at end of file 2021-07-21 13:38:26 -07:00
Keegan George
2030f4543b DEV: Fix space before i18n 2021-07-21 13:37:26 -07:00
Keegan George
4b14ec526d FIX: Use SCSS variables for color manipulation functions 2021-06-29 14:44:37 -07:00
Keegan George
7b7b8adb13 DEV: Fix linting 2021-06-29 11:08:29 -07:00
Keegan George
f48b433b83 DEV: Convert scss variables to CSS Custom properties 2021-06-28 17:58:17 -07:00
Keegan George
058d03f371 DEV: Add space between selectors 2021-06-28 17:40:28 -07:00
Keegan George
d440ab11f4 DEV: Undo formatting fixes auto applied 2021-06-28 17:37:14 -07:00
Keegan George
ed8a61e836 DEV: Undo formatting fixes and add pro support button strings 2021-06-22 13:38:48 -07:00
Keegan George
dbc6baee0b DEV: Remove support url localization 2021-06-22 13:36:00 -07:00
Keegan George
8bf5261c2a DEV: Hardcode pro-support url in button 2021-06-22 13:24:45 -07:00
Keegan George
0ed9e896a5 DEV: Use variables and change selector specificity for pro button 2021-06-22 13:17:55 -07:00
Keegan George
628d3f31db Merge branch 'pro-release' of https://github.com/paviliondev/discourse-custom-wizard into support_buttons 2021-06-22 06:48:31 -07:00
Keegan George
bf536df198 DEV: Format stylesheet code 2021-06-08 18:39:58 -07:00
Keegan George
f005e8f277 UX: Change support button name to Pro Support 2021-06-08 18:35:57 -07:00
Keegan George
247e80649c DEV: Move pro support url to setting 2021-06-08 18:35:03 -07:00
Keegan George
5981db90b4 UX: Merge support button focus styling with hover 2021-06-08 18:33:59 -07:00
Keegan George
3b0b6736e4 UX: Create pro button custom styling 2021-06-08 18:30:45 -07:00
Keegan George
0ba6d5767a Update pro support url 2021-06-08 16:06:53 -07:00
Keegan George
eaf648ea05 FIX: Security vulnerabilities with _blank anchor link 2021-06-08 15:54:43 -07:00
Keegan George
4c923e17d7 Add support button to admin-nav 2021-06-08 15:48:55 -07:00
577 geänderte Dateien mit 7805 neuen und 66760 gelöschten Zeilen

Datei anzeigen

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

Datei anzeigen

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

55
.github/workflows/plugin-linting.yml gevendort Normale Datei
Datei anzeigen

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

138
.github/workflows/plugin-tests.yml gevendort Normale Datei
Datei anzeigen

@ -0,0 +1,138 @@
name: Plugin Tests
on:
push:
branches:
- master
- main
pull_request:
schedule:
- cron: '0 0 * * *'
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: ["4.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: actions/checkout@v2
with:
repository: discourse/discourse
fetch-depth: 1
- run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
shell: bash
- name: Install plugin
uses: actions/checkout@v2
with:
path: plugins/${{ env.REPOSITORY_NAME }}
fetch-depth: 1
- name: Check spec existence
id: check_spec
uses: andstor/file-existence-action@v1
with:
files: "plugins/${{ env.REPOSITORY_NAME }}/spec"
- name: Check qunit existence
id: check_qunit
uses: andstor/file-existence-action@v1
with:
files: "plugins/${{ env.REPOSITORY_NAME }}/test/javascripts"
- 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/${{ env.REPOSITORY_NAME }}/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' && steps.check_spec.outputs.files_exists == 'true'
run: SIMPLECOV=1 bin/rake plugin:spec[${{ env.REPOSITORY_NAME }}]
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true'
run: bundle exec rake plugin:qunit['${{ env.REPOSITORY_NAME }}','1200000']
timeout-minutes: 30

3
.gitignore gevendort
Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -3,12 +3,27 @@ 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"),
@ -25,13 +40,9 @@ export default Component.extend({
const serializers = this.get(`${klass}Serializers`);
if (serializers) {
return serializers.reduce((result, key) => {
result.push({
id: key,
name: I18n.t(`admin.wizard.custom_field.serializers.${key}`),
});
return result;
}, []);
return generateContent(serializers, "serializers");
} else {
return [];
}
},

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,21 @@
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,7 +1,8 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import { empty, equal, or } from "@ember/object/computed";
import { and, 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";
@ -15,7 +16,6 @@ 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,6 +25,8 @@ 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",
@ -34,14 +36,22 @@ 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_x.notification_level.${type}`),
name: I18n.t(
`admin.wizard.action.watch_categories.notification_level.${type}`
),
};
}),
messageUrl: "https://discourse.pluginmanager.org/t/action-settings",
messageUrl: "https://thepavilion.io/t/2810",
@discourseComputed("action.type")
messageKey(type) {
@ -91,14 +101,4 @@ 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 { equal, or } from "@ember/object/computed";
import { alias, equal, or } from "@ember/object/computed";
import { computed } from "@ember/object";
import { selectKitContent } from "../lib/wizard";
import UndoChanges from "../mixins/undo-changes";
@ -25,9 +25,9 @@ export default Component.extend(UndoChanges, {
showContent: or("isCategory", "isTag", "isGroup", "isDropdown"),
showLimit: or("isCategory", "isTag"),
isTextType: or("isText", "isTextarea", "isComposer"),
isComposerPreview: equal("field.type", "composer_preview"),
categoryPropertyTypes: selectKitContent(["id", "slug"]),
messageUrl: "https://discourse.pluginmanager.org/t/field-settings",
showAdvanced: alias("field.type"),
messageUrl: "https://thepavilion.io/t/2809",
@discourseComputed("field.type")
validations(type) {
@ -85,8 +85,8 @@ export default Component.extend(UndoChanges, {
if (this.isDropdown) {
options.wizardFieldSelection = "key,value";
options.userFieldOptionsSelection = "output";
options.textSelection = "key,value";
options.inputTypes = "association,conditional,assignment";
options.textSelection = "key,value,output";
options.inputTypes = "conditional,association,assignment";
options.pairConnector = "association";
options.keyPlaceholder = "admin.wizard.key";
options.valuePlaceholder = "admin.wizard.value";
@ -143,17 +143,11 @@ export default Component.extend(UndoChanges, {
actions: {
imageUploadDone(upload) {
this.setProperties({
"field.image": upload.url,
"field.image_upload_id": upload.id,
});
this.set("field.image", upload.url);
},
imageUploadDeleted() {
this.setProperties({
"field.image": null,
"field.image_upload_id": null,
});
this.set("field.image", null);
},
},
});

Datei anzeigen

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

Datei anzeigen

@ -1,10 +1,15 @@
import discourseComputed from "discourse-common/utils/decorators";
import {
default as discourseComputed,
observes,
on,
} from "discourse-common/utils/decorators";
import { generateName } from "../lib/wizard";
import {
setWizardDefaults,
default as wizardSchema,
} from "../lib/wizard-schema";
import { notEmpty } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import EmberObject from "@ember/object";
import Component from "@ember/component";
import { A } from "@ember/array";
@ -14,12 +19,28 @@ export default Component.extend({
items: A(),
anyLinks: notEmpty("links"),
@on("didInsertElement")
@observes("links.[]")
setupSortable() {
scheduleOnce("afterRender", () => this.applySortable());
},
applySortable() {
$(this.element)
.find(".link-list")
.sortable({ tolerance: "pointer" })
.on("sortupdate", (e, ui) => {
this.updateItemOrder(ui.item.data("id"), ui.item.index());
});
},
updateItemOrder(itemId, newIndex) {
const items = this.items;
const item = items.findBy("id", itemId);
items.removeObject(item);
item.set("index", newIndex);
items.insertAt(newIndex, item);
scheduleOnce("afterRender", this, () => this.applySortable());
},
@discourseComputed("itemType")
@ -37,7 +58,7 @@ export default Component.extend({
return;
}
return items.map((item, index) => {
return items.map((item) => {
if (item) {
let link = {
id: item.id,
@ -56,32 +77,12 @@ export default Component.extend({
}
link.classes = classes;
link.index = index;
if (index === 0) {
link.first = true;
}
if (index === items.length - 1) {
link.last = true;
}
return link;
}
});
},
getNextIndex() {
const items = this.items;
if (!items || items.length === 0) {
return 0;
}
const numbers = items
.map((i) => Number(i.id.split("_").pop()))
.sort((a, b) => a - b);
return numbers[numbers.length - 1];
},
actions: {
add() {
const items = this.get("items");
@ -89,9 +90,15 @@ export default Component.extend({
let params = setWizardDefaults({}, itemType);
params.isNew = true;
params.index = this.getNextIndex();
let id = `${itemType}_${params.index + 1}`;
let index = 0;
if (items.length) {
index = items.length;
}
params.index = index;
let id = `${itemType}_${index + 1}`;
if (itemType === "field") {
id = `${this.parentId}_${id}`;
}
@ -111,14 +118,6 @@ export default Component.extend({
this.set("current", newItem);
},
back(item) {
this.updateItemOrder(item.id, item.index - 1);
},
forward(item) {
this.updateItemOrder(item.id, item.index + 1);
},
change(itemId) {
this.set("current", this.items.findBy("id", itemId));
},

Datei anzeigen

@ -15,7 +15,6 @@ import {
import Component from "@ember/component";
import { bind, later } from "@ember/runloop";
import I18n from "I18n";
import Subscription from "../mixins/subscription";
const customFieldActionMap = {
topic: ["create_topic", "send_message"],
@ -25,9 +24,7 @@ const customFieldActionMap = {
user: ["update_profile"],
};
const values = ["present", "true", "false"];
export default Component.extend(Subscription, {
export default Component.extend({
classNameBindings: [":mapper-selector", "activeType"],
showText: computed("activeType", function () {
@ -63,9 +60,6 @@ export default Component.extend(Subscription, {
showCustomField: computed("activeType", function () {
return this.showInput("customField");
}),
showValue: computed("activeType", function () {
return this.showInput("value");
}),
textEnabled: computed("options.textSelection", "inputType", function () {
return this.optionEnabled("textSelection");
}),
@ -117,50 +111,21 @@ export default Component.extend(Subscription, {
groupEnabled: computed("options.groupSelection", "inputType", function () {
return this.optionEnabled("groupSelection");
}),
guestGroup: computed("options.guestGroup", "inputType", function () {
return this.optionEnabled("guestGroup");
}),
userEnabled: computed("options.userSelection", "inputType", function () {
return this.optionEnabled("userSelection");
}),
listEnabled: computed("options.listSelection", "inputType", function () {
return this.optionEnabled("listSelection");
}),
valueEnabled: computed("connector", function () {
return this.connector === "is";
}),
@discourseComputed("site.groups", "guestGroup", "subscriptionType")
groups(groups, guestGroup, subscriptionType) {
let result = groups;
if (!guestGroup) {
return result;
}
if (["standard", "business"].includes(subscriptionType)) {
let guestIndex;
result.forEach((r, index) => {
if (r.id === 0) {
r.name = I18n.t("admin.wizard.selector.label.users");
guestIndex = index;
}
});
result.splice(guestIndex, 0, {
id: -1,
name: I18n.t("admin.wizard.selector.label.guests"),
});
}
return result;
},
groups: alias("site.groups"),
categories: alias("site.categories"),
showComboBox: or(
"showWizardField",
"showWizardAction",
"showUserField",
"showUserFieldOptions",
"showCustomField",
"showValue"
"showCustomField"
),
showMultiSelect: or("showCategory", "showGroup"),
hasTypes: gt("selectorTypes.length", 1),
@ -192,7 +157,7 @@ export default Component.extend(Subscription, {
}
},
@discourseComputed("connector")
@discourseComputed
selectorTypes() {
return selectionTypes
.filter((type) => this[`${type}Enabled`])
@ -303,13 +268,6 @@ export default Component.extend(Subscription, {
}));
}
if (activeType === "value") {
content = values.map((value) => ({
id: value,
name: value,
}));
}
return content;
},
@ -379,7 +337,7 @@ export default Component.extend(Subscription, {
resetActiveType() {
this.set(
"activeType",
defaultSelectionType(this.selectorType, this.options, this.connector)
defaultSelectionType(this.selectorType, this.options)
);
},
@ -403,7 +361,7 @@ export default Component.extend(Subscription, {
this.changeValue(event.target.value);
},
changeUserValue(value) {
changeUserValue(previousValue, value) {
this.changeValue(value);
},
},

Datei anzeigen

@ -32,7 +32,6 @@ 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,7 +6,6 @@ import I18n from "I18n";
const icons = {
error: "times-circle",
success: "check-circle",
warn: "exclamation-circle",
info: "info-circle",
};

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -5,7 +5,11 @@ import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
import I18n from "I18n";
const excludedUserProperties = ["profile_background", "card_background"];
const excludedUserProperties = [
"avatar",
"profile_background",
"card_background",
];
export default Component.extend({
classNames: "wizard-text-editor",
@ -48,12 +52,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,7 +1,3 @@
{{#if currentUser.admin}}
{{nav-item route="adminWizards" label="admin.wizard.nav_label"}}
{{#if wizardErrorNotice}}
{{d-icon "exclaimation-circle"}}
{{/if}}
{{/if}}
{{/if}}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,10 +1,7 @@
{{#each site.complete_custom_wizard as |wizard|}}
<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}}
{{/each}}

Datei anzeigen

@ -4,18 +4,14 @@ 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(["PUT", "POST", "PATCH", "DELETE"]),
endpointMethods: selectKitContent(["GET", "PUT", "POST", "PATCH", "DELETE"]),
showRemove: not("isNew"),
showRedirectUri: and("threeLeggedOauth", "api.name"),
responseIcon: null,
@ -24,8 +20,29 @@ 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(
@ -71,11 +88,6 @@ 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({});
@ -101,7 +113,7 @@ export default Controller.extend({
if (authType === "oauth_2") {
this.set("authorizing", true);
ajax(`/admin/wizards/apis/${underscore(name)}/authorize`)
ajax(`/admin/wizards/apis/${name.underscore()}/authorize`)
.catch(popupAjaxError)
.then((result) => {
if (result.success) {
@ -137,6 +149,7 @@ 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) {
@ -151,6 +164,11 @@ 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;
}
@ -170,11 +188,11 @@ export default Controller.extend({
if (!api[rp]) {
let key = rp.replace("auth", "");
error = `${I18n.t(
`admin.wizard.api.auth.${underscore(key)}`
`admin.wizard.api.auth.${key.underscore()}`
)} is required for ${authType}`;
break;
}
data[underscore(rp)] = api[rp];
data[rp.underscore()] = api[rp];
}
}
@ -204,7 +222,7 @@ export default Controller.extend({
this.set("updating", true);
ajax(`/admin/wizards/api/${underscore(name)}`, {
ajax(`/admin/wizards/api/${name.underscore()}`, {
type: "PUT",
data,
})
@ -227,7 +245,7 @@ export default Controller.extend({
this.set("updating", true);
ajax(`/admin/wizards/api/${underscore(name)}`, {
ajax(`/admin/wizards/api/${name.underscore()}`, {
type: "DELETE",
})
.catch(popupAjaxError)
@ -245,13 +263,13 @@ export default Controller.extend({
return;
}
ajax(`/admin/wizards/api/${underscore(name)}/logs`, {
ajax(`/admin/wizards/api/${name.underscore()}/logs`, {
type: "DELETE",
})
.catch(popupAjaxError)
.then((result) => {
if (result.success) {
this.router.transitionTo("adminWizardsApis").then(() => {
this.transitionToRoute("adminWizardsApis").then(() => {
this.send("refreshModel");
});
}

Datei anzeigen

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

Datei anzeigen

@ -1,52 +0,0 @@
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,34 +1,50 @@
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({
documentationUrl: "https://thepavilion.io/t/2818",
refreshing: false,
hasLogs: notEmpty("logs"),
page: 0,
canLoadMore: true,
logs: [],
@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";
loadLogs() {
if (!this.canLoadMore) {
return;
}
return key;
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();
},
},
});

Datei anzeigen

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

Datei anzeigen

@ -1,74 +1,6 @@
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

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

@ -3,18 +3,15 @@ import {
observes,
} from "discourse-common/utils/decorators";
import { notEmpty } from "@ember/object/computed";
import { inject as service } from "@ember/service";
import NextSessionScheduledModal from "../components/modal/next-session-scheduled";
import showModal from "discourse/lib/show-modal";
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")
@ -39,8 +36,7 @@ export default Controller.extend({
@discourseComputed("wizard.id")
wizardUrl(wizardId) {
let baseUrl = window.location.href.split("/admin");
return baseUrl[0] + "/w/" + dasherize(wizardId);
return window.location.origin + "/w/" + dasherize(wizardId);
},
@discourseComputed("wizard.after_time_scheduled")
@ -63,34 +59,6 @@ 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;
}
let errorType = "failed";
let errorParams = {};
if (result.error) {
errorType = result.error.type;
errorParams = result.error.params;
}
return I18n.t(`admin.wizard.error.${errorType}`, errorParams);
},
actions: {
save() {
this.setProperties({
@ -109,14 +77,21 @@ 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(", "));
}
this.send("afterSave", result.wizard_id);
})
.catch((result) => {
this.set("error", this.getErrorMessage(result));
let errorType = "failed";
let errorParams = {};
if (result.error) {
errorType = result.error.type;
errorParams = result.error.params;
}
this.set(
"error",
I18n.t(`admin.wizard.error.${errorType}`, errorParams)
);
later(() => this.set("error", null), 10000);
})
@ -128,13 +103,19 @@ export default Controller.extend({
},
setNextSessionScheduled() {
this.modal.show(NextSessionScheduledModal, {
let controller = showModal("next-session-scheduled", {
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,6 +21,5 @@ export default Controller.extend({
return key;
},
messageUrl:
"https://discourse.pluginmanager.org/c/discourse-custom-wizard/documentation",
messageUrl: "https://thepavilion.io/c/knowledge/discourse/custom-wizard",
});

Datei anzeigen

@ -1,9 +0,0 @@
import Controller from "@ember/controller";
import { equal, or } from "@ember/object/computed";
export default Controller.extend({
businessSubscription: equal("subscriptionType", "business"),
communitySubscription: equal("subscriptionType", "community"),
standardSubscription: equal("subscriptionType", "standard"),
showApi: or("businessSubscription", "communitySubscription"),
});

Datei anzeigen

@ -1,24 +0,0 @@
import Controller from "@ember/controller";
import { or } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
const reasons = {
noWizard: "none",
requiresLogin: "requires_login",
notPermitted: "not_permitted",
completed: "completed",
};
export default Controller.extend({
noAccess: or("noWizard", "requiresLogin", "notPermitted", "completed"),
@discourseComputed("noAccessReason")
noAccessI18nKey(reason) {
return reason ? `wizard.${reasons[reason]}` : "wizard.none";
},
@discourseComputed
noAccessReason() {
return Object.keys(reasons).find((reason) => this.get(reason));
},
});

Datei anzeigen

@ -1,5 +0,0 @@
import Controller from "@ember/controller";
export default Controller.extend({
queryParams: ["reset"],
});

Datei anzeigen

@ -0,0 +1,27 @@
import { default as discourseComputed } from "discourse-common/utils/decorators";
import Controller from "@ember/controller";
export default Controller.extend({
title: "admin.wizard.after_time_modal.title",
setup() {
this.set("bufferedDateTime", moment(this.model.dateTime));
},
@discourseComputed("bufferedDateTime")
submitDisabled(dateTime) {
return moment().isAfter(dateTime);
},
actions: {
submit() {
const dateTime = this.get("bufferedDateTime");
this.get("model.update")(moment(dateTime).utc().toISOString());
this.send("closeModal");
},
dateTimeChanged(dateTime) {
this.set("bufferedDateTime", dateTime);
},
},
});

Datei anzeigen

@ -43,16 +43,7 @@ export default {
}
);
this.route(
"adminWizardsLogs",
{ path: "/logs", resetNamespace: true },
function () {
this.route("adminWizardsLogsShow", {
path: "/:wizardId/",
resetNamespace: true,
});
}
);
this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true });
this.route("adminWizardsManager", {
path: "/manager",

Datei anzeigen

@ -1,12 +0,0 @@
export default function () {
this.route(
"customWizard",
{ path: "/w/:wizard_id", resetNamespace: true },
function () {
this.route("customWizardStep", {
path: "/steps/:step_id",
resetNamespace: true,
});
}
);
}

Datei anzeigen

@ -0,0 +1,6 @@
import { registerUnbound } from "discourse-common/lib/helpers";
import { dasherize } from "@ember/string";
registerUnbound("dasherize", function (string) {
return dasherize(string);
});

Datei anzeigen

@ -1,25 +0,0 @@
import { registerUnbound } from "discourse-common/lib/helpers";
import I18n from "I18n";
import Handlebars from "handlebars";
export default registerUnbound(
"wizard-char-counter",
function (body, maxLength) {
let bodyLength = body ? body.length : 0;
let finalString;
if (maxLength) {
let isOverMax = bodyLength > maxLength ? "true" : "false";
finalString = `<div class="body-length" data-length=${bodyLength} data-over-max=${isOverMax}>${bodyLength} / ${I18n.t(
"wizard.x_characters",
{ count: parseInt(maxLength, 10) }
)}</div>`;
} else {
finalString = `<div class="body-length">${I18n.t("wizard.x_characters", {
count: parseInt(bodyLength, 10),
})}</div>`;
}
return new Handlebars.SafeString(finalString);
}
);

Datei anzeigen

@ -1,12 +1,9 @@
import DiscourseURL from "discourse/lib/url";
import { withPluginApi } from "discourse/lib/plugin-api";
import getUrl from "discourse-common/lib/get-url";
import { observes } from "discourse-common/utils/decorators";
export default {
name: "custom-wizard-edits",
initialize(container) {
const siteSettings = container.lookup("service:site-settings");
const siteSettings = container.lookup("site-settings:main");
if (!siteSettings.custom_wizard_enabled) {
return;
@ -19,80 +16,5 @@ export default {
}
return existing.apply(this, [path, opts]);
};
withPluginApi("0.8.36", (api) => {
api.modifyClass("component:d-navigation", {
pluginId: "custom-wizard",
actions: {
clickCreateTopicButton() {
let createTopicWizard = this.get(
"category.custom_fields.create_topic_wizard"
);
if (createTopicWizard) {
window.location.href = getUrl(`/w/${createTopicWizard}`);
} else {
this._super();
}
},
},
});
api.modifyClass("component:uppy-image-uploader", {
pluginId: "custom-wizard",
// Needed to ensure appEvents get registered when navigating between steps
@observes("id")
initOnStepChange() {
if (/wizard-field|wizard-step/.test(this.id)) {
this._initialize();
}
},
});
api.modifyClass("component:d-editor", {
pluginId: "custom-wizard",
didInsertElement() {
this._super(...arguments);
if (this.wizardComposer) {
this.appEvents.on(
`wizard-editor:insert-text`,
this,
"_wizardInsertText"
);
this.appEvents.on(
"wizard-editor:replace-text",
this,
"_wizardReplaceText"
);
}
},
_wizardInsertText(text, options) {
if (
this.session.wizardEventFieldId === this.fieldId &&
this.element
) {
this.insertText(text, options);
}
},
_wizardReplaceText(oldVal, newVal, opts = {}) {
if (this.session.wizardEventFieldId === this.fieldId) {
this.replaceText(oldVal, newVal, opts);
}
},
});
api.modifyClass("component:category-chooser", {
categoriesByScope(options = {}) {
let categories = this._super(options);
return categories.filter((category) => {
return !category.custom_fields?.create_topic_wizard;
});
},
});
});
},
};

Datei anzeigen

@ -1,16 +1,14 @@
import DiscourseURL from "discourse/lib/url";
import { withPluginApi } from "discourse/lib/plugin-api";
import { dasherize } from "@ember/string";
import ApplicationRoute from "discourse/routes/application";
export default {
name: "custom-wizard-redirect",
after: "message-bus",
initialize(container) {
const messageBus = container.lookup("service:message-bus");
const siteSettings = container.lookup("service:site-settings");
initialize: function (container) {
const messageBus = container.lookup("message-bus:main");
const siteSettings = container.lookup("site-settings:main");
if (!siteSettings.custom_wizard_enabled) {
if (!siteSettings.custom_wizard_enabled || !messageBus) {
return;
}
@ -19,27 +17,28 @@ export default {
window.location.href = wizardUrl;
});
withPluginApi("0.8.36", (api) => {
api.onAppEvent("page:changed", (data) => {
const currentUser = api.getCurrentUser();
if (currentUser) {
const redirectToWizard = currentUser.redirect_to_wizard;
const excludedPaths = siteSettings.wizard_redirect_exclude_paths
ApplicationRoute.reopen({
actions: {
willTransition(transition) {
const redirectToWizard = this.get("currentUser.redirect_to_wizard");
const excludedPaths = this.siteSettings.wizard_redirect_exclude_paths
.split("|")
.concat(["loading"]);
if (
redirectToWizard &&
!data.url.includes("ignore_redirect") &&
data.currentRouteName !== "customWizardStep" &&
!excludedPaths.find((p) => {
return data.currentRouteName.indexOf(p) > -1;
})
(!transition.intent.name ||
!excludedPaths.find((p) => {
return transition.intent.name.indexOf(p) > -1;
}))
) {
DiscourseURL.routeTo(`/w/${dasherize(redirectToWizard)}`);
transition.abort();
window.location = "/w/" + redirectToWizard.dasherize();
}
}
});
return this._super(transition);
},
},
});
},
};

Datei anzeigen

@ -97,6 +97,11 @@ function buildObjectArray(json, type) {
if (present(json)) {
json.forEach((objJson, objectIndex) => {
let object = buildObject(objJson, type, objectIndex);
if (hasAdvancedProperties(object, type)) {
object.set("showAdvanced", true);
}
array.pushObject(object);
});
}
@ -107,11 +112,21 @@ function buildObjectArray(json, type) {
function buildBasicProperties(json, type, props, objectIndex = null) {
listProperties(type).forEach((p) => {
props[p] = buildProperty(json, p, type, objectIndex);
if (hasAdvancedProperties(json, type)) {
props.showAdvanced = true;
}
});
return props;
}
function hasAdvancedProperties(object, type) {
return Object.keys(object).some((p) => {
return wizardSchema[type].advanced.indexOf(p) > -1 && present(object[p]);
});
}
/// to be removed: necessary due to action array being moved from step to wizard
function actionPatch(json) {
let actions = json.actions || [];

Datei anzeigen

@ -35,7 +35,6 @@ function inputTypesContent(options = {}) {
const connectors = {
pair: [
"equal",
"not_equal",
"greater",
"less",
"greater_or_equal",
@ -106,18 +105,13 @@ const selectionTypes = [
"tag",
"user",
"customField",
"value",
];
function defaultSelectionType(inputType, options = {}, connector = null) {
function defaultSelectionType(inputType, options = {}) {
if (options[`${inputType}DefaultSelection`]) {
return options[`${inputType}DefaultSelection`];
}
if (connector === "is") {
return "value";
}
let type = selectionTypes[0];
for (let t of selectionTypes) {

Datei anzeigen

@ -14,11 +14,11 @@ const wizard = {
required: null,
prompt_completion: null,
restart_on_revisit: null,
resume_on_revisit: null,
theme_id: null,
permitted: null,
},
mapped: ["permitted"],
advanced: ["restart_on_revisit"],
required: ["id"],
dependent: {
after_time: "after_time_scheduled",
@ -40,8 +40,8 @@ const step = {
id: null,
index: null,
title: null,
key: null,
banner: null,
banner_upload_id: null,
raw_description: null,
required_data: null,
required_data_message: null,
@ -50,6 +50,7 @@ const step = {
force_final: false,
},
mapped: ["required_data", "permitted_params", "condition", "index"],
advanced: ["required_data", "permitted_params", "condition", "index"],
required: ["id"],
dependent: {},
objectArrays: {
@ -66,16 +67,15 @@ const field = {
index: null,
label: null,
image: null,
image_upload_id: null,
description: null,
property: null,
required: null,
key: null,
type: null,
condition: null,
tag_groups: null,
},
types: {},
mapped: ["prefill", "content", "condition", "index"],
advanced: ["property", "key", "condition", "index"],
required: ["id", "type"],
dependent: {},
objectArrays: {},
@ -99,8 +99,6 @@ const action = {
custom_fields: null,
skip_redirect: null,
suppress_notifications: null,
add_event: null,
add_location: null,
},
send_message: {
title: null,
@ -133,12 +131,6 @@ const action = {
wizard_user: true,
usernames: null,
},
watch_tags: {
tags: null,
notification_level: null,
wizard_user: true,
usernames: null,
},
send_to_api: {
api: null,
api_endpoint: null,
@ -203,83 +195,35 @@ const action = {
"messageable_level",
"visibility_level",
"members_visibility_level",
"add_event",
"add_location",
],
advanced: [
"code",
"custom_fields",
"skip_redirect",
"suppress_notifications",
"required",
],
required: ["id", "type"],
dependent: {},
objectArrays: {},
};
const filters = {
allow_guests: {
field: {
type: [
"text",
"textarea",
"text_only",
"date",
"time",
"date_time",
"number",
"checkbox",
"url",
"dropdown",
"tag",
"category",
"group",
"user_selector",
],
},
action: {
type: ["route_to", "send_message"],
},
},
};
const custom_field = {
klass: ["topic", "post", "group", "category"],
type: ["string", "boolean", "integer", "json"],
};
export function buildFieldTypes(types) {
wizardSchema.field.types = types;
wizardSchema.field.type = Object.keys(types);
}
field.type = Object.keys(field.types);
action.type = Object.keys(action.types);
const wizardSchema = {
wizard,
step,
field,
custom_field,
action,
filters,
};
export function buildFieldTypes(types) {
wizardSchema.field.types = types;
}
export function buildFieldValidations(validations) {
wizardSchema.field.validations = validations;
}
export function filterValues(currentWizard, feature, attribute, values = null) {
values = values || wizardSchema[feature][attribute];
if (currentWizard && currentWizard.allowGuests) {
const filteredFeature = wizardSchema.filters.allow_guests[feature];
if (filteredFeature) {
const filtered = filteredFeature[attribute];
if (filtered) {
values = values.filter((v) => filtered.includes(v));
}
}
}
return values;
}
const siteSettings = getOwner(this).lookup("service:site-settings");
const siteSettings = getOwner(this).lookup("site-settings:main");
if (siteSettings.wizard_apis_enabled) {
wizardSchema.action.types.send_to_api = {
api: null,

Datei anzeigen

@ -1,39 +0,0 @@
import EmberObject from "@ember/object";
function formatModel(model) {
let fields = [
EmberObject.create({
id: "submitted_at",
label: "Submitted At",
enabled: true,
}),
EmberObject.create({ id: "username", label: "User", enabled: true }),
];
let submissions = [];
model.submissions.forEach((s) => {
let submission = {
submitted_at: s.submitted_at,
username: s.user,
};
Object.keys(s.fields).forEach((fieldId) => {
if (!fields.some((field) => field.id === fieldId)) {
fields.push(
EmberObject.create({
id: fieldId,
label: s.fields[fieldId].label,
enabled: true,
})
);
}
submission[fieldId] = s.fields[fieldId];
});
submissions.push(EmberObject.create(submission));
});
return { fields, submissions };
}
export { formatModel };

Datei anzeigen

@ -1,6 +1,5 @@
import EmberObject from "@ember/object";
import wizardSchema from "./wizard-schema";
import I18n from "I18n";
function selectKitContent(content) {
return content.map((i) => ({ id: i, name: i }));
@ -34,10 +33,6 @@ function camelCase(string) {
});
}
function translationOrText(i18nKey, text) {
return I18n.findTranslation(i18nKey) ? I18n.t(i18nKey) : text;
}
const userProperties = [
"name",
"username",
@ -126,5 +121,4 @@ export {
notificationLevels,
wizardFieldList,
sentenceCase,
translationOrText,
};

Datei anzeigen

@ -1,53 +0,0 @@
import Mixin from "@ember/object/mixin";
import { getOwner } from "discourse-common/lib/get-owner";
import { readOnly } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
const PRODUCT_PAGE = "https://custom-wizard.pavilion.tech";
const SUPPORT_MESSAGE =
"https://coop.pavilion.tech/new-message?username=support&title=Custom%20Wizard%20Support";
const MANAGER_CATEGORY =
"https://discourse.pluginmanager.org/c/discourse-custom-wizard";
export default Mixin.create({
subscriptionLandingUrl: PRODUCT_PAGE,
subscriptionClientUrl: "/admin/plugins/subscription-client",
@discourseComputed
adminWizards() {
return getOwner(this).lookup("controller:admin-wizards");
},
subscribed: readOnly("adminWizards.subscribed"),
subscriptionType: readOnly("adminWizards.subscriptionType"),
businessSubscription: readOnly("adminWizards.businessSubscription"),
communitySubscription: readOnly("adminWizards.communitySubscription"),
standardSubscription: readOnly("adminWizards.standardSubscription"),
subscriptionAttributes: readOnly("adminWizards.subscriptionAttributes"),
subscriptionClientInstalled: readOnly(
"adminWizards.subscriptionClientInstalled"
),
@discourseComputed("subscriptionClientInstalled")
subscriptionLink(subscriptionClientInstalled) {
return subscriptionClientInstalled
? this.subscriptionClientUrl
: this.subscriptionLandingUrl;
},
@discourseComputed("subscriptionType")
subscriptionCtaLink(subscriptionType) {
switch (subscriptionType) {
case "none":
return PRODUCT_PAGE;
case "standard":
return SUPPORT_MESSAGE;
case "business":
return SUPPORT_MESSAGE;
case "community":
return MANAGER_CATEGORY;
default:
return PRODUCT_PAGE;
}
},
});

Datei anzeigen

@ -4,8 +4,6 @@ import { get, set } from "@ember/object";
import Mixin from "@ember/object/mixin";
import { deepEqual } from "discourse-common/lib/object";
const observedCache = [];
export default Mixin.create({
didInsertElement() {
this._super(...arguments);
@ -34,13 +32,7 @@ export default Mixin.create({
};
listProperties(componentType, opts).forEach((property) => {
if (observedCache.includes(property)) {
obj.removeObserver(property, this, this.toggleUndo);
let index = observedCache.indexOf(property);
if (index !== -1) {
observedCache.splice(index, 1);
}
}
obj.removeObserver(property, this, this.toggleUndo);
});
},
@ -53,9 +45,6 @@ export default Mixin.create({
};
listProperties(componentType, opts).forEach((property) => {
if (observedCache.indexOf(property) === -1) {
observedCache.push(property);
}
obj.addObserver(property, this, this.toggleUndo);
});
},

Datei anzeigen

@ -1,36 +0,0 @@
import discourseComputed from "discourse-common/utils/decorators";
export const States = {
UNCHECKED: 0,
INVALID: 1,
VALID: 2,
};
export default {
_validState: null,
errorDescription: null,
init() {
this._super(...arguments);
this.set("_validState", States.UNCHECKED);
},
@discourseComputed("_validState")
valid: (state) => state === States.VALID,
@discourseComputed("_validState")
invalid: (state) => state === States.INVALID,
@discourseComputed("_validState")
unchecked: (state) => state === States.UNCHECKED,
setValid(valid, description) {
this.set("_validState", valid ? States.VALID : States.INVALID);
if (!valid && description && description.length) {
this.set("errorDescription", description);
} else {
this.set("errorDescription", null);
}
},
};

Datei anzeigen

@ -1,242 +0,0 @@
import EmberObject from "@ember/object";
import { buildProperties, mapped, present } from "../lib/wizard-json";
import { listProperties, snakeCase } from "../lib/wizard";
import wizardSchema from "../lib/wizard-schema";
import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse-common/utils/decorators";
const GUEST_GROUP_ID = -1;
const CustomWizardAdmin = EmberObject.extend({
@discourseComputed("permitted.@each.output")
allowGuests(permitted) {
return (
permitted &&
permitted.filter((p) => p.output && p.output.includes(GUEST_GROUP_ID))
.length
);
},
save(opts) {
return new Promise((resolve, reject) => {
let wizard = this.buildJson(this, "wizard");
if (wizard.error) {
reject(wizard);
}
let data = {
wizard,
};
if (opts.create) {
data.create = true;
}
ajax(`/admin/wizards/wizard/${wizard.id}`, {
type: "PUT",
contentType: "application/json",
data: JSON.stringify(data),
}).then((result) => {
if (result.backend_validation_error) {
reject(result);
} else {
resolve(result);
}
});
});
},
buildJson(object, type, result = {}) {
let objectType = object.type || null;
if (wizardSchema[type].types) {
if (!objectType) {
result.error = {
type: "required",
params: { type, property: "type" },
};
return result;
}
}
for (let property of listProperties(type, { objectType })) {
let value = object.get(property);
result = this.validateValue(property, value, object, type, result);
if (result.error) {
break;
}
if (mapped(property, type)) {
value = this.buildMappedJson(value);
}
if (value !== undefined && value !== null) {
result[property] = value;
}
}
if (!result.error) {
for (let arrayObjectType of Object.keys(
wizardSchema[type].objectArrays
)) {
let arraySchema = wizardSchema[type].objectArrays[arrayObjectType];
let objectArray = object.get(arraySchema.property);
if (arraySchema.required && !present(objectArray)) {
result.error = {
type: "required",
params: { type, property: arraySchema.property },
};
break;
}
result[arraySchema.property] = [];
for (let item of objectArray) {
let itemProps = this.buildJson(item, arrayObjectType);
if (itemProps.error) {
result.error = itemProps.error;
break;
} else {
result[arraySchema.property].push(itemProps);
}
}
}
}
return result;
},
validateValue(property, value, object, type, result) {
if (wizardSchema[type].required.indexOf(property) > -1 && !value) {
result.error = {
type: "required",
params: { type, property },
};
}
let dependent = wizardSchema[type].dependent[property];
if (dependent && value && !object[dependent]) {
result.error = {
type: "dependent",
params: { property, dependent },
};
}
if (property === "api_body") {
try {
value = JSON.parse(value);
} catch (e) {
result.error = {
type: "invalid",
params: { type, property },
};
}
}
return result;
},
buildMappedJson(value) {
if (typeof value === "string" || Number.isInteger(value)) {
return value;
}
if (!value || !value.length) {
return false;
}
let inputs = value;
let result = [];
inputs.forEach((inpt) => {
let input = {
type: inpt.type,
};
if (inpt.connector) {
input.connector = inpt.connector;
}
if (present(inpt.output)) {
input.output = inpt.output;
input.output_type = snakeCase(inpt.output_type);
input.output_connector = inpt.output_connector;
}
if (present(inpt.pairs)) {
input.pairs = [];
inpt.pairs.forEach((pr) => {
if (present(pr.key) && present(pr.value)) {
let pairParams = {
index: pr.index,
key: pr.key,
key_type: snakeCase(pr.key_type),
value: pr.value,
value_type: snakeCase(pr.value_type),
connector: pr.connector,
};
input.pairs.push(pairParams);
}
});
}
if (
(input.type === "assignment" && present(input.output)) ||
present(input.pairs)
) {
result.push(input);
}
});
if (!result.length) {
result = false;
}
return result;
},
remove() {
return ajax(`/admin/wizards/wizard/${this.id}`, {
type: "DELETE",
})
.then(() => this.destroy())
.catch(popupAjaxError);
},
});
CustomWizardAdmin.reopenClass({
all() {
return ajax("/admin/wizards/wizard", {
type: "GET",
})
.then((result) => {
return result.wizard_list;
})
.catch(popupAjaxError);
},
submissions(wizardId, page = 0) {
return ajax(`/admin/wizards/submissions/${wizardId}`, {
type: "GET",
data: {
page,
},
}).catch(popupAjaxError);
},
create(wizardJson = {}) {
const wizard = this._super.apply(this);
wizard.setProperties(buildProperties(wizardJson));
return wizard;
},
});
export default CustomWizardAdmin;

Datei anzeigen

@ -1,79 +0,0 @@
import EmberObject from "@ember/object";
import ValidState from "discourse/plugins/discourse-custom-wizard/discourse/mixins/valid-state";
import discourseComputed from "discourse-common/utils/decorators";
import { translationOrText } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard";
const StandardFieldValidation = [
"text",
"number",
"textarea",
"dropdown",
"tag",
"image",
"user_selector",
"text_only",
"composer",
"category",
"group",
"date",
"time",
"date_time",
];
export default EmberObject.extend(ValidState, {
id: null,
type: null,
value: null,
required: null,
warning: null,
@discourseComputed("wizardId", "stepId", "id")
i18nKey(wizardId, stepId, id) {
return `${wizardId}.${stepId}.${id}`;
},
@discourseComputed("i18nKey", "label")
translatedLabel(i18nKey, label) {
return translationOrText(`${i18nKey}.label`, label);
},
@discourseComputed("i18nKey", "placeholder")
translatedPlaceholder(i18nKey, placeholder) {
return translationOrText(`${i18nKey}.placeholder`, placeholder);
},
@discourseComputed("i18nKey", "description")
translatedDescription(i18nKey, description) {
return translationOrText(`${i18nKey}.description`, description);
},
check() {
if (this.customCheck) {
return this.customCheck();
}
let valid = this.valid;
if (!this.required) {
this.setValid(true);
return true;
}
const val = this.get("value");
const type = this.get("type");
if (type === "checkbox") {
valid = val;
} else if (type === "upload") {
valid = val && val.id > 0;
} else if (StandardFieldValidation.indexOf(type) > -1) {
valid = val && val.toString().length > 0;
} else if (type === "url") {
valid = true;
}
this.setValid(Boolean(valid));
return valid;
},
});

Datei anzeigen

@ -3,54 +3,14 @@ import { popupAjaxError } from "discourse/lib/ajax-error";
import EmberObject from "@ember/object";
const CustomWizardLogs = EmberObject.extend();
const logItemTypes = {
date: "date_time",
action: "text",
message: "long_text",
user: "user",
username: "text",
};
function logItem(item, attr) {
return {
value: item[attr],
type: logItemTypes[attr],
};
}
CustomWizardLogs.reopenClass({
list(wizardId, page = 0) {
let data = {
page,
};
return ajax(`/admin/wizards/logs/${wizardId}`, { data })
.catch(popupAjaxError)
.then((result) => {
if (result.logs) {
result.logs = result.logs.map((item) => {
let map = {};
if (item.date) {
map.date = logItem(item, "date");
}
if (item.action) {
map.action = logItem(item, "action");
}
if (item.user) {
map.user = item.user;
} else {
map.user = logItem(item, "username");
}
if (item.message) {
map.message = logItem(item, "message");
}
return map;
});
}
return result;
});
list(page = 0) {
return ajax("/admin/wizards/logs", {
data: {
page,
},
}).catch(popupAjaxError);
},
});

Datei anzeigen

@ -1,7 +1,6 @@
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import EmberObject from "@ember/object";
import getURL from "discourse-common/lib/get-url";
const CustomWizardManager = EmberObject.extend();
@ -18,7 +17,7 @@ CustomWizardManager.reopenClass({
},
export(wizardIds) {
let url = `${getURL()}/${basePath}/export?`;
let url = `${Discourse.BaseUrl}/${basePath}/export?`;
wizardIds.forEach((wizardId, index) => {
let step = "wizard_ids[]=" + wizardId;

Datei anzeigen

@ -1,114 +0,0 @@
import EmberObject from "@ember/object";
import ValidState from "discourse/plugins/discourse-custom-wizard/discourse/mixins/valid-state";
import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators";
import { later } from "@ember/runloop";
import { translationOrText } from "discourse/plugins/discourse-custom-wizard/discourse/lib/wizard";
export default EmberObject.extend(ValidState, {
id: null,
@discourseComputed("wizardId", "id")
i18nKey(wizardId, stepId) {
return `${wizardId}.${stepId}`;
},
@discourseComputed("i18nKey", "title")
translatedTitle(i18nKey, title) {
return translationOrText(`${i18nKey}.title`, title);
},
@discourseComputed("i18nKey", "description")
translatedDescription(i18nKey, description) {
return translationOrText(`${i18nKey}.description`, description);
},
@discourseComputed("index")
displayIndex: (index) => index + 1,
@discourseComputed("fields.[]")
fieldsById(fields) {
const lookup = {};
fields.forEach((field) => (lookup[field.get("id")] = field));
return lookup;
},
validate() {
let allValid = true;
const result = { warnings: [] };
this.fields.forEach((field) => {
allValid = allValid && field.check();
const warning = field.get("warning");
if (warning) {
result.warnings.push(warning);
}
});
this.setValid(allValid);
return result;
},
fieldError(id, description) {
const field = this.fields.findBy("id", id);
if (field) {
field.setValid(false, description);
}
},
save() {
const wizardId = this.get("wizardId");
const fields = {};
this.get("fields").forEach((f) => {
if (f.type !== "text_only") {
fields[f.id] = f.value;
}
});
return ajax({
url: `/w/${wizardId}/steps/${this.get("id")}`,
type: "PUT",
data: { fields },
}).catch((response) => {
if (response.jqXHR) {
response = response.jqXHR;
}
if (response && response.responseJSON && response.responseJSON.errors) {
let wizardErrors = [];
response.responseJSON.errors.forEach((err) => {
if (err.field === wizardId) {
wizardErrors.push(err.description);
} else if (err.field) {
this.fieldError(err.field, err.description);
} else if (err) {
wizardErrors.push(err);
}
});
if (wizardErrors.length) {
this.handleWizardError(wizardErrors.join("\n"));
}
this.animateInvalidFields();
throw response;
}
if (response && response.responseText) {
const responseText = response.responseText;
const start = responseText.indexOf(">") + 1;
const end = responseText.indexOf("plugins");
const message = responseText.substring(start, end);
this.handleWizardError(message);
throw message;
}
});
},
handleWizardError(message) {
this.set("message", {
state: "error",
text: message,
});
later(() => this.set("message", null), 6000);
},
});

Datei anzeigen

@ -1,127 +1,227 @@
import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import EmberObject from "@ember/object";
import { buildProperties, mapped, present } from "../lib/wizard-json";
import { listProperties, snakeCase } from "../lib/wizard";
import wizardSchema from "../lib/wizard-schema";
import { Promise } from "rsvp";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseComputed from "discourse-common/utils/decorators";
import getUrl from "discourse-common/lib/get-url";
import CustomWizardField from "./custom-wizard-field";
import CustomWizardStep from "./custom-wizard-step";
const CustomWizard = EmberObject.extend({
@discourseComputed("steps.length")
totalSteps: (length) => length,
save(opts) {
return new Promise((resolve, reject) => {
let wizard = this.buildJson(this, "wizard");
skip() {
if (this.required && !this.completed && this.permitted) {
return;
}
CustomWizard.skip(this.id);
if (wizard.error) {
reject(wizard);
}
let data = {
wizard,
};
if (opts.create) {
data.create = true;
}
ajax(`/admin/wizards/wizard/${wizard.id}`, {
type: "PUT",
contentType: "application/json",
data: JSON.stringify(data),
}).then((result) => {
if (result.error) {
reject(result);
} else {
resolve(result);
}
});
});
},
restart() {
CustomWizard.restart(this.id);
buildJson(object, type, result = {}) {
let objectType = object.type || null;
if (wizardSchema[type].types) {
if (!objectType) {
result.error = {
type: "required",
params: { type, property: "type" },
};
return result;
}
}
for (let property of listProperties(type, { objectType })) {
let value = object.get(property);
result = this.validateValue(property, value, object, type, result);
if (result.error) {
break;
}
if (mapped(property, type)) {
value = this.buildMappedJson(value);
}
if (value !== undefined && value !== null) {
result[property] = value;
}
}
if (!result.error) {
for (let arrayObjectType of Object.keys(
wizardSchema[type].objectArrays
)) {
let arraySchema = wizardSchema[type].objectArrays[arrayObjectType];
let objectArray = object.get(arraySchema.property);
if (arraySchema.required && !present(objectArray)) {
result.error = {
type: "required",
params: { type, property: arraySchema.property },
};
break;
}
result[arraySchema.property] = [];
for (let item of objectArray) {
let itemProps = this.buildJson(item, arrayObjectType);
if (itemProps.error) {
result.error = itemProps.error;
break;
} else {
result[arraySchema.property].push(itemProps);
}
}
}
}
return result;
},
validateValue(property, value, object, type, result) {
if (wizardSchema[type].required.indexOf(property) > -1 && !value) {
result.error = {
type: "required",
params: { type, property },
};
}
let dependent = wizardSchema[type].dependent[property];
if (dependent && value && !object[dependent]) {
result.error = {
type: "dependent",
params: { property, dependent },
};
}
if (property === "api_body") {
try {
value = JSON.parse(value);
} catch (e) {
result.error = {
type: "invalid",
params: { type, property },
};
}
}
return result;
},
buildMappedJson(value) {
if (typeof value === "string" || Number.isInteger(value)) {
return value;
}
if (!value || !value.length) {
return false;
}
let inputs = value;
let result = [];
inputs.forEach((inpt) => {
let input = {
type: inpt.type,
};
if (inpt.connector) {
input.connector = inpt.connector;
}
if (present(inpt.output)) {
input.output = inpt.output;
input.output_type = snakeCase(inpt.output_type);
input.output_connector = inpt.output_connector;
}
if (present(inpt.pairs)) {
input.pairs = [];
inpt.pairs.forEach((pr) => {
if (present(pr.key) && present(pr.value)) {
let pairParams = {
index: pr.index,
key: pr.key,
key_type: snakeCase(pr.key_type),
value: pr.value,
value_type: snakeCase(pr.value_type),
connector: pr.connector,
};
input.pairs.push(pairParams);
}
});
}
if (
(input.type === "assignment" && present(input.output)) ||
present(input.pairs)
) {
result.push(input);
}
});
if (!result.length) {
result = false;
}
return result;
},
remove() {
return ajax(`/admin/wizards/wizard/${this.id}`, {
type: "DELETE",
})
.then(() => this.destroy())
.catch(popupAjaxError);
},
});
CustomWizard.reopenClass({
skip(wizardId) {
ajax({ url: `/w/${wizardId}/skip`, type: "PUT" })
all() {
return ajax("/admin/wizards/wizard", {
type: "GET",
})
.then((result) => {
CustomWizard.finished(result);
return result.wizard_list;
})
.catch(popupAjaxError);
},
restart(wizardId) {
ajax({ url: `/w/${wizardId}/skip`, type: "PUT" })
.then(() => {
window.location.href = `/w/${wizardId}`;
})
.catch(popupAjaxError);
submissions(wizardId) {
return ajax(`/admin/wizards/submissions/${wizardId}`, {
type: "GET",
}).catch(popupAjaxError);
},
finished(result) {
let url = "/";
if (result.redirect_on_complete) {
url = result.redirect_on_complete;
}
window.location.href = getUrl(url);
},
build(wizardJson) {
if (!wizardJson) {
return null;
}
if (!wizardJson.completed && wizardJson.steps) {
wizardJson.steps = wizardJson.steps
.map((step) => {
const stepObj = CustomWizardStep.create(step);
stepObj.wizardId = wizardJson.id;
stepObj.fields.sort((a, b) => {
return parseFloat(a.number) - parseFloat(b.number);
});
let tabindex = 1;
stepObj.fields.forEach((f) => {
f.tabindex = tabindex;
if (["date_time"].includes(f.type)) {
tabindex = tabindex + 2;
} else {
tabindex++;
}
});
stepObj.fields = stepObj.fields.map((f) => {
f.wizardId = wizardJson.id;
f.stepId = stepObj.id;
return CustomWizardField.create(f);
});
return stepObj;
})
.sort((a, b) => {
return parseFloat(a.index) - parseFloat(b.index);
});
}
return CustomWizard.create(wizardJson);
create(wizardJson = {}) {
const wizard = this._super.apply(this);
wizard.setProperties(buildProperties(wizardJson));
return wizard;
},
});
export function findCustomWizard(wizardId, params = {}) {
let url = `/w/${wizardId}.json`;
let paramKeys = Object.keys(params).filter((k) => {
if (k === "wizard_id") {
return false;
}
return !!params[k];
});
if (paramKeys.length) {
url += "?";
paramKeys.forEach((k, i) => {
if (i > 0) {
url += "&";
}
url += `${k}=${params[k]}`;
});
}
return ajax(url).then((result) => {
return CustomWizard.build(result);
});
}
let _wizard_store;
export function updateCachedWizard(wizard) {
_wizard_store = wizard;
}
export function getCachedWizard() {
return _wizard_store;
}
export default CustomWizard;

Datei anzeigen

@ -1,10 +1,7 @@
import CustomWizardApi from "../models/custom-wizard-api";
import DiscourseRoute from "discourse/routes/discourse";
import { inject as service } from "@ember/service";
export default DiscourseRoute.extend({
router: service(),
model(params) {
if (params.name === "create") {
return CustomWizardApi.create({ isNew: true });
@ -13,12 +10,6 @@ export default DiscourseRoute.extend({
}
},
afterModel(model) {
if (model === null) {
return this.router.transitionTo("adminWizardsApi");
}
},
setupController(controller, model) {
controller.set("api", model);
},

Datei anzeigen

@ -1,10 +1,7 @@
import DiscourseRoute from "discourse/routes/discourse";
import CustomWizardApi from "../models/custom-wizard-api";
import { inject as service } from "@ember/service";
export default DiscourseRoute.extend({
router: service(),
model() {
return CustomWizardApi.list();
},
@ -28,11 +25,11 @@ export default DiscourseRoute.extend({
actions: {
changeApi(apiName) {
this.controllerFor("adminWizardsApi").set("apiName", apiName);
this.router.transitionTo("adminWizardsApiShow", apiName);
this.transitionTo("adminWizardsApiShow", apiName);
},
afterDestroy() {
this.router.transitionTo("adminWizardsApi").then(() => this.refresh());
this.transitionTo("adminWizardsApi").then(() => this.refresh());
},
afterSave(apiName) {
@ -41,7 +38,7 @@ export default DiscourseRoute.extend({
createApi() {
this.controllerFor("adminWizardsApi").set("apiName", "create");
this.router.transitionTo("adminWizardsApiShow", "create");
this.transitionTo("adminWizardsApiShow", "create");
},
},
});

Datei anzeigen

@ -8,10 +8,7 @@ export default DiscourseRoute.extend({
},
setupController(controller, model) {
const customFields = A(model.custom_fields || []);
controller.setProperties({
customFields,
});
const customFields = A(model || []);
controller.set("customFields", customFields);
},
});

Datei anzeigen

@ -1,26 +0,0 @@
import CustomWizardLogs from "../models/custom-wizard-logs";
import DiscourseRoute from "discourse/routes/discourse";
import { A } from "@ember/array";
import { inject as service } from "@ember/service";
export default DiscourseRoute.extend({
router: service(),
model(params) {
return CustomWizardLogs.list(params.wizardId);
},
afterModel(model) {
if (model === null) {
return this.router.transitionTo("adminWizardsLogs");
}
},
setupController(controller, model) {
controller.setProperties({
wizard: model.wizard,
logs: A(model.logs),
total: model.total,
});
},
});

Datei anzeigen

@ -1,27 +1,12 @@
import CustomWizardLogs from "../models/custom-wizard-logs";
import DiscourseRoute from "discourse/routes/discourse";
import { ajax } from "discourse/lib/ajax";
import { inject as service } from "@ember/service";
export default DiscourseRoute.extend({
router: service(),
model() {
return ajax(`/admin/wizards/wizard`);
return CustomWizardLogs.list();
},
setupController(controller, model) {
const showParams = this.paramsFor("adminWizardsLogsShow");
controller.setProperties({
wizardId: showParams.wizardId,
wizardList: model.wizard_list,
});
},
actions: {
changeWizard(wizardId) {
this.controllerFor("adminWizardsLogs").set("wizardId", wizardId);
this.router.transitionTo("adminWizardsLogsShow", wizardId);
},
controller.set("logs", model);
},
});

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