Version bump
This commit is contained in:
commit
8c3d3060d1
@ -14,7 +14,6 @@
|
||||
"moduleForComponent",
|
||||
"Pretender",
|
||||
"sandbox",
|
||||
"integration",
|
||||
"controllerFor",
|
||||
"test",
|
||||
"ok",
|
||||
|
||||
@ -272,7 +272,7 @@ GEM
|
||||
omniauth-twitter (1.0.1)
|
||||
multi_json (~> 1.3)
|
||||
omniauth-oauth (~> 1.0)
|
||||
onebox (1.5.13)
|
||||
onebox (1.5.16)
|
||||
moneta (~> 0.7)
|
||||
multi_json (~> 1.7)
|
||||
mustache (~> 0.99)
|
||||
|
||||
12
README.md
12
README.md
@ -10,12 +10,12 @@ To learn more about the philosophy and goals of the project, [visit **discourse.
|
||||
|
||||
## Screenshots
|
||||
|
||||
[](http://bbs.boingboing.net)
|
||||
[](http://discuss.howtogeek.com)
|
||||
[](http://discuss.newrelic.com)
|
||||
[](https://talk.turtlerockstudios.com/)
|
||||
[](http://discuss.atom.io)
|
||||
[](http://discourse.soylent.me)
|
||||
[](http://bbs.boingboing.net)
|
||||
[](http://discuss.howtogeek.com)
|
||||
[](http://discuss.newrelic.com)
|
||||
[](https://talk.turtlerockstudios.com/)
|
||||
[](http://discuss.atom.io)
|
||||
[](http://discourse.soylent.me)
|
||||
|
||||
Browse [lots more notable Discourse instances](http://www.discourse.org/faq/customers/).
|
||||
|
||||
|
||||
Binary file not shown.
BIN
app/assets/fonts/fontawesome-webfont.eot
Executable file → Normal file
BIN
app/assets/fonts/fontawesome-webfont.eot
Executable file → Normal file
Binary file not shown.
989
app/assets/fonts/fontawesome-webfont.svg
Executable file → Normal file
989
app/assets/fonts/fontawesome-webfont.svg
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 306 KiB |
BIN
app/assets/fonts/fontawesome-webfont.ttf
Executable file → Normal file
BIN
app/assets/fonts/fontawesome-webfont.ttf
Executable file → Normal file
Binary file not shown.
BIN
app/assets/fonts/fontawesome-webfont.woff
Executable file → Normal file
BIN
app/assets/fonts/fontawesome-webfont.woff
Executable file → Normal file
Binary file not shown.
BIN
app/assets/fonts/fontawesome-webfont.woff2
Normal file
BIN
app/assets/fonts/fontawesome-webfont.woff2
Normal file
Binary file not shown.
@ -21,7 +21,8 @@ export default Ember.Component.extend({
|
||||
tokenSeparators: ["|"],
|
||||
tags : this.get("choices") || [],
|
||||
width: 'off',
|
||||
dropdownCss: this.get("choices") ? {} : {display: 'none'}
|
||||
dropdownCss: this.get("choices") ? {} : {display: 'none'},
|
||||
selectOnBlur: this.get("choices") ? false : true
|
||||
};
|
||||
|
||||
var settingName = this.get('settingName');
|
||||
|
||||
@ -14,6 +14,7 @@ Discourse.ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuff
|
||||
tagName: "button",
|
||||
classNames: ["btn", "ru"],
|
||||
classNameBindings: ["isUploading"],
|
||||
attributeBindings: ["translatedTitle:title"],
|
||||
|
||||
resumable: null,
|
||||
|
||||
@ -22,6 +23,11 @@ Discourse.ResumableUploadComponent = Ember.Component.extend(Discourse.StringBuff
|
||||
|
||||
rerenderTriggers: ['isUploading', 'progress'],
|
||||
|
||||
translatedTitle: function() {
|
||||
const title = this.get('title');
|
||||
return title ? I18n.t(title) : this.get('text');
|
||||
}.property('title', 'text'),
|
||||
|
||||
text: function() {
|
||||
if (this.get("isUploading")) {
|
||||
return this.get("progress") + " %";
|
||||
|
||||
@ -1,49 +1,24 @@
|
||||
export default Ember.ArrayController.extend({
|
||||
needs: ["adminBackups"],
|
||||
status: Em.computed.alias("controllers.adminBackups"),
|
||||
|
||||
uploadText: function() { return I18n.t("admin.backups.upload.text"); }.property(),
|
||||
|
||||
readOnlyModeDisabled: Em.computed.alias("status.isOperationRunning"),
|
||||
|
||||
isOperationRunning: Em.computed.alias("status.isOperationRunning"),
|
||||
restoreDisabled: Em.computed.alias("status.restoreDisabled"),
|
||||
|
||||
uploadLabel: function() { return I18n.t("admin.backups.upload.label"); }.property(),
|
||||
|
||||
restoreTitle: function() {
|
||||
if (!this.get('status.allowRestore')) {
|
||||
return I18n.t("admin.backups.operations.restore.is_disabled");
|
||||
return "admin.backups.operations.restore.is_disabled";
|
||||
} else if (this.get("status.isOperationRunning")) {
|
||||
return I18n.t("admin.backups.operation_already_running");
|
||||
return "admin.backups.operations.is_running";
|
||||
} else {
|
||||
return I18n.t("admin.backups.operations.restore.title");
|
||||
return "admin.backups.operations.restore.title";
|
||||
}
|
||||
}.property("status.isOperationRunning"),
|
||||
|
||||
destroyDisabled: Em.computed.alias("status.isOperationRunning"),
|
||||
|
||||
destroyTitle: function() {
|
||||
if (this.get("status.isOperationRunning")) {
|
||||
return I18n.t("admin.backups.operation_already_running");
|
||||
} else {
|
||||
return I18n.t("admin.backups.operations.destroy.title");
|
||||
}
|
||||
}.property("status.isOperationRunning"),
|
||||
|
||||
readOnlyModeTitle: function() { return this._readOnlyModeI18n("title"); }.property("site.isReadOnly"),
|
||||
readOnlyModeText: function() { return this._readOnlyModeI18n("text"); }.property("site.isReadOnly"),
|
||||
|
||||
_readOnlyModeI18n: function(value) {
|
||||
var action = this.site.get("isReadOnly") ? "disable" : "enable";
|
||||
return I18n.t("admin.backups.read_only." + action + "." + value);
|
||||
},
|
||||
}.property("status.{allowRestore,isOperationRunning}"),
|
||||
|
||||
actions: {
|
||||
|
||||
/**
|
||||
Toggle read-only mode
|
||||
|
||||
@method toggleReadOnlyMode
|
||||
**/
|
||||
toggleReadOnlyMode: function() {
|
||||
toggleReadOnlyMode() {
|
||||
var self = this;
|
||||
if (!this.site.get("isReadOnly")) {
|
||||
bootbox.confirm(
|
||||
@ -64,7 +39,7 @@ export default Ember.ArrayController.extend({
|
||||
|
||||
},
|
||||
|
||||
_toggleReadOnlyMode: function(enable) {
|
||||
_toggleReadOnlyMode(enable) {
|
||||
var site = this.site;
|
||||
Discourse.ajax("/admin/backups/readonly", {
|
||||
type: "PUT",
|
||||
|
||||
@ -25,31 +25,31 @@ export default Em.ObjectController.extend({
|
||||
}.property(),
|
||||
|
||||
actions: {
|
||||
next: function() {
|
||||
next() {
|
||||
if (this.get("showingLast")) { return; }
|
||||
|
||||
var group = this.get("model"),
|
||||
offset = Math.min(group.get("offset") + group.get("limit"), group.get("user_count"));
|
||||
const group = this.get("model"),
|
||||
offset = Math.min(group.get("offset") + group.get("limit"), group.get("user_count"));
|
||||
|
||||
group.set("offset", offset);
|
||||
|
||||
return group.findMembers();
|
||||
},
|
||||
|
||||
previous: function() {
|
||||
previous() {
|
||||
if (this.get("showingFirst")) { return; }
|
||||
|
||||
var group = this.get("model"),
|
||||
offset = Math.max(group.get("offset") - group.get("limit"), 0);
|
||||
const group = this.get("model"),
|
||||
offset = Math.max(group.get("offset") - group.get("limit"), 0);
|
||||
|
||||
group.set("offset", offset);
|
||||
|
||||
return group.findMembers();
|
||||
},
|
||||
|
||||
removeMember: function(member) {
|
||||
var self = this,
|
||||
message = I18n.t("admin.groups.delete_member_confirm", { username: member.get("username"), group: this.get("name") });
|
||||
removeMember(member) {
|
||||
const self = this,
|
||||
message = I18n.t("admin.groups.delete_member_confirm", { username: member.get("username"), group: this.get("name") });
|
||||
return bootbox.confirm(message, I18n.t("no_value"), I18n.t("yes_value"), function(confirm) {
|
||||
if (confirm) {
|
||||
self.get("model").removeMember(member);
|
||||
@ -57,57 +57,49 @@ export default Em.ObjectController.extend({
|
||||
});
|
||||
},
|
||||
|
||||
addMembers: function() {
|
||||
addMembers() {
|
||||
if (Em.isEmpty(this.get("usernames"))) { return; }
|
||||
this.get("model").addMembers(this.get("usernames"));
|
||||
// clear the user selector
|
||||
this.set("usernames", null);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
var self = this,
|
||||
group = this.get('model'),
|
||||
groupsController = this.get("controllers.adminGroupsType");
|
||||
save() {
|
||||
const group = this.get('model'),
|
||||
groupsController = this.get("controllers.adminGroupsType");
|
||||
|
||||
this.set('disableSave', true);
|
||||
|
||||
var promise;
|
||||
if (group.get("id")) {
|
||||
promise = group.save();
|
||||
} else {
|
||||
promise = group.create().then(function() {
|
||||
groupsController.addObject(group);
|
||||
});
|
||||
}
|
||||
promise.then(function() {
|
||||
self.transitionToRoute("adminGroup", group);
|
||||
}, function(e) {
|
||||
var message = $.parseJSON(e.responseText).errors;
|
||||
bootbox.alert(message);
|
||||
}).finally(function() {
|
||||
self.set('disableSave', false);
|
||||
});
|
||||
let promise = group.get("id") ? group.save() : group.create().then(() => groupsController.addObject(group));
|
||||
|
||||
promise.then(() => this.transitionToRoute("adminGroup", group))
|
||||
.catch(e => bootbox.alert($.parseJSON(e.responseText).errors))
|
||||
.finally(() => this.set('disableSave', false));
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
var group = this.get('model'),
|
||||
groupsController = this.get('controllers.adminGroupsType'),
|
||||
self = this;
|
||||
destroy() {
|
||||
const group = this.get('model'),
|
||||
groupsController = this.get('controllers.adminGroupsType'),
|
||||
self = this;
|
||||
|
||||
this.set('disableSave', true);
|
||||
|
||||
bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(confirmed) {
|
||||
if (confirmed) {
|
||||
group.destroy().then(function() {
|
||||
groupsController.get('model').removeObject(group);
|
||||
self.transitionToRoute('adminGroups.index');
|
||||
}, function() {
|
||||
bootbox.alert(I18n.t("admin.groups.delete_failed"));
|
||||
}).finally(function() {
|
||||
bootbox.confirm(
|
||||
I18n.t("admin.groups.delete_confirm"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
function(confirmed) {
|
||||
if (confirmed) {
|
||||
group.destroy().then(() => {
|
||||
groupsController.get('model').removeObject(group);
|
||||
self.transitionToRoute('adminGroups.index');
|
||||
}).catch(() => bootbox.alert(I18n.t("admin.groups.delete_failed")))
|
||||
.finally(() => self.set('disableSave', false));
|
||||
} else {
|
||||
self.set('disableSave', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
export default Ember.ArrayController.extend({
|
||||
sortProperties: ['name'],
|
||||
refreshingAutoGroups: false,
|
||||
isAuto: function(){
|
||||
return this.get('type') === 'automatic';
|
||||
}.property('type'),
|
||||
|
||||
actions: {
|
||||
refreshAutoGroups: function(){
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
export default Discourse.Route.extend({
|
||||
model(params) {
|
||||
this.set("type", params.type);
|
||||
return Discourse.Group.findAll().then(function(groups) {
|
||||
return groups.filterBy("type", params.type);
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, model){
|
||||
controller.set("type", this.get("type"));
|
||||
controller.set("model", model);
|
||||
},
|
||||
|
||||
actions: {
|
||||
newGroup() {
|
||||
const self = this;
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
{{#if canRollback}}
|
||||
{{d-button action="rollback"
|
||||
class="btn-rollback"
|
||||
label="admin.backups.operations.rollback.text"
|
||||
label="admin.backups.operations.rollback.label"
|
||||
title="admin.backups.operations.rollback.title"
|
||||
icon="ambulance"
|
||||
disabled=rollbackDisabled}}
|
||||
@ -18,13 +18,13 @@
|
||||
{{d-button action="cancelOperation"
|
||||
class="btn-danger"
|
||||
title="admin.backups.operations.cancel.title"
|
||||
label="admin.backups.operations.cancel.text"
|
||||
label="admin.backups.operations.cancel.label"
|
||||
icon="times"}}
|
||||
{{else}}
|
||||
{{d-button action="startBackup"
|
||||
class="btn-primary"
|
||||
title="admin.backups.operations.backup.title"
|
||||
label="admin.backups.operations.backup.text"
|
||||
label="admin.backups.operations.backup.label"
|
||||
icon="rocket"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -4,8 +4,12 @@
|
||||
<th width="10%">{{i18n 'admin.backups.columns.size'}}</th>
|
||||
<th>
|
||||
<div class="pull-right">
|
||||
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadText}}
|
||||
<button {{action "toggleReadOnlyMode"}} class="btn" {{bind-attr disabled="readOnlyModeDisabled" title="readOnlyModeTitle"}}><i class="fa fa-eye"></i>{{readOnlyModeText}}</button>
|
||||
{{resumable-upload target="/admin/backups/upload" success="uploadSuccess" error="uploadError" uploadText=uploadLabel title="admin.backups.upload.title"}}
|
||||
{{#if site.isReadOnly}}
|
||||
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
|
||||
{{else}}
|
||||
{{d-button icon="eye" action="toggleReadOnlyMode" disabled=isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@ -15,9 +19,14 @@
|
||||
<td>{{human-size backup.size}}</td>
|
||||
<td>
|
||||
<div class="pull-right">
|
||||
<a {{bind-attr href="backup.link"}} class="btn download" title="{{i18n 'admin.backups.operations.download.title'}}"><i class="fa fa-download"></i>{{i18n 'admin.backups.operations.download.text'}}</a>
|
||||
<button {{action "destroyBackup" backup}} class="btn btn-danger no-text" {{bind-attr disabled="destroyDisabled" title="destroyTitle"}}><i class="fa fa-trash-o"></i></button>
|
||||
<button {{action "startRestore" backup}} class="btn" {{bind-attr disabled="restoreDisabled" title="restoreTitle"}}><i class="fa fa-play"></i>{{i18n 'admin.backups.operations.restore.text'}}</button>
|
||||
<a {{bind-attr href="backup.link"}} class="btn download" title="{{i18n 'admin.backups.operations.download.title'}}">{{fa-icon "download"}}{{i18n 'admin.backups.operations.download.label'}}</a>
|
||||
{{#if isOperationRunning}}
|
||||
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger no-text" disabled="true" title="admin.backups.operations.is_running"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{else}}
|
||||
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger no-text" title="admin.backups.operations.destroy.title"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=restoreDisabled title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -33,13 +33,15 @@
|
||||
<li class='toggle-mobile'>
|
||||
<a {{bind-attr class="view.mobile:active"}} {{action "toggleMobile" target="view"}}>{{fa-icon "mobile"}}</a>
|
||||
</li>
|
||||
<li class='toggle-maximize'><a {{action "toggleMaximize" target="view"}}>
|
||||
{{#if view.maximized}}
|
||||
{{fa-icon "compress"}}
|
||||
{{else}}
|
||||
{{fa-icon "expand"}}
|
||||
{{/if}}
|
||||
</a>
|
||||
<li class='toggle-maximize'>
|
||||
<a {{action "toggleMaximize" target="view"}}>
|
||||
{{#if view.maximized}}
|
||||
{{fa-icon "compress"}}
|
||||
{{else}}
|
||||
{{fa-icon "expand"}}
|
||||
{{/if}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@ -38,6 +38,15 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{{#unless automatic}}
|
||||
<div>
|
||||
<label for="primary_group">
|
||||
{{input type="checkbox" checked=primary_group}}
|
||||
{{i18n 'admin.groups.primary_group'}}
|
||||
</label>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div>
|
||||
<label for="alias">{{i18n 'groups.alias_levels.title'}}</label>
|
||||
{{combo-box name="alias" valueAttribute="value" value=alias_level content=aliasLevelOptions}}
|
||||
@ -52,6 +61,13 @@
|
||||
{{i18n 'admin.groups.automatic_membership_retroactive'}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="title">
|
||||
{{i18n 'admin.groups.default_title'}}
|
||||
</label>
|
||||
{{input value=title}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class='buttons'>
|
||||
|
||||
@ -1 +1 @@
|
||||
{{avatar member imageSize="small"}} {{member.username}} {{#unless automatic}}<a class='remove' {{action "removeMember" member}}>{{fa-icon "times"}}</a>{{/unless}}
|
||||
<a href='{{unbound member.adminPath}}'>{{avatar member imageSize="small"}}</a> {{member.username}} {{#unless automatic}}<a class='remove' {{action "removeMember" member}}>{{fa-icon "times"}}</a>{{/unless}}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{{#admin-nav}}
|
||||
{{admin-nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom.label'}}
|
||||
{{admin-nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic.label'}}
|
||||
{{admin-nav-item route='adminGroupsType' routeParam='custom' label='admin.groups.custom'}}
|
||||
{{admin-nav-item route='adminGroupsType' routeParam='automatic' label='admin.groups.automatic'}}
|
||||
{{/admin-nav}}
|
||||
|
||||
<div class="admin-container">
|
||||
|
||||
@ -10,7 +10,9 @@
|
||||
</ul>
|
||||
<div class='controls'>
|
||||
{{d-button action="newGroup" icon="plus" label="admin.groups.new"}}
|
||||
{{#if controller.isAuto}}
|
||||
{{d-button action="refreshAutoGroups" icon="refresh" label="admin.groups.refresh" disabled=refreshingAutoGroups}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
{{i18n 'admin.logs.screened_emails.description'}}
|
||||
<button class="btn pull-right" {{action "exportScreenedEmailList"}} title="{{i18n 'admin.export_csv.button_title.screened_email'}}">{{fa-icon "download"}}{{i18n 'admin.export_csv.button_text'}}</button>
|
||||
</p>
|
||||
</br>
|
||||
<br>
|
||||
|
||||
{{#loading-spinner condition=loading}}
|
||||
{{#if model.length}}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
{{i18n 'admin.logs.screened_urls.description'}}
|
||||
<button class="btn pull-right" {{action "exportScreenedUrlList"}} title="{{i18n 'admin.export_csv.button_title.screened_url'}}">{{fa-icon "download"}}{{i18n 'admin.export_csv.button_text'}}</button>
|
||||
</p>
|
||||
</br>
|
||||
<br>
|
||||
|
||||
{{#loading-spinner condition=loading}}
|
||||
{{#if model.length}}
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
<div class="pull-right">
|
||||
{{d-button action="exportStaffActionLogs" label="admin.export_csv.button_text" icon="download"}}
|
||||
</div>
|
||||
</br>
|
||||
<br>
|
||||
|
||||
<div class="staff-action-logs-instructions" {{bind-attr class=":staff-action-logs-instructions showInstructions::invisible"}}>
|
||||
{{i18n 'admin.logs.staff_actions.instructions'}}
|
||||
|
||||
@ -83,29 +83,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if currentUser.admin}}
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.groups.automatic.title'}}</div>
|
||||
<div class='value'>{{automaticGroups}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.groups.custom.title'}}</div>
|
||||
<div class='value'>
|
||||
{{admin-group-selector selected=customGroups available=availableGroups}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if customGroups}}
|
||||
{{i18n 'admin.groups.primary'}}
|
||||
{{combo-box content=customGroups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{/if}}
|
||||
{{#if primaryGroupDirty}}
|
||||
{{d-button icon="check" class="ok no-text" action="savePrimaryGroup"}}
|
||||
{{d-button icon="times" class="cancel no-text" action="resetPrimaryGroup"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'user.ip_address.title'}}</div>
|
||||
<div class='value'>{{ip_address}}</div>
|
||||
@ -159,7 +136,6 @@
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
|
||||
<section class='details'>
|
||||
<h1>{{i18n 'admin.user.permissions'}}</h1>
|
||||
|
||||
@ -355,6 +331,33 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class='details'>
|
||||
<h1>{{i18n 'admin.groups.title'}}</h1>
|
||||
|
||||
{{#if currentUser.admin}}
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.groups.automatic'}}</div>
|
||||
<div class='value'>{{automaticGroups}}</div>
|
||||
</div>
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'admin.groups.custom'}}</div>
|
||||
<div class='value'>
|
||||
{{admin-group-selector selected=customGroups available=availableGroups}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if customGroups}}
|
||||
{{i18n 'admin.groups.primary'}}
|
||||
{{combo-box content=customGroups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{/if}}
|
||||
{{#if primaryGroupDirty}}
|
||||
{{d-button icon="check" class="ok no-text" action="savePrimaryGroup"}}
|
||||
{{d-button icon="times" class="cancel no-text" action="resetPrimaryGroup"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
<section class='details'>
|
||||
<h1>{{i18n 'admin.user.activity'}}</h1>
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{{d-button action="sendInvites" title="admin.invite.button_title" icon="envelope" label="admin.invite.button_text"}}
|
||||
{{d-button action="sendInvites" title="admin.invite.button_title" icon="user-plus" label="admin.invite.button_text"}}
|
||||
{{d-button action="exportUsers" title="admin.export_csv.button_title.user" icon="download" label="admin.export_csv.button_text"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@ export default Ember.Object.extend({
|
||||
pathFor(store, type, findArgs) {
|
||||
let path = "/" + Ember.String.underscore(store.pluralize(type));
|
||||
|
||||
if (ADMIN_MODELS.indexOf(type) !== -1) { path = "/admin/" + path; }
|
||||
if (ADMIN_MODELS.indexOf(type) !== -1) { path = "/admin" + path; }
|
||||
|
||||
if (findArgs) {
|
||||
if (typeof findArgs === "object") {
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'span'
|
||||
});
|
||||
@ -6,11 +6,10 @@ export default Ember.TextArea.extend({
|
||||
}.property('placeholderKey'),
|
||||
|
||||
_signalParentInsert: function() {
|
||||
return this.get('parentView').childDidInsertElement(this);
|
||||
this.get('parentView').childDidInsertElement(this);
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_signalParentDestroy: function() {
|
||||
return this.get('parentView').childWillDestroyElement(this);
|
||||
this.get('parentView').childWillDestroyElement(this);
|
||||
}.on('willDestroyElement')
|
||||
});
|
||||
|
||||
|
||||
@ -4,8 +4,14 @@ import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
tagName: 'th',
|
||||
classNames: ['sortable'],
|
||||
attributeBindings: ['title'],
|
||||
rerenderTriggers: ['order', 'asc'],
|
||||
|
||||
title: function() {
|
||||
const labelKey = 'directory.' + this.get('field');
|
||||
return I18n.t(labelKey + '_long', { defaultValue: I18n.t(labelKey) });
|
||||
}.property('field'),
|
||||
|
||||
renderString(buffer) {
|
||||
|
||||
const icon = this.get('icon');
|
||||
|
||||
@ -4,18 +4,22 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
rerenderTriggers: ['site.isReadOnly'],
|
||||
|
||||
renderString: function(buffer) {
|
||||
var notices = [];
|
||||
let notices = [];
|
||||
|
||||
if (this.site.get("isReadOnly")) {
|
||||
notices.push(I18n.t("read_only_mode.enabled"));
|
||||
notices.push([I18n.t("read_only_mode.enabled"), 'alert-read-only']);
|
||||
}
|
||||
|
||||
if (this.siteSettings.disable_emails) {
|
||||
notices.push(I18n.t("emails_are_disabled"));
|
||||
notices.push([I18n.t("emails_are_disabled"), 'alert-emails-disabled']);
|
||||
}
|
||||
|
||||
if (this.siteSettings.enable_s3_uploads) {
|
||||
notices.push([I18n.t("s3_deprecation_warning"), 'alert-s3-deprecation']);
|
||||
}
|
||||
|
||||
if (Discourse.User.currentProp('admin') && this.siteSettings.show_create_topics_notice) {
|
||||
var topic_count = 0,
|
||||
let topic_count = 0,
|
||||
post_count = 0;
|
||||
_.each(this.site.get('categories'), function(c) {
|
||||
if (!c.get('read_restricted')) {
|
||||
@ -24,18 +28,16 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
}
|
||||
});
|
||||
if (topic_count < 5 || post_count < this.siteSettings.tl1_requires_read_posts) {
|
||||
notices.push(I18n.t("too_few_topics_notice", {posts: this.siteSettings.tl1_requires_read_posts}));
|
||||
notices.push([I18n.t("too_few_topics_notice", { posts: this.siteSettings.tl1_requires_read_posts }), 'alert-too-few-topics']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isEmpty(this.siteSettings.global_notice)) {
|
||||
notices.push(this.siteSettings.global_notice);
|
||||
notices.push([this.siteSettings.global_notice, 'alert-global-notice']);
|
||||
}
|
||||
|
||||
if (notices.length > 0) {
|
||||
buffer.push(_.map(notices, function(text) {
|
||||
return "<div class='row'><div class='alert alert-info'>" + text + "</div></div>";
|
||||
}).join(""));
|
||||
buffer.push(_.map(notices, n => "<div class='row'><div class='alert alert-info " + n[1] + "'>" + n[0] + "</div></div>").join(""));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -10,14 +10,14 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
title: function() {
|
||||
var categoryName = this.get('content.categoryName'),
|
||||
name = this.get('content.name'),
|
||||
extra;
|
||||
extra = {};
|
||||
|
||||
if (categoryName) {
|
||||
extra = { categoryName: categoryName };
|
||||
name = "category";
|
||||
extra.categoryName = categoryName;
|
||||
}
|
||||
return I18n.t("filters." + name + ".help", extra);
|
||||
}.property("content.name"),
|
||||
return I18n.t("filters." + name.replace("/", ".") + ".help", extra);
|
||||
}.property("content.{categoryName,name}"),
|
||||
|
||||
active: function() {
|
||||
return this.get('content.filterMode') === this.get('filterMode') ||
|
||||
@ -33,11 +33,11 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
name = 'category';
|
||||
extra.categoryName = Discourse.Formatter.toTitleCase(categoryName);
|
||||
}
|
||||
return I18n.t("filters." + name + ".title", extra);
|
||||
}.property('content.count'),
|
||||
return I18n.t("filters." + name.replace("/", ".") + ".title", extra);
|
||||
}.property('content.{categoryName,name,count}'),
|
||||
|
||||
renderString: function(buffer) {
|
||||
var content = this.get('content');
|
||||
renderString(buffer) {
|
||||
const content = this.get('content');
|
||||
buffer.push("<a href='" + content.get('href') + "'>");
|
||||
if (content.get('hasIcon')) {
|
||||
buffer.push("<span class='" + content.get('name') + "'></span>");
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
import StringBuffer from 'discourse/mixins/string-buffer';
|
||||
|
||||
export default Ember.Component.extend(StringBuffer, {
|
||||
@ -5,14 +6,16 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
|
||||
rerenderTriggers: ['topic.archived', 'topic.closed', 'topic.pinned', 'topic.visible', 'topic.unpinned', 'topic.is_warning'],
|
||||
|
||||
click: function() {
|
||||
var topic = this.get('topic');
|
||||
click(e) {
|
||||
if ($(e.target).hasClass('fa-thumb-tack')) {
|
||||
const topic = this.get('topic');
|
||||
|
||||
// only pin unpin for now
|
||||
if (topic.get('pinned')) {
|
||||
topic.clearPin();
|
||||
} else {
|
||||
topic.rePin();
|
||||
// only pin unpin for now
|
||||
if (topic.get('pinned')) {
|
||||
topic.clearPin();
|
||||
} else {
|
||||
topic.rePin();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -22,24 +25,26 @@ export default Ember.Component.extend(StringBuffer, {
|
||||
return Discourse.User.current() && !this.get('disableActions');
|
||||
}.property('disableActions'),
|
||||
|
||||
renderString: function(buffer) {
|
||||
renderString(buffer) {
|
||||
const self = this;
|
||||
|
||||
var self = this;
|
||||
|
||||
var renderIconIf = function(conditionProp, name, key, actionable) {
|
||||
const renderIconIf = function(conditionProp, name, key, actionable) {
|
||||
if (!self.get(conditionProp)) { return; }
|
||||
var title = Handlebars.Utils.escapeExpression(I18n.t("topic_statuses." + key + ".help"));
|
||||
var startTag = actionable ? "a href" : "span";
|
||||
var endTag = actionable ? "a" : "span";
|
||||
|
||||
buffer.push("<" + startTag + " title='" + title + "' class='topic-status'><i class='fa fa-" + name + "'></i></" + endTag + ">");
|
||||
const title = Handlebars.Utils.escapeExpression(I18n.t("topic_statuses." + key + ".help")),
|
||||
startTag = actionable ? "a href" : "span",
|
||||
endTag = actionable ? "a" : "span",
|
||||
iconArgs = key === 'unpinned' ? { 'class': 'unpinned' } : null,
|
||||
icon = iconHTML(name, iconArgs);
|
||||
|
||||
buffer.push("<" + startTag + " title='" + title + "' class='topic-status'>" + icon + "</" + endTag + ">");
|
||||
};
|
||||
|
||||
renderIconIf('topic.is_warning', 'envelope', 'warning');
|
||||
renderIconIf('topic.closed', 'lock', 'locked');
|
||||
renderIconIf('topic.archived', 'lock', 'archived');
|
||||
renderIconIf('topic.pinned', 'thumb-tack', 'pinned', self.get("canAct") );
|
||||
renderIconIf('topic.unpinned', 'thumb-tack unpinned', 'unpinned', self.get("canAct"));
|
||||
renderIconIf('topic.pinned', 'thumb-tack', 'pinned', this.get("canAct") );
|
||||
renderIconIf('topic.unpinned', 'thumb-tack', 'unpinned', this.get("canAct"));
|
||||
renderIconIf('topic.invisible', 'eye-slash', 'invisible');
|
||||
}
|
||||
});
|
||||
|
||||
@ -57,6 +57,10 @@ export default TextField.extend({
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_removeAutocomplete: function() {
|
||||
this.$().autocomplete('destroy');
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
// THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT
|
||||
_clearInput: function() {
|
||||
if (arguments.length > 1) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['user-small'],
|
||||
|
||||
userPath: Discourse.computed.url('username', '/users/%@'),
|
||||
userPath: Discourse.computed.url('user.username', '/users/%@'),
|
||||
|
||||
name: function() {
|
||||
const name = this.get('user.name');
|
||||
|
||||
@ -3,16 +3,21 @@ import DiscourseController from 'discourse/controllers/controller';
|
||||
|
||||
export default DiscourseController.extend(ModalFunctionality, {
|
||||
uploadedAvatarTemplate: null,
|
||||
saveDisabled: Em.computed.alias("uploading"),
|
||||
hasUploadedAvatar: Em.computed.or('uploadedAvatarTemplate', 'custom_avatar_upload_id'),
|
||||
|
||||
selectedUploadId: function() {
|
||||
switch (this.get("selected")) {
|
||||
case "system": return this.get("system_avatar_upload_id");
|
||||
case "gravatar": return this.get("gravatar_avatar_upload_id");
|
||||
default: return this.get("custom_avatar_upload_id");
|
||||
}
|
||||
switch (this.get("selected")) {
|
||||
case "system": return this.get("system_avatar_upload_id");
|
||||
case "gravatar": return this.get("gravatar_avatar_upload_id");
|
||||
default: return this.get("custom_avatar_upload_id");
|
||||
}
|
||||
}.property('selected', 'system_avatar_upload_id', 'gravatar_avatar_upload_id', 'custom_avatar_upload_id'),
|
||||
|
||||
allowImageUpload: function() {
|
||||
return Discourse.Utilities.allowsImages();
|
||||
}.property(),
|
||||
|
||||
actions: {
|
||||
useUploadedAvatar() { this.set("selected", "uploaded"); },
|
||||
useGravatar() { this.set("selected", "gravatar"); },
|
||||
|
||||
@ -348,7 +348,7 @@ export default DiscourseController.extend({
|
||||
|
||||
// If we show the subcategory list, scope the categories drop down to
|
||||
// the category we opened the composer with.
|
||||
if (Discourse.SiteSettings.show_subcategory_list) {
|
||||
if (this.siteSettings.show_subcategory_list && opts.draftKey !== 'reply_as_new_topic') {
|
||||
this.set('scopedCategoryId', opts.categoryId);
|
||||
}
|
||||
|
||||
|
||||
@ -69,12 +69,20 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||
return I18n.t('user.password.instructions', {count: Discourse.SiteSettings.min_password_length});
|
||||
}.property(),
|
||||
|
||||
// Validate the name. It's not required.
|
||||
nameInstructions: function() {
|
||||
return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
|
||||
}.property(),
|
||||
|
||||
// Validate the name.
|
||||
nameValidation: function() {
|
||||
if (this.get('accountPasswordConfirm') === 0) {
|
||||
this.fetchConfirmationValue();
|
||||
}
|
||||
|
||||
if (Discourse.SiteSettings.full_name_required && this.blank('accountName')) {
|
||||
return Discourse.InputValidation.create({ failed: true });
|
||||
}
|
||||
|
||||
return Discourse.InputValidation.create({ok: true});
|
||||
}.property('accountName'),
|
||||
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
|
||||
export default ObjectController.extend(ModalFunctionality, {
|
||||
modalClass: 'invite',
|
||||
|
||||
isAdmin: function(){
|
||||
return Discourse.User.currentProp("admin");
|
||||
}.property(),
|
||||
|
||||
onShow: function(){
|
||||
this.set('controllers.modal.modalClass', 'invite-modal');
|
||||
this.set('emailOrUsername', '');
|
||||
},
|
||||
|
||||
disabled: function() {
|
||||
if (this.get('saving')) return true;
|
||||
return this.blank('emailOrUsername');
|
||||
}.property('emailOrUsername', 'saving'),
|
||||
|
||||
buttonTitle: function() {
|
||||
if (this.get('saving')) return I18n.t('topic.inviting');
|
||||
return I18n.t('topic.invite_private.action');
|
||||
}.property('saving'),
|
||||
|
||||
actions: {
|
||||
invite: function() {
|
||||
if (this.get('disabled')) return;
|
||||
|
||||
var self = this;
|
||||
this.setProperties({saving: true, error: false});
|
||||
|
||||
// Invite the user to the private message
|
||||
this.get('model').createInvite(this.get('emailOrUsername')).then(function(result) {
|
||||
self.setProperties({saving: true, finished: true});
|
||||
|
||||
if(result && result.user) {
|
||||
self.get('model.details.allowed_users').pushObject(result.user);
|
||||
}
|
||||
}).catch(function() {
|
||||
self.setProperties({error: true, saving: false});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -6,103 +6,104 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
|
||||
// If this isn't defined, it will proxy to the user model on the preferences
|
||||
// page which is wrong.
|
||||
email: null,
|
||||
emailOrUsername: null,
|
||||
|
||||
isAdmin: function(){
|
||||
return Discourse.User.currentProp("admin");
|
||||
}.property(),
|
||||
|
||||
/**
|
||||
Can we submit the form?
|
||||
|
||||
@property disabled
|
||||
**/
|
||||
disabled: function() {
|
||||
if (this.get('saving')) return true;
|
||||
if (this.blank('email')) return true;
|
||||
if (!Discourse.Utilities.emailValid(this.get('email'))) return true;
|
||||
if (this.blank('emailOrUsername')) return true;
|
||||
if (!this.get('invitingToTopic') && !Discourse.Utilities.emailValid(this.get('emailOrUsername'))) return true;
|
||||
if (this.get('model.details.can_invite_to')) return false;
|
||||
if (this.get('isPrivateTopic') && this.blank('groupNames')) return true;
|
||||
return false;
|
||||
}.property('email', 'isPrivateTopic', 'groupNames', 'saving'),
|
||||
}.property('emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'),
|
||||
|
||||
/**
|
||||
The current text for the invite button
|
||||
|
||||
@property buttonTitle
|
||||
**/
|
||||
buttonTitle: function() {
|
||||
if (this.get('saving')) return I18n.t('topic.inviting');
|
||||
return I18n.t('topic.invite_reply.action');
|
||||
return this.get('saving') ? I18n.t('topic.inviting') : I18n.t('topic.invite_reply.action');
|
||||
}.property('saving'),
|
||||
|
||||
/**
|
||||
We are inviting to a topic if the model isn't the current user. The current user would
|
||||
mean we are inviting to the forum in general.
|
||||
|
||||
@property invitingToTopic
|
||||
**/
|
||||
// We are inviting to a topic if the model isn't the current user.
|
||||
// The current user would mean we are inviting to the forum in general.
|
||||
invitingToTopic: function() {
|
||||
return this.get('model') !== Discourse.User.current();
|
||||
}.property('model'),
|
||||
|
||||
/**
|
||||
Is Private Topic? (i.e. visible only to specific group members)
|
||||
|
||||
@property isPrivateTopic
|
||||
**/
|
||||
// Is Private Topic? (i.e. visible only to specific group members)
|
||||
isPrivateTopic: Em.computed.and('invitingToTopic', 'model.category.read_restricted'),
|
||||
|
||||
/**
|
||||
Instructional text for the modal.
|
||||
isMessage: Em.computed.equal('model.archetype', 'private_message'),
|
||||
|
||||
@property inviteInstructions
|
||||
**/
|
||||
// Allow Existing Members? (username autocomplete)
|
||||
allowExistingMembers: function() {
|
||||
return this.get('invitingToTopic') && !this.get('isPrivateTopic');
|
||||
}.property('invitingToTopic', 'isPrivateTopic'),
|
||||
|
||||
// Show Groups? (add invited user to private group)
|
||||
showGroups: function() {
|
||||
return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso;
|
||||
}.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'invitingToTopic'),
|
||||
|
||||
// Instructional text for the modal.
|
||||
inviteInstructions: function() {
|
||||
if (this.get('invitingToTopic')) {
|
||||
return I18n.t('topic.invite_reply.to_topic');
|
||||
if (Discourse.SiteSettings.enable_sso) {
|
||||
// inviting existing user when SSO enabled
|
||||
return I18n.t('topic.invite_reply.sso_enabled');
|
||||
} else if (this.get('isMessage')) {
|
||||
// inviting to a message
|
||||
return I18n.t('topic.invite_private.email_or_username');
|
||||
} else if (this.get('invitingToTopic')) {
|
||||
// when inviting to topic, display instructions based on provided entity
|
||||
if (this.blank('emailOrUsername')) {
|
||||
return I18n.t('topic.invite_reply.to_topic_blank');
|
||||
} else if (Discourse.Utilities.emailValid(this.get('emailOrUsername'))) {
|
||||
return I18n.t('topic.invite_reply.to_topic_email');
|
||||
} else {
|
||||
return I18n.t('topic.invite_reply.to_topic_username');
|
||||
}
|
||||
} else {
|
||||
// inviting to forum
|
||||
return I18n.t('topic.invite_reply.to_forum');
|
||||
}
|
||||
}.property('invitingToTopic'),
|
||||
}.property('isMessage', 'invitingToTopic', 'emailOrUsername'),
|
||||
|
||||
/**
|
||||
Instructional text for the group selection.
|
||||
|
||||
@property groupInstructions
|
||||
**/
|
||||
// Instructional text for the group selection.
|
||||
groupInstructions: function() {
|
||||
if (this.get('isPrivateTopic')) {
|
||||
return I18n.t('topic.automatically_add_to_groups_required');
|
||||
} else {
|
||||
return I18n.t('topic.automatically_add_to_groups_optional');
|
||||
}
|
||||
return this.get('isPrivateTopic') ?
|
||||
I18n.t('topic.automatically_add_to_groups_required') :
|
||||
I18n.t('topic.automatically_add_to_groups_optional');
|
||||
}.property('isPrivateTopic'),
|
||||
|
||||
/**
|
||||
Function to find groups.
|
||||
**/
|
||||
groupFinder: function(term) {
|
||||
groupFinder(term) {
|
||||
return Discourse.Group.findAll({search: term, ignore_automatic: true});
|
||||
},
|
||||
|
||||
/**
|
||||
The "success" text for when the invite was created.
|
||||
|
||||
@property successMessage
|
||||
**/
|
||||
successMessage: function() {
|
||||
return I18n.t('topic.invite_reply.success', { email: this.get('email') });
|
||||
}.property('email'),
|
||||
if (this.get('isMessage')) {
|
||||
return I18n.t('topic.invite_private.success');
|
||||
} else if ( Discourse.Utilities.emailValid(this.get('emailOrUsername')) ) {
|
||||
return I18n.t('topic.invite_reply.success_email', { emailOrUsername: this.get('emailOrUsername') });
|
||||
} else {
|
||||
return I18n.t('topic.invite_reply.success_username');
|
||||
}
|
||||
}.property('isMessage', 'emailOrUsername'),
|
||||
|
||||
/**
|
||||
Reset the modal to allow a new user to be invited.
|
||||
errorMessage: function() {
|
||||
return this.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error');
|
||||
}.property('isMessage'),
|
||||
|
||||
@method reset
|
||||
**/
|
||||
reset: function() {
|
||||
placeholderKey: function() {
|
||||
return Discourse.SiteSettings.enable_sso ?
|
||||
'topic.invite_reply.username_placeholder' :
|
||||
'topic.invite_private.email_or_username_placeholder';
|
||||
}.property(),
|
||||
|
||||
// Reset the modal to allow a new user to be invited.
|
||||
reset() {
|
||||
this.setProperties({
|
||||
email: null,
|
||||
emailOrUsername: null,
|
||||
groupNames: null,
|
||||
error: false,
|
||||
saving: false,
|
||||
@ -112,34 +113,26 @@ export default ObjectController.extend(ModalFunctionality, {
|
||||
|
||||
actions: {
|
||||
|
||||
/**
|
||||
Create the invite and update the modal accordingly.
|
||||
|
||||
@method createInvite
|
||||
**/
|
||||
createInvite: function() {
|
||||
|
||||
createInvite() {
|
||||
if (this.get('disabled')) { return; }
|
||||
|
||||
var self = this;
|
||||
var groupNames = this.get('groupNames');
|
||||
var userInvitedController = this.get('controllers.user-invited');
|
||||
const groupNames = this.get('groupNames'),
|
||||
userInvitedController = this.get('controllers.user-invited');
|
||||
|
||||
this.setProperties({ saving: true, error: false });
|
||||
this.get('model').createInvite(this.get('email'), groupNames).then(function() {
|
||||
self.setProperties({ saving: false, finished: true });
|
||||
if (!self.get('invitingToTopic')) {
|
||||
Discourse.Invite.findInvitedBy(Discourse.User.current()).then(function (invite_model) {
|
||||
userInvitedController.set('model', invite_model);
|
||||
userInvitedController.set('totalInvites', invite_model.invites.length);
|
||||
});
|
||||
}
|
||||
}).catch(function() {
|
||||
self.setProperties({ saving: false, error: true });
|
||||
});
|
||||
return false;
|
||||
|
||||
return this.get('model').createInvite(this.get('emailOrUsername'), groupNames).then(result => {
|
||||
this.setProperties({ saving: false, finished: true });
|
||||
if (!this.get('invitingToTopic')) {
|
||||
Discourse.Invite.findInvitedBy(Discourse.User.current()).then(invite_model => {
|
||||
userInvitedController.set('model', invite_model);
|
||||
userInvitedController.set('totalInvites', invite_model.invites.length);
|
||||
});
|
||||
} else if (this.get('isMessage') && result && result.user) {
|
||||
this.get('model.details.allowed_users').pushObject(result.user);
|
||||
}
|
||||
}).catch(() => this.setProperties({ saving: false, error: true }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
@ -1,11 +1,26 @@
|
||||
import DiscourseController from 'discourse/controllers/controller';
|
||||
|
||||
export default DiscourseController.extend({
|
||||
needs: ['discovery', 'discovery/topics'],
|
||||
|
||||
categories: function() {
|
||||
return Discourse.Category.list();
|
||||
}.property(),
|
||||
|
||||
navItems: function() {
|
||||
return Discourse.NavItem.buildList();
|
||||
}.property()
|
||||
return Discourse.NavItem.buildList(null, {filterMode: this.get('filterMode')});
|
||||
}.property('filterMode'),
|
||||
|
||||
isSearch: Em.computed.equal('filterMode', 'search'),
|
||||
|
||||
searchTerm: Em.computed.alias('controllers.discovery/topics.model.params.search'),
|
||||
|
||||
actions: {
|
||||
search: function(){
|
||||
var discovery = this.get('controllers.discovery/topics');
|
||||
var model = discovery.get('model');
|
||||
discovery.set('search', this.get("searchTerm"));
|
||||
model.refreshSort();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -39,6 +39,10 @@ export default ObjectController.extend(CanCheckEmails, {
|
||||
|
||||
canEditName: Discourse.computed.setting('enable_names'),
|
||||
|
||||
nameInstructions: function() {
|
||||
return I18n.t(Discourse.SiteSettings.full_name_required ? 'user.name.instructions_required' : 'user.name.instructions');
|
||||
}.property(),
|
||||
|
||||
canSelectTitle: function() {
|
||||
return this.siteSettings.enable_badges && this.get('model.has_title_badges');
|
||||
}.property('model.badge_count'),
|
||||
|
||||
@ -19,8 +19,11 @@ export default DiscourseController.extend({
|
||||
// anonymous users cannot "quote-reply"
|
||||
if (!this.currentUser) return;
|
||||
|
||||
// don't display the "quote-reply" button if we can't at least reply as a new topic
|
||||
if (!this.get('controllers.topic.model.details.can_reply_as_new_topic')) return;
|
||||
// don't display the "quote-reply" button if we can't reply
|
||||
const topicDetails = this.get('controllers.topic.model.details');
|
||||
if (!(topicDetails.get('can_reply_as_new_topic') || topicDetails.get('can_create_post'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = window.getSelection();
|
||||
// no selections
|
||||
|
||||
@ -427,8 +427,8 @@ export default ObjectController.extend(Discourse.SelectedPostsCount, BufferedCon
|
||||
}).then(function() {
|
||||
return Em.isEmpty(quotedText) ? Discourse.Post.loadQuote(post.get('id')) : quotedText;
|
||||
}).then(function(q) {
|
||||
const postUrl = "" + location.protocol + "//" + location.host + (post.get('url')),
|
||||
postLink = "[" + self.get('title') + "](" + postUrl + ")";
|
||||
const postUrl = "" + location.protocol + "//" + location.host + post.get('url'),
|
||||
postLink = "[" + Handlebars.escapeExpression(self.get('title')) + "](" + postUrl + ")";
|
||||
composerController.appendText(I18n.t("post.continue_discussion", { postLink: postLink }) + "\n\n" + q);
|
||||
});
|
||||
},
|
||||
|
||||
@ -1,10 +1,25 @@
|
||||
export default Ember.ArrayController.extend({
|
||||
showAdminLinks: Em.computed.alias("currentUser.staff"),
|
||||
|
||||
allowAnon: function(){
|
||||
return this.siteSettings.allow_anonymous_posting &&
|
||||
(this.get("currentUser.trust_level") >= this.siteSettings.anonymous_posting_min_trust_level ||
|
||||
this.get("isAnon"));
|
||||
}.property(),
|
||||
|
||||
isAnon: function(){
|
||||
return this.get("currentUser.is_anonymous");
|
||||
}.property(),
|
||||
|
||||
actions: {
|
||||
logout() {
|
||||
Discourse.logout();
|
||||
return false;
|
||||
},
|
||||
toggleAnon() {
|
||||
Discourse.ajax("/users/toggle-anon", {method: 'POST'}).then(function(){
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
|
||||
const TITLE_SUBS = { yearly: 'this_year',
|
||||
monthly: 'this_month',
|
||||
daily: 'today',
|
||||
all: 'all' };
|
||||
const TITLE_SUBS = {
|
||||
all: 'all_time',
|
||||
yearly: 'this_year',
|
||||
monthly: 'this_month',
|
||||
daily: 'today',
|
||||
};
|
||||
|
||||
export default Ember.Handlebars.makeBoundHelper(function (period) {
|
||||
const title = I18n.t('filters.top.' + (TITLE_SUBS[period] || 'this_week'));
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
|
||||
const Safe = Handlebars.SafeString;
|
||||
|
||||
registerUnbound('user-status', function(user) {
|
||||
export default Ember.Handlebars.makeBoundHelper(function(user, args) {
|
||||
if (!user) { return; }
|
||||
|
||||
var name = Handlebars.Utils.escapeExpression(user.get('name'));
|
||||
const name = Handlebars.Utils.escapeExpression(user.get('name'));
|
||||
const currentUser = args.hash.currentUser;
|
||||
|
||||
if(Discourse.User.currentProp("admin") || Discourse.User.currentProp("moderator")) {
|
||||
if(user.get('admin')) {
|
||||
var adminDesc = I18n.t('user.admin', {user: name});
|
||||
return new Safe('<i class="fa fa-shield" title="' + adminDesc + '" alt="' + adminDesc + '"></i>');
|
||||
}
|
||||
if (currentUser && user.get('admin') && currentUser.get('staff')) {
|
||||
return new Safe(iconHTML('shield', { label: I18n.t('user.admin', { user: name }) }));
|
||||
}
|
||||
if(user.get('moderator')){
|
||||
var modDesc = I18n.t('user.moderator', {user: name});
|
||||
return new Safe('<i class="fa fa-shield" title="' + modDesc + '" alt="' + modDesc + '"></i>');
|
||||
if (user.get('moderator')) {
|
||||
return new Safe(iconHTML('shield', { label: I18n.t('user.moderator', { user: name }) }));
|
||||
}
|
||||
});
|
||||
|
||||
@ -1282,19 +1282,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-indent on shift-enter
|
||||
util.addEvent(inputBox, "keyup", function (key) {
|
||||
if (key.shiftKey && !key.ctrlKey && !key.metaKey) {
|
||||
var keyCode = key.charCode || key.keyCode;
|
||||
// Character 13 is Enter
|
||||
if (keyCode === 13) {
|
||||
var fakeButton = {};
|
||||
fakeButton.textOp = bindCommand("doAutoindent");
|
||||
doClick(fakeButton);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Perform the button's action.
|
||||
@ -1542,57 +1529,83 @@
|
||||
};
|
||||
|
||||
commandProto.doBold = function (chunk, postProcessing) {
|
||||
return this.doBorI(chunk, postProcessing, 2, this.getString("boldexample"));
|
||||
return this.doSurroundLines(chunk, postProcessing, 2, this.getString("boldexample"));
|
||||
};
|
||||
|
||||
commandProto.doItalic = function (chunk, postProcessing) {
|
||||
return this.doBorI(chunk, postProcessing, 1, this.getString("italicexample"));
|
||||
return this.doSurroundLines(chunk, postProcessing, 1, this.getString("italicexample"));
|
||||
};
|
||||
|
||||
// chunk: The selected region that will be enclosed with */**
|
||||
commandProto.doSurroundLines = function(realChunk, postProcessing, nStars, fallbackText) {
|
||||
realChunk.trimWhitespace();
|
||||
|
||||
// Look for stars before and after, absorb them into the selection.
|
||||
var starsBefore = /(\**$)/.exec(realChunk.before)[0];
|
||||
var starsAfter = /(^\**)/.exec(realChunk.after)[0];
|
||||
|
||||
realChunk.before = realChunk.before.replace(/(\**$)/, "");
|
||||
realChunk.after = realChunk.after.replace(/(^\**)/, "");
|
||||
|
||||
var lines = (starsBefore + realChunk.selection + starsAfter).split("\n");
|
||||
|
||||
// Don't show the fallback text if more than one line is selected,
|
||||
// it's probably a break between paragraphs.
|
||||
if (lines.length > 1) {
|
||||
fallbackText = "";
|
||||
}
|
||||
|
||||
for(var i=0; i<lines.length; i++) {
|
||||
// Split before, selection and after up.
|
||||
var lineMatch = lines[i].match(/^(\s*\**)(.*?)(\**\s*)$/);
|
||||
|
||||
var newChunk = new Chunks();
|
||||
newChunk.before = lineMatch[1];
|
||||
newChunk.selection = lineMatch[2];
|
||||
newChunk.after = lineMatch[3];
|
||||
|
||||
this.doSurroundLine(newChunk, postProcessing, nStars, fallbackText);
|
||||
|
||||
if (lines.length > 1) {
|
||||
lines[i] = newChunk.before + newChunk.selection + newChunk.after;
|
||||
} else {
|
||||
realChunk.startTag = newChunk.before;
|
||||
realChunk.endTag = newChunk.after;
|
||||
lines[i] = newChunk.selection;
|
||||
}
|
||||
}
|
||||
|
||||
realChunk.selection = lines.join("\n");
|
||||
};
|
||||
|
||||
// chunk: The selected region that will be enclosed with * or **
|
||||
// nStars: 1 for italics, 2 for bold
|
||||
// insertText: If you just click the button without highlighting text, this gets inserted
|
||||
commandProto.doBorI = function (chunk, postProcessing, nStars, insertText) {
|
||||
|
||||
// Get rid of whitespace and fixup newlines.
|
||||
// fallbackText: If you just click the button without highlighting text, this gets inserted
|
||||
commandProto.doSurroundLine = function (chunk, postProcessing, nStars, fallbackText) {
|
||||
// Get rid of whitespace
|
||||
chunk.trimWhitespace();
|
||||
chunk.selection = chunk.selection.replace(/\n{2,}/g, "\n");
|
||||
|
||||
// Look for stars before and after. Is the chunk already marked up?
|
||||
// note that these regex matches cannot fail
|
||||
var starsBefore = /(\**$)/.exec(chunk.before)[0];
|
||||
var starsAfter = /(^\**)/.exec(chunk.after)[0];
|
||||
|
||||
var prevStars = Math.min(starsBefore.length, starsAfter.length);
|
||||
var minStars = Math.min(chunk.before.length, chunk.after.length);
|
||||
|
||||
// Remove stars if we have to since the button acts as a toggle.
|
||||
if ((prevStars >= nStars) && (prevStars != 2 || nStars != 1)) {
|
||||
if ((minStars >= nStars) && (minStars != 2 || nStars != 1)) {
|
||||
chunk.before = chunk.before.replace(re("[*]{" + nStars + "}$", ""), "");
|
||||
chunk.after = chunk.after.replace(re("^[*]{" + nStars + "}", ""), "");
|
||||
}
|
||||
else if (!chunk.selection && starsAfter) {
|
||||
// It's not really clear why this code is necessary. It just moves
|
||||
// some arbitrary stuff around.
|
||||
chunk.after = chunk.after.replace(/^([*_]*)/, "");
|
||||
chunk.before = chunk.before.replace(/(\s?)$/, "");
|
||||
var whitespace = re.$1;
|
||||
chunk.before = chunk.before + starsAfter + whitespace;
|
||||
}
|
||||
else {
|
||||
|
||||
// In most cases, if you don't have any selected text and click the button
|
||||
// you'll get a selected, marked up region with the default text inserted.
|
||||
if (!chunk.selection && !starsAfter) {
|
||||
chunk.selection = insertText;
|
||||
if (!chunk.selection && !chunk.after) {
|
||||
chunk.selection = fallbackText;
|
||||
}
|
||||
|
||||
// Add the true markup.
|
||||
var markup = nStars <= 1 ? "*" : "**"; // shouldn't the test be = ?
|
||||
chunk.before = chunk.before + markup;
|
||||
chunk.after = markup + chunk.after;
|
||||
// Only operate if it's not a blank line
|
||||
if (chunk.selection) {
|
||||
// Add the true markup.
|
||||
var markup = nStars === 1 ? "*" : "**";
|
||||
chunk.before = chunk.before + markup;
|
||||
chunk.after = markup + chunk.after;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
commandProto.stripLinkDefs = function (text, defsToAdd) {
|
||||
@ -1765,51 +1778,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
// When making a list, hitting shift-enter will put your cursor on the next line
|
||||
// at the current indent level.
|
||||
commandProto.doAutoindent = function (chunk, postProcessing) {
|
||||
|
||||
var commandMgr = this,
|
||||
fakeSelection = false;
|
||||
|
||||
chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]*\n$/, "\n\n");
|
||||
chunk.before = chunk.before.replace(/(\n|^)[ ]{0,3}>[ \t]*\n$/, "\n\n");
|
||||
chunk.before = chunk.before.replace(/(\n|^)[ \t]+\n$/, "\n\n");
|
||||
|
||||
// There's no selection, end the cursor wasn't at the end of the line:
|
||||
// The user wants to split the current list item / code line / blockquote line
|
||||
// (for the latter it doesn't really matter) in two. Temporarily select the
|
||||
// (rest of the) line to achieve this.
|
||||
if (!chunk.selection && !/^[ \t]*(?:\n|$)/.test(chunk.after)) {
|
||||
chunk.after = chunk.after.replace(/^[^\n]*/, function (wholeMatch) {
|
||||
chunk.selection = wholeMatch;
|
||||
return "";
|
||||
});
|
||||
fakeSelection = true;
|
||||
}
|
||||
|
||||
if (/(\n|^)[ ]{0,3}([*+-]|\d+[.])[ \t]+.*\n$/.test(chunk.before)) {
|
||||
if (commandMgr.doList) {
|
||||
commandMgr.doList(chunk);
|
||||
}
|
||||
}
|
||||
if (/(\n|^)[ ]{0,3}>[ \t]+.*\n$/.test(chunk.before)) {
|
||||
if (commandMgr.doBlockquote) {
|
||||
commandMgr.doBlockquote(chunk);
|
||||
}
|
||||
}
|
||||
if (/(\n|^)(\t|[ ]{4,}).*\n$/.test(chunk.before)) {
|
||||
if (commandMgr.doCode) {
|
||||
commandMgr.doCode(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
if (fakeSelection) {
|
||||
chunk.after = chunk.selection + chunk.after;
|
||||
chunk.selection = "";
|
||||
}
|
||||
};
|
||||
|
||||
commandProto.doBlockquote = function (chunk, postProcessing) {
|
||||
|
||||
chunk.selection = chunk.selection.replace(/^(\n*)([^\r]+?)(\n*)$/,
|
||||
|
||||
@ -33,11 +33,23 @@ var keys = {
|
||||
};
|
||||
|
||||
|
||||
let inputTimeout;
|
||||
|
||||
export default function(options) {
|
||||
var autocompletePlugin = this;
|
||||
|
||||
if (this.length === 0) return;
|
||||
|
||||
if (options === 'destroy') {
|
||||
Ember.run.cancel(inputTimeout);
|
||||
|
||||
$(this).off('keypress.autocomplete')
|
||||
.off('keydown.autocomplete')
|
||||
.off('paste.autocomplete');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options && options.cancel && this.data("closeAutocomplete")) {
|
||||
this.data("closeAutocomplete")();
|
||||
return this;
|
||||
@ -252,13 +264,13 @@ export default function(options) {
|
||||
closeAutocomplete();
|
||||
});
|
||||
|
||||
$(this).on('paste', function() {
|
||||
$(this).on('paste.autocomplete', function() {
|
||||
_.delay(function(){
|
||||
me.trigger("keydown");
|
||||
}, 50);
|
||||
});
|
||||
|
||||
$(this).keypress(function(e) {
|
||||
$(this).on('keypress.autocomplete', function(e) {
|
||||
var caretPosition, term;
|
||||
|
||||
// keep hunting backwards till you hit a the @ key
|
||||
@ -277,7 +289,7 @@ export default function(options) {
|
||||
}
|
||||
});
|
||||
|
||||
$(this).keydown(function(e) {
|
||||
$(this).on('keydown.autocomplete', function(e) {
|
||||
var c, caretPosition, i, initial, next, prev, prevIsGood, stopFound, term, total, userToComplete;
|
||||
|
||||
if(e.ctrlKey || e.altKey || e.metaKey){
|
||||
@ -286,7 +298,9 @@ export default function(options) {
|
||||
|
||||
if(options.allowAny){
|
||||
// saves us wiring up a change event as well, keypress is while its pressed
|
||||
_.delay(function(){
|
||||
|
||||
Ember.run.cancel(inputTimeout);
|
||||
inputTimeout = Ember.run.later(function(){
|
||||
if(inputSelectedItems.length === 0) {
|
||||
inputSelectedItems.push("");
|
||||
}
|
||||
@ -299,7 +313,7 @@ export default function(options) {
|
||||
}
|
||||
}
|
||||
|
||||
},50);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (!options.key) {
|
||||
@ -332,7 +346,7 @@ export default function(options) {
|
||||
|
||||
// ESC
|
||||
if (e.which === keys.esc) {
|
||||
if (completeStart !== null) {
|
||||
if (div !== null) {
|
||||
closeAutocomplete();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,23 +1,10 @@
|
||||
/**
|
||||
Used for tracking when the user clicks on a link
|
||||
export default {
|
||||
trackClick(e) {
|
||||
// cancel click if triggered as part of selection.
|
||||
if (Discourse.Utilities.selectedText() !== "") { return false; }
|
||||
|
||||
@class ClickTrack
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
Discourse.ClickTrack = {
|
||||
|
||||
/**
|
||||
Track a click on a link
|
||||
|
||||
@method trackClick
|
||||
@param {jQuery.Event} e The click event that occurred
|
||||
**/
|
||||
trackClick: function(e) {
|
||||
if (Discourse.Utilities.selectedText()!=="") return false; //cancle click if triggered as part of selection.
|
||||
var $link = $(e.currentTarget);
|
||||
if ($link.hasClass('lightbox')) return true;
|
||||
if ($link.hasClass('lightbox')) { return true; }
|
||||
|
||||
var href = $link.attr('href') || $link.data('href'),
|
||||
$article = $link.closest('article'),
|
||||
@ -25,9 +12,7 @@ Discourse.ClickTrack = {
|
||||
topicId = $('#topic').data('topic-id'),
|
||||
userId = $link.data('user-id');
|
||||
|
||||
if (!href || href.trim().length === 0){
|
||||
return;
|
||||
}
|
||||
if (!href || href.trim().length === 0) { return; }
|
||||
|
||||
if (!userId) userId = $article.data('user-id');
|
||||
|
||||
@ -261,6 +261,9 @@ var number = function(val) {
|
||||
val = parseInt(val, 10);
|
||||
if (isNaN(val)) val = 0;
|
||||
|
||||
if (val > 999999) {
|
||||
return (val / 1000000).toFixed(1) + "M";
|
||||
}
|
||||
if (val > 999) {
|
||||
return (val / 1000).toFixed(1) + "K";
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import loadScript from 'discourse/lib/load-script';
|
||||
export default function($elem) {
|
||||
$("a.lightbox", $elem).each(function(i, e) {
|
||||
loadScript("/javascripts/jquery.magnific-popup-min.js").then(function() {
|
||||
var $e = $(e);
|
||||
const $e = $(e);
|
||||
// do not lightbox spoiled images
|
||||
if ($e.parents(".spoiler").length > 0 || $e.parents(".spoiled").length > 0) { return; }
|
||||
|
||||
@ -14,30 +14,30 @@ export default function($elem) {
|
||||
mainClass: "mfp-zoom-in",
|
||||
|
||||
callbacks: {
|
||||
open: function() {
|
||||
var wrap = this.wrap,
|
||||
img = this.currItem.img,
|
||||
maxHeight = img.css("max-height");
|
||||
open() {
|
||||
const wrap = this.wrap,
|
||||
img = this.currItem.img,
|
||||
maxHeight = img.css("max-height");
|
||||
|
||||
wrap.on("click.pinhandler", "img", function() {
|
||||
wrap.toggleClass("mfp-force-scrollbars");
|
||||
img.css("max-height", wrap.hasClass("mfp-force-scrollbars") ? "none" : maxHeight);
|
||||
});
|
||||
},
|
||||
beforeClose: function() {
|
||||
beforeClose() {
|
||||
this.wrap.off("click.pinhandler");
|
||||
this.wrap.removeClass("mfp-force-scrollbars");
|
||||
}
|
||||
},
|
||||
|
||||
image: {
|
||||
titleSrc: function(item) {
|
||||
var href = item.el.data("download-href") || item.src;
|
||||
return [
|
||||
item.el.attr("title"),
|
||||
$("span.informations", item.el).text().replace('x', '×'),
|
||||
'<a class="image-source-link" href="' + href + '">' + I18n.t("lightbox.download") + '</a>'
|
||||
].join(' · ');
|
||||
titleSrc(item) {
|
||||
const href = item.el.data("download-href") || item.src;
|
||||
let src = [item.el.attr("title"), $("span.informations", item.el).text().replace('x', '×')];
|
||||
if (!Discourse.SiteSettings.prevent_anons_from_downloading_files || Discourse.User.current()) {
|
||||
src.push('<a class="image-source-link" href="' + href + '">' + I18n.t("lightbox.download") + '</a>');
|
||||
}
|
||||
return src.join(' · ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,42 +16,88 @@ function positioningWorkaround($fixedElement) {
|
||||
|
||||
const fixedElement = $fixedElement[0];
|
||||
|
||||
var done = false;
|
||||
|
||||
var blurredNow = function(evt) {
|
||||
if (!done && _.include($(document.activeElement).parents(), fixedElement)) {
|
||||
// something in focus so skip
|
||||
return;
|
||||
}
|
||||
|
||||
done = true;
|
||||
fixedElement.style.position = '';
|
||||
fixedElement.style.top = '';
|
||||
if (evt) {
|
||||
evt.target.removeEventListener('blur', blurred);
|
||||
}
|
||||
};
|
||||
|
||||
var blurred = _.debounce(blurredNow, 250);
|
||||
|
||||
var positioningHack = function(evt){
|
||||
|
||||
const self = this;
|
||||
done = false;
|
||||
|
||||
if (fixedElement.style.position !== 'absolute') {
|
||||
evt.preventDefault();
|
||||
fixedElement.style.position = 'absolute';
|
||||
fixedElement.style.top = (window.scrollY + $('.d-header').height() + 10) + 'px';
|
||||
// we need this, otherwise changing focus means we never clear
|
||||
self.addEventListener('blur', blurred);
|
||||
|
||||
if (fixedElement.style.position === 'absolute') {
|
||||
if (this !== document.activeElement) {
|
||||
evt.preventDefault();
|
||||
self.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var blurred = function() {
|
||||
if (_.include($(document.activeElement).parents(), fixedElement)) {
|
||||
// something in focus so skip
|
||||
fixedElement.style.position = 'absolute';
|
||||
// get out of the way while opening keyboard
|
||||
fixedElement.style.top = '0px';
|
||||
|
||||
var iPadOffset = 0;
|
||||
if (window.innerHeight > window.innerWidth && navigator.userAgent.match(/iPad/)) {
|
||||
// there is no way to get virtual keyboard height
|
||||
iPadOffset = 640 - $(fixedElement).height();
|
||||
}
|
||||
|
||||
var oldScrollY = 0;
|
||||
|
||||
var positionElement = function(){
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
fixedElement.style.position = '';
|
||||
fixedElement.style.top = '';
|
||||
self.removeEventListener('blur', blurred);
|
||||
if (Math.abs(oldScrollY - window.scrollY) < 20) {
|
||||
return;
|
||||
}
|
||||
oldScrollY = window.scrollY;
|
||||
fixedElement.style.top = window.scrollY + iPadOffset + 'px';
|
||||
};
|
||||
|
||||
blurred = _.debounce(blurred, 300);
|
||||
// position once, correctly, after keyboard is shown
|
||||
setTimeout(positionElement, 500);
|
||||
|
||||
if (this !== document.activeElement) {
|
||||
self.focus();
|
||||
}
|
||||
|
||||
self.addEventListener('blur', blurred);
|
||||
evt.preventDefault();
|
||||
self.focus();
|
||||
};
|
||||
|
||||
function attachTouchStart(elem, fn) {
|
||||
if (!$(elem).data('listening')) {
|
||||
elem.addEventListener('touchstart', fn);
|
||||
$(elem).data('listening', true);
|
||||
}
|
||||
}
|
||||
|
||||
const checkForInputs = _.debounce(function(){
|
||||
$fixedElement.find('button,a').each(function(){
|
||||
attachTouchStart(this, function(evt){
|
||||
done = true;
|
||||
$(document.activeElement).blur();
|
||||
evt.preventDefault();
|
||||
$(this).click();
|
||||
});
|
||||
});
|
||||
$fixedElement.find('input,textarea').each(function(){
|
||||
if (!$(this).data('listening')) {
|
||||
this.addEventListener('touchstart', positioningHack);
|
||||
$(this).data('listening', true);
|
||||
}
|
||||
attachTouchStart(this, positioningHack);
|
||||
});
|
||||
}, 100);
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ function searchForTerm(term, opts) {
|
||||
};
|
||||
}
|
||||
|
||||
return Discourse.ajax('/search/', { data: data }).then(function(results){
|
||||
return Discourse.ajax('/search/query', { data: data }).then(function(results){
|
||||
// Topics might not be included
|
||||
if (!results.topics) { results.topics = []; }
|
||||
if (!results.users) { results.users = []; }
|
||||
|
||||
@ -12,4 +12,6 @@ export default (name, model) => {
|
||||
if (controller.onShow) { controller.onShow(); }
|
||||
controller.set('flashMessage', null);
|
||||
}
|
||||
|
||||
return controller;
|
||||
};
|
||||
|
||||
@ -256,7 +256,12 @@ Discourse.Utilities = {
|
||||
@param {String} path The path
|
||||
**/
|
||||
isAnImage: function(path) {
|
||||
return (/\.(png|jpg|jpeg|gif|bmp|tif|tiff|svg|webp)$/i).test(path);
|
||||
return (/\.(png|jpe?g|gif|bmp|tiff?|svg|webp)$/i).test(path);
|
||||
},
|
||||
|
||||
allowsImages: function() {
|
||||
return Discourse.Utilities.authorizesAllExtensions() ||
|
||||
(/(png|jpe?g|gif|bmp|tiff?|svg|webp)/i).test(Discourse.Utilities.authorizedExtensions());
|
||||
},
|
||||
|
||||
/**
|
||||
@ -266,7 +271,7 @@ Discourse.Utilities = {
|
||||
**/
|
||||
allowsAttachments: function() {
|
||||
return Discourse.Utilities.authorizesAllExtensions() ||
|
||||
!(/((png|jpg|jpeg|gif|bmp|tif|tiff|svg|webp)(,\s)?)+$/i).test(Discourse.Utilities.authorizedExtensions());
|
||||
!(/((png|jpe?g|gif|bmp|tiff?|svg|webp)(,\s)?)+$/i).test(Discourse.Utilities.authorizedExtensions());
|
||||
},
|
||||
|
||||
displayErrorForUpload: function(data) {
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
// Mix this in to a view that has a `archetype` property to automatically
|
||||
// add it to the body as the view is entered / left / model is changed.
|
||||
// This is used for keeping the `body` style in sync for the background image.
|
||||
export default {
|
||||
_init: function() { this.get('archetype'); }.on('init'),
|
||||
|
||||
_cleanUp() {
|
||||
$('body').removeClass((_, css) => (css.match(/\barchetype-\S+/g) || []).join(' '));
|
||||
},
|
||||
|
||||
_archetypeChanged: function() {
|
||||
const archetype = this.get('archetype');
|
||||
this._cleanUp();
|
||||
|
||||
if (archetype) {
|
||||
$('body').addClass('archetype-' + archetype);
|
||||
}
|
||||
}.observes('archetype'),
|
||||
|
||||
_willDestroyElement: function() { this._cleanUp(); }.on('willDestroyElement')
|
||||
};
|
||||
@ -4,14 +4,12 @@
|
||||
export default {
|
||||
_enterView: function() { this.get('categoryFullSlug'); }.on('init'),
|
||||
|
||||
_removeClasses: function() {
|
||||
$('body').removeClass(function(idx, css) {
|
||||
return (css.match(/\bcategory-\S+/g) || []).join(' ');
|
||||
});
|
||||
_removeClasses() {
|
||||
$('body').removeClass((_, css) => (css.match(/\bcategory-\S+/g) || []).join(' '));
|
||||
},
|
||||
|
||||
_categoryChanged: function() {
|
||||
var categoryFullSlug = this.get('categoryFullSlug');
|
||||
const categoryFullSlug = this.get('categoryFullSlug');
|
||||
this._removeClasses();
|
||||
|
||||
if (categoryFullSlug) {
|
||||
|
||||
@ -86,7 +86,7 @@ const Composer = Discourse.Model.extend({
|
||||
const replyUsername = post.get('reply_to_user.username');
|
||||
const replyAvatarTemplate = post.get('reply_to_user.avatar_template');
|
||||
if (replyUsername && replyAvatarTemplate && this.get('action') === EDIT) {
|
||||
postDescription += " " + I18n.t("post.in_reply_to") + " " + Discourse.Utilities.tinyAvatar(replyAvatarTemplate) + " " + replyUsername;
|
||||
postDescription += " <i class='fa fa-mail-forward reply-to-glyph'></i> " + Discourse.Utilities.tinyAvatar(replyAvatarTemplate) + " " + replyUsername;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -542,7 +542,6 @@ const Composer = Discourse.Model.extend({
|
||||
// It's no longer a new post
|
||||
createdPost.set('newPost', false);
|
||||
topic.set('draft_sequence', result.draft_sequence);
|
||||
topic.set('details.auto_close_at', result.topic_auto_close_at);
|
||||
postStream.commitPost(createdPost);
|
||||
addedToStream = true;
|
||||
} else {
|
||||
|
||||
@ -61,7 +61,9 @@ const Group = Discourse.Model.extend({
|
||||
alias_level: this.get('alias_level'),
|
||||
visible: !!this.get('visible'),
|
||||
automatic_membership_email_domains: this.get('emailDomains'),
|
||||
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive')
|
||||
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive'),
|
||||
title: this.get('title'),
|
||||
primary_group: !!this.get('primary_group')
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -84,7 +84,15 @@ Discourse.NavItem.reopenClass({
|
||||
args = args || {};
|
||||
if (category) { args.category = category }
|
||||
|
||||
return Discourse.SiteSettings.top_menu.split("|").map(function(i) {
|
||||
var items = Discourse.SiteSettings.top_menu.split("|");
|
||||
|
||||
if (args.filterMode && !_.some(items, function(i){
|
||||
return i.indexOf(args.filterMode) !== -1;
|
||||
})) {
|
||||
items.push(args.filterMode);
|
||||
}
|
||||
|
||||
return items.map(function(i) {
|
||||
return Discourse.NavItem.fromText(i, args);
|
||||
}).filter(function(i) {
|
||||
return i !== null && !(category && i.get("name").indexOf("categor") === 0);
|
||||
|
||||
@ -624,9 +624,6 @@ const PostStream = Ember.Object.extend({
|
||||
return existing;
|
||||
}
|
||||
|
||||
// Update the auto_close_at value of the topic
|
||||
this.set("topic.details.auto_close_at", post.get("topic_auto_close_at"));
|
||||
|
||||
post.set('topic', this.get('topic'));
|
||||
postIdentityMap.set(post.get('id'), post);
|
||||
|
||||
|
||||
@ -41,8 +41,13 @@ Discourse.TopicList = Discourse.Model.extend({
|
||||
var self = this,
|
||||
params = this.get('params');
|
||||
|
||||
params.order = order;
|
||||
params.ascending = ascending;
|
||||
params.order = order || params.order;
|
||||
|
||||
if (ascending === undefined) {
|
||||
params.ascending = ascending;
|
||||
} else {
|
||||
params.ascending = ascending;
|
||||
}
|
||||
|
||||
this.set('loaded', false);
|
||||
var finder = finderFor(this.get('filter'), params);
|
||||
|
||||
@ -157,20 +157,21 @@ const ApplicationRoute = Discourse.Route.extend({
|
||||
const returnPath = encodeURIComponent(window.location.pathname);
|
||||
window.location = Discourse.getURL('/session/sso?return_path=' + returnPath);
|
||||
} else {
|
||||
this._autoLogin('login', () => this.controllerFor('login').resetForm());
|
||||
this._autoLogin('login', 'login-modal', () => this.controllerFor('login').resetForm());
|
||||
}
|
||||
},
|
||||
|
||||
handleShowCreateAccount() {
|
||||
this._autoLogin('createAccount');
|
||||
this._autoLogin('createAccount', 'create-account');
|
||||
},
|
||||
|
||||
_autoLogin(modal, notAuto) {
|
||||
_autoLogin(modal, modalClass, notAuto) {
|
||||
const methods = Em.get('Discourse.LoginMethod.all');
|
||||
if (!this.siteSettings.enable_local_logins && methods.length === 1) {
|
||||
this.controllerFor('login').send('externalLogin', methods[0]);
|
||||
} else {
|
||||
showModal(modal);
|
||||
this.controllerFor('modal').set('modalClass', modalClass);
|
||||
if (notAuto) { notAuto(); }
|
||||
}
|
||||
},
|
||||
|
||||
@ -9,7 +9,8 @@ const DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, Discourse.Ope
|
||||
redirect: function() { return this.redirectIfLoginRequired(); },
|
||||
|
||||
beforeModel: function(transition) {
|
||||
if (transition.targetName.indexOf("discovery.top") === -1 &&
|
||||
if (transition.intent.url === "/" &&
|
||||
transition.targetName.indexOf("discovery.top") === -1 &&
|
||||
Discourse.User.currentProp("should_be_redirected_to_top")) {
|
||||
Discourse.User.currentProp("should_be_redirected_to_top", false);
|
||||
this.replaceWith("discovery.top");
|
||||
|
||||
@ -56,6 +56,7 @@ export default RestrictedUserRoute.extend(ShowFooter, {
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
));
|
||||
bootbox.alert(I18n.t("user.change_avatar.cache_notice"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -69,16 +69,6 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, {
|
||||
this.controllerFor('invite').reset();
|
||||
},
|
||||
|
||||
showPrivateInvite() {
|
||||
showModal('invitePrivate', this.modelFor('topic'));
|
||||
this.controllerFor('invitePrivate').setProperties({
|
||||
email: null,
|
||||
error: false,
|
||||
saving: false,
|
||||
finished: false
|
||||
});
|
||||
},
|
||||
|
||||
showHistory(post) {
|
||||
showModal('history', post);
|
||||
this.controllerFor('history').refresh(post.get("id"), "latest");
|
||||
|
||||
@ -19,6 +19,5 @@
|
||||
</label>
|
||||
</div>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
{{#if category.unreadTopics}}
|
||||
<a href="{{unbound category.unreadUrl}}" class='badge new-posts badge-notification' title='{{i18n 'topic.unread_topics' count=category.unreadTopics}}'>{{i18n 'filters.unread.lower_title_with_count' count=category.unreadTopics}}</a>
|
||||
{{/if}}
|
||||
{{#if category.newTopics}}
|
||||
<a href="{{unbound category.newUrl}}" class='badge new-posts badge-notification' title='{{i18n 'topic.new_topics' count=ctegory.newTopics}}'>{{i18n 'filters.new.lower_title_with_count' count=category.newTopics}}</a>
|
||||
{{/if}}
|
||||
@ -62,7 +62,7 @@
|
||||
<tbody>
|
||||
{{#each a in other_accounts}}
|
||||
<tr>
|
||||
<td>{{#link-to "adminUser" a}}{{avatar a usernamePath="user.username" imageSize="small"}} {{username}}{{/link-to}}</td>
|
||||
<td>{{#link-to "adminUser" a}}{{avatar a usernamePath="user.username" imageSize="small"}} {{a.username}}{{/link-to}}</td>
|
||||
<td>{{a.trustLevel.id}}</td>
|
||||
<td>{{a.time_read}}</td>
|
||||
<td>{{a.topics_entered}}</td>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
<div id='wmd-button-bar'></div>
|
||||
{{textarea value=value elementId="wmd-input"}}
|
||||
<div id='wmd-preview' {{bind-attr class=":preview value::hidden"}}>
|
||||
</div>
|
||||
|
||||
@ -22,7 +22,7 @@ so I'm going to stop rendering it until we figure out what's up
|
||||
{{plugin-outlet "composer-open"}}
|
||||
|
||||
<div class='reply-to'>
|
||||
{{{model.actionTitle}}}:
|
||||
{{{model.actionTitle}}}
|
||||
{{#if canEdit}}
|
||||
{{#if showEditReason}}
|
||||
<div class="edit-reason-input">
|
||||
|
||||
@ -15,12 +15,9 @@
|
||||
<div>
|
||||
<div class="pull-left">
|
||||
{{category-title-link category=c}}
|
||||
{{#if c.unreadTopics}}
|
||||
<a href="{{unbound c.unreadUrl}}" class='badge new-posts badge-notification' title='{{i18n 'topic.unread_topics' count=c.unreadTopics}}'>{{i18n 'filters.unread.lower_title_with_count' count=c.unreadTopics}}</a>
|
||||
{{/if}}
|
||||
{{#if c.newTopics}}
|
||||
<a href="{{unbound c.newUrl}}" class='badge new-posts badge-notification' title='{{i18n 'topic.new_topics' count=c.newTopics}}'>{{i18n 'filters.new.lower_title_with_count' count=c.newTopics}}</a>
|
||||
{{/if}}
|
||||
{{#unless c.logo_url}}
|
||||
{{category-unread category=c}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
@ -28,17 +25,16 @@
|
||||
<div class="category-description">
|
||||
{{{c.description_excerpt}}}
|
||||
</div>
|
||||
|
||||
{{#if c.logo_url}}
|
||||
{{category-unread category=c}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if c.subcategories}}
|
||||
<div class='subcategories'>
|
||||
{{#each s in c.subcategories}}
|
||||
{{category-link s hideParent="true"}}
|
||||
{{#if s.unreadTopics}}
|
||||
<a href={{unbound s.unreadUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.unread_topics' count=s.unreadTopics}}'>{{unbound s.unreadTopics}}</a>
|
||||
{{/if}}
|
||||
{{#if s.newTopics}}
|
||||
<a href={{unbound s.newUrl}} class='badge new-posts badge-notification' title='{{i18n 'topic.new_topics' count=s.newTopics}}'>{{unbound s.newTopics}}</a>
|
||||
{{/if}}
|
||||
{{category-unread category=s}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@ -13,14 +13,14 @@
|
||||
<ul class='icons clearfix' role='navigation'>
|
||||
{{#if currentUser}}
|
||||
<li class='notifications'>
|
||||
<a class='icon' href="#" {{action "showNotifications" target="view"}} data-notifications="notifications-dropdown" id='user-notifications' title='{{i18n 'notifications.title'}}'>
|
||||
<a class='icon' href {{action "showNotifications" target="view"}} data-notifications="notifications-dropdown" id='user-notifications' title='{{i18n 'notifications.title'}}'>
|
||||
{{fa-icon "comment" label="notifications.title"}}
|
||||
</a>
|
||||
{{#if currentUser.unread_notifications}}
|
||||
<a href='#' class='badge-notification unread-notifications'>{{currentUser.unread_notifications}}</a>
|
||||
<a href class='badge-notification unread-notifications'>{{currentUser.unread_notifications}}</a>
|
||||
{{/if}}
|
||||
{{#if currentUser.unread_private_messages}}
|
||||
<a href='#' class='badge-notification unread-private-messages'>{{currentUser.unread_private_messages}}</a>
|
||||
<a href class='badge-notification unread-private-messages'>{{currentUser.unread_private_messages}}</a>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class='num posts'>{{number t.posts_count}}</td>
|
||||
<td class='num age'><span class="{{cold-age-class t.created_at}}" title='{{raw-date t.created_at}}'>{{{format-age t.created_at}}}</span></td>
|
||||
<td class='num age'><span class="{{cold-age-class t.last_posted_at}}" title='{{raw-date t.last_posted_at}}'>{{{format-age t.last_posted_at}}}</span></td>
|
||||
</tr>
|
||||
|
||||
{{/each}}
|
||||
|
||||
@ -9,24 +9,27 @@
|
||||
<label class="radio" for="gravatar">{{bound-avatar controller "large" gravatar_avatar_upload_id}} {{{i18n 'user.change_avatar.gravatar'}}} {{email}}</label>
|
||||
{{d-button action="refreshGravatar" title="user.change_avatar.refresh_gravatar_title" disabled=gravatarRefreshDisabled class="no-text" icon="refresh"}}
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded" {{action "useUploadedAvatar"}}>
|
||||
<label class="radio" for="uploaded_avatar">
|
||||
{{#if hasUploadedAvatar}}
|
||||
{{#if uploadedAvatarTemplate}}
|
||||
{{bound-avatar-template uploadedAvatarTemplate "large"}}
|
||||
{{#if allowImageUpload}}
|
||||
<div>
|
||||
<input type="radio" id="uploaded_avatar" name="avatar" value="uploaded" {{action "useUploadedAvatar"}}>
|
||||
<label class="radio" for="uploaded_avatar">
|
||||
{{#if hasUploadedAvatar}}
|
||||
{{#if uploadedAvatarTemplate}}
|
||||
{{bound-avatar-template uploadedAvatarTemplate "large"}}
|
||||
{{else}}
|
||||
{{bound-avatar controller "large" custom_avatar_upload_id}} {{i18n 'user.change_avatar.uploaded_avatar'}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{bound-avatar controller "large" custom_avatar_upload_id}} {{i18n 'user.change_avatar.uploaded_avatar'}}
|
||||
{{i18n 'user.change_avatar.uploaded_avatar_empty'}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{i18n 'user.change_avatar.uploaded_avatar_empty'}}
|
||||
{{/if}}
|
||||
</label>
|
||||
{{avatar-uploader username=username
|
||||
uploadedAvatarTemplate=uploadedAvatarTemplate
|
||||
custom_avatar_upload_id=custom_avatar_upload_id
|
||||
done="useUploadedAvatar"}}
|
||||
</div>
|
||||
</label>
|
||||
{{avatar-uploader username=username
|
||||
uploadedAvatarTemplate=uploadedAvatarTemplate
|
||||
custom_avatar_upload_id=custom_avatar_upload_id
|
||||
uploading=uploading
|
||||
done="useUploadedAvatar"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
</tr>
|
||||
<tr class="instructions">
|
||||
<td></td>
|
||||
<td><label>{{i18n 'user.name.instructions'}}</label></td>
|
||||
<td><label>{{nameInstructions}}</label></td>
|
||||
</tr>
|
||||
|
||||
{{#if passwordRequired}}
|
||||
|
||||
@ -27,15 +27,14 @@
|
||||
{{#if showDescription}}
|
||||
<section class='field'>
|
||||
<label>{{i18n 'category.description'}}</label>
|
||||
|
||||
{{#if description}}
|
||||
{{description}}
|
||||
{{{description}}}
|
||||
{{else}}
|
||||
{{i18n 'category.no_description'}}
|
||||
{{/if}}
|
||||
{{#if topic_url}}
|
||||
<br/>
|
||||
<button class="btn btn-small" {{action "showCategoryTopic"}}><i class="fa fa-pencil"></i>{{i18n 'category.change_in_category_topic'}}</button>
|
||||
{{d-button class="btn-small" action="showCategoryTopic" icon="pencil" label="category.change_in_category_topic"}}
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
{{#if pinned_at}}
|
||||
<div class="feature-section">
|
||||
<div class="button">
|
||||
{{d-button action="unpin" icon="thumb-tack" label="topic.actions.unpin" class="btn-primary"}}
|
||||
{{d-button action="unpin" icon="thumb-tack" label="topic.feature.unpin" class="btn-primary"}}
|
||||
</div>
|
||||
<div class="desc">
|
||||
<p>{{{unPinMessage}}}</p>
|
||||
@ -26,7 +26,7 @@
|
||||
{{else}}
|
||||
<div class="feature-section">
|
||||
<div class="button">
|
||||
{{d-button action="pin" icon="thumb-tack" label="topic.actions.pin" class="btn-primary"}}
|
||||
{{d-button action="pin" icon="thumb-tack" label="topic.feature.pin" class="btn-primary"}}
|
||||
</div>
|
||||
<div class="desc">
|
||||
<p>{{{pinMessage}}}</p>
|
||||
@ -41,7 +41,7 @@
|
||||
<hr>
|
||||
<div class="feature-section">
|
||||
<div class="button">
|
||||
{{d-button action="pinGlobally" icon="thumb-tack" label="topic.actions.pin_globally" class="btn-primary"}}
|
||||
{{d-button action="pinGlobally" icon="thumb-tack" label="topic.feature.pin_globally" class="btn-primary"}}
|
||||
</div>
|
||||
<div class="desc">
|
||||
<p>{{i18n "topic.feature_topic.pin_globally"}}</p>
|
||||
@ -58,9 +58,9 @@
|
||||
<div class="feature-section">
|
||||
<div class="button">
|
||||
{{#if isBanner}}
|
||||
{{d-button action="removeBanner" icon="thumb-tack" label="topic.actions.remove_banner" class="btn-primary"}}
|
||||
{{d-button action="removeBanner" icon="thumb-tack" label="topic.feature.remove_banner" class="btn-primary"}}
|
||||
{{else}}
|
||||
{{d-button action="makeBanner" icon="thumb-tack" label="topic.actions.make_banner" class="btn-primary"}}
|
||||
{{d-button action="makeBanner" icon="thumb-tack" label="topic.feature.make_banner" class="btn-primary"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="desc">
|
||||
|
||||
@ -2,18 +2,19 @@
|
||||
{{#if error}}
|
||||
<div class="alert alert-error">
|
||||
<button class="close" data-dismiss="alert">×</button>
|
||||
{{i18n 'topic.invite_reply.error'}}
|
||||
{{errorMessage}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if finished}}
|
||||
{{{successMessage}}}
|
||||
{{else}}
|
||||
|
||||
<label>{{inviteInstructions}}</label>
|
||||
{{text-field value=email placeholderKey="topic.invite_reply.email_placeholder"}}
|
||||
|
||||
{{#if isAdmin}}
|
||||
{{#if allowExistingMembers}}
|
||||
{{user-selector single="true" allowAny=true excludeCurrentUser="true" usernames=emailOrUsername includeGroups="true" placeholderKey=placeholderKey}}
|
||||
{{else}}
|
||||
{{text-field value=emailOrUsername placeholderKey="topic.invite_reply.email_placeholder"}}
|
||||
{{/if}}
|
||||
{{#if showGroups}}
|
||||
<label>{{{groupInstructions}}}</label>
|
||||
{{group-selector groupFinder=groupFinder groupNames=groupNames placeholderKey="topic.invite_private.group_name"}}
|
||||
{{/if}}
|
||||
@ -21,9 +22,8 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{#if finished}}
|
||||
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n 'close'}}</button>
|
||||
{{d-button class="btn-primary" action="closeModal" label="close"}}
|
||||
{{else}}
|
||||
<button class='btn btn-primary' {{bind-attr disabled="disabled"}} {{action "createInvite"}}><i class="fa fa-envelope"></i>{{buttonTitle}}</button>
|
||||
<button class='btn btn-primary' {{bind-attr disabled="disabled"}} {{action "createInvite"}}>{{fa-icon "user-plus"}}{{buttonTitle}}</button>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
<div class="modal-body">
|
||||
{{#if error}}
|
||||
<div class="alert alert-error">
|
||||
<button class="close" data-dismiss="alert">×</button>
|
||||
{{i18n 'topic.invite_private.error'}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if finished}}
|
||||
{{i18n 'topic.invite_private.success'}}
|
||||
{{else}}
|
||||
<label>{{i18n 'topic.invite_private.email_or_username'}}</label>
|
||||
{{user-selector single="true" allowAny=true usernames=emailOrUsername includeGroups="true" placeholderKey="topic.invite_private.email_or_username_placeholder"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{#if finished}}
|
||||
<button class='btn btn-primary' {{action "closeModal"}}>{{i18n 'close'}}</button>
|
||||
{{else}}
|
||||
<button class='btn btn-primary' {{bind-attr disabled="disabled"}} {{action "invite"}}>{{buttonTitle}}</button>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
@ -1,23 +1,19 @@
|
||||
<div class="modal-outer-container">
|
||||
<div class="modal-middle-container">
|
||||
<div class="modal-inner-container">
|
||||
|
||||
<div class="modal-header">
|
||||
<a class="close" {{action "closeModal"}}><i class='fa fa-times'></i></a>
|
||||
<a class="close" {{action "closeModal"}}>{{fa-icon "times"}}</a>
|
||||
<h3>{{title}}</h3>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div id='modal-alert'></div>
|
||||
|
||||
{{outlet "modalBody"}}
|
||||
|
||||
{{#each error in errors}}
|
||||
<div class="alert alert-error">
|
||||
<button class="close" data-dismiss="alert">×</button>
|
||||
{{error}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
{{#if isSearch}}
|
||||
<div class="search row">
|
||||
{{input type="text" value=searchTerm class="input-xxlarge search" action="search"}}
|
||||
<button {{action "search"}} class="btn btn-primary"><i class='fa fa-search'></i></button>
|
||||
</div>
|
||||
{{else}}
|
||||
{{bread-crumbs categories=categories}}
|
||||
|
||||
<ul class="nav nav-pills" id='navigation-bar'>
|
||||
@ -10,3 +16,4 @@
|
||||
{{#if canCreateTopic}}
|
||||
<button id="create-topic" class='btn btn-default' {{action "createTopic"}}><i class='fa fa-plus'></i>{{i18n 'topic.create'}}</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@ -1,35 +1,32 @@
|
||||
{{view 'search-text-field' value=term searchContextEnabled=searchContextEnabled searchContext=searchContext id="search-term"}}
|
||||
<div class='search-context'>
|
||||
{{view "search-text-field" value=term searchContextEnabled=searchContextEnabled searchContext=searchContext id="search-term"}}
|
||||
<div class="search-context">
|
||||
{{#if searchContext}}
|
||||
<label>
|
||||
{{input type="checkbox" name="searchContext" checked=searchContextEnabled}} {{searchContextDescription}}
|
||||
{{input type="checkbox" name="searchContext" checked=searchContextEnabled}} {{searchContextDescription}}
|
||||
</label>
|
||||
{{/if}}
|
||||
<a href="#" class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n 'show_help'}}</a>
|
||||
<a href class="show-help" {{action "showSearchHelp" bubbles=false}}>{{i18n "show_help"}}</a>
|
||||
</div>
|
||||
{{#if loading}}
|
||||
<div class='searching'>{{loading-spinner}}</div>
|
||||
<div class="searching">{{loading-spinner}}</div>
|
||||
{{else}}
|
||||
<div class="results">
|
||||
{{#unless noResults}}
|
||||
{{#if noResults}}
|
||||
<div class="no-results">
|
||||
{{i18n "search.no_results"}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{#each resultType in content.resultTypes}}
|
||||
<ul>
|
||||
<li class='heading row'>
|
||||
{{resultType.name}}
|
||||
</li>
|
||||
{{view 'search-results-type' type=resultType.type displayType=resultType.displayType content=resultType.results}}
|
||||
<li class="heading row">{{resultType.name}}</li>
|
||||
{{view "search-results-type" type=resultType.type displayType=resultType.displayType content=resultType.results}}
|
||||
</ul>
|
||||
<div class='no-results'>
|
||||
{{#if resultType.more}}
|
||||
<a href='#' class='filter' {{action "moreOfType" resultType.type bubbles=false}}>{{i18n 'show_more'}} <i class="fa fa-chevron-down"></i></a>
|
||||
{{else}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="no-results">
|
||||
{{#if resultType.more}}
|
||||
<a href class="filter" {{action "moreOfType" resultType.type bubbles=false}}>{{i18n "show_more"}} {{fa-icon "chevron-down"}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div class='no-results'>
|
||||
{{i18n 'search.no_results'}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@ -22,7 +22,9 @@
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
<li>{{#link-to 'users'}}{{i18n "directory.title"}}{{/link-to}}</li>
|
||||
{{#if siteSettings.enable_user_directory}}
|
||||
<li>{{#link-to 'users'}}{{i18n "directory.title"}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet "site-map-links"}}
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<span class='topic-post-badges'>
|
||||
{{#if unread ~}}
|
||||
<a href='{{url}}' class='badge badge-notification unread' title='{{view.unreadTitle}}'>{{unread}}</a>
|
||||
<a href='{{url}}' class='badge badge-notification unread' title='{{i18n "topic.unread_posts" count=unread}}'>{{unread}}</a>
|
||||
{{/if }}
|
||||
{{#if newPosts ~}}
|
||||
<a href='{{url}}' class='badge badge-notification new-posts' title='{{view.newTitle}}'>{{newPosts}}</a>
|
||||
<a href='{{url}}' class='badge badge-notification new-posts' title='{{i18n "topic.total_unread_posts" count=newPosts}}'>{{newPosts}}</a>
|
||||
{{/if}}
|
||||
{{#if unseen ~}}
|
||||
<a href='{{url}}' class='badge badge-notification new-topic' title='{{view.unseenTitle}}'>{{i18n 'filters.new.lower_title'}}</a>
|
||||
<a href='{{url}}' class='badge badge-notification new-topic' title='{{i18n "topic.new"}}'>{{i18n 'filters.new.lower_title'}}</a>
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
{{autofocus-text-field id='edit-title' value=buffered.title maxLength=maxTitleLength}}
|
||||
{{else}}
|
||||
{{autofocus-text-field id='edit-title' value=buffered.title maxLength=maxTitleLength}}
|
||||
</br>
|
||||
<br>
|
||||
{{category-chooser valueAttribute="id" value=buffered.category_id source=buffered.category_id}}
|
||||
{{/if}}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<div class="names">
|
||||
<span>
|
||||
<h1 {{bind-attr class="staff new_user"}}>
|
||||
{{#link-to 'user' user}}{{username}} {{user-status user}}{{/link-to}}
|
||||
{{#link-to 'user' user}}{{username}} {{user-status user currentUser=currentUser}}{{/link-to}}
|
||||
</h1>
|
||||
|
||||
{{#if user.name}}
|
||||
|
||||
@ -8,6 +8,9 @@
|
||||
</li>
|
||||
<li>{{#link-to 'userActivity.bookmarks' currentUser}}{{i18n 'user.bookmarks'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'preferences' currentUser}}{{i18n 'user.preferences'}}{{/link-to}}</li>
|
||||
{{#if allowAnon}}
|
||||
<li><a href {{action toggleAnon}}>{{#if isAnon}}{{i18n 'switch_from_anon'}}{{else}}{{i18n 'switch_to_anon'}}{{/if}}</a></li>
|
||||
{{/if}}
|
||||
<li>{{d-button action="logout" class="btn-danger right logout" icon="sign-out" label="user.log_out"}}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
{{#if invite.reinvited}}
|
||||
{{i18n 'user.invited.reinvited'}}
|
||||
{{else}}
|
||||
<button class='btn' {{action "reinvite" invite}}><i class="fa fa-envelope"></i>{{i18n 'user.invited.reinvite'}}</button>
|
||||
<button class='btn' {{action "reinvite" invite}}><i class="fa fa-user-plus"></i>{{i18n 'user.invited.reinvite'}}</button>
|
||||
{{/if}}
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='instructions'>
|
||||
{{i18n 'user.name.instructions'}}
|
||||
{{nameInstructions}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@ -62,7 +62,7 @@
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="controls">
|
||||
{{d-button action="checkEmail" actionParam=this title="admin.users.check_email.title" icon="envelope-o" label="admin.users.check_email.text"}}
|
||||
{{d-button action="checkEmail" actionParam=model title="admin.users.check_email.title" icon="envelope-o" label="admin.users.check_email.text"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -55,13 +55,13 @@
|
||||
<li>{{#link-to 'preferences' class="btn right"}}{{fa-icon "cog"}}{{i18n 'user.preferences'}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
{{#if canInviteToForum}}
|
||||
<li>{{#link-to 'user.invited' class="btn right"}}{{fa-icon "envelope-o"}}{{i18n 'user.invited.title'}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'user.invited' class="btn right"}}{{fa-icon "user-plus"}}{{i18n 'user.invited.title'}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="primary-textual">
|
||||
<h1>{{username}} {{user-status model}}</h1>
|
||||
<h1>{{username}} {{user-status model currentUser=currentUser}}</h1>
|
||||
<h2>{{name}}</h2>
|
||||
{{#if title}}
|
||||
<h3>{{title}}</h3>
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
{{directory-toggle field="topic_count" order=order asc=asc}}
|
||||
{{directory-toggle field="post_count" order=order asc=asc}}
|
||||
{{directory-toggle field="topics_entered" order=order asc=asc}}
|
||||
{{directory-toggle field="posts_read" order=order asc=asc}}
|
||||
{{directory-toggle field="days_visited" order=order asc=asc}}
|
||||
{{#if showTimeRead}}
|
||||
<th>{{i18n "directory.time_read"}}</th>
|
||||
{{/if}}
|
||||
@ -31,6 +33,8 @@
|
||||
<td>{{number item.model.topic_count}}</td>
|
||||
<td>{{number item.model.post_count}}</td>
|
||||
<td>{{number item.model.topics_entered}}</td>
|
||||
<td>{{number item.model.posts_read}}</td>
|
||||
<td>{{number item.model.days_visited}}</td>
|
||||
{{#if showTimeRead}}
|
||||
<td><span class='time-read'>{{unbound item.model.time_read}}</span></td>
|
||||
{{/if}}
|
||||
|
||||
@ -9,11 +9,11 @@ export default ComboboxView.extend({
|
||||
castInteger: true,
|
||||
|
||||
content: function() {
|
||||
var scopedCategoryId = this.get('scopedCategoryId');
|
||||
let scopedCategoryId = this.get('scopedCategoryId');
|
||||
|
||||
// Always scope to the parent of a category, if present
|
||||
if (scopedCategoryId) {
|
||||
var scopedCat = Discourse.Category.findById(scopedCategoryId);
|
||||
const scopedCat = Discourse.Category.findById(scopedCategoryId);
|
||||
scopedCategoryId = scopedCat.get('parent_category_id') || scopedCat.get('id');
|
||||
}
|
||||
|
||||
@ -41,13 +41,13 @@ export default ComboboxView.extend({
|
||||
}
|
||||
}.property(),
|
||||
|
||||
template: function(item) {
|
||||
template(item) {
|
||||
|
||||
var category;
|
||||
let category;
|
||||
|
||||
// If we have no id, but text with the uncategorized name, we can use that badge.
|
||||
if (Ember.isEmpty(item.id)) {
|
||||
var uncat = Discourse.Category.findUncategorized();
|
||||
const uncat = Discourse.Category.findUncategorized();
|
||||
if (uncat && uncat.get('name') === item.text) {
|
||||
category = uncat;
|
||||
}
|
||||
@ -56,15 +56,16 @@ export default ComboboxView.extend({
|
||||
}
|
||||
|
||||
if (!category) return item.text;
|
||||
var result = categoryBadgeHTML(category, {link: false, allowUncategorized: true, hideParent: true}),
|
||||
parentCategoryId = category.get('parent_category_id');
|
||||
let result = categoryBadgeHTML(category, {link: false, allowUncategorized: true, hideParent: true});
|
||||
const parentCategoryId = category.get('parent_category_id');
|
||||
|
||||
if (parentCategoryId) {
|
||||
result = categoryBadgeHTML(Discourse.Category.findById(parentCategoryId), {link: false}) + " " + result;
|
||||
}
|
||||
|
||||
result += " <span class='topic-count'>× " + category.get('topic_count') + "</span>";
|
||||
|
||||
var description = category.get('description');
|
||||
const description = category.get('description');
|
||||
// TODO wtf how can this be null?;
|
||||
if (description && description !== 'null') {
|
||||
result += '<div class="category-desc">' +
|
||||
|
||||
@ -125,7 +125,6 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
this.set('controller.view', this);
|
||||
|
||||
positioningWorkaround(this.$());
|
||||
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_unlinkView: function() {
|
||||
@ -520,19 +519,25 @@ const ComposerView = Discourse.View.extend(Ember.Evented, {
|
||||
},
|
||||
|
||||
childDidInsertElement() {
|
||||
return this.initEditor();
|
||||
this.initEditor();
|
||||
|
||||
// Disable links in the preview
|
||||
$('#wmd-preview').on('click.preview', (e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
childWillDestroyElement() {
|
||||
const self = this;
|
||||
|
||||
this._unbindUploadTarget();
|
||||
|
||||
Em.run.next(function() {
|
||||
$('#wmd-preview').off('click.preview');
|
||||
|
||||
Em.run.next(() => {
|
||||
$('#main-outlet').css('padding-bottom', 0);
|
||||
// need to wait a bit for the "slide down" transition of the composer
|
||||
Em.run.later(function() {
|
||||
self.appEvents.trigger("composer:closed");
|
||||
Em.run.later(() => {
|
||||
this.appEvents.trigger("composer:closed");
|
||||
}, 400);
|
||||
});
|
||||
},
|
||||
|
||||
@ -8,15 +8,13 @@ export default ModalBodyView.extend({
|
||||
}.property('controller.flagTopic'),
|
||||
|
||||
selectedChanged: function() {
|
||||
const self = this;
|
||||
Em.run.next(() => {
|
||||
this.$("input[type='radio']").prop('checked', false);
|
||||
|
||||
Em.run.next(function() {
|
||||
self.$("input[type='radio']").prop('checked', false);
|
||||
|
||||
const nameKey = self.get('controller.selected.name_key');
|
||||
const nameKey = this.get('controller.selected.name_key');
|
||||
if (!nameKey) { return; }
|
||||
|
||||
self.$('#radio_' + nameKey).prop('checked', 'true');
|
||||
this.$('#radio_' + nameKey).prop('checked', 'true');
|
||||
});
|
||||
}.observes('controller.selected.name_key')
|
||||
});
|
||||
|
||||
@ -125,6 +125,7 @@ export default Discourse.View.extend({
|
||||
$(document).unbind('touchmove.discourse-dock');
|
||||
this.$('a.unread-private-messages, a.unread-notifications, a[data-notifications]').off('click.notifications');
|
||||
this.$('a[data-dropdown]').off('click.dropdown');
|
||||
$('body').off('keydown.header');
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
_setup: function() {
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
import ModalBodyView from "discourse/views/modal-body";
|
||||
|
||||
export default ModalBodyView.extend({
|
||||
templateName: 'modal/invite_private',
|
||||
title: I18n.t('topic.invite_private.title')
|
||||
});
|
||||
@ -6,11 +6,11 @@ export default ButtonView.extend({
|
||||
attributeBindings: ['disabled'],
|
||||
disabled: Em.computed.or('controller.archived', 'controller.closed', 'controller.deleted'),
|
||||
|
||||
renderIcon: function(buffer) {
|
||||
renderIcon(buffer) {
|
||||
buffer.push("<i class='fa fa-users'></i>");
|
||||
},
|
||||
|
||||
click: function() {
|
||||
return this.get('controller').send('showInvite');
|
||||
click() {
|
||||
this.get('controller').send('showInvite');
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user