Version bump

This commit is contained in:
Neil Lalonde 2019-01-28 11:15:39 -05:00
commit 0a32b86f23
341 changed files with 8494 additions and 3778 deletions

View File

@ -1,5 +1,8 @@
language: ruby
git:
depth: false
branches:
only:
- master

View File

@ -303,7 +303,7 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.0.4)
loofah (~> 2.2, >= 2.2.2)
rails_multisite (2.0.4)
rails_multisite (2.0.6)
activerecord (> 4.2, < 6)
railties (> 4.2, < 6)
railties (5.2.2)

View File

@ -14,6 +14,10 @@ export default RestAdapter.extend({
let mapped = theme.get("child_themes") || [];
mapped = mapped.map(t => map[t.id]);
theme.set("childThemes", mapped);
let mappedParents = theme.get("parent_themes") || [];
mappedParents = mappedParents.map(t => map[t.id]);
theme.set("parentThemes", mappedParents);
});
return results;
},

View File

@ -60,7 +60,11 @@ export default Ember.Component.extend({
backgroundColor: prevChartData.length
? "transparent"
: model.secondary_color,
borderColor: model.primary_color
borderColor: model.primary_color,
pointRadius: 3,
borderWidth: 1,
pointBackgroundColor: model.primary_color,
pointBorderColor: model.primary_color
}
]
};

View File

