0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-22 17:30:29 +01:00

Add user-selector and checkbox as field options

Dieser Commit ist enthalten in:
Angus McLeod 2017-11-23 17:03:19 +08:00
Ursprung 37b1b73f90
Commit e731ecba23
10 geänderte Dateien mit 435 neuen und 1 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,4 @@
//= require discourse/lib/autocomplete
//= require discourse/lib/utilities
//= require discourse/lib/offset-calculator
//= require discourse/lib/lock-on

Datei anzeigen

@ -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 = {};

Datei anzeigen

@ -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();
}
}
}
});

Datei anzeigen

@ -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/')

Datei anzeigen

@ -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));
});
});
}

Datei anzeigen

@ -0,0 +1 @@
{{input type='checkbox' checked=field.value}}

Datei anzeigen

@ -0,0 +1 @@
{{custom-user-selector usernames=field.value placeholderKey=field.placeholder}}

Datei anzeigen

@ -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);

Datei anzeigen

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

Datei anzeigen

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