mirror of
https://github.com/discourse/discourse.git
synced 2025-05-25 00:32:52 +08:00
work in progress add support for groups
This commit is contained in:
@ -0,0 +1,10 @@
|
|||||||
|
Discourse.AdminGroupsController = Ember.ArrayController.extend({
|
||||||
|
itemController: 'adminGroup',
|
||||||
|
edit: function(action){
|
||||||
|
this.get('content').select(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Discourse.AdminGroupController = Ember.ObjectController.extend({
|
||||||
|
|
||||||
|
});
|
24
app/assets/javascripts/admin/models/group.js
Normal file
24
app/assets/javascripts/admin/models/group.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Discourse.Group = Discourse.Model.extend({
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Discourse.Group.reopenClass({
|
||||||
|
findAll: function(){
|
||||||
|
var list = Discourse.SelectableArray.create();
|
||||||
|
|
||||||
|
list.addObject(Discourse.Group.create({id: 1, name: "all mods", members: ["A","b","c"]}));
|
||||||
|
list.addObject(Discourse.Group.create({id: 2, name: "other mods", members: ["A","b","c"]}));
|
||||||
|
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
|
||||||
|
find: function(id) {
|
||||||
|
var promise = new Em.Deferred();
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
promise.resolve(Discourse.Group.create({id: 1, name: "all mods", members: ["A","b","c"]}));
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,9 @@
|
|||||||
|
Discourse.AdminGroupsRoute = Discourse.Route.extend({
|
||||||
|
model: function() {
|
||||||
|
return Discourse.Group.findAll();
|
||||||
|
},
|
||||||
|
renderTemplate: function() {
|
||||||
|
this.render('admin/templates/groups',{into: 'admin/templates/admin'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -25,6 +25,8 @@ Discourse.Route.buildRoutes(function() {
|
|||||||
this.route('old', { path: '/old' });
|
this.route('old', { path: '/old' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route('groups', {path: '/groups'});
|
||||||
|
|
||||||
this.resource('adminUsers', { path: '/users' }, function() {
|
this.resource('adminUsers', { path: '/users' }, function() {
|
||||||
this.resource('adminUser', { path: '/:username' });
|
this.resource('adminUser', { path: '/:username' });
|
||||||
this.resource('adminUsersList', { path: '/list' }, function() {
|
this.resource('adminUsersList', { path: '/list' }, function() {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<li>{{#linkTo 'admin.site_settings'}}{{i18n admin.site_settings.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'admin.site_settings'}}{{i18n admin.site_settings.title}}{{/linkTo}}</li>
|
||||||
<li>{{#linkTo 'adminSiteContents'}}{{i18n admin.site_content.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'adminSiteContents'}}{{i18n admin.site_content.title}}{{/linkTo}}</li>
|
||||||
<li>{{#linkTo 'adminUsersList.active'}}{{i18n admin.users.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'adminUsersList.active'}}{{i18n admin.users.title}}{{/linkTo}}</li>
|
||||||
|
<li>{{#linkTo 'admin.groups'}}{{i18n admin.groups.title}}{{/linkTo}}</li>
|
||||||
<li>{{#linkTo 'admin.email_logs'}}{{i18n admin.email_logs.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'admin.email_logs'}}{{i18n admin.email_logs.title}}{{/linkTo}}</li>
|
||||||
<li>{{#linkTo 'adminFlags.active'}}{{i18n admin.flags.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'adminFlags.active'}}{{i18n admin.flags.title}}{{/linkTo}}</li>
|
||||||
<li>{{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}</li>
|
<li>{{#linkTo 'admin.customize'}}{{i18n admin.customize.title}}{{/linkTo}}</li>
|
||||||
|
23
app/assets/javascripts/admin/templates/groups.js.handlebars
Normal file
23
app/assets/javascripts/admin/templates/groups.js.handlebars
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<div class='row'>
|
||||||
|
<div class='content-list span6'>
|
||||||
|
<h3>{{i18n admin.groups.edit}}</h3>
|
||||||
|
<ul>
|
||||||
|
{{#each group in controller}}
|
||||||
|
<li>
|
||||||
|
<a href="#" {{action "edit" group}} {{bindAttr class="group.active"}}>{{group.name}}</a>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='content-editor'>
|
||||||
|
{{#if content.active}}
|
||||||
|
{{#with content.active}}
|
||||||
|
{{name}}
|
||||||
|
{{view Discourse.UserSelector id="private-message-users" class="span8" placeholderKey="admin.groups.selector_placeholder" tabindex="1" usernamesBinding="usernames"}}
|
||||||
|
{{/with}}
|
||||||
|
{{else}}
|
||||||
|
nothing here yet
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
4
app/assets/javascripts/admin/views/admin_groups_view.js
Executable file
4
app/assets/javascripts/admin/views/admin_groups_view.js
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
Discourse.AdminGroupsView = Discourse.View.extend({
|
||||||
|
});
|
||||||
|
|
||||||
|
|
19
app/assets/javascripts/discourse/models/selectable_array.js
Normal file
19
app/assets/javascripts/discourse/models/selectable_array.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// this allows you to track the selected item in an array, ghetto for now
|
||||||
|
Discourse.SelectableArray = Em.ArrayProxy.extend({
|
||||||
|
content: [],
|
||||||
|
selectIndex: function(index){
|
||||||
|
this.select(this[index]);
|
||||||
|
},
|
||||||
|
select: function(selected){
|
||||||
|
this.content.each(function(item){
|
||||||
|
if(item === selected){
|
||||||
|
Em.set(item, "active", true)
|
||||||
|
} else {
|
||||||
|
if (item.get("active")) {
|
||||||
|
Em.set(item, "active", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.set("active", selected);
|
||||||
|
}
|
||||||
|
});
|
@ -28,7 +28,7 @@
|
|||||||
{{#if content.editTitle}}
|
{{#if content.editTitle}}
|
||||||
<div class='form-element clearfix'>
|
<div class='form-element clearfix'>
|
||||||
{{#if content.creatingPrivateMessage}}
|
{{#if content.creatingPrivateMessage}}
|
||||||
{{view Discourse.TextField id="private-message-users" class="span8" placeholderKey="composer.users_placeholder" tabindex="1"}}
|
{{view Discourse.UserSelector topicIdBinding="controller.controllers.topic.content.id" excludeCurrentUser="true" id="private-message-users" class="span8" placeholderKey="composer.users_placeholder" tabindex="1" usernamesBinding="content.targetUsernames"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{view Discourse.TextField valueBinding="content.title" tabindex="2" id="reply-title" maxlength="255" class="span8" placeholderKey="composer.title_placeholder"}}
|
{{view Discourse.TextField valueBinding="content.title" tabindex="2" id="reply-title" maxlength="255" class="span8" placeholderKey="composer.title_placeholder"}}
|
||||||
{{#unless content.creatingPrivateMessage}}
|
{{#unless content.creatingPrivateMessage}}
|
||||||
|
@ -167,17 +167,7 @@ Discourse.ComposerView = Discourse.View.extend({
|
|||||||
|
|
||||||
$LAB.script(assetPath('defer/html-sanitizer-bundle'));
|
$LAB.script(assetPath('defer/html-sanitizer-bundle'));
|
||||||
Discourse.ComposerView.trigger("initWmdEditor");
|
Discourse.ComposerView.trigger("initWmdEditor");
|
||||||
template = Handlebars.compile("<div class='autocomplete'>" +
|
template = Discourse.UserSelector.templateFunction();
|
||||||
"<ul>" +
|
|
||||||
"{{#each options}}" +
|
|
||||||
"<li>" +
|
|
||||||
"<a href='#'>{{avatar this imageSize=\"tiny\"}} " +
|
|
||||||
"<span class='username'>{{this.username}}</span> " +
|
|
||||||
"<span class='name'>{{this.name}}</span></a>" +
|
|
||||||
"</li>" +
|
|
||||||
"{{/each}}" +
|
|
||||||
"</ul>" +
|
|
||||||
"</div>");
|
|
||||||
|
|
||||||
transformTemplate = Handlebars.compile("{{avatar this imageSize=\"tiny\"}} {{this.username}}");
|
transformTemplate = Handlebars.compile("{{avatar this imageSize=\"tiny\"}} {{this.username}}");
|
||||||
$wmdInput.data('init', true);
|
$wmdInput.data('init', true);
|
||||||
@ -193,38 +183,6 @@ Discourse.ComposerView = Discourse.View.extend({
|
|||||||
transformComplete: function(v) { return v.username; }
|
transformComplete: function(v) { return v.username; }
|
||||||
});
|
});
|
||||||
|
|
||||||
selected = [];
|
|
||||||
$('#private-message-users').val(this.get('content.targetUsernames')).autocomplete({
|
|
||||||
template: template,
|
|
||||||
|
|
||||||
dataSource: function(term) {
|
|
||||||
return Discourse.UserSearch.search({
|
|
||||||
term: term,
|
|
||||||
topicId: _this.get('controller.controllers.topic.content.id'),
|
|
||||||
exclude: selected.concat([Discourse.get('currentUser.username')])
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onChangeItems: function(items) {
|
|
||||||
items = $.map(items, function(i) {
|
|
||||||
if (i.username) {
|
|
||||||
return i.username;
|
|
||||||
} else {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_this.set('content.targetUsernames', items.join(","));
|
|
||||||
selected = items;
|
|
||||||
},
|
|
||||||
|
|
||||||
transformComplete: transformTemplate,
|
|
||||||
|
|
||||||
reverseTransform: function(i) {
|
|
||||||
return { username: i };
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
topic = this.get('topic');
|
topic = this.get('topic');
|
||||||
this.editor = editor = Discourse.Markdown.createEditor({
|
this.editor = editor = Discourse.Markdown.createEditor({
|
||||||
lookupAvatar: function(username) {
|
lookupAvatar: function(username) {
|
||||||
|
65
app/assets/javascripts/discourse/views/user_selector_view.js
Normal file
65
app/assets/javascripts/discourse/views/user_selector_view.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
Discourse.UserSelector = Discourse.TextField.extend({
|
||||||
|
|
||||||
|
didInsertElement: function(){
|
||||||
|
var _this = this;
|
||||||
|
var selected = [];
|
||||||
|
var transformTemplate = Handlebars.compile("{{avatar this imageSize=\"tiny\"}} {{this.username}}");
|
||||||
|
var template = Discourse.UserSelector.templateFunction();
|
||||||
|
|
||||||
|
$(this.get('element')).val(this.get('usernames')).autocomplete({
|
||||||
|
template: template,
|
||||||
|
|
||||||
|
dataSource: function(term) {
|
||||||
|
var exclude = selected;
|
||||||
|
if (_this.get('excludeCurrentUser')){
|
||||||
|
exclude = exclude.concat([Discourse.get('currentUser.username')]);
|
||||||
|
}
|
||||||
|
return Discourse.UserSearch.search({
|
||||||
|
term: term,
|
||||||
|
topicId: _this.get('topicId'),
|
||||||
|
exclude: exclude
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeItems: function(items) {
|
||||||
|
items = $.map(items, function(i) {
|
||||||
|
if (i.username) {
|
||||||
|
return i.username;
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_this.set('usernames', items.join(","));
|
||||||
|
selected = items;
|
||||||
|
},
|
||||||
|
|
||||||
|
transformComplete: transformTemplate,
|
||||||
|
|
||||||
|
reverseTransform: function(i) {
|
||||||
|
return { username: i };
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Discourse.UserSelector.reopenClass({
|
||||||
|
// I really want to move this into a template file, but I need a handlebars template here, not an ember one
|
||||||
|
templateFunction: function(){
|
||||||
|
this.compiled = this.compiled || Handlebars.compile("<div class='autocomplete'>" +
|
||||||
|
"<ul>" +
|
||||||
|
"{{#each options}}" +
|
||||||
|
"<li>" +
|
||||||
|
"<a href='#'>{{avatar this imageSize=\"tiny\"}} " +
|
||||||
|
"<span class='username'>{{this.username}}</span> " +
|
||||||
|
"<span class='name'>{{this.name}}</span></a>" +
|
||||||
|
"</li>" +
|
||||||
|
"{{/each}}" +
|
||||||
|
"</ul>" +
|
||||||
|
"</div>");
|
||||||
|
return this.compiled;
|
||||||
|
}
|
||||||
|
});
|
145
app/assets/stylesheets/application/compose.css.scss
Normal file → Executable file
145
app/assets/stylesheets/application/compose.css.scss
Normal file → Executable file
@ -54,6 +54,40 @@
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.autocomplete {
|
||||||
|
z-index: 9999;
|
||||||
|
position: absolute;
|
||||||
|
width: 200px;
|
||||||
|
background-color: $white;
|
||||||
|
border: 1px solid $gray;
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
li {
|
||||||
|
border-bottom: 1px solid $light_gray;
|
||||||
|
a[href] {
|
||||||
|
padding: 5px;
|
||||||
|
display: block;
|
||||||
|
span.username {
|
||||||
|
color: lighten($black, 20);
|
||||||
|
}
|
||||||
|
span.name {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
background-color: $light_gray;
|
||||||
|
}
|
||||||
|
@include hover {
|
||||||
|
background-color: $light_gray;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#reply-control {
|
#reply-control {
|
||||||
.requirements-not-met {
|
.requirements-not-met {
|
||||||
background-color: rgba(255, 0, 0, 0.12);
|
background-color: rgba(255, 0, 0, 0.12);
|
||||||
@ -181,38 +215,7 @@
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
float: none;
|
float: none;
|
||||||
}
|
}
|
||||||
.autocomplete {
|
|
||||||
z-index: 999;
|
|
||||||
position: absolute;
|
|
||||||
width: 200px;
|
|
||||||
background-color: $white;
|
|
||||||
border: 1px solid $gray;
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
li {
|
|
||||||
border-bottom: 1px solid $light_gray;
|
|
||||||
a[href] {
|
|
||||||
padding: 5px;
|
|
||||||
display: block;
|
|
||||||
span.username {
|
|
||||||
color: lighten($black, 20);
|
|
||||||
}
|
|
||||||
span.name {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
&.selected {
|
|
||||||
background-color: $light_gray;
|
|
||||||
}
|
|
||||||
@include hover {
|
|
||||||
background-color: $light_gray;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// When the post is new (new topic) the sizings are different
|
// When the post is new (new topic) the sizings are different
|
||||||
&.edit-title {
|
&.edit-title {
|
||||||
&.open {
|
&.open {
|
||||||
@ -309,50 +312,50 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main #reply-control {
|
|
||||||
div.ac-wrap {
|
div.ac-wrap {
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
@include border-radius-all(3px);
|
@include border-radius-all(3px);
|
||||||
div.item {
|
div.item {
|
||||||
float: left;
|
float: left;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
span {
|
span {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
|
||||||
a {
|
|
||||||
margin-left: 4px;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 10px;
|
|
||||||
padding: 2px 1px 1px 3px;
|
|
||||||
border-radius: 10px;
|
|
||||||
width: 10px;
|
|
||||||
display: inline-block;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0);
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($red, 45);
|
|
||||||
border: 1px solid lighten($red, 20);
|
|
||||||
text-decoration: none;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
input[type="text"] {
|
a {
|
||||||
float: left;
|
margin-left: 4px;
|
||||||
margin-top: 5px;
|
font-size: 10px;
|
||||||
border: 0;
|
line-height: 10px;
|
||||||
padding: 0;
|
padding: 2px 1px 1px 3px;
|
||||||
margin: 4px 0 0;
|
border-radius: 10px;
|
||||||
box-shadow: none;
|
width: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0);
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($red, 45);
|
||||||
|
border: 1px solid lighten($red, 20);
|
||||||
|
text-decoration: none;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
input[type="text"] {
|
||||||
|
float: left;
|
||||||
|
margin-top: 5px;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 4px 0 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#reply-control.edit-title.private-message {
|
#reply-control.edit-title.private-message {
|
||||||
.wmd-controls {
|
.wmd-controls {
|
||||||
top: 140px;
|
top: 140px;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
require_dependency 'sql_builder'
|
|
||||||
|
|
||||||
class Admin::FlagsController < Admin::AdminController
|
class Admin::FlagsController < Admin::AdminController
|
||||||
def index
|
def index
|
||||||
|
|
||||||
|
4
app/controllers/admin/groups_controller.rb
Normal file
4
app/controllers/admin/groups_controller.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class Admin::GroupsController < Admin::AdminController
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
end
|
5
app/models/group.rb
Normal file
5
app/models/group.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class Group < ActiveRecord::Base
|
||||||
|
def self.builtin
|
||||||
|
Enum.new(:moderators, :admins, :trust_level_1, :trust_level_2)
|
||||||
|
end
|
||||||
|
end
|
2
app/models/group_user.rb
Normal file
2
app/models/group_user.rb
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class GroupUser < ActiveRecord::Base
|
||||||
|
end
|
@ -861,6 +861,10 @@ en:
|
|||||||
flagged_by: "Flagged by"
|
flagged_by: "Flagged by"
|
||||||
error: "Something went wrong"
|
error: "Something went wrong"
|
||||||
|
|
||||||
|
groups:
|
||||||
|
title: "Groups"
|
||||||
|
edit: "Edit Groups"
|
||||||
|
|
||||||
api:
|
api:
|
||||||
title: "API"
|
title: "API"
|
||||||
customize:
|
customize:
|
||||||
|
@ -27,6 +27,7 @@ Discourse::Application.routes.draw do
|
|||||||
resources :site_settings
|
resources :site_settings
|
||||||
get 'reports/:type' => 'reports#show'
|
get 'reports/:type' => 'reports#show'
|
||||||
|
|
||||||
|
resources :groups
|
||||||
resources :users, id: USERNAME_ROUTE_FORMAT do
|
resources :users, id: USERNAME_ROUTE_FORMAT do
|
||||||
collection do
|
collection do
|
||||||
get 'list/:query' => 'users#index'
|
get 'list/:query' => 'users#index'
|
||||||
|
8
db/migrate/20130416004607_create_groups.rb
Normal file
8
db/migrate/20130416004607_create_groups.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class CreateGroups < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :groups do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
db/migrate/20130416004933_group_users.rb
Normal file
11
db/migrate/20130416004933_group_users.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class GroupUsers < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :group_users do |t|
|
||||||
|
t.integer :group_id, null: false
|
||||||
|
t.integer :user_id, null: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :group_users, [:group_id, :user_id], unique: true
|
||||||
|
end
|
||||||
|
end
|
Reference in New Issue
Block a user