@ -68,14 +68,21 @@ export default Ember.Component.extend({
return {
type: "bar",
data,
responsive: true,
maintainAspectRatio: false,
options: {
responsive: true,
maintainAspectRatio: false,
hover: { mode: "index" },
tooltips: {
mode: "index",
intersect: false,
callbacks: {
beforeFooter: tooltipItem => {
let total = 0;
tooltipItem.forEach(
item => (total += parseInt(item.yLabel || 0, 10))
);
return `= ${total}`;
},
title: tooltipItem =>
moment(tooltipItem[0].xLabel, "YYYY-MM-DD").format("LL")
}

View File

@ -1,3 +1,4 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
import { ajax } from "discourse/lib/ajax";
import AdminUser from "admin/models/admin-user";
import copyText from "discourse/lib/copy-text";
@ -5,43 +6,39 @@ import copyText from "discourse/lib/copy-text";
export default Ember.Component.extend({
classNames: ["ip-lookup"],
otherAccountsToDelete: function() {
@computed("other_accounts.length", "totalOthersWithSameIP")
otherAccountsToDelete(otherAccountsLength, totalOthersWithSameIP) {
// can only delete up to 50 accounts at a time
var total = Math.min(50, this.get("totalOthersWithSameIP") || 0);
var visible = Math.min(50, this.get("other_accounts.length") || 0);
const total = Math.min(50, totalOthersWithSameIP || 0);
const visible = Math.min(50, otherAccountsLength || 0);
return Math.max(visible, total);
}.property("other_accounts", "totalOthersWithSameIP"),
},
actions: {
lookup: function() {
var self = this;
lookup() {
this.set("show", true);
if (!this.get("location")) {
ajax("/admin/users/ip-info", {
data: { ip: this.get("ip") }
}).then(function(location) {
self.set("location", Ember.Object.create(location));
});
ajax("/admin/users/ip-info", { data: { ip: this.get("ip") } }).then(
location => this.set("location", Ember.Object.create(location))
);
}
if (!this.get("other_accounts")) {
this.set("otherAccountsLoading", true);
var data = {
const data = {
ip: this.get("ip"),
exclude: this.get("userId"),
order: "trust_level DESC"
};
ajax("/admin/users/total-others-with-same-ip", { data }).then(function(
result
) {
self.set("totalOthersWithSameIP", result.total);
});
ajax("/admin/users/total-others-with-same-ip", { data }).then(result =>
this.set("totalOthersWithSameIP", result.total)
);
AdminUser.findAll("active", data).then(function(users) {
self.setProperties({
AdminUser.findAll("active", data).then(users => {
this.setProperties({
other_accounts: users,
otherAccountsLoading: false
});
@ -49,11 +46,11 @@ export default Ember.Component.extend({
}
},
hide: function() {
hide() {
this.set("show", false);
},
copy: function() {
copy() {
let text = `IP: ${this.get("ip")}\n`;
const location = this.get("location");
if (location) {
@ -73,25 +70,25 @@ export default Ember.Component.extend({
text += `: ${location.organization}\n`;
}
}
const copyRange = $('<p id="copy-range"></p>');
copyRange.html(text.trim().replace(/\n/g, "<br>"));
$(document.body).append(copyRange);
if (copyText(text, copyRange[0])) {
const $copyRange = $('<p id="copy-range"></p>');
$copyRange.html(text.trim().replace(/\n/g, "<br>"));
$(document.body).append($copyRange);
if (copyText(text, $copyRange[0])) {
this.set("copied", true);
Ember.run.later(() => this.set("copied", false), 2000);
}
copyRange.remove();
$copyRange.remove();
},
deleteOtherAccounts: function() {
var self = this;
deleteOtherAccounts() {
bootbox.confirm(
I18n.t("ip_lookup.confirm_delete_other_accounts"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
confirmed => {
if (confirmed) {
self.setProperties({
this.setProperties({
other_accounts: null,
otherAccountsLoading: true,
totalOthersWithSameIP: null
@ -100,13 +97,11 @@ export default Ember.Component.extend({
ajax("/admin/users/delete-others-with-same-ip.json", {
type: "DELETE",
data: {
ip: self.get("ip"),
exclude: self.get("userId"),
ip: this.get("ip"),
exclude: this.get("userId"),
order: "trust_level DESC"
}
}).then(function() {
self.send("lookup");
});
}).then(() => this.send("lookup"));
}
}
);

View File

@ -1,70 +1,76 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
import { fmt } from "discourse/lib/computed";
import Permalink from "admin/models/permalink";
export default Ember.Component.extend({
classNames: ["permalink-form"],
formSubmitted: false,
permalinkType: "topic_id",
permalinkTypePlaceholder: fmt("permalinkType", "admin.permalink.%@"),
permalinkTypes: function() {
@computed
permalinkTypes() {
return [
{ id: "topic_id", name: I18n.t("admin.permalink.topic_id") },
{ id: "post_id", name: I18n.t("admin.permalink.post_id") },
{ id: "category_id", name: I18n.t("admin.permalink.category_id") },
{ id: "external_url", name: I18n.t("admin.permalink.external_url") }
];
}.property(),
},
permalinkTypePlaceholder: function() {
return "admin.permalink." + this.get("permalinkType");
}.property("permalinkType"),
focusPermalink() {
Ember.run.schedule("afterRender", () => this.$(".permalink-url").focus());
},
actions: {
submit: function() {
submit() {
if (!this.get("formSubmitted")) {
const self = this;
self.set("formSubmitted", true);
const permalink = Permalink.create({
url: self.get("url"),
permalink_type: self.get("permalinkType"),
permalink_type_value: self.get("permalink_type_value")
});
permalink.save().then(
function(result) {
self.set("url", "");
self.set("permalink_type_value", "");
self.set("formSubmitted", false);
self.action(Permalink.create(result.permalink));
Ember.run.schedule("afterRender", function() {
self.$(".permalink-url").focus();
});
},
function(e) {
self.set("formSubmitted", false);
let error;
if (e.responseJSON && e.responseJSON.errors) {
error = I18n.t("generic_error_with_reason", {
error: e.responseJSON.errors.join(". ")
this.set("formSubmitted", true);
Permalink.create({
url: this.get("url"),
permalink_type: this.get("permalinkType"),
permalink_type_value: this.get("permalink_type_value")
})
.save()
.then(
result => {
this.setProperties({
url: "",
permalink_type_value: "",
formSubmitted: false
});
} else {
error = I18n.t("generic_error");
this.action(Permalink.create(result.permalink));
this.focusPermalink();
},
e => {
this.set("formSubmitted", false);
let error;
if (e.responseJSON && e.responseJSON.errors) {
error = I18n.t("generic_error_with_reason", {
error: e.responseJSON.errors.join(". ")
});
} else {
error = I18n.t("generic_error");
}
bootbox.alert(error, () => this.focusPermalink());
}
bootbox.alert(error, function() {
self.$(".permalink-url").focus();
});
}
);
);
}
}
},
didInsertElement: function() {
var self = this;
self._super();
Ember.run.schedule("afterRender", function() {
self.$(".external-url").keydown(function(e) {
didInsertElement() {
this._super(...arguments);
Ember.run.schedule("afterRender", () => {
this.$(".external-url").keydown(e => {
// enter key
if (e.keyCode === 13) {
// enter key
self.send("submit");
this.send("submit");
}
});
});

View File

@ -8,7 +8,7 @@ export default Ember.Component.extend({
classNames: ["themes-list"],
hasThemes: Ember.computed.gt("themesList.length", 0),
hasUserThemes: Ember.computed.gt("userThemes.length", 0),
hasActiveThemes: Ember.computed.gt("activeThemes.length", 0),
hasInactiveThemes: Ember.computed.gt("inactiveThemes.length", 0),
themesTabActive: Ember.computed.equal("currentTab", THEMES),
@ -31,7 +31,7 @@ export default Ember.Component.extend({
)
inactiveThemes(themes) {
if (this.get("componentsTabActive")) {
return [];
return themes.filter(theme => theme.get("parent_themes.length") <= 0);
}
return themes.filter(
theme => !theme.get("user_selectable") && !theme.get("default")
@ -44,20 +44,21 @@ export default Ember.Component.extend({
"themesList.@each.user_selectable",
"themesList.@each.default"
)
userThemes(themes) {
activeThemes(themes) {
if (this.get("componentsTabActive")) {
return [];
return themes.filter(theme => theme.get("parent_themes.length") > 0);
} else {
themes = themes.filter(
theme => theme.get("user_selectable") || theme.get("default")
);
return _.sortBy(themes, t => {
return [
!t.get("default"),
!t.get("user_selectable"),
t.get("name").toLowerCase()
];
});
}
themes = themes.filter(
theme => theme.get("user_selectable") || theme.get("default")
);
return _.sortBy(themes, t => {
return [
!t.get("default"),
!t.get("user_selectable"),
t.get("name").toLowerCase()
];
});
},
didRender() {

View File

@ -30,7 +30,7 @@ export default Ember.Component.extend({
result.className = "disagreed";
result.label = `${result.disagreed}%`;
} else {
result.icon = "external-link";
result.icon = "external-link-alt";
result.className = "ignored";
result.label = `${result.ignored}%`;
}

View File

@ -1,6 +1,12 @@
import ApiKey from "admin/models/api-key";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
@computed("model.[]")
hasMasterKey(model) {
return !!model.findBy("user", null);
},
actions: {
generateMasterKey() {
ApiKey.generateMasterKey().then(key => this.get("model").pushObject(key));
@ -31,10 +37,5 @@ export default Ember.Controller.extend({
}
);
}
},
// Has a master key already been generated?
hasMasterKey: function() {
return !!this.get("model").findBy("user", null);
}.property("model.[]")
}
});

View File

@ -1,41 +1,36 @@
import { ajax } from "discourse/lib/ajax";
import computed from "ember-addons/ember-computed-decorators";
import { default as computed } from "ember-addons/ember-computed-decorators";
import { setting, i18n } from "discourse/lib/computed";
export default Ember.Controller.extend({
adminBackups: Ember.inject.controller(),
status: Ember.computed.alias("adminBackups.model"),
uploadLabel: i18n("admin.backups.upload.label"),
backupLocation: setting("backup_location"),
localBackupStorage: Ember.computed.equal("backupLocation", "local"),
@computed
localBackupStorage() {
return this.siteSettings.backup_location === "local";
},
uploadLabel: function() {
return I18n.t("admin.backups.upload.label");
}.property(),
restoreTitle: function() {
if (!this.get("status.allowRestore")) {
@computed("status.allowRestore", "status.isOperationRunning")
restoreTitle(allowRestore, isOperationRunning) {
if (!allowRestore) {
return "admin.backups.operations.restore.is_disabled";
} else if (this.get("status.isOperationRunning")) {
} else if (isOperationRunning) {
return "admin.backups.operations.is_running";
} else {
return "admin.backups.operations.restore.title";
}
}.property("status.{allowRestore,isOperationRunning}"),
},
actions: {
toggleReadOnlyMode() {
var self = this;
if (!this.site.get("isReadOnly")) {
bootbox.confirm(
I18n.t("admin.backups.read_only.enable.confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
confirmed => {
if (confirmed) {
Discourse.User.currentProp("hideReadOnlyAlert", true);
self._toggleReadOnlyMode(true);
this._toggleReadOnlyMode(true);
}
}
);
@ -45,20 +40,17 @@ export default Ember.Controller.extend({
},
download(backup) {
let link = backup.get("filename");
ajax("/admin/backups/" + link, { type: "PUT" }).then(() => {
bootbox.alert(I18n.t("admin.backups.operations.download.alert"));
});
const link = backup.get("filename");
ajax(`/admin/backups/${link}`, { type: "PUT" }).then(() =>
bootbox.alert(I18n.t("admin.backups.operations.download.alert"))
);
}
},
_toggleReadOnlyMode(enable) {
var site = this.site;
ajax("/admin/backups/readonly", {
type: "PUT",
data: { enable: enable }
}).then(() => {
site.set("isReadOnly", enable);
});
data: { enable }
}).then(() => this.site.set("isReadOnly", enable));
}
});

View File

@ -8,22 +8,11 @@ import { THEMES, COMPONENTS } from "admin/models/theme";
const THEME_UPLOAD_VAR = 2;
export default Ember.Controller.extend({
downloadUrl: url("model.id", "/admin/themes/%@"),
downloadUrl: url("model.id", "/admin/customize/themes/%@/export"),
previewUrl: url("model.id", "/admin/themes/%@/preview"),
addButtonDisabled: Ember.computed.empty("selectedChildThemeId"),
editRouteName: "adminCustomizeThemes.edit",
@computed("model", "allThemes", "model.component")
parentThemes(model, allThemes) {
if (!model.get("component")) {
return null;
}
const parents = allThemes.filter(theme =>
_.contains(theme.get("childThemes"), model)
);
return parents.length === 0 ? null : parents;
},
@computed("model.editedFields")
editedFieldsFormatted() {
const descriptions = [];
@ -214,7 +203,7 @@ export default Ember.Controller.extend({
},
editTheme() {
if (this.get("model.remote_theme")) {
if (this.get("model.remote_theme.is_git")) {
bootbox.confirm(
I18n.t("admin.customize.theme.edit_confirm"),
result => {

View File

@ -11,8 +11,7 @@ export default Ember.Controller.extend({
show: debounce(function() {
this.set("loading", true);
ScreenedIpAddress.findAll(this.get("filter")).then(result => {
this.set("model", result);
this.set("loading", false);
this.setProperties({ model: result, loading: false });
});
}, 250).observes("filter"),
@ -35,8 +34,9 @@ export default Ember.Controller.extend({
},
cancel(record) {
if (this.get("savedIpAddress") && record.get("editing")) {
record.set("ip_address", this.get("savedIpAddress"));
const savedIpAddress = this.get("savedIpAddress");
if (savedIpAddress && record.get("editing")) {
record.set("ip_address", savedIpAddress);
}
record.set("editing", false);
},
@ -46,9 +46,7 @@ export default Ember.Controller.extend({
record.set("editing", false);
record
.save()
.then(() => {
this.set("savedIpAddress", null);
})
.then(() => this.set("savedIpAddress", null))
.catch(e => {
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
bootbox.alert(
@ -84,7 +82,7 @@ export default Ember.Controller.extend({
.catch(e => {
bootbox.alert(
I18n.t("generic_error_with_reason", {
error: "http: " + e.status + " - " + e.body
error: `http: ${e.status} - ${e.body}`
})
);
});
@ -98,25 +96,24 @@ export default Ember.Controller.extend({
},
rollUp() {
const self = this;
return bootbox.confirm(
I18n.t("admin.logs.screened_ips.roll_up_confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
confirmed => {
if (confirmed) {
self.set("loading", true);
return ScreenedIpAddress.rollUp().then(function(results) {
this.set("loading", true);
return ScreenedIpAddress.rollUp().then(results => {
if (results && results.subnets) {
if (results.subnets.length > 0) {
self.send("show");
this.send("show");
bootbox.alert(
I18n.t("admin.logs.screened_ips.rolled_up_some_subnets", {
subnets: results.subnets.join(", ")
})
);
} else {
self.set("loading", false);
this.set("loading", false);
bootbox.alert(
I18n.t("admin.logs.screened_ips.rolled_up_no_subnet")
);

View File

@ -3,7 +3,8 @@ import CanCheckEmails from "discourse/mixins/can-check-emails";
import { propertyNotEqual, setting } from "discourse/lib/computed";
import { userPath } from "discourse/lib/url";
import { popupAjaxError } from "discourse/lib/ajax-error";
import computed from "ember-addons/ember-computed-decorators";
import { default as computed } from "ember-addons/ember-computed-decorators";
import { fmt } from "discourse/lib/computed";
export default Ember.Controller.extend(CanCheckEmails, {
adminTools: Ember.inject.service(),
@ -64,24 +65,22 @@ export default Ember.Controller.extend(CanCheckEmails, {
.join(", ");
},
userFields: function() {
const siteUserFields = this.site.get("user_fields"),
userFields = this.get("model.user_fields");
@computed("model.user_fields.[]")
userFields(userFields) {
const siteUserFields = this.site.get("user_fields");
if (!Ember.isEmpty(siteUserFields)) {
return siteUserFields.map(function(uf) {
let value = userFields ? userFields[uf.get("id").toString()] : null;
return { name: uf.get("name"), value: value };
return siteUserFields.map(uf => {
const value = userFields ? userFields[uf.get("id").toString()] : null;
return { name: uf.get("name"), value };
});
}
return [];
}.property("model.user_fields.[]"),
@computed("model.username_lower")
preferencesPath(username) {
return userPath(`${username}/preferences`);
return [];
},
preferencesPath: fmt("model.username_lower", userPath("%@/preferences")),
@computed("model.can_delete_all_posts", "model.staff", "model.post_count")
deleteAllPostsExplanation(canDeleteAllPosts, staff, postCount) {
if (canDeleteAllPosts) {
@ -120,9 +119,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
groupAdded(added) {
this.get("model")
.groupAdded(added)
.catch(function() {
bootbox.alert(I18n.t("generic_error"));
});
.catch(() => bootbox.alert(I18n.t("generic_error")));
},
groupRemoved(groupId) {
@ -133,9 +130,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
this.set("originalPrimaryGroupId", null);
}
})
.catch(function() {
bootbox.alert(I18n.t("generic_error"));
});
.catch(() => bootbox.alert(I18n.t("generic_error")));
},
actions: {
@ -201,13 +196,11 @@ export default Ember.Controller.extend(CanCheckEmails, {
},
clearPenaltyHistory() {
let user = this.get("model");
return ajax(`/admin/users/${user.get("id")}/penalty_history`, {
type: "DELETE"
})
.then(() => {
user.set("tl3_requirements.penalty_counts.total", 0);
})
const user = this.get("model");
const path = `/admin/users/${user.get("id")}/penalty_history`;
return ajax(path, { type: "DELETE" })
.then(() => user.set("tl3_requirements.penalty_counts.total", 0))
.catch(popupAjaxError);
},
@ -225,7 +218,6 @@ export default Ember.Controller.extend(CanCheckEmails, {
target_user: this.get("model.username")
});
},
showFlagsReceived() {
this.get("adminTools").showFlagsReceived(this.get("model"));
},
@ -245,10 +237,9 @@ export default Ember.Controller.extend(CanCheckEmails, {
const oldUsername = this.get("model.username");
this.set("model.username", newUsername);
return ajax(`/users/${oldUsername.toLowerCase()}/preferences/username`, {
data: { new_username: newUsername },
type: "PUT"
})
const path = `/users/${oldUsername.toLowerCase()}/preferences/username`;
return ajax(path, { data: { new_username: newUsername }, type: "PUT" })
.catch(e => {
this.set("model.username", oldUsername);
popupAjaxError(e);
@ -260,13 +251,9 @@ export default Ember.Controller.extend(CanCheckEmails, {
const oldName = this.get("model.name");
this.set("model.name", newName);
return ajax(
userPath(`${this.get("model.username").toLowerCase()}.json`),
{
data: { name: newName },
type: "PUT"
}
)
const path = userPath(`${this.get("model.username").toLowerCase()}.json`);
return ajax(path, { data: { name: newName }, type: "PUT" })
.catch(e => {
this.set("model.name", oldName);
popupAjaxError(e);
@ -276,15 +263,11 @@ export default Ember.Controller.extend(CanCheckEmails, {
saveTitle(newTitle) {
const oldTitle = this.get("model.title");
this.set("model.title", newTitle);
return ajax(
userPath(`${this.get("model.username").toLowerCase()}.json`),
{
data: { title: newTitle },
type: "PUT"
}
)
const path = userPath(`${this.get("model.username").toLowerCase()}.json`);
return ajax(path, { data: { title: newTitle }, type: "PUT" })
.catch(e => {
this.set("model.title", oldTitle);
popupAjaxError(e);
@ -303,9 +286,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
bufferedIds
.filter(id => !currentIds.includes(id))
.forEach(id => {
this.groupAdded(availableGroups.findBy("id", id));
});
.forEach(id => this.groupAdded(availableGroups.findBy("id", id)));
currentIds
.filter(id => !bufferedIds.includes(id))
@ -317,21 +298,15 @@ export default Ember.Controller.extend(CanCheckEmails, {
},
savePrimaryGroup() {
const self = this;
const primaryGroupId = this.get("model.primary_group_id");
const path = `/admin/users/${this.get("model.id")}/primary_group`;
return ajax("/admin/users/" + this.get("model.id") + "/primary_group", {
return ajax(path, {
type: "PUT",
data: { primary_group_id: this.get("model.primary_group_id") }
data: { primary_group_id: primaryGroupId }
})
.then(function() {
self.set(
"originalPrimaryGroupId",
self.get("model.primary_group_id")
);
})
.catch(function() {
bootbox.alert(I18n.t("generic_error"));
});
.then(() => this.set("originalPrimaryGroupId", primaryGroupId))
.catch(() => bootbox.alert(I18n.t("generic_error")));
},
resetPrimaryGroup() {
@ -339,30 +314,26 @@ export default Ember.Controller.extend(CanCheckEmails, {
},
regenerateApiKey() {
const self = this;
bootbox.confirm(
I18n.t("admin.api.confirm_regen"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(result) {
result => {
if (result) {
self.get("model").generateApiKey();
this.get("model").generateApiKey();
}
}
);
},
revokeApiKey() {
const self = this;
bootbox.confirm(
I18n.t("admin.api.confirm_revoke"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(result) {
result => {
if (result) {
self.get("model").revokeApiKey();
this.get("model").revokeApiKey();
}
}
);

View File

@ -9,7 +9,7 @@ export default Ember.Helper.extend({
let title = "admin.flags.dispositions." + disposition;
switch (disposition) {
case "deferred": {
icon = "external-link";
icon = "external-link-alt";
break;
}
case "agreed": {

View File

@ -1,32 +1,22 @@
import AdminUser from "admin/models/admin-user";
import { ajax } from "discourse/lib/ajax";
const ApiKey = Discourse.Model.extend({
/**
Regenerates the api key
const KEY_ENDPOINT = "/admin/api/key";
const KEYS_ENDPOINT = "/admin/api/keys";
@method regenerate
@returns {Promise} a promise that resolves to the key
**/
regenerate: function() {
var self = this;
return ajax("/admin/api/key", {
const ApiKey = Discourse.Model.extend({
regenerate() {
return ajax(KEY_ENDPOINT, {
type: "PUT",
data: { id: this.get("id") }
}).then(function(result) {
self.set("key", result.api_key.key);
return self;
}).then(result => {
this.set("key", result.api_key.key);
return this;
});
},
/**
Revokes the current key
@method revoke
@returns {Promise} a promise that resolves when the key has been revoked
**/
revoke: function() {
return ajax("/admin/api/key", {
revoke() {
return ajax(KEY_ENDPOINT, {
type: "DELETE",
data: { id: this.get("id") }
});
@ -34,45 +24,24 @@ const ApiKey = Discourse.Model.extend({
});
ApiKey.reopenClass({
/**
Creates an API key instance with internal user object
@method create
@param {...} var_args the properties to initialize this with
@returns {ApiKey} the ApiKey instance
**/
create() {
var result = this._super.apply(this, arguments);
const result = this._super.apply(this, arguments);
if (result.user) {
result.user = AdminUser.create(result.user);
}
return result;
},
/**
Finds a list of API keys
@method find
@returns {Promise} a promise that resolves to the array of `ApiKey` instances
**/
find: function() {
return ajax("/admin/api/keys").then(function(keys) {
return keys.map(function(key) {
return ApiKey.create(key);
});
});
find() {
return ajax(KEYS_ENDPOINT).then(keys =>
keys.map(key => ApiKey.create(key))
);
},
/**
Generates a master api key and returns it.
@method generateMasterKey
@returns {Promise} a promise that resolves to a master `ApiKey`
**/
generateMasterKey: function() {
return ajax("/admin/api/key", { type: "POST" }).then(function(result) {
return ApiKey.create(result.api_key);
});
generateMasterKey() {
return ajax(KEY_ENDPOINT, { type: "POST" }).then(result =>
ApiKey.create(result.api_key)
);
}
});

View File

@ -47,15 +47,15 @@ export default Discourse.Route.extend({
},
model() {
return PreloadStore.getAndRemove("operations_status", function() {
return ajax("/admin/backups/status.json");
}).then(status => {
return BackupStatus.create({
return PreloadStore.getAndRemove("operations_status", () =>
ajax("/admin/backups/status.json")
).then(status =>
BackupStatus.create({
isOperationRunning: status.is_operation_running,
canRollback: status.can_rollback,
allowRestore: status.allow_restore
});
});
})
);
},
deactivate() {
@ -74,33 +74,30 @@ export default Discourse.Route.extend({
},
destroyBackup(backup) {
const self = this;
bootbox.confirm(
I18n.t("admin.backups.operations.destroy.confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
confirmed => {
if (confirmed) {
backup.destroy().then(function() {
self
.controllerFor("adminBackupsIndex")
backup.destroy().then(() =>
this.controllerFor("adminBackupsIndex")
.get("model")
.removeObject(backup);
});
.removeObject(backup)
);
}
}
);
},
startRestore(backup) {
const self = this;
bootbox.confirm(
I18n.t("admin.backups.operations.restore.confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
confirmed => {
if (confirmed) {
self.transitionTo("admin.backups.logs");
this.transitionTo("admin.backups.logs");
backup.restore();
}
}
@ -108,17 +105,17 @@ export default Discourse.Route.extend({
},
cancelOperation() {
const self = this;
bootbox.confirm(
I18n.t("admin.backups.operations.cancel.confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
confirmed => {
if (confirmed) {
Backup.cancel().then(function() {
self
.controllerFor("adminBackups")
.set("model.isOperationRunning", false);
Backup.cancel().then(() => {
this.controllerFor("adminBackups").set(
"model.isOperationRunning",
false
);
});
}
}
@ -130,7 +127,7 @@ export default Discourse.Route.extend({
I18n.t("admin.backups.operations.rollback.confirm"),
I18n.t("no_value"),
I18n.t("yes_value"),
function(confirmed) {
confirmed => {
if (confirmed) {
Backup.rollback();
}
@ -139,17 +136,12 @@ export default Discourse.Route.extend({
},
uploadSuccess(filename) {
bootbox.alert(
I18n.t("admin.backups.upload.success", { filename: filename })
);
bootbox.alert(I18n.t("admin.backups.upload.success", { filename }));
},
uploadError(filename, message) {
bootbox.alert(
I18n.t("admin.backups.upload.error", {
filename: filename,
message: message
})
I18n.t("admin.backups.upload.error", { filename, message })
);
},

View File

@ -0,0 +1,23 @@
import { emojiUrlFor } from "discourse/lib/text";
const badgeIntroLinks = [
{
text: "admin.badges.badge_intro.what_are_badges_title",
href: "https://meta.discourse.org/t/32540",
icon: "book"
},
{
text: "admin.badges.badge_intro.badge_query_examples_title",
href: "https://meta.discourse.org/t/18978",
icon: "book"
}
];
export default Ember.Route.extend({
setupController(controller) {
controller.setProperties({
badgeIntroLinks,
badgeIntroEmoji: emojiUrlFor("woman_student:t4")
});
}
});

View File

@ -1,35 +1,47 @@
{{#if model}}
<table class='api-keys grid'>
<table class="api-keys grid">
<thead>
<th>{{i18n 'admin.api.key'}}</th>
<th>{{i18n 'admin.api.user'}}</th>
<th>{{i18n "admin.api.key"}}</th>
<th>{{i18n "admin.api.user"}}</th>
<th>&nbsp;</th>
</thead>
<tbody>
{{#each model as |k|}}
<tr>
<td class='key'>{{k.key}}</td>
<td class="key">{{k.key}}</td>
<td class="key-user">
{{#if k.user}}
{{#link-to 'adminUser' k.user}}
{{#link-to "adminUser" k.user}}
{{avatar k.user imageSize="small"}}
{{/link-to}}
{{else}}
{{i18n 'admin.api.all_users'}}
{{i18n "admin.api.all_users"}}
{{/if}}
</td>
<td class="key-controls">
{{d-button class="btn-default" action=(action "regenerateKey") actionParam=k icon="undo" label='admin.api.regenerate'}}
{{d-button class="btn-default" action=(action "revokeKey") actionParam=k icon="times" label='admin.api.revoke'}}
{{d-button
class="btn-default"
action=(action "regenerateKey")
actionParam=k icon="undo"
label="admin.api.regenerate"}}
{{d-button
class="btn-default"
action=(action "revokeKey")
actionParam=k
icon="times"
label="admin.api.revoke"}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<p>{{i18n 'admin.api.none'}}</p>
<p>{{i18n "admin.api.none"}}</p>
{{/if}}
{{#unless hasMasterKey}}
<button class='btn btn-icon no-text btn-primary' {{action "generateMasterKey"}}>{{d-icon "key"}}</button>
{{d-button
class="btn-primary"
action=(action "generateMasterKey")
icon="key"}}
{{/unless}}

View File

@ -1,20 +1,38 @@
<div class="backup-options">
{{#if localBackupStorage}}
{{resumable-upload target="/admin/backups/upload" success=(route-action "uploadSuccess") error=(route-action "uploadError") uploadText=uploadLabel title="admin.backups.upload.title" class="btn-default"}}
{{else}}
{{backup-uploader done=(route-action "remoteUploadSuccess")}}
{{/if}}
<div class="backup-options">
{{#if localBackupStorage}}
{{resumable-upload
target="/admin/backups/upload"
success=(route-action "uploadSuccess")
error=(route-action "uploadError")
uploadText=uploadLabel
title="admin.backups.upload.title"
class="btn-default"}}
{{else}}
{{backup-uploader done=(route-action "remoteUploadSuccess")}}
{{/if}}
{{#if site.isReadOnly}}
{{d-button class="btn-default" icon="far-eye" action=(action "toggleReadOnlyMode") disabled=status.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
{{else}}
{{d-button class="btn-default" icon="far-eye" action=(action "toggleReadOnlyMode") disabled=status.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
{{/if}}
</div>
{{#if site.isReadOnly}}
{{d-button
class="btn-default"
icon="far-eye"
action=(action "toggleReadOnlyMode")
disabled=status.isOperationRunning
title="admin.backups.read_only.disable.title"
label="admin.backups.read_only.disable.label"}}
{{else}}
{{d-button
class="btn-default"
icon="far-eye"
action=(action "toggleReadOnlyMode")
disabled=status.isOperationRunning
title="admin.backups.read_only.enable.title"
label="admin.backups.read_only.enable.label"}}
{{/if}}
</div>
<table class="grid">
<thead>
<th width="55%">{{i18n 'admin.backups.columns.filename'}}</th>
<th width="10%">{{i18n 'admin.backups.columns.size'}}</th>
<th width="55%">{{i18n "admin.backups.columns.filename"}}</th>
<th width="10%">{{i18n "admin.backups.columns.size"}}</th>
<th></th>
</thead>
<tbody>
@ -25,24 +43,47 @@
<td class="backup-controls">
<div>
{{d-button class="btn-default download"
action=(action "download")
actionParam=backup
icon="download"
title="admin.backups.operations.download.title"
label="admin.backups.operations.download.label"}}
action=(action "download")
actionParam=backup
icon="download"
title="admin.backups.operations.download.title"
label="admin.backups.operations.download.label"}}
{{#if status.isOperationRunning}}
{{d-button icon="far-trash-alt" action=(route-action "destroyBackup") actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}}
{{d-button icon="play" action=(route-action "startRestore") actionParam=backup disabled=status.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}}
{{d-button
icon="far-trash-alt"
action=(route-action "destroyBackup")
actionParam=backup class="btn-danger"
disabled="true"
title="admin.backups.operations.is_running"}}
{{d-button
icon="play"
action=(route-action "startRestore")
actionParam=backup disabled=status.restoreDisabled
class="btn-default"
title=restoreTitle
label="admin.backups.operations.restore.label"}}
{{else}}
{{d-button icon="far-trash-alt" action=(route-action "destroyBackup") actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}}
{{d-button icon="play" action=(route-action "startRestore") actionParam=backup disabled=status.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}}
{{d-button
icon="far-trash-alt"
action=(route-action "destroyBackup")
actionParam=backup
class="btn-danger"
title="admin.backups.operations.destroy.title"}}
{{d-button
icon="play"
action=(route-action "startRestore")
actionParam=backup
disabled=status.restoreDisabled
class="btn-default"
title=restoreTitle
label="admin.backups.operations.restore.label"}}
{{/if}}
</div>
</td>
</tr>
{{else}}
<tr>
<td>{{i18n 'admin.backups.none'}}</td>
<td>{{i18n "admin.backups.none"}}</td>
<td></td>
<td></td>
</tr>

View File

@ -1,9 +1,16 @@
{{#d-section class="current-badge content-body"}}
<p>{{i18n 'admin.badges.none_selected'}}</p>
<div>
{{#link-to 'adminBadges.show' 'new' class="btn btn-default"}}
{{d-icon "plus"}} {{i18n 'admin.badges.new'}}
{{/link-to}}
{{#d-section class="current-badges"}}
<div class="badge-intro">
<img src={{badgeIntroEmoji}} class="badge-intro-emoji">
<div class="content-wrapper">
<h1>{{i18n 'admin.badges.badge_intro.title'}}</h1>
<div class="external-resources">
{{#each badgeIntroLinks as |link|}}
<a href={{link.href}} class="external-link" target="_blank">
{{d-icon link.icon}}
<span>{{I18n link.text}}</span>
</a>
{{/each}}
</div>
</div>
</div>
{{/d-section}}
{{/d-section}}

View File

@ -39,7 +39,7 @@
content=badgeGroupings
class="badge-selector"
nameProperty="name"}}
<button {{action "editGroupings"}} class='btn btn-icon no-text btn-default'>{{d-icon 'pencil'}}</button>
<button {{action "editGroupings"}} class='btn btn-icon no-text btn-default'>{{d-icon "pencil-alt"}}</button>
</div>
</div>

View File

@ -1,25 +1,26 @@
<div class="badges">
<div class="badges-header">
<h3 class="badges-heading">{{i18n 'admin.badges.title'}}</h3>
<div class="create-new-badge">
{{#link-to 'adminBadges.show' 'new' class="btn btn-primary"}}
{{d-icon "plus"}}
<span>{{i18n 'admin.badges.new'}}</span>
{{/link-to}}
</div>
</div>
<div class='content-list'>
<h3>{{i18n 'admin.badges.title'}}</h3>
<ul>
<ul class="admin-badge-list">
{{#each model as |badge|}}
<li>
{{#link-to 'adminBadges.show' badge.id}}
{{badge-button badge=badge}}
{{#if badge.newBadge}}
<span class="list-badge">{{i18n 'filters.new.lower_title'}}</span>
{{/if}}
{{/link-to}}
</li>
<li class="admin-badge-list-item">
{{#link-to 'adminBadges.show' badge.id}}
{{badge-button badge=badge}}
{{#if badge.newBadge}}
<span class="list-badge">{{i18n 'filters.new.lower_title'}}</span>
{{/if}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{#link-to 'adminBadges.show' 'new' class="btn btn-default"}}
{{d-icon "plus"}} {{i18n 'admin.badges.new'}}
{{/link-to}}
<br>
<br>
</div>
{{outlet}}
</div>
</div>

View File

@ -11,6 +11,6 @@
{{d-button class="btn-default" action=(action "save") label="admin.user_fields.save"}}
<a href {{action "edit"}}>{{i18n 'cancel'}}</a>
{{else}}
{{d-button class="btn-default" action=(action "edit") icon="pencil"}}
{{d-button class="btn-default" action=(action "edit") icon="pencil-alt"}}
{{/if}}
</div>

View File

@ -1,3 +1,3 @@
<div class="chart-canvas-container">
<canvas class="chart-canvas" height="250"></canvas>
<canvas class="chart-canvas"></canvas>
</div>

View File

@ -46,9 +46,9 @@
</div>
<div class='form-display'>{{fieldName}}</div>
<div class='form-element controls'>
{{d-button action=(action "edit") class="btn-default" icon="pencil" label="admin.user_fields.edit"}}
{{d-button action=(action "edit") class="btn-default" icon="pencil-alt" label="admin.user_fields.edit"}}
{{d-button action=destroyAction actionParam=userField class="btn-danger" icon="trash-o" label="admin.user_fields.delete"}}
{{d-button action=destroyAction actionParam=userField class="btn-danger" icon="far-trash-alt" label="admin.user_fields.delete"}}
{{d-button action=moveUpAction actionParam=userField class="btn-default" icon="arrow-up" disabled=cantMoveUp}}
{{d-button action=moveDownAction actionParam=userField class="btn-default" icon="arrow-down" disabled=cantMoveDown}}
</div>

View File

@ -25,7 +25,7 @@
<td><div class="label">{{i18n "admin.embedding.path_whitelist"}}</div>{{host.path_whitelist}}</td>
<td><div class="label">{{i18n "admin.embedding.category"}}</div>{{category-badge host.category}}</td>
<td class="controls">
{{d-button icon="pencil" action=(action "edit")}}
{{d-button icon="trash-o" action=(action "delete") class='btn-danger'}}
{{d-button icon="pencil-alt" action=(action "edit")}}
{{d-button icon="far-trash-alt" action=(action "delete") class='btn-danger'}}
</td>
{{/if}}

View File

@ -7,7 +7,7 @@
{{/link-to}}
{{#if flaggedPost.wasEdited}}
<div class='edited-after'>
{{d-icon "pencil" title="admin.flags.was_edited"}}
{{d-icon "pencil-alt" title="admin.flags.was_edited"}}
</div>
{{/if}}
{{/if}}
@ -94,7 +94,7 @@
class="btn-default defer-flag"
title="admin.flags.ignore_flag_title"
action=(action "defer")
icon="external-link"
icon="external-link-alt"
label="admin.flags.ignore_flag"}}
{{admin-delete-flag-dropdown

View File

@ -1,5 +1,25 @@
<b>{{i18n 'admin.permalink.form.label'}}</b>
{{text-field value=url disabled=formSubmitted class="permalink-url" placeholderKey="admin.permalink.url" autocorrect="off" autocapitalize="off"}}
<b>{{i18n "admin.permalink.form.label"}}</b>
{{text-field
value=url
disabled=formSubmitted
class="permalink-url"
placeholderKey="admin.permalink.url"
autocorrect="off"
autocapitalize="off"}}
{{combo-box content=permalinkTypes value=permalinkType}}
{{text-field value=permalink_type_value disabled=formSubmitted class="external-url" placeholderKey=permalinkTypePlaceholder autocorrect="off" autocapitalize="off"}}
{{d-button class="btn-default" action=(action "submit") disabled=formSubmitted label="admin.permalink.form.add"}}
{{text-field
value=permalink_type_value
disabled=formSubmitted
class="external-url"
placeholderKey=permalinkTypePlaceholder
autocorrect="off"
autocapitalize="off"}}
{{d-button
class="btn-default"
action=(action "submit")
disabled=formSubmitted
label="admin.permalink.form.add"}}

View File

@ -11,7 +11,7 @@
</div>
{{else if setting.overridden}}
{{#if setting.secret}}
{{d-button action=(action "toggleSecret") icon="eye-slash"}}
{{d-button action=(action "toggleSecret") icon="far-eye-slash"}}
{{/if}}
{{d-button class="btn-default undo" action=(action "resetDefault") icon="undo" label="admin.settings.reset"}}
{{/if}}

View File

@ -12,7 +12,7 @@
{{d-icon "check" class="default-indicator" title="admin.customize.theme.default_theme_tooltip"}}
{{/if}}
{{#if theme.isPendingUpdates}}
{{d-icon "refresh" title="admin.customize.theme.updates_available_tooltip" class="light-grey-icon"}}
{{d-icon "sync" title="admin.customize.theme.updates_available_tooltip" class="light-grey-icon"}}
{{/if}}
{{#if theme.isBroken}}
{{d-icon "exclamation-circle" class="broken-indicator" title="admin.customize.theme.broken_theme_tooltip"}}

View File

@ -9,32 +9,32 @@
<div class="themes-list-container">
{{#if hasThemes}}
{{#if componentsTabActive}}
{{#each themesList as |theme|}}
{{#if hasActiveThemes}}
{{#each activeThemes as |theme|}}
{{themes-list-item theme=theme navigateToTheme=(action "navigateToTheme" theme)}}
{{/each}}
{{else}}
{{#if hasUserThemes}}
{{#each userThemes as |theme|}}
{{themes-list-item theme=theme navigateToTheme=(action "navigateToTheme" theme)}}
{{/each}}
{{#if hasInactiveThemes}}
<div class="themes-list-item inactive-indicator">
<span class="empty">{{I18n "admin.customize.theme.inactive_themes"}}</span>
</div>
{{/if}}
{{/if}}
{{#if hasInactiveThemes}}
{{#each inactiveThemes as |theme|}}
{{themes-list-item theme=theme navigateToTheme=(action "navigateToTheme" theme)}}
{{/each}}
<div class="themes-list-item inactive-indicator">
<span class="empty">
{{#if themesTabActive}}
{{I18n "admin.customize.theme.inactive_themes"}}
{{else}}
{{I18n "admin.customize.theme.inactive_components"}}
{{/if}}
</span>
</div>
{{/if}}
{{/if}}
{{#if hasInactiveThemes}}
{{#each inactiveThemes as |theme|}}
{{themes-list-item theme=theme navigateToTheme=(action "navigateToTheme" theme)}}
{{/each}}
{{/if}}
{{else}}
<div class="themes-list-item">
<span class="empty">{{I18n "admin.customize.theme.empty"}}</span>
</div>
{{/if}}
</div>
</div>

View File

@ -11,7 +11,7 @@
{{i18n "admin.customize.theme_owner"}}
{{#link-to "adminCustomizeThemes.show" model.theme_id}}{{model.theme_name}}{{/link-to}}
{{else}}
<button {{action "destroy"}} class='btn btn-danger'>{{d-icon "trash-o"}} {{i18n 'admin.customize.delete'}}</button>
<button {{action "destroy"}} class='btn btn-danger'>{{d-icon "far-trash-alt"}} {{i18n 'admin.customize.delete'}}</button>
{{/if}}
<span class="saving {{unless model.savingStatus 'hidden'}}">{{model.savingStatus}}</span>
</div>

View File

@ -4,7 +4,7 @@
<h1>{{I18n "admin.customize.theme.themes_intro"}}</h1>
<div class="external-resources">
{{#each externalResources as |resource|}}
<a href={{resource.link}} class="external-link" target="_blank">
<a href={{resource.link}} class="external-link-alt" target="_blank">
{{d-icon resource.icon}}
{{I18n resource.key}}
</a>

View File

@ -5,7 +5,7 @@
{{d-button action=(action "finishedEditingName") class="btn-primary submit-edit" icon="check"}}
{{d-button action=(action "cancelEditingName") class="btn-default cancel-edit" icon="times"}}
{{else}}
{{model.name}} <a {{action "startEditingName"}}>{{d-icon "pencil"}}</a>
{{model.name}} <a {{action "startEditingName"}}>{{d-icon "pencil-alt"}}</a>
{{/if}}
</div>
@ -16,33 +16,91 @@
</div>
{{/each}}
{{#if model.remote_theme}}
{{#if model.remote_theme.remote_url}}
<a class="remote-url" href="{{model.remote_theme.remote_url}}">{{model.remote_theme.remote_url}}</a>
{{/if}}
<a class="url about-url" href="{{model.remote_theme.about_url}}">{{i18n "admin.customize.theme.about_theme"}}</a>
{{#if model.remote_theme.license_url}}
<a class="url license-url" href="{{model.remote_theme.license_url}}">{{i18n "admin.customize.theme.license"}} {{d-icon "copyright"}}</a>
{{/if}}
{{/if}}
{{#if parentThemes}}
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.component_of"}}</div>
<ul>
{{#each parentThemes as |theme|}}
<li>{{#link-to 'adminCustomizeThemes.show' theme replace=true}}{{theme.name}}{{/link-to}}</li>
{{/each}}
</ul>
{{#unless model.enabled}}
<div class="alert alert-error">
{{i18n "admin.customize.theme.required_version.error"}}
{{#if model.remote_theme.minimum_discourse_version}}
{{i18n "admin.customize.theme.required_version.minimum" version=model.remote_theme.minimum_discourse_version}}
{{/if}}
{{#if model.remote_theme.maximum_discourse_version}}
{{i18n "admin.customize.theme.required_version.minimum" version=model.remote_theme.maximum_discourse_version}}
{{/if}}
</div>
{{/if}}
{{/unless}}
{{#unless model.component}}
<div class="control-unit">
{{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}}
{{inline-edit-checkbox action=(action "applyUserSelectable") labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
</div>
{{/unless}}
{{#if model.remote_theme}}
{{#if model.remote_theme.remote_url}}
<a class="remote-url" href="{{model.remote_theme.remote_url}}">{{i18n "admin.customize.theme.source_url"}} {{d-icon "link"}}</a>
{{/if}}
{{#if model.remote_theme.about_url}}
<a class="url about-url" href="{{model.remote_theme.about_url}}">{{i18n "admin.customize.theme.about_theme"}} {{d-icon "link"}}</a>
{{/if}}
{{#if model.remote_theme.license_url}}
<a class="url license-url" href="{{model.remote_theme.license_url}}">{{i18n "admin.customize.theme.license"}} {{d-icon "link"}}</a>
{{/if}}
{{#if model.description}}
<span class="theme-description">{{model.description}}</span>
{{/if}}
<span class="metadata">
{{#if model.remote_theme.authors}}<span class="authors"><span class="heading">{{i18n "admin.customize.theme.authors"}}</span> {{model.remote_theme.authors}}</span>{{/if}}
{{#if model.remote_theme.theme_version}}<span class="version"><span class="heading">{{i18n "admin.customize.theme.version"}}</span> {{model.remote_theme.theme_version}}</span>{{/if}}
</span>
<div class="control-unit">
{{#if model.remote_theme.is_git}}
{{#if showRemoteError}}
<div class="error-message">
{{d-icon "exclamation-triangle"}} {{I18n "admin.customize.theme.repo_unreachable"}}
</div>
<div class="raw-error">
<code>{{model.remoteError}}</code>
</div>
{{/if}}
{{#if model.remote_theme.commits_behind}}
{{#d-button action=(action "updateToLatest") icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
{{else}}
{{#d-button action=(action "checkForThemeUpdates") icon="refresh" class="btn-default"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
{{/if}}
<span class='status-message'>
{{#if updatingRemote}}
{{i18n 'admin.customize.theme.updating'}}
{{else}}
{{#if model.remote_theme.commits_behind}}
{{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}}
{{#if model.remote_theme.github_diff_link}}
<a href="{{model.remote_theme.github_diff_link}}">
{{i18n 'admin.customize.theme.compare_commits'}}
</a>
{{/if}}
{{else}}
{{#unless showRemoteError}}
{{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}}
{{/unless}}
{{/if}}
{{/if}}
</span>
{{else}}
<span class='status-message'>
{{d-icon "info-circle"}} {{i18n "admin.customize.theme.imported_from_archive"}}
</span>
{{/if}}
</div>
{{/if}}
{{#unless model.component}}
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.color_scheme"}}</div>
<div class="description">{{i18n "admin.customize.theme.color_scheme_select"}}</div>
@ -60,6 +118,17 @@
</div>
{{/unless}}
{{#if parentThemes}}
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.component_of"}}</div>
<ul>
{{#each parentThemes as |theme|}}
<li>{{#link-to 'adminCustomizeThemes.show' theme replace=true}}{{theme.name}}{{/link-to}}</li>
{{/each}}
</ul>
</div>
{{/if}}
<div class="control-unit">
<div class="mini-title">{{i18n "admin.customize.theme.css_html"}}</div>
{{#if model.hasEditedFields}}
@ -75,43 +144,7 @@
</div>
{{/if}}
{{#if model.remote_theme}}
{{#if model.remote_theme.commits_behind}}
{{#d-button action=(action "updateToLatest") icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
{{else}}
{{#d-button action=(action "checkForThemeUpdates") icon="refresh" class="btn-default"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
{{/if}}
{{/if}}
{{#d-button action=(action "editTheme") class="btn btn-default edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}}
{{#if model.remote_theme}}
<span class='status-message'>
{{#if updatingRemote}}
{{i18n 'admin.customize.theme.updating'}}
{{else}}
{{#if model.remote_theme.commits_behind}}
{{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}}
{{#if model.remote_theme.github_diff_link}}
<a href="{{model.remote_theme.github_diff_link}}">
{{i18n 'admin.customize.theme.compare_commits'}}
</a>
{{/if}}
{{else}}
{{#unless showRemoteError}}
{{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}}
{{/unless}}
{{/if}}
{{/if}}
</span>
{{#if showRemoteError}}
<div class="error-message">
{{d-icon "exclamation-triangle"}} {{I18n "admin.customize.theme.repo_unreachable"}}
</div>
<div class="raw-error">
<code>{{model.remoteError}}</code>
</div>
{{/if}}
{{/if}}
</div>
<div class="control-unit">
@ -178,5 +211,5 @@
<a class="btn btn-default export" target="_blank" href={{downloadUrl}}>{{d-icon "download"}} {{i18n 'admin.export_json.button_text'}}</a>
{{d-button action=(action "switchType") label="admin.customize.theme.convert" icon=convertIcon class="btn-default btn-normal" title=convertTooltip}}
{{d-button action=(action "destroy") label="admin.customize.delete" icon="trash" class="btn-danger"}}
{{d-button action=(action "destroy") label="admin.customize.delete" icon="trash-alt" class="btn-danger"}}
</div>

View File

@ -20,7 +20,7 @@
<tr>
<th><img class="emoji emoji-custom" src="{{unbound e.url}}" title="{{unbound e.name}}"></th>
<th>:{{e.name}}:</th>
<th><button {{action "destroy" e}} class='btn btn-danger no-text pull-right'>{{d-icon 'trash-o'}} </button></th>
<th><button {{action "destroy" e}} class='btn btn-danger no-text pull-right'>{{d-icon "far-trash-alt"}} </button></th>
</tr>
{{/each}}
</tbody>

View File

@ -1,26 +1,38 @@
<p>{{i18n 'admin.logs.screened_ips.description'}}</p>
<p>{{i18n "admin.logs.screened_ips.description"}}</p>
<div class="screened-ip-controls">
<div class="filter-screened-ip-address">
{{text-field value=filter class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.filter" autocorrect="off" autocapitalize="off"}}
{{d-button class="btn-default" action=(action "rollUp") title="admin.logs.screened_ips.roll_up.title" label="admin.logs.screened_ips.roll_up.text"}}
{{d-button class="btn-default" action=(action "exportScreenedIpList") icon="download" title="admin.export_csv.button_title.screened_ip" label="admin.export_csv.button_text"}}
<div class="filter-screened-ip-address">
{{text-field
value=filter
class="ip-address-input"
placeholderKey="admin.logs.screened_ips.form.filter"
autocorrect="off"
autocapitalize="off"}}
{{d-button
class="btn-default"
action=(action "rollUp")
title="admin.logs.screened_ips.roll_up.title"
label="admin.logs.screened_ips.roll_up.text"}}
{{d-button
class="btn-default"
action=(action "exportScreenedIpList")
icon="download"
title="admin.export_csv.button_title.screened_ip"
label="admin.export_csv.button_text"}}
</div>
{{screened-ip-address-form action=(action "recordAdded")}}
{{screened-ip-address-form action=(action "recordAdded")}}
</div>
{{#conditional-loading-spinner condition=loading}}
{{#if model.length}}
<table class='admin-logs-table screened-ip-addresses grid'>
<table class="admin-logs-table screened-ip-addresses grid">
<thead class="heading-container">
<th class="col heading first ip_address">{{i18n 'admin.logs.ip_address'}}</th>
<th class="col heading action">{{i18n 'admin.logs.action'}}</th>
<th class="col heading match_count">{{i18n 'admin.logs.match_count'}}</th>
<th class="col heading created_at">{{i18n 'admin.logs.created_at'}}</th>
<th class="col heading last_match_at">{{i18n 'admin.logs.last_match_at'}}</th>
<th class="col heading first ip_address">{{i18n "admin.logs.ip_address"}}</th>
<th class="col heading action">{{i18n "admin.logs.action"}}</th>
<th class="col heading match_count">{{i18n "admin.logs.match_count"}}</th>
<th class="col heading created_at">{{i18n "admin.logs.created_at"}}</th>
<th class="col heading last_match_at">{{i18n "admin.logs.last_match_at"}}</th>
<th class="col heading actions"></th>
</thead>
<tbody>
@ -47,34 +59,64 @@
{{/if}}
{{item.actionName}}
</td>
<td class="col match_count"><div class="label">{{i18n 'admin.logs.match_count'}}</div> {{item.match_count}}</td>
<td class="col created_at"><div class="label">{{i18n 'admin.logs.created_at'}}</div> {{age-with-tooltip item.created_at}}</td>
<td class="col match_count">
<div class="label">{{i18n "admin.logs.match_count"}}</div>
{{item.match_count}}
</td>
<td class="col created_at">
<div class="label">{{i18n "admin.logs.created_at"}}</div>
{{age-with-tooltip item.created_at}}
</td>
<td class="col last_match_at">
{{#if item.last_match_at}}
<div class="label">{{i18n 'admin.logs.last_match_at'}} {{age-with-tooltip item.last_match_at}}</div>
<div class="label">
{{i18n "admin.logs.last_match_at"}}
{{age-with-tooltip item.last_match_at}}
</div>
{{/if}}
</td>
<td class="col actions">
{{#unless item.editing}}
{{d-button class="btn-default" action=(action "destroy") actionParam=item icon="trash-o" class="btn-danger"}}
{{d-button class="btn-default"action=(action "edit") actionParam=item icon="pencil"}}
{{d-button
class="btn-default"
action=(action "destroy")
actionParam=item
icon="far-trash-alt"
class="btn-danger"}}
{{d-button
class="btn-default"
action=(action "edit")
actionParam=item
icon="pencil-alt"}}
{{#if item.isBlocked}}
{{d-button class="btn-default" action=(action "allow") actionParam=item icon="check" label="admin.logs.screened_ips.actions.do_nothing"}}
{{d-button
class="btn-default"
action=(action "allow")
actionParam=item
icon="check"
label="admin.logs.screened_ips.actions.do_nothing"}}
{{else}}
{{d-button class="btn-default" action=(action "block") actionParam=item icon="ban" label="admin.logs.screened_ips.actions.block"}}
{{d-button
class="btn-default"
action=(action "block")
actionParam=item
icon="ban"
label="admin.logs.screened_ips.actions.block"}}
{{/if}}
{{else}}
{{d-button class="btn-default" action=(action "save") actionParam=item label="admin.logs.save"}}
<a {{action "cancel" item}}>{{i18n 'cancel'}}</a>
{{d-button
class="btn-default"
action=(action "save")
actionParam=item
label="admin.logs.save"}}
<a {{action "cancel" item}}>{{i18n "cancel"}}</a>
{{/unless}}
</td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
{{i18n 'search.no_results'}}
{{i18n "search.no_results"}}
{{/if}}
{{/conditional-loading-spinner}}

View File

@ -62,7 +62,7 @@
<a {{action "filterByStaffUser" item.acting_user}}>{{item.acting_user.username}}</a>
{{else}}
<span class="deleted-user" title="{{i18n 'admin.user.deleted'}}">
{{d-icon "trash-o"}}
{{d-icon "far-trash-alt"}}
</span>
{{/if}}
</div>

View File

@ -1,30 +1,30 @@
{{#d-modal-body title="admin.badges.badge_groupings.modal_title"}}
<div>
<ul class='badge-groupings'>
{{#each workingCopy as |wc|}}
<li class="badge-grouping-item">
<div class="badge-grouping">
{{#if wc.editing}}
{{input value=wc.name class="badge-grouping-name-input"}}
<button {{action "save" wc}} class="btn no-text">{{d-icon 'check'}}</button>
{{else}}
{{wc.displayName}}
{{/if}}
</div>
<div class='actions'>
<button {{action "edit" wc}} class="btn no-text" disabled={{wc.system}}>{{d-icon 'pencil'}}</button>
<button {{action "up" wc}} class="btn no-text">{{d-icon 'toggle-up'}}</button>
<button {{action "down" wc}} class="btn no-text">{{d-icon 'toggle-down'}}</button>
<button {{action "delete" wc}} class="btn no-text btn-danger" disabled={{wc.system}}>{{d-icon 'times'}}</button>
</div>
</li>
{{/each}}
{{#d-modal-body title="admin.badges.badge_groupings.modal_title" class="badge-groupings-modal"}}
<div class="badge-groupings">
<ul class='badge-groupings-list'>
{{#each workingCopy as |wc|}}
<li class="badge-grouping-item">
<div class="badge-grouping">
{{#if wc.editing}}
{{input value=wc.name class="badge-grouping-name-input"}}
<button {{action "save" wc}} class="btn no-text">{{d-icon 'check'}}</button>
{{else}}
<span>{{wc.displayName}}</span>
{{/if}}
</div>
<div class='actions'>
<button {{action "edit" wc}} class="btn no-text" disabled={{wc.system}}>{{d-icon 'pencil-alt'}}</button>
<button {{action "up" wc}} class="btn no-text">{{d-icon 'chevron-up'}}</button>
<button {{action "down" wc}} class="btn no-text">{{d-icon 'chevron-down'}}</button>
<button {{action "delete" wc}} class="btn no-text btn-danger"
disabled={{wc.system}}>{{d-icon 'times'}}</button>
</div>
</li>
{{/each}}
</ul>
</div>
<button class='btn' {{action "add"}}>{{i18n 'admin.badges.new'}}</button>
<button class='btn new-badge-grouping' {{action "add"}}>{{i18n 'admin.badges.new'}}</button>
{{/d-modal-body}}
<div class="modal-footer">
<button class='btn btn-primary' {{action "saveAll"}} disabled={{submitDisabled}}>{{i18n 'admin.badges.save'}}</button>
{{d-modal-cancel close=(route-action "closeModal")}}
</div>
</div>

View File

@ -4,7 +4,7 @@
<label class="radio" for="local">{{i18n 'upload_selector.from_my_computer'}}</label>
{{#if local}}
<div class="inputs">
<input onchange={{action "uploadLocaleFile"}} type="file" id="file-input" accept='.dcstyle.json,application/json'><br>
<input onchange={{action "uploadLocaleFile"}} type="file" id="file-input" accept='.dcstyle.json,application/json,.tar.gz,application/x-gzip'><br>
<span class="description">{{i18n 'admin.customize.theme.import_file_tip'}}</span>
</div>
{{/if}}
@ -19,7 +19,7 @@
<span class="description">{{i18n 'admin.customize.theme.import_web_tip'}}</span>
</div>
<div class='branch'>
{{input value=branch placeholder="beta"}}
{{input value=branch placeholder="master"}}
<span class="description">{{i18n 'admin.customize.theme.remote_branch'}}</span>
</div>
<div class='check-private'>

View File

@ -42,7 +42,7 @@
{{/if}}
</td>
<td class="col action">
{{d-button action=(action "destroy") actionParam=pl icon="trash-o" class="btn-danger"}}
{{d-button action=(action "destroy") actionParam=pl icon="far-trash-alt" class="btn-danger"}}
</td>
</tr>
{{/each}}

View File

@ -49,7 +49,7 @@
<td class="settings">
{{#if currentUser.admin}}
{{#if plugin.enabled_setting}}
{{d-button class="btn-default" action=(route-action "showSettings") actionParam=plugin icon="gear" label="admin.plugins.change_settings_short"}}
{{d-button class="btn-default" action=(route-action "showSettings") actionParam=plugin icon="cog" label="admin.plugins.change_settings_short"}}
{{/if}}
{{/if}}
</td>

View File

@ -5,7 +5,7 @@
{{#if currentUser.admin}}
{{d-button label="admin.plugins.change_settings"
icon="gear"
icon="cog"
class="btn-default settings-button"
action=(route-action "showSettings")}}
{{/if}}

View File

@ -1,58 +1,80 @@
<section class="details {{unless model.active 'not-activated'}}">
<div class='user-controls'>
<div class="user-controls">
{{#if model.canViewProfile}}
{{#link-to 'user' model class="btn btn-default"}}
{{#link-to "user" model class="btn btn-default"}}
{{d-icon "user"}}
{{i18n 'admin.user.show_public_profile'}}
{{i18n "admin.user.show_public_profile"}}
{{/link-to}}
{{/if}}
{{#if model.can_view_action_logs}}
{{d-button action=(action "viewActionLogs") class="btn-default" actionParam=model.username icon="list-alt" label="admin.user.action_logs"}}
{{d-button
action=(action "viewActionLogs")
class="btn-default"
actionParam=model.username
icon="far-list-alt"
label="admin.user.action_logs"}}
{{/if}}
{{#if model.active}}
{{#if currentUser.admin}}
{{d-button class="btn-default" action=(action "logOut") icon="power-off" label="admin.user.log_out"}}
{{d-button
class="btn-default"
action=(action "logOut")
icon="power-off"
label="admin.user.log_out"}}
{{/if}}
{{/if}}
{{plugin-outlet name="admin-user-controls-after" args=(hash model=model) tagName="" connectorTagName=""}}
{{plugin-outlet
name="admin-user-controls-after"
args=(hash model=model)
tagName=""
connectorTagName=""}}
</div>
<div class='display-row username'>
{{admin-editable-field name='user.username.title'
<div class="display-row username">
{{admin-editable-field name="user.username.title"
value=model.username
action=(action 'saveUsername')
action=(action "saveUsername")
editing=editingUsername}}
</div>
<div class='display-row'>
{{admin-editable-field name='user.name.title'
<div class="display-row">
{{admin-editable-field name="user.name.title"
value=model.name
action=(action 'saveName')
action=(action "saveName")
editing=editingName}}
</div>
{{plugin-outlet name="admin-user-below-names" args=(hash user=model) tagName='' connectorTagName=''}}
{{plugin-outlet
name="admin-user-below-names"
args=(hash user=model)
tagName=""
connectorTagName=""}}
{{#if canCheckEmails}}
<div class='display-row email'>
<div class='field'>{{i18n 'user.email.primary'}}</div>
<div class='value'>
<div class="display-row email">
<div class="field">{{i18n "user.email.primary"}}</div>
<div class="value">
{{#unless model.active}}
<div class='controls'>{{i18n 'admin.users.not_verified'}}</div>
<div class="controls">{{i18n "admin.users.not_verified"}}</div>
{{/unless}}
{{#if model.email}}
<a href="mailto:{{unbound model.email}}">{{model.email}}</a>
{{else}}
{{d-button class="btn-default" action=(route-action "checkEmail") actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}}
{{d-button
class="btn-default"
action=(route-action "checkEmail")
actionParam=model icon="far-envelope"
label="admin.users.check_email.text"
title="admin.users.check_email.title"}}
{{/if}}
</div>
</div>
<div class='display-row secondary-emails'>
<div class='field'>{{i18n 'user.email.secondary'}}</div>
<div class="display-row secondary-emails">
<div class="field">{{i18n "user.email.secondary"}}</div>
<div class='value'>
<div class="value">
{{#if model.email}}
{{#if model.secondary_emails}}
<ul>
@ -61,13 +83,13 @@
{{/each}}
</ul>
{{else}}
{{i18n 'user.email.no_secondary'}}
{{i18n "user.email.no_secondary"}}
{{/if}}
{{else}}
{{d-button action=(route-action "checkEmail")
class="btn-default"
actionParam=model
icon="envelope-o"
icon="far-envelope"
label="admin.users.check_email.text"
title="admin.users.check_email.title"}}
{{/if}}
@ -75,58 +97,70 @@
</div>
<div class="display-row bounce-score">
<div class='field'><a href="{{model.bounceLink}}">{{i18n 'admin.user.bounce_score'}}</a></div>
<div class='value'>{{model.bounceScore}}</div>
<div class='controls'>
<div class="field"><a href="{{model.bounceLink}}">{{i18n "admin.user.bounce_score"}}</a></div>
<div class="value">{{model.bounceScore}}</div>
<div class="controls">
{{#if model.canResetBounceScore}}
{{d-button class="btn-default" action=(action "resetBounceScore") label="admin.user.reset_bounce_score.label" title="admin.user.reset_bounce_score.title"}}
{{d-button
class="btn-default"
action=(action "resetBounceScore")
label="admin.user.reset_bounce_score.label"
title="admin.user.reset_bounce_score.title"}}
{{/if}}
{{model.bounceScoreExplanation}}
</div>
</div>
<div class='display-row associations'>
<div class='field'>{{i18n 'user.associated_accounts.title'}}</div>
<div class='value'>
<div class="display-row associations">
<div class="field">{{i18n "user.associated_accounts.title"}}</div>
<div class="value">
{{#if associatedAccountsLoaded}}
{{associatedAccounts}}
{{else}}
{{d-button class="btn-default" action=(route-action "checkEmail") actionParam=model icon="envelope-o" label="admin.users.check_email.text" title="admin.users.check_email.title"}}
{{d-button
class="btn-default"
action=(route-action "checkEmail")
actionParam=model icon="far-envelope"
label="admin.users.check_email.text"
title="admin.users.check_email.title"}}
{{/if}}
</div>
</div>
{{/if}}
<div class='display-row'>
<div class='field'>{{i18n 'user.avatar.title'}}</div>
<div class='value'>{{avatar model imageSize="large"}}</div>
<div class="display-row">
<div class="field">{{i18n "user.avatar.title"}}</div>
<div class="value">{{avatar model imageSize="large"}}</div>
<div class="controls">
{{{i18n "admin.user.visit_profile" url=preferencesPath}}}
</div>
</div>
<div class='display-row'>
{{admin-editable-field name='user.title.title'
<div class="display-row">
{{admin-editable-field name="user.title.title"
value=model.title
action=(action 'saveTitle')
action=(action "saveTitle")
editing=editingTitle}}
</div>
<div class='display-row last-ip'>
<div class='field'>{{i18n 'user.ip_address.title'}}</div>
<div class='value'>{{model.ip_address}}</div>
<div class='controls'>
<div class="display-row last-ip">
<div class="field">{{i18n "user.ip_address.title"}}</div>
<div class="value">{{model.ip_address}}</div>
<div class="controls">
{{#if currentUser.staff}}
{{d-button class="btn-default" action=(action "refreshBrowsers") label="admin.user.refresh_browsers"}}
{{d-button
class="btn-default"
action=(action "refreshBrowsers")
label="admin.user.refresh_browsers"}}
{{ip-lookup ip=model.ip_address userId=model.id}}
{{/if}}
</div>
</div>
<div class='display-row registration-ip'>
<div class='field'>{{i18n 'user.registration_ip_address.title'}}</div>
<div class='value'>{{model.registration_ip_address}}</div>
<div class='controls'>
<div class="display-row registration-ip">
<div class="field">{{i18n "user.registration_ip_address.title"}}</div>
<div class="value">{{model.registration_ip_address}}</div>
<div class="controls">
{{#if currentUser.staff}}
{{ip-lookup ip=model.registration_ip_address userId=model.id}}
{{/if}}
@ -134,40 +168,47 @@
</div>
{{#if showBadges}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.badges.title'}}</div>
<div class='value'>
{{i18n 'badges.badge_count' count=model.badge_count}}
<div class="display-row">
<div class="field">{{i18n "admin.badges.title"}}</div>
<div class="value">
{{i18n "badges.badge_count" count=model.badge_count}}
</div>
<div class='controls'>
{{#link-to 'adminUser.badges' model class="btn"}}{{d-icon "certificate"}}{{i18n 'admin.badges.edit_badges'}}{{/link-to}}
<div class="controls">
{{#link-to "adminUser.badges" model class="btn"}}
{{d-icon "certificate"}}
{{i18n "admin.badges.edit_badges"}}
{{/link-to}}
</div>
</div>
{{/if}}
<div class='display-row'>
<div class='field'>{{i18n 'user.second_factor.title'}}</div>
<div class='value'>
<div class="display-row">
<div class="field">{{i18n "user.second_factor.title"}}</div>
<div class="value">
{{#if model.second_factor_enabled}}
{{i18n "yes_value"}}
{{else}}
{{i18n "no_value"}}
{{/if}}
</div>
<div class='controls'>
<div class="controls">
{{#if canDisableSecondFactor}}
{{d-button class="btn-default" action=(action "disableSecondFactor") icon="unlock-alt" label="user.second_factor.disable"}}
{{d-button
class="btn-default"
action=(action "disableSecondFactor")
icon="unlock-alt"
label="user.second_factor.disable"}}
{{/if}}
</div>
</div>
</section>
{{#if userFields}}
<section class='details'>
<section class="details">
{{#each userFields as |uf|}}
<div class='display-row'>
<div class='field'>{{uf.name}}</div>
<div class='value'>
<div class="display-row">
<div class="field">{{uf.name}}</div>
<div class="value">
{{#if uf.value}}
{{uf.value}}
{{else}}
@ -181,107 +222,154 @@
{{plugin-outlet name="admin-user-details" args=(hash model=model)}}
<section class='details'>
<h1>{{i18n 'admin.user.permissions'}}</h1>
<section class="details">
<h1>{{i18n "admin.user.permissions"}}</h1>
{{#if showApproval}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.users.approved'}}</div>
<div class='value'>
<div class="display-row">
<div class="field">{{i18n "admin.users.approved"}}</div>
<div class="value">
{{#if model.approved}}
{{i18n 'admin.user.approved_by'}}
{{#link-to 'adminUser' model.approvedBy}}{{avatar model.approvedBy imageSize="small"}}{{/link-to}}
{{#link-to 'adminUser' model.approvedBy}}{{model.approvedBy.username}}{{/link-to}}
{{i18n "admin.user.approved_by"}}
{{#link-to "adminUser" model.approvedBy}}
{{avatar model.approvedBy imageSize="small"}}
{{/link-to}}
{{#link-to "adminUser" model.approvedBy}}
{{model.approvedBy.username}}
{{/link-to}}
{{else}}
{{i18n 'no_value'}}
{{i18n "no_value"}}
{{/if}}
</div>
<div class='controls'>
<div class="controls">
{{#if model.approved}}
{{i18n 'admin.user.approve_success'}}
{{i18n "admin.user.approve_success"}}
{{else}}
{{#if model.can_approve}}
{{d-button class="btn-default" action=(action "approve") icon="check" label="admin.user.approve"}}
{{d-button
class="btn-default"
action=(action "approve")
icon="check"
label="admin.user.approve"}}
{{/if}}
{{/if}}
</div>
</div>
{{/if}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.users.active'}}</div>
<div class='value'>{{i18n-yes-no model.active}}</div>
<div class='controls'>
<div class="display-row">
<div class="field">{{i18n "admin.users.active"}}</div>
<div class="value">{{i18n-yes-no model.active}}</div>
<div class="controls">
{{#if model.active}}
{{#if model.can_deactivate}}
{{d-button class="btn-default" action=(action "deactivate") label="admin.user.deactivate_account"}}
{{i18n 'admin.user.deactivate_explanation'}}
{{d-button
class="btn-default"
action=(action "deactivate")
label="admin.user.deactivate_account"}}
{{i18n "admin.user.deactivate_explanation"}}
{{/if}}
{{else}}
{{#if model.can_send_activation_email}}
{{d-button class="btn-default" action=(action "sendActivationEmail") icon="envelope" label="admin.user.send_activation_email"}}
{{d-button
class="btn-default"
action=(action "sendActivationEmail")
icon="envelope"
label="admin.user.send_activation_email"}}
{{/if}}
{{#if model.can_activate}}
{{d-button class="btn-default" action=(action "activate") icon="check" label="admin.user.activate"}}
{{d-button
class="btn-default"
action=(action "activate")
icon="check"
label="admin.user.activate"}}
{{/if}}
{{/if}}
</div>
</div>
<div class="display-row">
<div class='field'>{{i18n 'admin.user.staged'}}</div>
<div class='value'>{{i18n-yes-no model.staged}}</div>
<div class='controls'>{{i18n 'admin.user.staged_explanation'}}</div>
<div class="field">{{i18n "admin.user.staged"}}</div>
<div class="value">{{i18n-yes-no model.staged}}</div>
<div class="controls">{{i18n "admin.user.staged_explanation"}}</div>
</div>
{{#if currentUser.admin}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.api.key'}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.api.key"}}</div>
{{#if model.api_key}}
<div class='long-value'>
<div class="long-value">
{{model.api_key.key}}
{{d-button class="btn-default" action=(action "regenerateApiKey") icon="undo" label="admin.api.regenerate"}}
{{d-button class="btn-default" action=(action "revokeApiKey") icon="times" label="admin.api.revoke"}}
{{d-button
class="btn-default"
action=(action "regenerateApiKey")
icon="undo"
label="admin.api.regenerate"}}
{{d-button
class="btn-default"
action=(action "revokeApiKey")
icon="times"
label="admin.api.revoke"}}
</div>
{{else}}
<div class='value'>
<div class="value">
&mdash;
</div>
<div class='controls'>
{{d-button class="btn-default" action=(action "generateApiKey") icon="key" label="admin.api.generate"}}
<div class="controls">
{{d-button
class="btn-default"
action=(action "generateApiKey")
icon="key"
label="admin.api.generate"}}
</div>
{{/if}}
</div>
{{/if}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.admin'}}</div>
<div class='value'>{{i18n-yes-no model.admin}}</div>
<div class='controls'>
<div class="display-row">
<div class="field">{{i18n "admin.user.admin"}}</div>
<div class="value">{{i18n-yes-no model.admin}}</div>
<div class="controls">
{{#if model.can_revoke_admin}}
{{d-button class="btn-default" action=(action "revokeAdmin") icon="shield" label="admin.user.revoke_admin"}}
{{d-button
class="btn-default"
action=(action "revokeAdmin")
icon="shield-alt"
label="admin.user.revoke_admin"}}
{{/if}}
{{#if model.can_grant_admin}}
{{d-button class="btn-default" action=(action "grantAdmin") icon="shield" label="admin.user.grant_admin"}}
{{d-button
class="btn-default"
action=(action "grantAdmin")
icon="shield-alt"
label="admin.user.grant_admin"}}
{{/if}}
</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.moderator'}}</div>
<div class='value'>{{i18n-yes-no model.moderator}}</div>
<div class='controls'>
<div class="display-row">
<div class="field">{{i18n "admin.user.moderator"}}</div>
<div class="value">{{i18n-yes-no model.moderator}}</div>
<div class="controls">
{{#if model.can_revoke_moderation}}
{{d-button class="btn-default" action=(action "revokeModeration") icon="shield" label="admin.user.revoke_moderation"}}
{{d-button
class="btn-default"
action=(action "revokeModeration")
icon="shield-alt"
label="admin.user.revoke_moderation"}}
{{/if}}
{{#if model.can_grant_moderation}}
{{d-button class="btn-default" action=(action "grantModeration") icon="shield" label="admin.user.grant_moderation"}}
{{d-button
class="btn-default"
action=(action "grantModeration")
icon="shield-alt"
label="admin.user.grant_moderation"}}
{{/if}}
</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'trust_level'}}</div>
<div class="display-row">
<div class="field">{{i18n "trust_level"}}</div>
<div class="value">
{{combo-box content=site.trustLevels value=model.trust_level nameProperty="detailedName"}}
{{#if model.dirty}}
@ -295,21 +383,31 @@
{{#if model.canLockTrustLevel}}
{{#if hasLockedTrustLevel}}
{{d-icon "lock" title="admin.user.trust_level_locked_tip"}}
{{d-button class="btn-default" action=(action "lockTrustLevel") actionParam=false label="admin.user.unlock_trust_level"}}
{{d-button
class="btn-default"
action=(action "lockTrustLevel")
actionParam=false
label="admin.user.unlock_trust_level"}}
{{else}}
{{d-icon "unlock" title="admin.user.trust_level_unlocked_tip"}}
{{d-button class="btn-default" action=(action "lockTrustLevel") actionParam=true label="admin.user.lock_trust_level"}}
{{d-button
class="btn-default"
action=(action "lockTrustLevel")
actionParam=true
label="admin.user.lock_trust_level"}}
{{/if}}
{{/if}}
{{#if model.tl3Requirements}}
{{#link-to 'adminUser.tl3Requirements' model class="btn btn-default"}}{{i18n 'admin.user.trust_level_3_requirements'}}{{/link-to}}
{{#link-to "adminUser.tl3Requirements" model class="btn btn-default"}}
{{i18n "admin.user.trust_level_3_requirements"}}
{{/link-to}}
{{/if}}
</div>
</div>
<div class="user-suspended display-row {{if model.suspended 'highlight-danger'}}">
<div class='field'>{{i18n 'admin.user.suspended'}}</div>
<div class='value'>
<div class="user-suspended display-row {{if model.suspended "highlight-danger"}}">
<div class="field">{{i18n "admin.user.suspended"}}</div>
<div class="value">
{{i18n-yes-no model.suspended}}
{{#if model.suspended}}
{{#unless model.suspendedForever}}
@ -317,14 +415,14 @@
{{/unless}}
{{/if}}
</div>
<div class='controls'>
<div class="controls">
{{#if model.suspended}}
{{d-button
class="btn-danger unsuspend-user"
action=(action "unsuspend")
icon="ban"
label="admin.user.unsuspend"}}
{{i18n 'admin.user.suspended_explanation'}}
{{i18n "admin.user.suspended_explanation"}}
{{else}}
{{#if model.canSuspend}}
{{d-button
@ -332,29 +430,33 @@
action=(action "showSuspendModal")
icon="ban"
label="admin.user.suspend"}}
{{i18n 'admin.user.suspended_explanation'}}
{{i18n "admin.user.suspended_explanation"}}
{{/if}}
{{/if}}
</div>
</div>
{{#if model.suspended}}
<div class='display-row highlight-danger suspension-info'>
<div class='field'>{{i18n 'admin.user.suspended_by'}}</div>
<div class='value'>
{{#link-to 'adminUser' model.suspendedBy}}{{avatar model.suspendedBy imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' model.suspendedBy}}{{model.suspendedBy.username}}{{/link-to}}
<div class="display-row highlight-danger suspension-info">
<div class="field">{{i18n "admin.user.suspended_by"}}</div>
<div class="value">
{{#link-to "adminUser" model.suspendedBy}}
{{avatar model.suspendedBy imageSize="tiny"}}
{{/link-to}}
{{#link-to "adminUser" model.suspendedBy}}
{{model.suspendedBy.username}}
{{/link-to}}
</div>
<div class='controls'>
<b>{{i18n 'admin.user.suspend_reason'}}</b>:
<div class='full-reason'>{{model.full_suspend_reason}}</div>
<div class="controls">
<b>{{i18n "admin.user.suspend_reason"}}</b>:
<div class="full-reason">{{model.full_suspend_reason}}</div>
</div>
</div>
{{/if}}
<div class="display-row {{if model.silenced 'highlight-danger'}}">
<div class='field'>{{i18n 'admin.user.silenced'}}</div>
<div class='value'>
<div class="display-row {{if model.silenced "highlight-danger"}}">
<div class="field">{{i18n "admin.user.silenced"}}</div>
<div class="value">
{{i18n-yes-no model.silenced}}
{{#if model.silenced}}
{{#unless model.silencedForever}}
@ -362,7 +464,7 @@
{{/unless}}
{{/if}}
</div>
<div class='controls'>
<div class="controls">
{{#conditional-loading-spinner size="small" condition=model.silencingUser}}
{{#if model.silenced}}
{{d-button
@ -370,39 +472,43 @@
action=(action "unsilence")
icon="microphone-slash"
label="admin.user.unsilence"}}
{{i18n 'admin.user.silence_explanation'}}
{{i18n "admin.user.silence_explanation"}}
{{else}}
{{d-button
class="btn-danger silence-user"
action=(action "showSilenceModal")
icon="microphone-slash"
label="admin.user.silence"}}
{{i18n 'admin.user.silence_explanation'}}
{{i18n "admin.user.silence_explanation"}}
{{/if}}
{{/conditional-loading-spinner}}
</div>
</div>
{{#if model.silenced}}
<div class='display-row highlight-danger silence-info'>
<div class='field'>{{i18n 'admin.user.silenced_by'}}</div>
<div class='value'>
{{#link-to 'adminUser' model.silencedBy}}{{avatar model.silencedBy imageSize="tiny"}}{{/link-to}}
{{#link-to 'adminUser' model.silencedBy}}{{model.silencedBy.username}}{{/link-to}}
<div class="display-row highlight-danger silence-info">
<div class="field">{{i18n "admin.user.silenced_by"}}</div>
<div class="value">
{{#link-to "adminUser" model.silencedBy}}
{{avatar model.silencedBy imageSize="tiny"}}
{{/link-to}}
{{#link-to "adminUser" model.silencedBy}}
{{model.silencedBy.username}}
{{/link-to}}
</div>
<div class='controls'>
<b>{{i18n 'admin.user.silence_reason'}}</b>:
<div class='full-reason'>{{model.silence_reason}}</div>
<div class="controls">
<b>{{i18n "admin.user.silence_reason"}}</b>:
<div class="full-reason">{{model.silence_reason}}</div>
</div>
</div>
{{/if}}
{{#if model.tl3_requirements.penalty_counts.total}}
<div class='display-row clear-penalty-history'>
<div class='field'>{{i18n 'admin.user.penalty_count'}}</div>
<div class='value'>{{model.tl3_requirements.penalty_counts.total}}</div>
<div class="display-row clear-penalty-history">
<div class="field">{{i18n "admin.user.penalty_count"}}</div>
<div class="value">{{model.tl3_requirements.penalty_counts.total}}</div>
{{#if currentUser.admin}}
<div class='controls'>
<div class="controls">
{{d-button label="admin.user.clear_penalty_history.title"
class="btn-default"
icon="times"
@ -416,32 +522,38 @@
</section>
{{#if currentUser.admin}}
<section class='details'>
<h1>{{i18n 'admin.groups.title'}}</h1>
<div class='display-row'>
<div class='field'>{{i18n 'admin.groups.automatic'}}</div>
<div class='value'>{{{automaticGroups}}}</div>
<section class="details">
<h1>{{i18n "admin.groups.title"}}</h1>
<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=model.customGroups available=availableGroups buffer=customGroupIdsBuffer}}
<div class="display-row">
<div class="field">{{i18n "admin.groups.custom"}}</div>
<div class="value">
{{admin-group-selector
selected=model.customGroups
available=availableGroups
buffer=customGroupIdsBuffer}}
</div>
{{#if customGroupsDirty}}
<div class='controls'>
<div class="controls">
{{d-button icon="check" class="ok" action=(action "saveCustomGroups")}}
{{d-button icon="times" class="cancel" action=(action "resetCustomGroups")}}
</div>
{{/if}}
</div>
{{#if model.customGroups}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.groups.primary'}}</div>
<div class='value'>
{{combo-box content=model.customGroups value=model.primary_group_id none="admin.groups.no_primary"}}
<div class="display-row">
<div class="field">{{i18n "admin.groups.primary"}}</div>
<div class="value">
{{combo-box
content=model.customGroups
value=model.primary_group_id
none="admin.groups.no_primary"}}
</div>
{{#if primaryGroupDirty}}
<div class='controls'>
<div class="controls">
{{d-button icon="check" class="ok" action=(action "savePrimaryGroup")}}
{{d-button icon="times" class="cancel" action=(action "resetPrimaryGroup")}}
</div>
@ -451,56 +563,60 @@
</section>
{{/if}}
<section class='details'>
<h1>{{i18n 'admin.user.activity'}}</h1>
<section class="details">
<h1>{{i18n "admin.user.activity"}}</h1>
<div class='display-row'>
<div class='field'>{{i18n 'created'}}</div>
<div class='value'>{{format-date model.created_at leaveAgo="true"}}</div>
<div class="display-row">
<div class="field">{{i18n "created"}}</div>
<div class="value">{{format-date model.created_at leaveAgo="true"}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.users.last_emailed'}}</div>
<div class='value'>{{format-date model.last_emailed_at}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.users.last_emailed"}}</div>
<div class="value">{{format-date model.last_emailed_at}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'last_seen'}}</div>
<div class='value'>{{format-date model.last_seen_at leaveAgo="true"}}</div>
<div class="display-row">
<div class="field">{{i18n "last_seen"}}</div>
<div class="value">{{format-date model.last_seen_at leaveAgo="true"}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.like_count'}}</div>
<div class='value'>{{model.like_given_count}} / {{model.like_count}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.like_count"}}</div>
<div class="value">{{model.like_given_count}} / {{model.like_count}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.topics_entered'}}</div>
<div class='value'>{{model.topics_entered}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.topics_entered"}}</div>
<div class="value">{{model.topics_entered}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.post_count'}}</div>
<div class='value'>{{model.post_count}}</div>
<div class='controls'>
<div class="display-row">
<div class="field">{{i18n "admin.user.post_count"}}</div>
<div class="value">{{model.post_count}}</div>
<div class="controls">
{{#if model.can_delete_all_posts}}
{{#if model.post_count}}
{{d-button class="btn-danger" action=(action "deleteAllPosts") icon="trash-o" label="admin.user.delete_all_posts"}}
{{d-button
class="btn-danger"
action=(action "deleteAllPosts")
icon="far-trash-alt"
label="admin.user.delete_all_posts"}}
{{/if}}
{{else}}
{{deleteAllPostsExplanation}}
{{/if}}
</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.posts_read_count'}}</div>
<div class='value'>{{model.posts_read_count}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.posts_read_count"}}</div>
<div class="value">{{model.posts_read_count}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.warnings_received_count'}}</div>
<div class='value'>{{model.warnings_received_count}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.warnings_received_count"}}</div>
<div class="value">{{model.warnings_received_count}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.flags_given_received_count'}}</div>
<div class='value'>
<div class="display-row">
<div class="field">{{i18n "admin.user.flags_given_received_count"}}</div>
<div class="value">
{{model.flags_given_count}} / {{model.flags_received_count}}
</div>
<div class='controls'>
<div class="controls">
{{#if model.flags_received_count}}
{{d-button
class="btn-default"
@ -511,46 +627,46 @@
{{/if}}
</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.private_topics_count'}}</div>
<div class='value'>{{model.private_topics_count}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.private_topics_count"}}</div>
<div class="value">{{model.private_topics_count}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.time_read'}}</div>
<div class='value'>{{{format-duration model.time_read}}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.time_read"}}</div>
<div class="value">{{{format-duration model.time_read}}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'user.invited.days_visited'}}</div>
<div class='value'>{{{model.days_visited}}}</div>
<div class="display-row">
<div class="field">{{i18n "user.invited.days_visited"}}</div>
<div class="value">{{{model.days_visited}}}</div>
</div>
</section>
{{#if model.single_sign_on_record}}
<section class='details'>
<h1>{{i18n 'admin.user.sso.title'}}</h1>
<section class="details">
<h1>{{i18n "admin.user.sso.title"}}</h1>
{{#with model.single_sign_on_record as |sso|}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_id'}}</div>
<div class='value'>{{sso.external_id}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_id"}}</div>
<div class="value">{{sso.external_id}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_username'}}</div>
<div class='value'>{{sso.external_username}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_username"}}</div>
<div class="value">{{sso.external_username}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_name'}}</div>
<div class='value'>{{sso.external_name}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_name"}}</div>
<div class="value">{{sso.external_name}}</div>
</div>
{{#if sso.external_email}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_email'}}</div>
<div class='value'>{{sso.external_email}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_email"}}</div>
<div class="value">{{sso.external_email}}</div>
</div>
{{/if}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_avatar_url'}}</div>
<div class='value'>{{sso.external_avatar_url}}</div>
<div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_avatar_url"}}</div>
<div class="value">{{sso.external_avatar_url}}</div>
</div>
{{/with}}
</section>
@ -563,7 +679,12 @@
<div class="pull-right">
{{#if model.active}}
{{#if model.can_impersonate}}
{{d-button class="btn-danger" action=(action "impersonate") icon="crosshairs" label="admin.impersonate.title" title="admin.impersonate.help"}}
{{d-button
class="btn-danger"
action=(action "impersonate")
icon="crosshairs"
label="admin.impersonate.title"
title="admin.impersonate.help"}}
{{/if}}
{{/if}}
@ -590,4 +711,5 @@
</div>
{{/if}}
</section>
<div class="clearfix"></div>

View File

@ -52,7 +52,7 @@
</a>
{{#link-to 'adminUser' user}}{{unbound user.username}}{{/link-to}}
{{#if user.staged}}
{{d-icon "envelope-o" title="user.staged" }}
{{d-icon "far-envelope" title="user.staged" }}
{{/if}}
</td>
<td class='email'>

View File

@ -9,13 +9,13 @@
</div>
{{#if versionCheck.noCheckPerformed}}
<div class="version-number">
<h4>{{i18n 'admin.dashboard.latest_version'}}</h4>
<div class="version-number">
<h4>{{i18n 'admin.dashboard.latest_version'}}</h4>
<h3>&mdash;</h3>
</div>
<div class="version-status">
<div class="face">
<span class="icon critical-updates-available">{{d-icon "frown-o"}}</span>
<span class="icon critical-updates-available">{{d-icon "far-frown"}}</span>
</div>
<div class="version-notes">
<span class="normal-note">{{i18n 'admin.dashboard.no_check_performed'}}</span>
@ -24,15 +24,15 @@
{{else}}
{{#if versionCheck.stale_data}}
<div class="version-number">
<h4>{{i18n 'admin.dashboard.latest_version'}}</h4>
<h4>{{i18n 'admin.dashboard.latest_version'}}</h4>
<h3>{{#if versionCheck.version_check_pending}}{{dash-if-empty versionCheck.installed_version}}{{/if}}</h3>
</div>
<div class="version-status">
<div class="face">
{{#if versionCheck.version_check_pending}}
<span class='icon up-to-date'>{{d-icon "smile-o"}}</span>
<span class='icon up-to-date'>{{d-icon "far-smile"}}</span>
{{else}}
<span class="icon critical-updates-available">{{d-icon "frown-o"}}</span>
<span class="icon critical-updates-available">{{d-icon "far-frown"}}</span>
{{/if}}
</div>
<div class="version-notes">
@ -47,19 +47,19 @@
</div>
{{else}}
<div class="version-number">
<h4>{{i18n 'admin.dashboard.latest_version'}}</h4>
<h4>{{i18n 'admin.dashboard.latest_version'}}</h4>
<h3>{{dash-if-empty versionCheck.latest_version}}</h3>
</div>
<div class="version-status">
<div class="face">
{{#if versionCheck.upToDate }}
<span class='icon up-to-date'>{{d-icon "smile-o"}}</span>
<span class='icon up-to-date'>{{d-icon "far-smile"}}</span>
{{else}}
<span class="icon {{if versionCheck.critical_updates 'critical-updates-available' 'updates-available'}}">
{{#if versionCheck.behindByOneVersion}}
{{d-icon "meh-o"}}
{{else}}
{{d-icon "frown-o"}}
{{d-icon "far-frown"}}
{{/if}}
</span>
{{/if}}

View File

@ -32,7 +32,7 @@ const REPLACEMENTS = {
"notification.moved_post": "sign-out",
"notification.linked": "link",
"notification.granted_badge": "certificate",
"notification.topic_reminder": "hand-point-right",
"notification.topic_reminder": "far-clock",
"notification.watching_first_post": "far-dot-circle",
"notification.group_message_summary": "group"
};

View File

@ -16,7 +16,7 @@ export default DropdownSelectBoxComponent.extend({
},
{
id: "logOut",
icon: "sign-out",
icon: "sign-out-alt",
name: I18n.t("user.log_out"),
description: ""
}

View File

@ -56,7 +56,7 @@ export default Ember.Component.extend({
if (originalUser) {
editTitle += `
${iconHTML("mail-forward", { class: "reply-to-glyph" })}
${iconHTML("share", { class: "reply-to-glyph" })}
${originalUser.avatar}
<span class="original-username">${originalUser.username}</span>
`;

View File

@ -163,7 +163,11 @@ export default Ember.Component.extend({
includeMentionableGroups: true
}),
key: "@",
transformComplete: v => v.username || v.name
transformComplete: v => v.username || v.name,
afterComplete() {
// ensures textarea scroll position is correct
Ember.run.scheduleOnce("afterRender", () => $input.blur().focus());
}
});
}
@ -816,7 +820,7 @@ export default Ember.Component.extend({
toolbar.addButton({
id: "quote",
group: "fontStyles",
icon: "comment-o",
icon: "far-comment",
sendAction: this.get("importQuote"),
title: "composer.quote_post_title",
unshift: true
@ -835,7 +839,7 @@ export default Ember.Component.extend({
toolbar.addButton({
id: "options",
group: "extras",
icon: "gear",
icon: "cog",
title: "composer.options",
sendAction: this.onExpandPopupMenuOptions.bind(this),
popupMenu: true

View File

@ -134,7 +134,7 @@ class Toolbar {
this.addButton({
id: "toggle-direction",
group: "extras",
icon: "exchange",
icon: "exchange-alt",
shortcut: "Shift+6",
title: "composer.toggle_direction",
perform: e => e.toggleDirection()
@ -385,10 +385,14 @@ export default Ember.Component.extend({
_applyCategoryHashtagAutocomplete() {
const siteSettings = this.siteSettings;
const self = this;
this.$(".d-editor-input").autocomplete({
template: findRawTemplate("category-tag-autocomplete"),
key: "#",
afterComplete() {
self._focusTextArea();
},
transformComplete(obj) {
return obj.text;
},
@ -416,6 +420,7 @@ export default Ember.Component.extend({
key: ":",
afterComplete(text) {
self.set("value", text);
self._focusTextArea();
},
onKeyUp(text, cp) {
@ -722,7 +727,7 @@ export default Ember.Component.extend({
$textarea.prop("selectionStart", (pre + text).length + 2);
$textarea.prop("selectionEnd", (pre + text).length + 2);
Ember.run.scheduleOnce("afterRender", () => $textarea.focus());
this._focusTextArea();
},
_addText(sel, text, options) {
@ -747,7 +752,8 @@ export default Ember.Component.extend({
$textarea.val(value);
$textarea.prop("selectionStart", insert.length);
$textarea.prop("selectionEnd", insert.length);
Ember.run.scheduleOnce("afterRender", () => $textarea.focus());
this._focusTextArea();
},
_extractTable(text) {
@ -838,6 +844,12 @@ export default Ember.Component.extend({
}
},
// ensures textarea scroll position is correct
_focusTextArea() {
const $textarea = this.$("textarea.d-editor-input");
Ember.run.scheduleOnce("afterRender", () => $textarea.blur().focus());
},
actions: {
emoji() {
if (this.get("disabled")) {
@ -850,6 +862,7 @@ export default Ember.Component.extend({
emojiSelected(code) {
let selected = this._getSelected();
const captures = selected.pre.match(/\B:(\w*)$/);
if (_.isEmpty(captures)) {
this._addText(selected, `:${code}:`);
} else {

View File

@ -1,15 +1,16 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
import UploadMixin from "discourse/mixins/upload";
export default Ember.Component.extend(UploadMixin, {
type: "emoji",
uploadUrl: "/admin/customize/emojis",
hasName: Ember.computed.notEmpty("name"),
addDisabled: Ember.computed.not("hasName"),
data: function() {
return Ember.isBlank(this.get("name")) ? {} : { name: this.get("name") };
}.property("name"),
@computed("hasName", "name")
data(hasName, name) {
return hasName ? { name } : {};
},
validateUploadedFilesOptions() {
return { imagesOnly: true };

View File

@ -32,7 +32,7 @@ export default DropdownSelectBoxComponent.extend({
description: I18n.t("groups.members.remove_owner_description", {
username: this.get("member.username")
}),
icon: "shield"
icon: "shield-alt"
});
} else {
items.push({
@ -41,7 +41,7 @@ export default DropdownSelectBoxComponent.extend({
description: I18n.t("groups.members.make_owner_description", {
username: this.get("member.username")
}),
icon: "shield"
icon: "shield-alt"
});
}
}

View File

@ -41,7 +41,7 @@ export default Ember.Component.extend(
let autoCloseHours = this.get("duration") || 0;
buffer.push(`<h3>${iconHTML("clock-o")} `);
buffer.push(`<h3>${iconHTML("far-clock")} `);
let options = {
timeLeft: duration.humanize(true),

View File

@ -222,7 +222,7 @@ export default Ember.Controller.extend({
@computed("model.action", "isWhispering")
saveIcon(action, isWhispering) {
if (isWhispering) {
return "eye-slash";
return "far-eye-slash";
}
return SAVE_ICONS[action];
},
@ -267,7 +267,7 @@ export default Ember.Controller.extend({
this._setupPopupMenuOption(() => {
return {
action: "toggleInvisible",
icon: "eye-slash",
icon: "far-eye-slash",
label: "composer.toggle_unlisted",
condition: "canUnlistTopic"
};
@ -278,7 +278,7 @@ export default Ember.Controller.extend({
this._setupPopupMenuOption(() => {
return {
action: "toggleWhisper",
icon: "eye-slash",
icon: "far-eye-slash",
label: "composer.toggle_whisper",
condition: "showWhisperToggle"
};

View File

@ -47,6 +47,7 @@ export default Ember.Controller.extend(PreferencesTabController, {
preferencesController: Ember.inject.controller("preferences"),
makeThemeDefault: true,
makeTextSizeDefault: true,
@computed()
availableLocales() {
@ -109,6 +110,11 @@ export default Ember.Controller.extend(PreferencesTabController, {
this.set("model.user_option.theme_ids", [this.get("themeId")]);
}
const makeTextSizeDefault = this.get("makeTextSizeDefault");
if (makeTextSizeDefault) {
this.set("model.user_option.text_size", this.get("textSize"));
}
return this.get("model")
.save(this.get("saveAttrNames"))
.then(() => {
@ -120,6 +126,9 @@ export default Ember.Controller.extend(PreferencesTabController, {
this.get("model.user_option.theme_key_seq")
);
}
if (!makeTextSizeDefault) {
this.get("model").updateTextSizeCookie(this.get("textSize"));
}
this.homeChanged();
})

View File

@ -1,16 +1,18 @@
import ModalFunctionality from "discourse/mixins/modal-functionality";
import computed from "ember-addons/ember-computed-decorators";
import BufferedContent from "discourse/mixins/buffered-content";
import { extractError } from "discourse/lib/ajax-error";
export default Ember.Controller.extend(ModalFunctionality, BufferedContent, {
renameDisabled: function() {
@computed("buffered.id", "id")
renameDisabled(inputTagName, currentTagName) {
const filterRegexp = new RegExp(this.site.tags_filter_regexp, "g"),
newId = this.get("buffered.id")
.replace(filterRegexp, "")
.trim();
newTagName = inputTagName
? inputTagName.replace(filterRegexp, "").trim()
: "";
return newId.length === 0 || newId === this.get("model.id");
}.property("buffered.id", "id"),
return newTagName.length === 0 || newTagName === currentTagName;
},
actions: {
performRename() {

View File

@ -22,7 +22,7 @@ function addBulkButton(action, key, opts) {
// Default buttons
addBulkButton("showChangeCategory", "change_category", {
icon: "pencil",
icon: "pencil-alt",
class: "btn-default"
});
addBulkButton("closeTopics", "close_topics", {
@ -42,12 +42,12 @@ addBulkButton("resetRead", "reset_read", {
class: "btn-default"
});
addBulkButton("unlistTopics", "unlist_topics", {
icon: "eye-slash",
icon: "far-eye-slash",
class: "btn-default",
buttonVisible: topics => topics.some(t => t.visible)
});
addBulkButton("relistTopics", "relist_topics", {
icon: "eye",
icon: "far-eye",
class: "btn-default",
buttonVisible: topics => topics.some(t => !t.visible)
});

View File

@ -14,10 +14,12 @@ export default htmlHelper((user, args) => {
}
if (currentUser && user.get("admin") && currentUser.get("staff")) {
return iconHTML("shield", { label: I18n.t("user.admin", { user: name }) });
return iconHTML("shield-alt", {
label: I18n.t("user.admin", { user: name })
});
}
if (user.get("moderator")) {
return iconHTML("shield", {
return iconHTML("shield-alt", {
label: I18n.t("user.moderator", { user: name })
});
}

View File

@ -16,7 +16,7 @@ export default {
toolbar.addButton({
id: "emoji",
group: "extras",
icon: "smile-o",
icon: "far-smile",
action: () => toolbar.context.send("emoji"),
title: "composer.emoji"
});

View File

@ -153,6 +153,8 @@ export default {
replyToTopic() {
this._replyToPost();
return false;
},
selectDown() {
@ -194,12 +196,21 @@ export default {
},
createTopic() {
if (this.currentUser && this.currentUser.can_create_topic) {
this.container.lookup("controller:composer").open({
action: Composer.CREATE_TOPIC,
draftKey: Composer.CREATE_TOPIC
});
if (!(this.currentUser && this.currentUser.can_create_topic)) {
return;
}
// If the page has a create-topic button, use it for context sensitive attributes like category
let $createTopicButton = $("#create-topic");
if ($createTopicButton.length) {
$createTopicButton.click();
return;
}
this.container.lookup("controller:composer").open({
action: Composer.CREATE_TOPIC,
draftKey: Composer.CREATE_TOPIC
});
},
focusComposer() {
@ -332,6 +343,8 @@ export default {
}
}
}
return false;
},
_bindToSelectedPost(action, binding) {

View File

@ -45,7 +45,10 @@ function show(image) {
};
copyImg.src = imageData.src;
copyImg.srcset = imageData.srcset || copyImg.srcset;
if (imageData.srcset) {
copyImg.srcset = imageData.srcset;
}
copyImg.style.position = "absolute";
copyImg.style.top = `${image.offsetTop}px`;

View File

@ -133,12 +133,20 @@ class PluginApi {
*
* // for the place in code that render a string
* string() {
* return "<i class='fa fa-smile-o'></i>";
* return "<svg class=\"fa d-icon d-icon-far-smile svg-icon\" aria-hidden=\"true\"><use xlink:href=\"#far-smile\"></use></svg>";
* },
*
* // for the places in code that render virtual dom elements
* node() {
* return h('i', { className: 'fa fa-smile-o' });
* return h("svg", {
* attributes: { class: "fa d-icon d-icon-far-smile", "aria-hidden": true },
* namespace: "http://www.w3.org/2000/svg"
* },[
* h("use", {
* "xlink:href": attributeHook("http://www.w3.org/1999/xlink", `#far-smile`),
* namespace: "http://www.w3.org/2000/svg"
* })]
* );
* }
* });
**/
@ -369,7 +377,7 @@ class PluginApi {
* api.addToolbarPopupMenuOptionsCallback(() => {
* return {
* action: 'toggleWhisper',
* icon: 'eye-slash',
* icon: 'far-eye-slash',
* label: 'composer.toggle_whisper',
* condition: "canWhisper"
* };

View File

@ -74,9 +74,7 @@ export default {
.finally(() => {
_processing--;
// when a request is done we want to start processing queue
// without waiting for debouncing
debounce(this, this._processQueue, DEBOUNCING_DELAY, true);
debounce(this, this._processQueue, DEBOUNCING_DELAY);
});
},

View File

@ -8,6 +8,9 @@ const msoListClasses = [
"MsoListParagraphCxSpMiddle",
"MsoListParagraphCxSpLast"
];
const hasChild = (e, n) => {
return (e.children || []).some(c => c.name === n);
};
export class Tag {
constructor(name, prefix = "", suffix = "", inline = false) {
@ -194,14 +197,19 @@ export class Tag {
}
decorate(text) {
const attr = this.element.attributes;
const e = this.element;
const attr = e.attributes;
if (/^mention/.test(attr.class) && "@" === text[0]) {
return text;
}
if ("hashtag" === attr.class && "#" === text[0]) {
} else if ("hashtag" === attr.class && "#" === text[0]) {
return text;
} else if (
["lightbox", "d-lazyload"].includes(attr.class) &&
hasChild(e, "img")
) {
text = attr.title || "";
return "![" + text + "](" + attr.href + ")";
}
if (attr.href && text !== attr.href) {

View File

@ -415,7 +415,7 @@ export function allowsAttachments() {
}
export function uploadIcon() {
return allowsAttachments() ? "upload" : "picture-o";
return allowsAttachments() ? "upload" : "far-image";
}
export function uploadLocation(url) {

View File

@ -59,6 +59,40 @@ const Topic = RestModel.extend({
return user || this.get("creator");
},
@computed("posters.[]", "participants.[]")
featuredUsers(posters, participants) {
let users = posters;
const maxUserCount = 5;
const posterCount = users.length;
if (
this.get("isPrivateMessage") &&
participants &&
posterCount < maxUserCount
) {
let pushOffset = 0;
if (posterCount > 1) {
const lastUser = users[posterCount - 1];
if (lastUser.extras && lastUser.extras.includes("latest")) {
pushOffset = 1;
}
}
const poster_ids = _.pluck(posters, "user_id");
participants.some(p => {
if (!poster_ids.includes(p.user_id)) {
users.splice(users.length - pushOffset, 0, p);
if (users.length === maxUserCount) {
return true;
}
}
return false;
});
}
return users;
},
@computed("fancy_title")
fancyTitle(title) {
let fancyTitle = censor(

View File

@ -141,7 +141,7 @@ const UserAction = RestModel.extend({
groups = {
likes: UserActionGroup.create({ icon: "heart" }),
stars: UserActionGroup.create({ icon: "star" }),
edits: UserActionGroup.create({ icon: "pencil" }),
edits: UserActionGroup.create({ icon: "pencil-alt" }),
bookmarks: UserActionGroup.create({ icon: "bookmark" })
};
}

View File

@ -45,7 +45,7 @@ export default RestModel.extend({
remove(draft) {
let content = this.get("content").filter(
item => item.sequence !== draft.sequence
item => item.draft_key !== draft.draft_key
);
this.setProperties({ content, itemsLoaded: content.length });
},

View File

@ -705,6 +705,25 @@ const User = RestModel.extend({
});
return _.uniq(titles).sort();
},
@computed("user_option.text_size_seq", "user_option.text_size")
currentTextSize(serverSeq, serverSize) {
if ($.cookie("text_size")) {
const [cookieSize, cookieSeq] = $.cookie("text_size").split("|");
if (cookieSeq >= serverSeq) {
return cookieSize;
}
}
return serverSize;
},
updateTextSizeCookie(newSize) {
const seq = this.get("user_option.text_size_seq");
$.cookie("text_size", `${newSize}|${seq}`, {
path: "/",
expires: 9999
});
}
});

View File

@ -5,7 +5,8 @@ export default RestrictedUserRoute.extend({
setupController(controller, user) {
controller.setProperties({
model: user
model: user,
textSize: user.get("currentTextSize")
});
}
});

View File

@ -6,6 +6,11 @@ import PermissionType from "discourse/models/permission-type";
export default Discourse.Route.extend({
navMode: "latest",
queryParams: {
ascending: { refreshModel: true },
order: { refreshModel: true }
},
renderTemplate() {
const controller = this.controllerFor("tags.show");
this.render("tags.show", { controller });
@ -60,11 +65,13 @@ export default Discourse.Route.extend({
return tag;
},
afterModel(tag) {
afterModel(tag, transition) {
const controller = this.controllerFor("tags.show");
controller.set("loading", true);
const params = controller.getProperties("order", "ascending");
params.order = transition.queryParams.order || params.order;
params.ascending = transition.queryParams.ascending || params.ascending;
const categorySlug = this.get("categorySlug");
const parentCategorySlug = this.get("parentCategorySlug");

View File

@ -11,8 +11,7 @@ export default Discourse.Route.extend(ViewingActionType, {
this.controllerFor("user-activity").set("userActionType", userActionType);
this.controllerFor("user-topics-list").setProperties({
model,
hideCategory: false,
showParticipants: false
hideCategory: false
});
}
});

View File

@ -97,7 +97,7 @@
{{#if contactInfo}}
<section class='about contact'>
<h3>{{d-icon "envelope-o"}} {{i18n 'about.contact'}}</h3>
<h3>{{d-icon "far-envelope"}} {{i18n 'about.contact'}}</h3>
<p>{{{contactInfo}}}</p>
</section>
{{/if}}

View File

@ -15,7 +15,7 @@
{{d-button
class="btn btn-default pad-left no-text"
action=(action "toggleSetUserTitle")
icon="pencil"}}
icon="pencil-alt"}}
</div>
{{/if}}
{{#if model.multiple_grant}}

View File

@ -7,5 +7,5 @@
{{d-button action=editActivationEmail
label="login.change_email"
icon="pencil"
icon="pencil-alt"
class="edit-email"}}

View File

@ -1,5 +1,5 @@
<label class="btn" disabled={{uploading}} title="{{i18n 'user.change_avatar.upload_title'}}">
{{d-icon "picture-o"}}&nbsp;{{uploadButtonText}}
{{d-icon "far-image"}}&nbsp;{{uploadButtonText}}
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" />
</label>
{{#if uploading}}

View File

@ -10,8 +10,7 @@
{{/if}}
{{#if topics}}
{{topic-list showParticipants=showParticipants
showPosters=showPosters
{{topic-list showPosters=showPosters
hideCategory=hideCategory
topics=topics
expandExcerpts=expandExcerpts

View File

@ -1,5 +1,5 @@
{{#if isEditing}}
{{d-icon "pencil"}}
{{d-icon "pencil-alt"}}
{{else}}
{{composer-actions
composerModel=model

View File

@ -6,10 +6,10 @@
{{i18n "user.desktop_notifications.perm_denied_expl"}}
{{else}}
{{#if isEnabled}}
{{d-button icon="bell-slash-o" class="btn-default" label="user.desktop_notifications.disable" action=(action "turnoff")}}
{{d-button icon="far-bell-slash" class="btn-default" label="user.desktop_notifications.disable" action=(action "turnoff")}}
{{i18n "user.desktop_notifications.currently_enabled"}}
{{else}}
{{d-button icon="bell-o" class="btn-default" label="user.desktop_notifications.enable" action=(action "turnon")}}
{{d-button icon="far-bell" class="btn-default" label="user.desktop_notifications.enable" action=(action "turnon")}}
{{i18n "user.desktop_notifications.currently_disabled"}}
{{/if}}
{{/if}}

View File

@ -31,7 +31,7 @@
{{/if}}
{{#if category.topic_url}}
<br>
{{d-button class="btn-default" action=(action "showCategoryTopic") icon="pencil" label="category.change_in_category_topic"}}
{{d-button class="btn-default" action=(action "showCategoryTopic") icon="pencil-alt" label="category.change_in_category_topic"}}
{{/if}}
</section>
{{/if}}
@ -56,7 +56,7 @@
<span class='add-on'>#</span>{{text-field value=category.text_color placeholderKey="category.color_placeholder" maxlength="6"}}
{{color-picker colors=foregroundColors value=category.text_color id='edit-text-color'}}
</div>
</div>
</div>
</div>
</section>
{{/unless}}

View File

@ -147,7 +147,7 @@
<section class='field'>
<label for="category-email-in">
{{d-icon "envelope-o"}}
{{d-icon "far-envelope"}}
{{i18n 'category.email_in'}}
</label>
{{text-field id="category-email-in" class="email-in" value=category.email_in}}

View File

@ -1,6 +1,11 @@
{{text-field name="name" placeholderKey="admin.emoji.name" value=name}}
<label class="btn btn-primary {{if addDisabled 'disabled'}}">
{{d-icon "plus"}}
{{i18n 'admin.emoji.add'}}
<input class="hidden-upload-field" disabled={{addDisabled}} type="file" accept=".png,.gif" />
{{i18n "admin.emoji.add"}}
<input
class="hidden-upload-field"
disabled={{addDisabled}}
type="file"
accept=".png,.gif" />
</label>

View File

@ -14,11 +14,11 @@
{{#if isCustom}}
<div class="control-group">
{{d-icon "calendar"}} {{date-picker-future value=date defaultDate=date}}
{{d-icon "calendar-alt"}} {{date-picker-future value=date defaultDate=date}}
</div>
<div class="control-group">
{{d-icon "clock-o"}}
{{d-icon "far-clock"}}
{{input type="time" value=time}}
</div>
{{/if}}

View File

@ -1,11 +1,11 @@
<div class="uploaded-image-preview input-xxlarge" style={{backgroundStyle}}>
<div class="image-upload-controls">
<label class="btn btn-default pad-left no-text {{if uploading 'disabled'}}">
{{d-icon "picture-o"}}
{{d-icon "far-image"}}
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" />
</label>
{{#if hasBackgroundStyle}}
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "trash-o"}}</button>
<button {{action "trash"}} class="btn btn-danger pad-left no-text">{{d-icon "far-trash-alt"}}</button>
{{/if}}
<span class="btn {{unless uploading 'hidden'}}">{{i18n 'upload_selector.uploading'}} {{uploadProgress}}%</span>
</div>

View File

@ -1,5 +1,5 @@
<label class="btn" disabled={{uploading}} title="{{i18n "admin.site_settings.uploaded_image_list.upload.title"}}">
{{d-icon "picture-o"}}&nbsp;{{uploadButtonText}}
{{d-icon "far-image"}}&nbsp;{{uploadButtonText}}
<input class="hidden-upload-field" disabled={{uploading}} type="file" accept="image/*" multiple />
</label>
{{#if uploading}}

View File

@ -1,36 +1,44 @@
{{#if ip}}
<button class="btn btn-default" {{action "lookup"}}>
{{d-icon "globe"}}{{i18n 'admin.user.ip_lookup'}}
{{d-icon "globe"}}
{{i18n "admin.user.ip_lookup"}}
</button>
{{/if}}
{{#if show}}
<div class="location-box">
<a class="close pull-right" {{action "hide"}}>{{d-icon "times"}}</a>
{{#if copied}}
<a class="btn btn-default btn-hover pull-right">{{d-icon "copy"}} {{i18n "ip_lookup.copied"}}</a>
<a class="btn btn-default btn-hover pull-right">
{{d-icon "copy"}}
{{i18n "ip_lookup.copied"}}
</a>
{{else}}
<a class="btn btn-default pull-right no-text" {{action "copy"}}>{{d-icon "copy"}}</a>
<a class="btn btn-default pull-right no-text" {{action "copy"}}>
{{d-icon "copy"}}
</a>
{{/if}}
<h4>{{i18n 'ip_lookup.title'}}</h4>
<p class='powered-by'>{{{i18n 'ip_lookup.powered_by'}}}</p>
<h4>{{i18n "ip_lookup.title"}}</h4>
<p class='powered-by'>{{{i18n "ip_lookup.powered_by"}}}</p>
<dl>
{{#if location}}
{{#if location.hostname}}
<dt>{{i18n 'ip_lookup.hostname'}}</dt>
<dt>{{i18n "ip_lookup.hostname"}}</dt>
<dd>{{location.hostname}}</dd>
{{/if}}
<dt>{{i18n 'ip_lookup.location'}}</dt>
<dt>{{i18n "ip_lookup.location"}}</dt>
<dd>
{{#if location.location}}
<a href="https://maps.google.com/maps?q={{unbound location.latitude}},{{unbound location.longitude}}" target="_blank">{{location.location}}</a>
<a href="https://maps.google.com/maps?q={{unbound location.latitude}},{{unbound location.longitude}}" target="_blank">
{{location.location}}
</a>
{{else}}
{{i18n 'ip_lookup.location_not_found'}}
{{i18n "ip_lookup.location_not_found"}}
{{/if}}
</dd>
{{#if location.organization}}
<dt>{{i18n 'ip_lookup.organisation'}}</dt>
<dt>{{i18n "ip_lookup.organisation"}}</dt>
<dd>{{location.organization}}</dd>
{{/if}}
{{else}}
@ -38,31 +46,39 @@
{{/if}}
<dt>
{{i18n 'ip_lookup.other_accounts'}}
{{i18n "ip_lookup.other_accounts"}}
<strong>{{totalOthersWithSameIP}}</strong>
{{#if other_accounts.length}}
<button class="btn btn-danger pull-right" {{action "deleteOtherAccounts"}}>
{{d-icon "warning"}}{{i18n 'ip_lookup.delete_other_accounts' count=otherAccountsToDelete}}
{{d-icon "warning"}}
{{i18n "ip_lookup.delete_other_accounts" count=otherAccountsToDelete}}
</button>
{{/if}}
</dt>
{{#conditional-loading-spinner size="small" condition=otherAccountsLoading}}
{{#if other_accounts.length}}
<dd class="other-accounts">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{{i18n 'ip_lookup.username'}}</th>
<th>{{i18n 'ip_lookup.trust_level'}}</th>
<th>{{i18n 'ip_lookup.read_time'}}</th>
<th>{{i18n 'ip_lookup.topics_entered'}}</th>
<th>{{i18n 'ip_lookup.post_count'}}</th>
<th>{{i18n "ip_lookup.username"}}</th>
<th>{{i18n "ip_lookup.trust_level"}}</th>
<th>{{i18n "ip_lookup.read_time"}}</th>
<th>{{i18n "ip_lookup.topics_entered"}}</th>
<th>{{i18n "ip_lookup.post_count"}}</th>
</tr>
</thead>
<tbody>
{{#each other_accounts as |a|}}
<tr>
<td>{{#link-to "adminUser" a}}{{avatar a usernamePath="user.username" imageSize="small"}}&nbsp;{{a.username}}{{/link-to}}</td>
<td>
{{#link-to "adminUser" a}}
{{avatar a usernamePath="user.username" imageSize="small"}}
&nbsp;
<span>{{a.username}}</span>
{{/link-to}}
</td>
<td>{{a.trustLevel.id}}</td>
<td>{{a.time_read}}</td>
<td>{{a.topics_entered}}</td>

View File

@ -81,13 +81,13 @@
{{d-button action=(action "deleteUser")
disabled=post.isSaving
label="queue.delete_user"
icon="trash"
icon="trash-alt"
class="btn-danger delete-user"}}
{{/if}}
{{d-button action=(action "edit")
disabled=post.isSaving
label="queue.edit"
icon="pencil"
icon="pencil-alt"
class="edit"}}
{{/if}}
</div>

View File

@ -24,5 +24,5 @@
{{/if}}
<div class='link'>
<a href {{action "close"}} class="close-share" aria-label={{i18n 'share.close'}} title={{i18n 'share.close'}}>{{d-icon "close"}}</a>
<a href {{action "close"}} class="close-share" aria-label={{i18n 'share.close'}} title={{i18n 'share.close'}}>{{d-icon "times"}}</a>
</div>

View File

@ -64,7 +64,7 @@
{{d-button class="btn-default edit-message"
title="topic.edit_message.help"
label="topic.edit_message.title"
icon="pencil"
icon="pencil-alt"
action=editFirstPost}}
{{/if}}

View File

@ -7,7 +7,6 @@
showPosters=showPosters
showLikes=showLikes
showOpLikes=showOpLikes
showParticipants=showParticipants
order=order
ascending=ascending
sortable=sortable
@ -35,7 +34,6 @@
showTopicPostBadges=showTopicPostBadges
hideCategory=hideCategory
showPosters=showPosters
showParticipants=showParticipants
showLikes=showLikes
showOpLikes=showOpLikes
expandGloballyPinned=expandGloballyPinned

View File

@ -133,11 +133,11 @@
</h3>
{{#if showCheckEmail}}
<h3 class="email">
{{d-icon "envelope-o" title="user.email.title"}}
{{d-icon "far-envelope" title="user.email.title"}}
{{#if user.email}}
{{user.email}}
{{else}}
{{d-button action=(action "checkEmail") actionParam=user icon="envelope-o" label="admin.users.check_email.text" class="btn-primary"}}
{{d-button action=(action "checkEmail") actionParam=user icon="far-envelope" label="admin.users.check_email.text" class="btn-primary"}}
{{/if}}
</h3>
{{/if}}

View File

@ -22,7 +22,7 @@
{{#if item.deleted_by}}
<span class="delete-info">
{{d-icon "trash-o"}}
{{d-icon "far-trash-alt"}}
{{avatar item.deleted_by imageSize="tiny" extraClasses="actor" ignoreTitle="true"}}
{{format-date item.deleted_at leaveAgo="true"}}
</span>
@ -61,7 +61,7 @@
{{#if item.editableDraft}}
<div class='user-stream-item-draft-actions'>
{{d-button action=resumeDraft actionParam=item icon="pencil" label='drafts.resume' class="btn-default resume-draft"}}
{{d-button action=removeDraft actionParam=item icon="trash-o" label='drafts.remove' class="btn-default remove-draft"}}
{{d-button action=resumeDraft actionParam=item icon="pencil-alt" label='drafts.resume' class="btn-default resume-draft"}}
{{d-button action=removeDraft actionParam=item icon="far-trash-alt" label='drafts.remove' class="btn-default remove-draft"}}
</div>
{{/if}}

View File

@ -26,7 +26,7 @@
{{#unless site.mobileView}}
{{#if isWhispering}}
<span class='whisper'>{{d-icon 'eye-slash'}}</span>
<span class='whisper'>{{d-icon "far-eye-slash"}}</span>
{{/if}}
{{#if model.unlistTopic}}
<span class='whisper'>({{i18n 'composer.unlist'}})</span>
@ -128,7 +128,7 @@
{{#if canEdit}}
{{d-icon "times"}}
{{else}}
{{d-icon "trash-o"}}
{{d-icon "far-trash-alt"}}
{{/if}}
</a>
{{else}}
@ -140,7 +140,7 @@
{{#if site.mobileView}}
{{#if whisperOrUnlistTopic}}
<span class='whisper'>
{{d-icon "eye-slash"}}
{{d-icon "far-eye-slash"}}
</span>
{{/if}}
{{#if model.noBump}}
@ -195,7 +195,7 @@
<div class='draft-text'>
{{#if model.topic}}
{{d-icon "mail-forward"}} {{{draftTitle}}}
{{d-icon "share"}} {{{draftTitle}}}
{{else}}
{{i18n "composer.saved_draft"}}
{{/if}}

View File

@ -1,3 +1,3 @@
<a href {{action "closeMessage"}} class='close'>{{d-icon "close"}}</a>
<a href {{action "closeMessage"}} class='close'>{{d-icon "times"}}</a>
{{#if message.title}}<h3>{{message.title}}</h3>{{/if}}
<p>{{{message.body}}}</p>

View File

@ -1,2 +1,2 @@
<a href {{action "closeMessage"}} class='close'>{{d-icon "close"}}</a>
<a href {{action "closeMessage"}} class='close'>{{d-icon "times"}}</a>
{{{message.body}}}

View File

@ -1,4 +1,4 @@
<a href {{action "closeMessage"}} class='close'>{{d-icon "close"}}</a>
<a href {{action "closeMessage"}} class='close'>{{d-icon "times"}}</a>
<h3>{{i18n 'composer.similar_topics'}}</h3>
<ul class='topics'>

View File

@ -31,7 +31,7 @@
<div class='section' data-section='recent'>
<div class='section-header'>
<span class="title">{{i18n 'emoji_picker.recent'}}</span>
<a href='#' class='clear-recent'>{{d-icon 'trash'}}</a>
<a href='#' class='clear-recent'>{{d-icon "trash-alt"}}</a>
</div>
<div class='section-group'></div>
</div>

View File

@ -47,7 +47,7 @@
{{#if currentUser.admin}}
{{d-button action=(action "destroy")
disabled=destroying
icon="trash"
icon="trash-alt"
class='btn-danger'
label="admin.groups.delete"}}
{{/if}}

View File

@ -60,7 +60,7 @@
{{#if group.public_admission}}
{{i18n 'groups.index.public'}}
{{else if group.isPrivate}}
{{d-icon "eye-slash"}}
{{d-icon "far-eye-slash"}}
{{i18n 'groups.index.private'}}
{{else}}
{{#if group.automatic}}

Some files were not shown because too many files have changed in this diff Show More