Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 09:20:29 +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_tree ./wizard/components
|
||||
//= require_tree ./wizard/controllers
|
||||
//= require_tree ./wizard/helpers
|
||||
//= require_tree ./wizard/initializers
|
||||
//= require_tree ./wizard/lib
|
||||
//= require_tree ./wizard/models
|
||||
//= require_tree ./wizard/routes
|
||||
//= require_tree ./wizard/templates
|
||||
|
||||
//= require discourse/components/user-selector
|
||||
//= require discourse/components/text-field
|
||||
//= require discourse/helpers/user-avatar
|
||||
|
||||
//= require lodash.js
|
||||
|
||||
window.Discourse = {}
|
||||
window.Wizard = {};
|
||||
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 getUrl = requirejs('discourse-common/lib/get-url').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({
|
||||
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 {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class CustomWizard::Field
|
||||
def self.types
|
||||
@types ||= ['text', 'textarea', 'dropdown', 'image', 'radio']
|
||||
@types ||= ['text', 'textarea', 'dropdown', 'image', 'checkbox', 'user-selector']
|
||||
end
|
||||
|
||||
def self.require_assets
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
<%= preload_script "ember_jquery" %>
|
||||
<%= preload_script "wizard-vendor" %>
|
||||
<%= preload_script "wizard-application" %>
|
||||
<%= preload_script "wizard-custom-lib" %>
|
||||
<%= preload_script "wizard-custom" %>
|
||||
<%= preload_script "wizard-plugin" %>
|
||||
<%= preload_script "pretty-text-bundle" %>
|
||||
<%= preload_script "locales/#{I18n.locale}" %>
|
||||
<%= render partial: "common/special_font_face" %>
|
||||
<script src="<%= Discourse.base_uri %>/extra-locales/wizard"></script>
|
||||
|
|
Laden …
In neuem Issue referenzieren