Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-25 10:40:28 +01:00
Add user-selector and checkbox as field options
Dieser Commit ist enthalten in:
Ursprung
37b1b73f90
Commit
e731ecba23
10 geänderte Dateien mit 435 neuen und 1 gelöschten Zeilen
4
assets/javascripts/wizard-custom-lib.js
Normale Datei
4
assets/javascripts/wizard-custom-lib.js
Normale Datei
|
@ -0,0 +1,4 @@
|
||||||
|
//= require discourse/lib/autocomplete
|
||||||
|
//= require discourse/lib/utilities
|
||||||
|
//= require discourse/lib/offset-calculator
|
||||||
|
//= require discourse/lib/lock-on
|
|
@ -1,12 +1,20 @@
|
||||||
//= require ./wizard/custom-wizard
|
//= require ./wizard/custom-wizard
|
||||||
|
//= require_tree ./wizard/components
|
||||||
//= require_tree ./wizard/controllers
|
//= require_tree ./wizard/controllers
|
||||||
//= require_tree ./wizard/helpers
|
//= require_tree ./wizard/helpers
|
||||||
//= require_tree ./wizard/initializers
|
//= require_tree ./wizard/initializers
|
||||||
|
//= require_tree ./wizard/lib
|
||||||
//= require_tree ./wizard/models
|
//= require_tree ./wizard/models
|
||||||
//= require_tree ./wizard/routes
|
//= require_tree ./wizard/routes
|
||||||
//= require_tree ./wizard/templates
|
//= require_tree ./wizard/templates
|
||||||
|
|
||||||
|
//= require discourse/components/user-selector
|
||||||
|
//= require discourse/components/text-field
|
||||||
|
//= require discourse/helpers/user-avatar
|
||||||
|
|
||||||
//= require lodash.js
|
//= require lodash.js
|
||||||
|
|
||||||
|
window.Discourse = {}
|
||||||
window.Wizard = {};
|
window.Wizard = {};
|
||||||
Wizard.SiteSettings = {};
|
Wizard.SiteSettings = {};
|
||||||
|
Discourse.__widget_helpers = {};
|
||||||
|
|
138
assets/javascripts/wizard/components/custom-user-selector.js.es6
Normale Datei
138
assets/javascripts/wizard/components/custom-user-selector.js.es6
Normale Datei
|
@ -0,0 +1,138 @@
|
||||||
|
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||||
|
import { renderAvatar } from 'discourse/helpers/user-avatar';
|
||||||
|
import userSearch from '../lib/user-search';
|
||||||
|
|
||||||
|
const template = function(params) {
|
||||||
|
const options = params.options;
|
||||||
|
let html = "<div class='autocomplete'>";
|
||||||
|
|
||||||
|
if (options.users) {
|
||||||
|
html += "<ul>";
|
||||||
|
options.users.forEach((u) => {
|
||||||
|
html += `<li><a href title="${u.name}">`;
|
||||||
|
html += renderAvatar(u, { imageSize: 'tiny' });
|
||||||
|
html += `<span class='username'>${u.username}</span>`;
|
||||||
|
if (u.name) {
|
||||||
|
html += `<span class='name'>${u.name}</span>`;
|
||||||
|
}
|
||||||
|
html += `</a></li>`;
|
||||||
|
});
|
||||||
|
html += "</ul>";
|
||||||
|
};
|
||||||
|
|
||||||
|
html += "</div>";
|
||||||
|
|
||||||
|
return new Handlebars.SafeString(html).string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Ember.TextField.extend({
|
||||||
|
attributeBindings: ['autofocus', 'maxLength'],
|
||||||
|
autocorrect: false,
|
||||||
|
autocapitalize: false,
|
||||||
|
name: 'user-selector',
|
||||||
|
id: "custom-member-selector",
|
||||||
|
|
||||||
|
@computed("placeholderKey")
|
||||||
|
placeholder(placeholderKey) {
|
||||||
|
return placeholderKey ? I18n.t(placeholderKey) : "";
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes('usernames')
|
||||||
|
_update() {
|
||||||
|
if (this.get('canReceiveUpdates') === 'true')
|
||||||
|
this.didInsertElement({updateData: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement(opts) {
|
||||||
|
this._super();
|
||||||
|
var self = this,
|
||||||
|
selected = [],
|
||||||
|
groups = [],
|
||||||
|
currentUser = this.currentUser,
|
||||||
|
includeMentionableGroups = this.get('includeMentionableGroups') === 'true',
|
||||||
|
includeMessageableGroups = this.get('includeMessageableGroups') === 'true',
|
||||||
|
includeGroups = this.get('includeGroups') === 'true',
|
||||||
|
allowedUsers = this.get('allowedUsers') === 'true';
|
||||||
|
|
||||||
|
function excludedUsernames() {
|
||||||
|
// hack works around some issues with allowAny eventing
|
||||||
|
const usernames = self.get('single') ? [] : selected;
|
||||||
|
|
||||||
|
if (currentUser && self.get('excludeCurrentUser')) {
|
||||||
|
return usernames.concat([currentUser.get('username')]);
|
||||||
|
}
|
||||||
|
return usernames;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$().val(this.get('usernames')).autocomplete({
|
||||||
|
template,
|
||||||
|
disabled: this.get('disabled'),
|
||||||
|
single: this.get('single'),
|
||||||
|
allowAny: this.get('allowAny'),
|
||||||
|
updateData: (opts && opts.updateData) ? opts.updateData : false,
|
||||||
|
|
||||||
|
dataSource(term) {
|
||||||
|
const termRegex = /[^a-zA-Z0-9_\-\.@\+]/;
|
||||||
|
|
||||||
|
var results = userSearch({
|
||||||
|
term: term.replace(termRegex, ''),
|
||||||
|
topicId: self.get('topicId'),
|
||||||
|
exclude: excludedUsernames(),
|
||||||
|
includeGroups,
|
||||||
|
allowedUsers,
|
||||||
|
includeMentionableGroups,
|
||||||
|
includeMessageableGroups,
|
||||||
|
group: self.get("group")
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
},
|
||||||
|
|
||||||
|
transformComplete(v) {
|
||||||
|
if (v.username || v.name) {
|
||||||
|
if (!v.username) { groups.push(v.name); }
|
||||||
|
return v.username || v.name;
|
||||||
|
} else {
|
||||||
|
var excludes = excludedUsernames();
|
||||||
|
return v.usernames.filter(function(item){
|
||||||
|
return excludes.indexOf(item) === -1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeItems(items) {
|
||||||
|
var hasGroups = false;
|
||||||
|
items = items.map(function(i) {
|
||||||
|
if (groups.indexOf(i) > -1) { hasGroups = true; }
|
||||||
|
return i.username ? i.username : i;
|
||||||
|
});
|
||||||
|
self.set('usernames', items.join(","));
|
||||||
|
self.set('hasGroups', hasGroups);
|
||||||
|
|
||||||
|
selected = items;
|
||||||
|
if (self.get('onChangeCallback')) self.sendAction('onChangeCallback');
|
||||||
|
},
|
||||||
|
|
||||||
|
reverseTransform(i) {
|
||||||
|
return { username: i };
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
this._super();
|
||||||
|
this.$().autocomplete('destroy');
|
||||||
|
},
|
||||||
|
|
||||||
|
// THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT
|
||||||
|
@observes('usernames')
|
||||||
|
_clearInput: function() {
|
||||||
|
if (arguments.length > 1) {
|
||||||
|
if (Em.isEmpty(this.get("usernames"))) {
|
||||||
|
this.$().parent().find("a").click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -11,6 +11,12 @@ export default {
|
||||||
const WizardStep = requirejs('wizard/components/wizard-step').default;
|
const WizardStep = requirejs('wizard/components/wizard-step').default;
|
||||||
const getUrl = requirejs('discourse-common/lib/get-url').default;
|
const getUrl = requirejs('discourse-common/lib/get-url').default;
|
||||||
const FieldModel = requirejs('wizard/models/wizard-field').default;
|
const FieldModel = requirejs('wizard/models/wizard-field').default;
|
||||||
|
const autocomplete = requirejs('discourse/lib/autocomplete').default;
|
||||||
|
|
||||||
|
$.fn.autocomplete = autocomplete;
|
||||||
|
|
||||||
|
// this is for discourse/lib/utilities.avatarImg;
|
||||||
|
Discourse.getURLWithCDN = getUrl;
|
||||||
|
|
||||||
Router.reopen({
|
Router.reopen({
|
||||||
rootURL: getUrl('/w/')
|
rootURL: getUrl('/w/')
|
||||||
|
|
134
assets/javascripts/wizard/lib/user-search.js.es6
Normale Datei
134
assets/javascripts/wizard/lib/user-search.js.es6
Normale Datei
|
@ -0,0 +1,134 @@
|
||||||
|
import { CANCELLED_STATUS } from 'discourse/lib/autocomplete';
|
||||||
|
import getUrl from 'discourse-common/lib/get-url';
|
||||||
|
|
||||||
|
var cache = {},
|
||||||
|
cacheTopicId,
|
||||||
|
cacheTime,
|
||||||
|
currentTerm,
|
||||||
|
oldSearch;
|
||||||
|
|
||||||
|
function performSearch(term, topicId, includeGroups, includeMentionableGroups, includeMessageableGroups, allowedUsers, group, resultsFn) {
|
||||||
|
var cached = cache[term];
|
||||||
|
if (cached) {
|
||||||
|
resultsFn(cached);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to be able to cancel this
|
||||||
|
oldSearch = $.ajax(getUrl('/u/search/users'), {
|
||||||
|
data: { term: term,
|
||||||
|
topic_id: topicId,
|
||||||
|
include_groups: includeGroups,
|
||||||
|
include_mentionable_groups: includeMentionableGroups,
|
||||||
|
include_messageable_groups: includeMessageableGroups,
|
||||||
|
group: group,
|
||||||
|
topic_allowed_users: allowedUsers }
|
||||||
|
});
|
||||||
|
|
||||||
|
var returnVal = CANCELLED_STATUS;
|
||||||
|
|
||||||
|
oldSearch.then(function (r) {
|
||||||
|
cache[term] = r;
|
||||||
|
cacheTime = new Date();
|
||||||
|
// If there is a newer search term, return null
|
||||||
|
if (term === currentTerm) { returnVal = r; }
|
||||||
|
|
||||||
|
}).always(function(){
|
||||||
|
oldSearch = null;
|
||||||
|
resultsFn(returnVal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var debouncedSearch = _.debounce(performSearch, 300);
|
||||||
|
|
||||||
|
function organizeResults(r, options) {
|
||||||
|
if (r === CANCELLED_STATUS) { return r; }
|
||||||
|
|
||||||
|
var exclude = options.exclude || [],
|
||||||
|
limit = options.limit || 5,
|
||||||
|
users = [],
|
||||||
|
emails = [],
|
||||||
|
groups = [],
|
||||||
|
results = [];
|
||||||
|
|
||||||
|
if (r.users) {
|
||||||
|
r.users.every(function(u) {
|
||||||
|
if (exclude.indexOf(u.username) === -1) {
|
||||||
|
users.push(u);
|
||||||
|
results.push(u);
|
||||||
|
}
|
||||||
|
return results.length <= limit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.term.match(/@/)) {
|
||||||
|
let e = { username: options.term };
|
||||||
|
emails = [ e ];
|
||||||
|
results.push(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.groups) {
|
||||||
|
r.groups.every(function(g) {
|
||||||
|
if (results.length > limit && options.term.toLowerCase() !== g.name.toLowerCase()) return false;
|
||||||
|
if (exclude.indexOf(g.name) === -1) {
|
||||||
|
groups.push(g);
|
||||||
|
results.push(g);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
results.users = users;
|
||||||
|
results.emails = emails;
|
||||||
|
results.groups = groups;
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function userSearch(options) {
|
||||||
|
var term = options.term || "",
|
||||||
|
includeGroups = options.includeGroups,
|
||||||
|
includeMentionableGroups = options.includeMentionableGroups,
|
||||||
|
includeMessageableGroups = options.includeMessageableGroups,
|
||||||
|
allowedUsers = options.allowedUsers,
|
||||||
|
topicId = options.topicId,
|
||||||
|
group = options.group;
|
||||||
|
|
||||||
|
|
||||||
|
if (oldSearch) {
|
||||||
|
oldSearch.abort();
|
||||||
|
oldSearch = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTerm = term;
|
||||||
|
|
||||||
|
return new Ember.RSVP.Promise(function(resolve) {
|
||||||
|
// TODO site setting for allowed regex in username
|
||||||
|
if (term.match(/[^\w_\-\.@\+]/)) {
|
||||||
|
resolve([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (((new Date() - cacheTime) > 30000) || (cacheTopicId !== topicId)) {
|
||||||
|
cache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheTopicId = topicId;
|
||||||
|
|
||||||
|
var clearPromise = setTimeout(function(){
|
||||||
|
resolve(CANCELLED_STATUS);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
debouncedSearch(term,
|
||||||
|
topicId,
|
||||||
|
includeGroups,
|
||||||
|
includeMentionableGroups,
|
||||||
|
includeMessageableGroups,
|
||||||
|
allowedUsers,
|
||||||
|
group,
|
||||||
|
function(r) {
|
||||||
|
clearTimeout(clearPromise);
|
||||||
|
resolve(organizeResults(r, options));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{{input type='checkbox' checked=field.value}}
|
|
@ -0,0 +1 @@
|
||||||
|
{{custom-user-selector usernames=field.value placeholderKey=field.placeholder}}
|
|
@ -221,6 +221,146 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-selector-field.wizard-field {
|
||||||
|
div.ac-wrap div.item a.remove, .remove-link {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 10px;
|
||||||
|
padding: 1.5px 1.5px 1.5px 2.5px;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f2ab9a;
|
||||||
|
border: 1px solid #ec8972;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #e45735;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.ac-wrap {
|
||||||
|
width: 98.5% !important;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 150px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
padding: 5px 4px 1px 4px;
|
||||||
|
|
||||||
|
div.item {
|
||||||
|
float: left;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
height: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ac-collapsed-button {
|
||||||
|
float: left;
|
||||||
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
float: left;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
height: 24px;
|
||||||
|
display: block;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img.avatar {
|
||||||
|
border-radius: 50%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete {
|
||||||
|
z-index: 999999;
|
||||||
|
position: absolute;
|
||||||
|
width: 240px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
.d-users {
|
||||||
|
color: #333;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
border-bottom: 1px solid #e9e9e9;
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 5px;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 14px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.username {
|
||||||
|
color: #000;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.name {
|
||||||
|
font-size: 11px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: #d1f0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #ffffa6;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-field {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&> label {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
&> .input-area {
|
||||||
|
float: right;
|
||||||
|
margin: 0 20px !important;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
cursor: pointer;
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes rotate-forever {
|
@keyframes rotate-forever {
|
||||||
0% {
|
0% {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class CustomWizard::Field
|
class CustomWizard::Field
|
||||||
def self.types
|
def self.types
|
||||||
@types ||= ['text', 'textarea', 'dropdown', 'image', 'radio']
|
@types ||= ['text', 'textarea', 'dropdown', 'image', 'checkbox', 'user-selector']
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.require_assets
|
def self.require_assets
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
<%= preload_script "ember_jquery" %>
|
<%= preload_script "ember_jquery" %>
|
||||||
<%= preload_script "wizard-vendor" %>
|
<%= preload_script "wizard-vendor" %>
|
||||||
<%= preload_script "wizard-application" %>
|
<%= preload_script "wizard-application" %>
|
||||||
|
<%= preload_script "wizard-custom-lib" %>
|
||||||
<%= preload_script "wizard-custom" %>
|
<%= preload_script "wizard-custom" %>
|
||||||
<%= preload_script "wizard-plugin" %>
|
<%= preload_script "wizard-plugin" %>
|
||||||
|
<%= preload_script "pretty-text-bundle" %>
|
||||||
<%= preload_script "locales/#{I18n.locale}" %>
|
<%= preload_script "locales/#{I18n.locale}" %>
|
||||||
<%= render partial: "common/special_font_face" %>
|
<%= render partial: "common/special_font_face" %>
|
||||||
<script src="<%= Discourse.base_uri %>/extra-locales/wizard"></script>
|
<script src="<%= Discourse.base_uri %>/extra-locales/wizard"></script>
|
||||||
|
|
Laden …
In neuem Issue referenzieren