Version bump

This commit is contained in:
Neil Lalonde 2018-08-21 11:55:00 -04:00
commit d8f0379931
6849 changed files with 11488 additions and 3241 deletions

View File

@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = el_GR: el, es_ES: es, fr_FR: fr, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi
lang_map = el_GR: el, es_ES: es, fr_FR: fr, hu_HU: hu, ko_KR: ko, pt_PT: pt, sk_SK: sk, vi_VN: vi
[discourse-org.core-client-yml]
file_filter = config/locales/client.<lang>.yml

View File

@ -184,7 +184,7 @@ GEM
logstash-event (1.2.02)
logstash-logger (0.26.1)
logstash-event (~> 1.2)
logster (1.2.9)
logster (1.2.11)
loofah (2.2.2)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)

View File

@ -1,5 +1,4 @@
import computed from "ember-addons/ember-computed-decorators";
import { registerTooltip, unregisterTooltip } from "discourse/lib/tooltip";
const PAGES_LIMIT = 8;
@ -11,19 +10,6 @@ export default Ember.Component.extend({
perPage: Ember.computed.alias("options.perPage"),
page: 0,
didRender() {
this._super(...arguments);
unregisterTooltip($(".text[data-tooltip]"));
registerTooltip($(".text[data-tooltip]"));
},
willDestroyElement() {
this._super(...arguments);
unregisterTooltip($(".text[data-tooltip]"));
},
@computed("model.computedLabels.length")
twoColumns(labelsLength) {
return labelsLength === 2;
@ -52,7 +38,12 @@ export default Ember.Component.extend({
@computed("totalsForSampleRow", "model.computedLabels")
totalsForSample(row, labels) {
return labels.map(label => label.compute(row));
return labels.map(label => {
const computedLabel = label.compute(row);
computedLabel.type = label.type;
computedLabel.property = label.mainProperty;
return computedLabel;
});
},
@computed("model.data", "model.computedLabels")
@ -119,7 +110,7 @@ export default Ember.Component.extend({
return {
page: v + 1,
index: v,
class: v === page ? "current" : null
class: v === page ? "is-current" : null
};
});

View File

@ -4,7 +4,10 @@ import { outputExportResult } from "discourse/lib/export-result";
import { ajax } from "discourse/lib/ajax";
import { SCHEMA_VERSION, default as Report } from "admin/models/report";
import computed from "ember-addons/ember-computed-decorators";
import { registerTooltip, unregisterTooltip } from "discourse/lib/tooltip";
import {
registerHoverTooltip,
unregisterHoverTooltip
} from "discourse/lib/tooltip";
const TABLE_OPTIONS = {
perPage: 8,
@ -35,12 +38,7 @@ function collapseWeekly(data, average) {
}
export default Ember.Component.extend({
classNameBindings: [
"isEnabled",
"isLoading",
"dasherizedDataSourceName",
"currentMode"
],
classNameBindings: ["isEnabled", "isLoading", "dasherizedDataSourceName"],
classNames: ["admin-report"],
isEnabled: true,
disabledLabel: "admin.dashboard.disabled",
@ -69,6 +67,7 @@ export default Ember.Component.extend({
"showDatesOptions",
"showGroupOptions"
),
shouldDisplayTrend: Ember.computed.and("showTrend", "model.prev_period"),
init() {
this._super(...arguments);
@ -80,6 +79,7 @@ export default Ember.Component.extend({
this._super(...arguments);
const state = this.get("filters") || {};
this.setProperties({
category: Category.findById(state.categoryId),
groupId: state.groupId,
@ -101,14 +101,13 @@ export default Ember.Component.extend({
didRender() {
this._super(...arguments);
unregisterTooltip($(".info[data-tooltip]"));
registerTooltip($(".info[data-tooltip]"));
registerHoverTooltip($(".info[data-tooltip]"));
},
willDestroyElement() {
this._super(...arguments);
unregisterTooltip($(".info[data-tooltip]"));
unregisterHoverTooltip($(".info[data-tooltip]"));
},
showError: Ember.computed.or("showTimeoutError", "showExceptionError"),
@ -140,8 +139,8 @@ export default Ember.Component.extend({
const modes = forcedModes ? forcedModes.split(",") : reportModes;
return Ember.makeArray(modes).map(mode => {
const base = `mode-button ${mode}`;
const cssClass = currentMode === mode ? `${base} current` : base;
const base = `mode-btn ${mode}`;
const cssClass = currentMode === mode ? `${base} is-current` : base;
return {
mode,
@ -157,7 +156,7 @@ export default Ember.Component.extend({
{ name: I18n.t("admin.dashboard.reports.groups"), value: "all" }
];
return arr.concat(
this.site.groups.map(i => {
(this.site.groups || []).map(i => {
return { name: i["name"], value: i["id"] };
})
);
@ -171,15 +170,25 @@ export default Ember.Component.extend({
@computed("startDate")
normalizedStartDate(startDate) {
return startDate && typeof startDate.isValid === "function"
? startDate.format("YYYYMMDD")
: startDate;
? moment
.utc(startDate.toISOString())
.locale("en")
.format("YYYYMMDD")
: moment(startDate)
.locale("en")
.format("YYYYMMDD");
},
@computed("endDate")
normalizedEndDate(endDate) {
return endDate && typeof endDate.isValid === "function"
? endDate.format("YYYYMMDD")
: endDate;
? moment
.utc(endDate.toISOString())
.locale("en")
.format("YYYYMMDD")
: moment(endDate)
.locale("en")
.format("YYYYMMDD");
},
@computed(
@ -317,16 +326,15 @@ export default Ember.Component.extend({
let payload = { data: { cache: true, facets } };
if (this.get("startDate")) {
payload.data.start_date = moment(
this.get("startDate"),
"YYYY-MM-DD"
).format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
payload.data.start_date = moment
.utc(this.get("startDate"), "YYYY-MM-DD")
.toISOString();
}
if (this.get("endDate")) {
payload.data.end_date = moment(this.get("endDate"), "YYYY-MM-DD").format(
"YYYY-MM-DD[T]HH:mm:ss.SSSZZ"
);
payload.data.end_date = moment
.utc(this.get("endDate"), "YYYY-MM-DD")
.toISOString();
}
if (this.get("groupId") && this.get("groupId") !== "all") {

View File

@ -1,5 +1,6 @@
import { ajax } from "discourse/lib/ajax";
import AdminUser from "admin/models/admin-user";
import copyText from "discourse/lib/copy-text";
export default Ember.Component.extend({
classNames: ["ip-lookup"],
@ -62,6 +63,38 @@ export default Ember.Component.extend({
this.set("show", false);
},
copy: function() {
let text = `IP: ${this.get("ip")}\n`;
const location = this.get("location");
if (location) {
if (location.hostname) {
text += `${I18n.t("ip_lookup.hostname")}: ${location.hostname}\n`;
}
text += I18n.t("ip_lookup.location");
if (location.loc) {
text += `: ${location.loc} ${this.get("city")}\n`;
} else {
text += `: ${I18n.t("ip_lookup.location_not_found")}\n`;
}
if (location.org) {
text += I18n.t("ip_lookup.organisation");
text += `: ${location.org}\n`;
}
if (location.phone) {
text += I18n.t("ip_lookup.phone");
text += `: ${location.phone}\n`;
}
}
const copyRange = $(`<textarea id="copy-range"></textarea>`);
copyRange.text(text);
$(document.body).append(copyRange);
copyText(text, copyRange[0]);
copyRange.remove();
},
deleteOtherAccounts: function() {
var self = this;
bootbox.confirm(

View File

@ -59,18 +59,14 @@ export default Ember.Component.extend({
screenedIpAddress
.save()
.then(result => {
if (result.success) {
this.setProperties({ ip_address: "", formSubmitted: false });
this.sendAction(
"action",
ScreenedIpAddress.create(result.screened_ip_address)
);
Ember.run.schedule("afterRender", () =>
this.$(".ip-address-input").focus()
);
} else {
bootbox.alert(result.errors);
}
this.setProperties({ ip_address: "", formSubmitted: false });
this.sendAction(
"action",
ScreenedIpAddress.create(result.screened_ip_address)
);
Ember.run.schedule("afterRender", () =>
this.$(".ip-address-input").focus()
);
})
.catch(e => {
this.set("formSubmitted", false);

View File

@ -1,4 +1,7 @@
import { default as computed } from "ember-addons/ember-computed-decorators";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";
import { url } from "discourse/lib/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
@ -9,6 +12,18 @@ const THEME_UPLOAD_VAR = 2;
export default Ember.Controller.extend({
editRouteName: "adminCustomizeThemes.edit",
@observes("allowChildThemes")
setSelectedThemeId() {
const available = this.get("selectableChildThemes");
if (
!this.get("selectedChildThemeId") &&
available &&
available.length > 0
) {
this.set("selectedChildThemeId", available[0].get("id"));
}
},
@computed("model", "allThemes")
parentThemes(model, allThemes) {
let parents = allThemes.filter(theme =>
@ -64,16 +79,21 @@ export default Ember.Controller.extend({
let themes = [];
available.forEach(t => {
if (!childThemes || childThemes.indexOf(t) === -1) {
if (
(!childThemes || childThemes.indexOf(t) === -1) &&
Em.isEmpty(t.get("childThemes")) &&
!t.get("user_selectable") &&
!t.get("default")
) {
themes.push(t);
}
});
return themes.length === 0 ? null : themes;
},
@computed("allThemes", "allThemes.length", "model")
@computed("allThemes", "allThemes.length", "model", "parentThemes")
availableChildThemes(allThemes, count) {
if (count === 1) {
if (count === 1 || this.get("parentThemes")) {
return null;
}

View File

@ -46,19 +46,14 @@ export default Ember.Controller.extend({
record.set("editing", false);
record
.save()
.then(saved => {
if (saved.success) {
this.set("savedIpAddress", null);
} else {
bootbox.alert(saved.errors);
if (wasEditing) record.set("editing", true);
}
.then(() => {
this.set("savedIpAddress", null);
})
.catch(e => {
if (e.responseJSON && e.responseJSON.errors) {
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
bootbox.alert(
I18n.t("generic_error_with_reason", {
error: e.responseJSON.errors.join(". ")
error: e.jqXHR.responseJSON.errors.join(". ")
})
);
} else {

View File

@ -11,6 +11,10 @@ EmailLog.reopenClass({
attrs.user = AdminUser.create(attrs.user);
}
if (attrs.post_url) {
attrs.post_url = Discourse.getURL(attrs.post_url);
}
return this._super(attrs);
},

View File

@ -285,7 +285,7 @@ const Report = Discourse.Model.extend({
value,
type,
property: mainProperty,
formatedValue: value ? escapeExpression(value) : "-"
formatedValue: value ? escapeExpression(value) : ""
};
}
};
@ -318,7 +318,7 @@ const Report = Discourse.Model.extend({
return {
value: username,
formatedValue: username ? formatedValue(username) : "-"
formatedValue: username ? formatedValue(username) : ""
};
},
@ -333,7 +333,7 @@ const Report = Discourse.Model.extend({
return {
value: topicTitle,
formatedValue: topicTitle ? formatedValue() : "-"
formatedValue: topicTitle ? formatedValue() : ""
};
},
@ -360,7 +360,7 @@ const Report = Discourse.Model.extend({
_percentLabel(value) {
return {
value,
formatedValue: value ? `${value}%` : "-"
formatedValue: value ? `${value}%` : ""
};
},
@ -373,14 +373,14 @@ const Report = Discourse.Model.extend({
return {
value,
formatedValue: value ? formatedValue() : "-"
formatedValue: value ? formatedValue() : ""
};
},
_dateLabel(value, date) {
return {
value,
formatedValue: value ? date.format("LL") : "-"
formatedValue: value ? date.format("LL") : ""
};
},
@ -389,7 +389,7 @@ const Report = Discourse.Model.extend({
return {
value,
formatedValue: value ? escaped : "-"
formatedValue: value ? escaped : ""
};
},
@ -404,7 +404,7 @@ const Report = Discourse.Model.extend({
return {
value,
formatedValue: value ? formatedValue(value, row[properties[1]]) : "-"
formatedValue: value ? formatedValue(value, row[properties[1]]) : ""
};
},

View File

@ -1,5 +1,6 @@
import RestModel from "discourse/models/rest";
import { default as computed } from "ember-addons/ember-computed-decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
const THEME_UPLOAD_VAR = 2;
@ -150,7 +151,9 @@ const Theme = RestModel.extend({
saveChanges() {
const hash = this.getProperties.apply(this, arguments);
return this.save(hash).then(() => this.set("changed", false));
return this.save(hash)
.finally(() => this.set("changed", false))
.catch(popupAjaxError);
},
saveSettings(name, value) {

View File

@ -5,14 +5,23 @@ export default Discourse.Route.extend({
if (!controller.get("start_date")) {
controller.set(
"start_date",
moment()
.subtract("30", "day")
moment
.utc()
.subtract(1, "day")
.subtract(1, "month")
.startOf("day")
.format("YYYY-MM-DD")
);
}
if (!controller.get("end_date")) {
controller.set("end_date", moment().format("YYYY-MM-DD"));
controller.set(
"end_date",
moment()
.utc()
.endOf("day")
.format("YYYY-MM-DD")
);
}
}
});

View File

@ -1,5 +1,5 @@
{{#if showSortingUI}}
{{d-button action=sortByLabel icon=sortIcon class="sort-button"}}
{{d-button action=sortByLabel icon=sortIcon class="sort-btn"}}
{{/if}}
<span>{{label.title}}</span>
<span class="title">{{label.title}}</span>

View File

@ -1,4 +1,4 @@
<table class="report-table">
<table class="table">
<thead>
<tr>
{{#if model.computedLabels}}
@ -30,7 +30,7 @@
</tr>
<tr class="admin-report-table-row">
{{#each totalsForSample as |total|}}
<td class="{{total.type}} {{total.property}}">
<td class="admin-report-table-cell {{total.type}} {{total.property}}">
{{total.formatedValue}}
</td>
{{/each}}
@ -44,8 +44,8 @@
</td>
</tr>
<tr class="admin-report-table-row">
<td class="date x">-</td>
<td class="number y">{{number model.total}}</td>
<td class="admin-report-table-cell date x">—</td>
<td class="admin-report-table-cell number y">{{number model.total}}</td>
</tr>
{{/if}}
</tbody>

View File

@ -1,50 +1,99 @@
{{#if isEnabled}}
{{#conditional-loading-section isLoading=isLoading}}
{{#if showHeader}}
<div class="report-header">
{{#if showTitle}}
<div class="report-title">
<h3 class="title">
{{#if showAllReportsLink}}
{{#link-to "adminReports" class="all-report-link"}}
{{i18n "admin.dashboard.all_reports"}}
{{/link-to}}
<span class="separator">|</span>
{{/if}}
{{#conditional-loading-section isLoading=isLoading}}
{{#if showHeader}}
<div class="header">
{{#if showTitle}}
<ul class="breadcrumb">
{{#if showAllReportsLink}}
<li class="item all-reports">
{{#link-to "adminReports" class="report-url"}}
{{i18n "admin.dashboard.all_reports"}}
{{/link-to}}
</li>
<li class="item separator">|</li>
{{/if}}
<a href="{{model.reportUrl}}" class="report-link">
{{model.title}}
</a>
</h3>
<li class="item report">
<a href="{{model.reportUrl}}" class="report-url">
{{model.title}}
</a>
{{#if model.description}}
<span class="info" data-tooltip="{{model.description}}">
{{d-icon "question-circle"}}
</span>
{{/if}}
</div>
{{/if}}
</li>
</ul>
{{/if}}
{{#if showTrend}}
{{#if model.prev_period}}
<div class="trend {{model.trend}}">
<span class="trend-value" title="{{model.trendTitle}}">
{{#if model.average}}
{{number model.currentAverage}}{{#if model.percent}}%{{/if}}
{{else}}
{{number model.currentTotal noTitle="true"}}{{#if model.percent}}%{{/if}}
{{/if}}
</span>
{{#if shouldDisplayTrend}}
<div class="trend {{model.trend}}">
<span class="value" title="{{model.trendTitle}}">
{{#if model.average}}
{{number model.currentAverage}}{{#if model.percent}}%{{/if}}
{{else}}
{{number model.currentTotal noTitle="true"}}{{#if model.percent}}%{{/if}}
{{/if}}
</span>
{{#if model.trendIcon}}
{{d-icon model.trendIcon class="trend-icon"}}
{{#if model.trendIcon}}
{{d-icon model.trendIcon class="icon"}}
{{/if}}
</div>
{{/if}}
</div>
{{/if}}
<div class="body">
<div class="main">
{{#unless showError}}
{{#if hasData}}
{{#if currentMode}}
{{component modeComponent model=model options=options}}
{{#if model.relatedReport}}
{{admin-report showFilteringUI=false dataSourceName=model.relatedReport.type}}
{{/if}}
{{/if}}
{{else}}
<div class="alert alert-info report-alert no-data">
{{d-icon "pie-chart"}}
{{#if model.reportUrl}}
<a href="{{model.reportUrl}}" class="report-url">
<span>
{{#if model.title}}
{{model.title}}
{{/if}}
{{i18n "admin.dashboard.reports.no_data"}}
</span>
</a>
{{else}}
<span>{{i18n "admin.dashboard.reports.no_data"}}</span>
{{/if}}
</div>
{{/if}}
{{else}}
{{#if showTimeoutError}}
<div class="alert alert-error report-alert timeout">
{{d-icon "exclamation-triangle"}}
<span>{{i18n "admin.dashboard.timeout_error"}}</span>
</div>
{{/if}}
{{#if showExceptionError}}
<div class="alert alert-error report-alert exception">
{{d-icon "exclamation-triangle"}}
<span>{{i18n "admin.dashboard.exception_error"}}</span>
</div>
{{/if}}
{{/unless}}
</div>
{{#if showFilteringUI}}
<div class="filters">
{{#if showModes}}
<ul class="mode-switch">
<ul class="modes">
{{#each displayedModes as |displayedMode|}}
<li class="mode">
{{d-button
@ -56,118 +105,84 @@
{{/each}}
</ul>
{{/if}}
{{#if showDatesOptions}}
<div class="control">
<span class="label">
{{i18n 'admin.dashboard.reports.start_date'}}
</span>
<div class="input">
{{date-picker-past
value=startDate
defaultDate=startDate}}
</div>
</div>
<div class="control">
<span class="label">
{{i18n 'admin.dashboard.reports.end_date'}}
</span>
<div class="input">
{{date-picker-past
value=endDate
defaultDate=endDate}}
</div>
</div>
{{/if}}
{{#if showCategoryOptions}}
<div class="control">
<div class="input">
{{search-advanced-category-chooser
filterable=true
value=category
castInteger=true}}
</div>
</div>
{{/if}}
{{#if showGroupOptions}}
<div class="control">
<div class="input">
{{combo-box
castInteger=true
filterable=true
valueAttribute="value"
content=groupOptions
value=groupId}}
</div>
</div>
{{/if}}
{{#if showExport}}
<div class="control">
<div class="input">
{{d-button
class="export-csv-btn"
action="exportCsv"
label="admin.export_csv.button_text"
icon="download"}}
</div>
</div>
{{/if}}
{{#if showRefresh}}
<div class="control">
<div class="input">
{{d-button
class="refresh-report-btn btn-primary"
action="refreshReport"
label="admin.dashboard.reports.refresh_report"
icon="refresh"}}
</div>
</div>
{{/if}}
</div>
{{/if}}
<div class="report-body">
{{#unless showError}}
{{#if hasData}}
{{#if currentMode}}
{{component modeComponent model=model options=options}}
{{/if}}
{{else}}
<div class="alert alert-info no-data">
{{d-icon "pie-chart"}}
<span>{{i18n "admin.dashboard.reports.no_data"}}</span>
</div>
{{/if}}
{{else}}
{{#if showTimeoutError}}
<div class="alert alert-error report-error timeout">
<span>{{i18n "admin.dashboard.timeout_error"}}</span>
</div>
{{/if}}
{{#if showExceptionError}}
<div class="alert alert-error report-error exception">
{{i18n "admin.dashboard.exception_error"}}
</div>
{{/if}}
{{/unless}}
{{#if showFilteringUI}}
<div class="report-filters">
{{#if showDatesOptions}}
<div class="filtering-control">
<span class="filtering-label">
{{i18n 'admin.dashboard.reports.start_date'}}
</span>
<div class="filtering-input">
{{date-picker-past
value=startDate
defaultDate=startDate}}
</div>
</div>
<div class="filtering-control">
<span class="filtering-label">
{{i18n 'admin.dashboard.reports.end_date'}}
</span>
<div class="filtering-input">
{{date-picker-past
value=endDate
defaultDate=endDate}}
</div>
</div>
{{/if}}
{{#if showCategoryOptions}}
<div class="filtering-control">
<div class="filtering-input">
{{search-advanced-category-chooser
filterable=true
value=category
castInteger=true}}
</div>
</div>
{{/if}}
{{#if showGroupOptions}}
<div class="filtering-control">
<div class="filtering-input">
{{combo-box
castInteger=true
filterable=true
valueAttribute="value"
content=groupOptions
value=groupId}}
</div>
</div>
{{/if}}
{{#if showExport}}
<div class="filtering-control">
<div class="filtering-input">
{{d-button
class="export-csv-btn"
action="exportCsv"
label="admin.export_csv.button_text"
icon="download"}}
</div>
</div>
{{/if}}
{{#if showRefresh}}
<div class="filtering-control">
<div class="filtering-input">
{{d-button
class="refresh-report-btn btn-primary"
action="refreshReport"
label="admin.dashboard.reports.refresh_report"
icon="refresh"}}
</div>
</div>
{{/if}}
</div>
{{/if}}
</div>
{{#if model.relatedReport}}
{{admin-report dataSourceName=model.relatedReport.type}}
{{/if}}
{{/conditional-loading-section}}
</div>
{{/conditional-loading-section}}
{{else}}
<div class="alert alert-info">
{{{i18n disabledLabel}}}

View File

@ -137,7 +137,7 @@
{{/unless}}
{{#if selectableChildThemes}}
<p>
{{combo-box content=selectableChildThemes value=selectedChildThemeId}}
{{combo-box filterable=true content=selectableChildThemes value=selectedChildThemeId}}
{{#d-button action="addChildTheme" icon="plus"}}{{i18n "admin.customize.theme.add"}}{{/d-button}}
</p>
{{/if}}

View File

@ -57,23 +57,23 @@
<div class="section-columns">
<div class="section-column">
<div class="admin-report activity-metrics">
<div class="report-header">
<div class="report-title">
<h3 class="title">
{{#link-to "adminReports" class="report-link"}}
<div class="header">
<ul class="breadcrumb">
<li class="item report">
{{#link-to "adminReports" class="report-url"}}
{{i18n "admin.dashboard.activity_metrics"}}
{{/link-to}}
</h3>
</div>
</li>
</ul>
</div>
<div class="report-body">
<div class="admin-report-counters-list">
<div class="counters-list">
<div class="counters-header">
<div class="header"></div>
<div class="header">{{i18n 'admin.dashboard.reports.today'}}</div>
<div class="header">{{i18n 'admin.dashboard.reports.yesterday'}}</div>
<div class="header">{{i18n 'admin.dashboard.reports.last_7_days'}}</div>
<div class="header">{{i18n 'admin.dashboard.reports.last_30_days'}}</div>
<div class="counters-cell"></div>
<div class="counters-cell">{{i18n 'admin.dashboard.reports.today'}}</div>
<div class="counters-cell">{{i18n 'admin.dashboard.reports.yesterday'}}</div>
<div class="counters-cell">{{i18n 'admin.dashboard.reports.last_7_days'}}</div>
<div class="counters-cell">{{i18n 'admin.dashboard.reports.last_30_days'}}</div>
</div>
{{#each activityMetrics as |metric|}}
@ -84,10 +84,11 @@
{{/each}}
</div>
</div>
{{#link-to "adminReports"}}
{{i18n "admin.dashboard.all_reports"}}
{{/link-to}}
</div>
{{#link-to "adminReports"}}
{{i18n "admin.dashboard.all_reports"}}
{{/link-to}}
<div class="user-metrics">
{{#conditional-loading-section isLoading=isLoading}}

View File

@ -38,6 +38,14 @@ import {
const REBUILD_SCROLL_MAP_EVENTS = ["composer:resized", "composer:typed-reply"];
const uploadHandlers = [];
export function addComposerUploadHandler(extensions, method) {
uploadHandlers.push({
extensions,
method
});
}
export default Ember.Component.extend({
classNameBindings: ["showToolbar:toolbar-visible", ":wmd-controls"],
@ -587,6 +595,19 @@ export default Ember.Component.extend({
});
$element.on("fileuploadsubmit", (e, data) => {
// Look for a matching file upload handler contributed from a plugin
const matcher = handler => {
const ext = handler.extensions.join("|");
const regex = new RegExp(`\\.(${ext})$`, "i");
return regex.test(data.files[0].name);
};
const matchingHandler = uploadHandlers.find(matcher);
if (data.files.length === 1 && matchingHandler) {
matchingHandler.method(data.files[0]);
return false;
}
// If no plugin, continue as normal
const isPrivateMessage = this.get("composer.privateMessage");
data.formData = { type: "composer" };

View File

@ -81,7 +81,7 @@ export default Ember.Component.extend({
if (!this.site.mobileView) {
$this.css({ left: "" + x + "px" });
}
this.set("link", url);
this.set("link", encodeURI(url));
this.set("visible", true);
Ember.run.scheduleOnce("afterRender", this, this._focusUrl);

View File

@ -61,7 +61,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
this.set("gravatarFailed", true);
} else {
this.set("gravatarFailed", false);
this.get("user").setProperties(result);
this.get("user").setProperties({
gravatar_avatar_upload_id: result.gravatar_upload_id,
gravatar_avatar_template: result.gravatar_avatar_template
});
}
})
.finally(() => this.set("gravatarRefreshDisabled", false));

View File

@ -53,7 +53,8 @@ function loadDraft(store, opts) {
composerTime: draft.composerTime,
typingTime: draft.typingTime,
whisper: draft.whisper,
tags: draft.tags
tags: draft.tags,
noBump: draft.noBump
});
return composer;
}

View File

@ -66,7 +66,7 @@ export default Ember.Controller.extend(PreferencesTabController, {
@observes("themeId")
themeIdChanged() {
const id = this.get("themeId");
previewTheme(id);
previewTheme([id]);
},
homeChanged() {

View File

@ -930,6 +930,10 @@ export default Ember.Controller.extend(BufferedContent, {
removeFeaturedLink() {
this.set("buffered.featured_link", null);
},
resetBumpDate() {
this.get("content").resetBumpDate();
}
},

View File

@ -1,5 +1,5 @@
import DiscourseURL from "discourse/lib/url";
import { currentThemeId, refreshCSS } from "discourse/lib/theme-selector";
import { currentThemeIds, refreshCSS } from "discourse/lib/theme-selector";
// Use the message bus for live reloading of components for faster development.
export default {
@ -58,12 +58,16 @@ export default {
// Refresh if necessary
document.location.reload(true);
} else {
let themeId = currentThemeId();
const themeIds = currentThemeIds();
$("link").each(function() {
if (me.hasOwnProperty("theme_id") && me.new_href) {
let target = $(this).data("target");
if (me.theme_id === themeId && target === me.target) {
const target = $(this).data("target");
const themeId = $(this).data("theme-id");
if (
themeIds.indexOf(me.theme_id) !== -1 &&
target === me.target &&
(!themeId || themeId === me.theme_id)
) {
refreshCSS(this, null, me.new_href);
}
} else if (this.href.match(me.name) && (me.hash || me.new_href)) {

View File

@ -29,6 +29,17 @@ export function handleLogoff(xhr) {
}
}
function handleRedirect(data) {
if (
data &&
data.getResponseHeader &&
data.getResponseHeader("Discourse-Xhr-Redirect")
) {
window.location.replace(data.responseText);
window.location.reload();
}
}
/**
Our own $.ajax method. Makes sure the .then method executes in an Ember runloop
for performance reasons. Also automatically adjusts the URL to support installs
@ -76,6 +87,7 @@ export function ajax() {
}
args.success = (data, textStatus, xhr) => {
handleRedirect(data);
handleLogoff(xhr);
Ember.run(() => {

View File

@ -66,7 +66,8 @@ const DiscourseLocation = Ember.Object.extend({
getURL() {
const location = get(this, "location");
let url = location.pathname;
url = url.replace(Discourse.BaseUri, "");
url = url.replace(new RegExp(`^${Discourse.BaseUri}`), "");
const search = location.search || "";
url += search;

View File

@ -35,9 +35,10 @@ import { registerCustomAvatarHelper } from "discourse/helpers/user-avatar";
import { disableNameSuppression } from "discourse/widgets/poster-name";
import { registerCustomPostMessageCallback as registerCustomPostMessageCallback1 } from "discourse/controllers/topic";
import Sharing from "discourse/lib/sharing";
import { addComposerUploadHandler } from "discourse/components/composer-editor";
// If you add any methods to the API ensure you bump up this number
const PLUGIN_API_VERSION = "0.8.23";
const PLUGIN_API_VERSION = "0.8.24";
class PluginApi {
constructor(version, container) {
@ -753,6 +754,22 @@ class PluginApi {
Sharing.addSharingId(options.id);
Sharing.addSource(options);
}
/**
*
* Registers a function to handle uploads for specified file types
* The normal uploading functionality will be bypassed
* This only for uploads of individual files
*
* Example:
*
* addComposerUploadHandler(["mp4", "mov"], (file) => {
* console.log("Handling upload for", file.name);
* })
*/
addComposerUploadHandler(extensions, method) {
addComposerUploadHandler(extensions, method);
}
}
let _pluginv01;

View File

@ -34,6 +34,10 @@ export default function(page) {
this.controllerFor("static").set("model", model);
},
titleToken() {
return I18n.t(page);
},
actions: {
didTransition() {
this.controllerFor("application").set("showFooter", true);

View File

@ -1,7 +1,7 @@
import { ajax } from "discourse/lib/ajax";
import deprecated from "discourse-common/lib/deprecated";
const keySelector = "meta[name=discourse_theme_id]";
const keySelector = "meta[name=discourse_theme_ids]";
export function currentThemeKey() {
if (console && console.warn && console.trace) {
@ -12,21 +12,26 @@ export function currentThemeKey() {
}
}
export function currentThemeId() {
let themeId = null;
let elem = _.first($(keySelector));
export function currentThemeIds() {
const themeIds = [];
const elem = _.first($(keySelector));
if (elem) {
themeId = elem.content;
if (_.isEmpty(themeId)) {
themeId = null;
} else {
themeId = parseInt(themeId);
}
elem.content.split(",").forEach(num => {
num = parseInt(num, 10);
if (!isNaN(num)) {
themeIds.push(num);
}
});
}
return themeId;
return themeIds;
}
export function currentThemeId() {
return currentThemeIds()[0];
}
export function setLocalTheme(ids, themeSeq) {
ids = ids.reject(id => !id);
if (ids && ids.length > 0) {
$.cookie("theme_ids", `${ids.join(",")}|${themeSeq}`, {
path: "/",
@ -76,23 +81,28 @@ export function refreshCSS(node, hash, newHref, options) {
$orig.data("copy", reloaded);
}
export function previewTheme(id) {
if (currentThemeId() !== id) {
export function previewTheme(ids = []) {
ids = ids.reject(id => !id);
if (!ids.includes(currentThemeId())) {
Discourse.set("assetVersion", "forceRefresh");
ajax(`/themes/assets/${id ? id : "default"}`).then(results => {
let elem = _.first($(keySelector));
if (elem) {
elem.content = id;
}
results.themes.forEach(theme => {
let node = $(`link[rel=stylesheet][data-target=${theme.target}]`)[0];
if (node) {
refreshCSS(node, null, theme.url, { force: true });
ajax(`/themes/assets/${ids.length > 0 ? ids.join("-") : "default"}`).then(
results => {
const elem = _.first($(keySelector));
if (elem) {
elem.content = ids.join(",");
}
});
});
results.themes.forEach(theme => {
const node = $(
`link[rel=stylesheet][data-target=${theme.target}]`
)[0];
if (node) {
refreshCSS(node, null, theme.new_href, { force: true });
}
});
}
);
}
}

View File

@ -1,8 +1,9 @@
import { escapeExpression } from "discourse/lib/utilities";
const fadeSpeed = 300;
const tooltipID = "#discourse-tooltip";
export function showTooltip() {
const fadeSpeed = 300;
const tooltipID = "#discourse-tooltip";
const $this = $(this);
const $parent = $this.offsetParent();
const content = escapeExpression($this.attr("data-tooltip"));
@ -16,9 +17,7 @@ export function showTooltip() {
pos.top -= delta.top;
pos.left -= delta.left;
$(tooltipID)
.fadeOut(fadeSpeed)
.remove();
hideTooltip(tooltipID);
$(this).after(`
<div id="discourse-tooltip" ${retina}>
@ -67,9 +66,24 @@ export function showTooltip() {
return false;
}
export function hideTooltip() {
$(tooltipID)
.fadeOut(fadeSpeed)
.remove();
}
export function registerTooltip(jqueryContext) {
if (jqueryContext.length) {
jqueryContext.on("click", showTooltip);
jqueryContext.off("click").on("click", showTooltip);
}
}
export function registerHoverTooltip(jqueryContext) {
if (jqueryContext.length) {
jqueryContext
.off("mouseenter mouseleave click")
.on("mouseenter click", showTooltip)
.on("mouseleave", hideTooltip);
}
}
@ -78,3 +92,9 @@ export function unregisterTooltip(jqueryContext) {
jqueryContext.off("click");
}
}
export function unregisterHoverTooltip(jqueryContext) {
if (jqueryContext.length) {
jqueryContext.off("mouseenter mouseleave click");
}
}

View File

@ -41,7 +41,8 @@ const CLOSED = "closed",
composer_open_duration_msecs: "composerTime",
tags: "tags",
featured_link: "featuredLink",
shared_draft: "sharedDraft"
shared_draft: "sharedDraft",
no_bump: "noBump"
},
_edit_topic_serializer = {
title: "topic.title",
@ -71,6 +72,7 @@ const SAVE_ICONS = {
const Composer = RestModel.extend({
_categoryId: null,
unlistTopic: false,
noBump: false,
archetypes: function() {
return this.site.get("archetypes");
@ -608,7 +610,8 @@ const Composer = RestModel.extend({
composerTotalOpened: opts.composerTime,
typingTime: opts.typingTime,
whisper: opts.whisper,
tags: opts.tags
tags: opts.tags,
noBump: opts.noBump
});
if (opts.post) {
@ -714,7 +717,8 @@ const Composer = RestModel.extend({
typingTime: 0,
composerOpened: null,
composerTotalOpened: 0,
featuredLink: null
featuredLink: null,
noBump: false
});
},
@ -964,7 +968,8 @@ const Composer = RestModel.extend({
usernames: this.get("targetUsernames"),
composerTime: this.get("composerTime"),
typingTime: this.get("typingTime"),
tags: this.get("tags")
tags: this.get("tags"),
noBump: this.get("noBump")
};
this.set("draftStatus", I18n.t("composer.saving_draft_tip"));

View File

@ -86,7 +86,10 @@ export function findAll(siteSettings, capabilities, isMobileDevice) {
});
// On Mobile, Android or iOS always go with full screen
if (isMobileDevice || capabilities.isIOS || capabilities.isAndroid) {
if (
isMobileDevice ||
(capabilities && (capabilities.isIOS || capabilities.isAndroid))
) {
methods.forEach(m => m.set("full_screen_login", true));
}

View File

@ -565,6 +565,12 @@ const Topic = RestModel.extend({
window.location.reload();
})
.catch(popupAjaxError);
},
resetBumpDate() {
return ajax(`/t/${this.get("id")}/reset-bump-date`, { type: "PUT" }).catch(
popupAjaxError
);
}
});

View File

@ -10,7 +10,7 @@
{{else}}
<li class="nav-item-faq">{{#link-to 'faq'}}{{i18n 'faq'}}{{/link-to}}</li>
{{/if}}
<li class="nav-item-tos">{{#link-to 'tos'}}{{i18n 'terms_of_service'}}{{/link-to}}</li>
<li class="nav-item-tos">{{#link-to 'tos'}}{{i18n 'tos'}}{{/link-to}}</li>
<li class="nav-item-privacy">{{#link-to 'privacy'}}{{i18n 'privacy'}}{{/link-to}}</li>
</ul>

View File

@ -13,14 +13,12 @@
{{#each categories as |c|}}
<tr data-category-id={{c.id}} class="{{if c.description_excerpt 'has-description' 'no-description'}} {{if c.uploaded_logo.url 'has-logo' 'no-logo'}}">
<td class="category" style={{border-color c.color}}>
<div>
{{category-title-link category=c}}
<div class="category-description">
{{{dir-span c.description_excerpt}}}
</div>
<div class="clearfix"></div>
</div>
{{#if c.subcategories}}
{{#if c.subcategories}}
<div class='subcategories'>
{{#each c.subcategories as |s|}}
<span class='subcategory'>
@ -31,6 +29,7 @@
{{/each}}
</div>
{{/if}}
</td>
<td class="topics">
<div title={{c.statTitle}}>{{{c.stat}}}</div>

View File

@ -6,6 +6,7 @@
{{#if show}}
<div class="location-box">
<a class="close pull-right" {{action "hide"}}>{{d-icon "times"}}</a>
<a class="btn pull-right" {{action "copy"}}>{{d-icon "copy"}}</a>
<h4>{{i18n 'ip_lookup.title'}}</h4>
<p class='powered-by'>{{{i18n 'ip_lookup.powered_by'}}}</p>
<dl>

View File

@ -13,6 +13,7 @@
showTopicStatusUpdate=showTopicStatusUpdate
showFeatureTopic=showFeatureTopic
showChangeTimestamp=showChangeTimestamp
resetBumpDate=resetBumpDate
convertToPublicTopic=convertToPublicTopic
convertToPrivateMessage=convertToPrivateMessage}}
{{/if}}

View File

@ -21,6 +21,9 @@
{{#if whisperOrUnlistTopicText}}
<span class='whisper'>({{whisperOrUnlistTopicText}})</span>
{{/if}}
{{#if model.noBump}}
<span class="no-bump">{{d-icon "anchor"}}</span>
{{/if}}
{{/unless}}
{{#if canEdit}}
@ -120,6 +123,9 @@
{{d-icon "eye-slash"}}
</span>
{{/if}}
{{#if model.noBump}}
<span class="no-bump">{{d-icon "anchor"}}</span>
{{/if}}
{{/if}}

View File

@ -15,7 +15,13 @@
<div class="avatar-choice">
{{radio-button id="gravatar" name="avatar" value="gravatar" selection=selected}}
<label class="radio" for="gravatar">{{bound-avatar-template user.gravatar_avatar_template "large"}} {{{i18n 'user.change_avatar.gravatar'}}} {{user.email}}</label>
{{d-button action="refreshGravatar" title="user.change_avatar.refresh_gravatar_title" disabled=gravatarRefreshDisabled icon="refresh"}}
{{d-button action="refreshGravatar"
title="user.change_avatar.refresh_gravatar_title"
disabled=gravatarRefreshDisabled
icon="refresh"
class="avatar-selector-refresh-gravatar"}}
{{#if gravatarFailed}}
<p class="error">{{I18n 'user.change_avatar.gravatar_failed'}}</p>
{{/if}}

View File

@ -12,7 +12,7 @@
{{d-button class="no-text" action="moveUp" actionParam=cat icon="arrow-up"}}
{{d-button class="no-text" action="moveDown" actionParam=cat icon="arrow-down"}}
{{#if cat.hasBufferedChanges}}
{{d-button class="no-text" action="commit" icon="check"}}
{{d-button class="no-text ok" action="commit" icon="check"}}
{{/if}}
</td>
<td>{{category-badge cat allowUncategorized="true"}}</td>

View File

@ -18,6 +18,7 @@
</ul>
</p>
<p><span>{{i18n "user.api_approved"}}</span> {{bound-date key.created_at}}</p>
<p><span>{{i18n "user.api_last_used_at"}}</span> {{bound-date key.last_used_at}}</p>
</div>
{{/each}}
</div>

View File

@ -1,11 +1,3 @@
{{#d-section pageClass="tags"}}
<div class="container list-container">
<div class="row">
<div class="full-width">
<div id='list-area'>
{{outlet}}
</div>
</div>
</div>
</div>
{{/d-section}}
{{#d-section pageClass="tags" tagName=""}}
{{outlet}}
{{/d-section}}

View File

@ -4,23 +4,7 @@
<div class="list-controls">
<div class="container">
{{#if tagNotification}}
{{#unless additionalTags}}
{{tag-notifications-button action="changeTagNotification"
notificationLevel=tagNotification.notification_level}}
{{/unless}}
{{/if}}
{{#if showAdminControls}}
{{d-button action="deleteTag" icon="trash-o" class="admin-tag btn-danger"}}
{{d-button action="renameTag" actionParam=tag icon="pencil" class="admin-tag"}}
{{/if}}
{{create-topic-button
canCreateTopic=canCreateTopic
disabled=createTopicDisabled
label=createTopicLabel
action=(route-action "createTopic")}}
<section class="navigation-container">
{{#if showTagFilter}}
{{bread-crumbs categories=categories
@ -40,35 +24,61 @@
{{/each}}
</h2>
{{/if}}
{{#if tagNotification}}
{{#unless additionalTags}}
{{tag-notifications-button action="changeTagNotification"
notificationLevel=tagNotification.notification_level}}
{{/unless}}
{{/if}}
{{create-topic-button
canCreateTopic=canCreateTopic
disabled=createTopicDisabled
label=createTopicLabel
action=(route-action "createTopic")}}
{{#if showAdminControls}}
{{d-button action="renameTag" actionParam=tag icon="pencil" class="admin-tag"}}
{{d-button action="deleteTag" icon="trash-o" class="admin-tag btn-danger"}}
{{/if}}
</section>
</div>
</div>
{{plugin-outlet name="discovery-list-container-top"}}
<div class="container list-container">
<div class="row">
<div class="full-width">
<div id='list-area'>
{{conditional-loading-spinner condition=loading}}
{{conditional-loading-spinner condition=loading}}
{{#unless loading}}
{{#if list.topics}}
{{#discovery-topics-list model=list refresh="refresh"}}
{{bulk-select-button selected=selected action="refresh"}}
{{#unless loading}}
{{#if list.topics}}
{{#discovery-topics-list model=list refresh="refresh"}}
{{bulk-select-button selected=selected action="refresh"}}
{{topic-list topics=list.topics
canBulkSelect=canBulkSelect
toggleBulkSelect="toggleBulkSelect"
bulkSelectEnabled=bulkSelectEnabled
selected=selected
showPosters=true
order=order
ascending=ascending
changeSort="changeSort"}}
{{/discovery-topics-list}}
{{else}}
<footer class='topic-list-bottom'>
<h3>
{{footerMessage}}{{#link-to "discovery.categories"}} {{i18n 'topic.browse_all_categories'}}{{/link-to}} {{i18n 'or'}} {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}}.
</h3>
</footer>
{{/if}}
{{/unless}}
{{topic-list topics=list.topics
canBulkSelect=canBulkSelect
toggleBulkSelect="toggleBulkSelect"
bulkSelectEnabled=bulkSelectEnabled
selected=selected
showPosters=true
order=order
ascending=ascending
changeSort="changeSort"}}
{{/discovery-topics-list}}
{{else}}
<footer class='topic-list-bottom'>
<h3>
{{footerMessage}}{{#link-to "discovery.categories"}} {{i18n 'topic.browse_all_categories'}}{{/link-to}} {{i18n 'or'}} {{#link-to 'discovery.latest'}}{{i18n 'topic.view_latest_topics'}}{{/link-to}}.
</h3>
</footer>
{{/if}}
{{/unless}}
</div>
</div>
</div>
</div>

View File

@ -94,6 +94,7 @@
showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate")
showFeatureTopic=(action "topicRouteAction" "showFeatureTopic")
showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp")
resetBumpDate=(action "resetBumpDate")
convertToPublicTopic=(action "convertToPublicTopic")
convertToPrivateMessage=(action "convertToPrivateMessage")}}
{{/if}}
@ -121,6 +122,7 @@
showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate")
showFeatureTopic=(action "topicRouteAction" "showFeatureTopic")
showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp")
resetBumpDate=(action "resetBumpDate")
convertToPublicTopic=(action "convertToPublicTopic")
convertToPrivateMessage=(action "convertToPrivateMessage")}}
{{else}}
@ -144,6 +146,7 @@
showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate")
showFeatureTopic=(action "topicRouteAction" "showFeatureTopic")
showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp")
resetBumpDate=(action "resetBumpDate")
convertToPublicTopic=(action "convertToPublicTopic")
convertToPrivateMessage=(action "convertToPrivateMessage")}}
{{/if}}
@ -256,6 +259,7 @@
showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate")
showFeatureTopic=(action "topicRouteAction" "showFeatureTopic")
showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp")
resetBumpDate=(action "resetBumpDate")
convertToPublicTopic=(action "convertToPublicTopic")
convertToPrivateMessage=(action "convertToPrivateMessage")
toggleBookmark=(action "toggleBookmark")

View File

@ -182,7 +182,6 @@ export default createWidget("search-menu", {
},
html(attrs) {
searchData.contextEnabled = attrs.contextEnabled;
const searchContext = this.searchContext();
const shouldTriggerSearch =
@ -196,6 +195,8 @@ export default createWidget("search-menu", {
this.triggerSearch();
}
searchData.contextEnabled = attrs.contextEnabled;
return this.attach("menu-panel", {
maxWidth: 500,
contents: () => this.panelContents()

View File

@ -203,6 +203,15 @@ export default createWidget("topic-admin-menu", {
});
}
if (this.currentUser.get("staff")) {
buttons.push({
className: "topic-admin-reset-bump-date",
action: "resetBumpDate",
icon: "anchor",
label: "actions.reset_bump_date"
});
}
if (!isPrivateMessage) {
buttons.push({
className: "topic-admin-archive",

View File

@ -0,0 +1,3 @@
//= depend_on 'client.hu.yml'
//= require locales/i18n
<%= JsLocaleHelper.output_locale(:hu) %>

View File

@ -192,6 +192,17 @@ export default DropdownSelectBoxComponent.extend({
});
}
const currentUser = Discourse.User.current();
if (action === REPLY && currentUser && currentUser.get("staff")) {
items.push({
name: I18n.t("composer.composer_actions.toggle_topic_bump.label"),
description: I18n.t("composer.composer_actions.toggle_topic_bump.desc"),
icon: "anchor",
id: "toggle_topic_bump"
});
}
return items;
},
@ -234,6 +245,10 @@ export default DropdownSelectBoxComponent.extend({
model.toggleProperty("whisper");
},
toggleTopicBumpSelected(options, model) {
model.toggleProperty("noBump");
},
replyToTopicSelected(options) {
options.action = REPLY;
options.topic = _topicSnapshot;

View File

@ -45,21 +45,26 @@ export default ComboBox.extend(Tags, {
);
},
willDestroyElement() {
this._super(...arguments);
this.$(".selected-name").off("touchend.select-kit pointerup.select-kit");
},
didInsertElement() {
this._super(...arguments);
this.$(".selected-name").on(
"touchend.select-kit pointerup.select-kit",
() => this.focusFilterOrHeader()
this.$(".select-kit-body").on(
"mousedown touchstart",
".selected-tag",
event => {
const $button = $(event.target);
this._destroyEvent(event);
this.destroyTags(this.computeContentItem($button.attr("data-value")));
}
);
},
willDestroyElement() {
this._super(...arguments);
this.$(".select-kit-body").off("mousedown touchstart");
},
@computed("hasReachedMaximum")
caretIcon(hasReachedMaximum) {
return hasReachedMaximum ? null : "plus fa-fw";
@ -74,37 +79,6 @@ export default ComboBox.extend(Tags, {
return computedContent;
},
didRender() {
this._super();
this.$(".select-kit-body").on(
"click.mini-tag-chooser",
".selected-tag",
event => {
event.stopImmediatePropagation();
this.destroyTags(
this.computeContentItem($(event.target).attr("data-value"))
);
}
);
this.$(".select-kit-header").on(
"focus.mini-tag-chooser",
".selected-name",
event => {
event.stopImmediatePropagation();
this.focus(event);
}
);
},
willDestroyElement() {
this._super();
this.$(".select-kit-body").off("click.mini-tag-chooser");
this.$(".select-kit-header").off("focus.mini-tag-chooser");
},
// we are directly mutatings tags to define the current selection
mutateValue() {},

View File

@ -18,6 +18,6 @@ export default NotificationOptionsComponent.extend({
@computed("iconForSelectedDetails")
headerIcon(iconForSelectedDetails) {
return [iconForSelectedDetails, "caret-down"];
return iconForSelectedDetails;
}
});

View File

@ -91,14 +91,25 @@ export default Ember.Mixin.create({
// next so we are sure it finised expand/collapse
Ember.run.next(() => {
Ember.run.schedule("afterRender", () => {
if (!context.$filterInput() || !context.$filterInput().is(":visible")) {
if (
!context.$filterInput() ||
!context.$filterInput().is(":visible") ||
context
.$filterInput()
.parent()
.hasClass("is-hidden")
) {
if (context.$header()) {
context.$header().focus();
} else {
$(context.element).focus();
}
} else {
context.$filterInput().focus();
if (this.site && this.site.isMobileDevice) {
this.expand();
} else {
context.$filterInput().focus();
}
}
});
});

View File

@ -1,3 +1,2 @@
{{input class="selected-name" value=label readonly="readonly" tabindex="-1"}}
<span class="selected-name">{{label}}</span>
{{d-icon caretIcon class="caret-icon"}}

View File

@ -1,64 +1,36 @@
.admin-report {
.report-error,
.no-data {
width: 100%;
width: 100%;
align-self: flex-start;
text-align: center;
padding: 3em;
box-sizing: border-box;
}
.report-error {
color: $danger;
border: 1px solid $danger;
}
.no-data {
background: $secondary;
border: 1px solid $primary-low;
color: $primary-low-mid;
.d-icon-pie-chart {
color: currentColor;
margin-bottom: 0.25em;
font-size: $font-up-5;
display: block;
.conditional-loading-section {
&.is-loading {
margin: 0;
}
}
.conditional-loading-section {
flex: 1;
margin: 0;
.header {
display: flex;
align-items: center;
border-bottom: 1px solid $primary-low;
margin-bottom: 0.5em;
padding-bottom: 0.5em;
}
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1em;
.header .breadcrumb {
margin: 0;
list-style: none;
.report-title {
align-items: center;
display: flex;
justify-content: space-between;
.item {
display: inline;
font-size: $font-up-1;
}
.title {
margin: 0;
padding: 0;
border: 0;
font-size: $font-up-1;
.separator {
font-weight: normal;
}
.all-reports .report-url {
font-weight: 700;
}
.report-link {
color: $primary;
}
.report {
font-weight: 700;
.separator + .report-link {
font-weight: normal;
}
.report-url {
color: $primary;
}
.info {
@ -71,167 +43,134 @@
}
}
}
}
.trend {
align-items: center;
&.trending-down,
&.high-trending-down {
color: $danger;
}
&.trending-up,
&.high-trending-up {
color: $success;
}
&.no-change {
color: $primary-medium;
}
.trend-value {
font-size: $font-up-1;
}
.trend-icon {
font-size: $font-up-1;
font-weight: 700;
}
.header .trend {
margin-left: auto;
&.trending-down,
&.high-trending-down {
color: $danger;
}
.mode-switch {
&.trending-up,
&.high-trending-up {
color: $success;
}
&.no-change {
color: $primary-medium;
}
.value {
font-size: $font-up-1;
}
.icon {
font-size: $font-up-1;
font-weight: 700;
}
}
.body {
display: flex;
}
.main {
flex: 1;
}
.main .report-alert {
margin: 0;
text-align: center;
padding: 3em;
border: 1px solid transparent;
a {
color: $primary-medium;
}
.d-icon {
color: currentColor;
margin-bottom: 0.25em;
font-size: $font-up-5;
display: block;
}
&.no-data {
background: $secondary;
border-color: $primary-low;
color: $primary-low-mid;
}
&.timeout,
&.exception {
border-color: $danger-low;
color: $danger;
}
}
.filters {
display: flex;
margin-left: 1em;
flex-direction: column;
width: 220px;
.modes {
margin: 0 0 1em 0;
padding: 0;
list-style: none;
display: flex;
margin: 0;
.mode {
display: inline;
display: inline-flex;
flex: 1;
.mode-button.current {
.mode-btn.is-current {
color: $tertiary;
}
}
}
}
.report-body {
display: flex;
justify-content: space-between;
.control {
margin-bottom: 1em;
}
.admin-report-table,
.admin-report-chart {
.control .label {
font-weight: 700;
width: 100%;
}
.report-filters {
margin-left: 1em;
min-width: 250px;
display: flex;
flex-direction: column;
.control .input,
.control .select-kit {
width: 100%;
.filtering-control {
display: flex;
flex-direction: column;
margin-bottom: 1em;
}
.filtering-label {
}
.filtering-input {
.export-csv-btn {
width: 100%;
}
.date-picker-wrapper,
.combo-box,
.export-csv-btn,
.refresh-report-btn {
.refresh-report-btn {
width: 100%;
}
.date-picker-wrapper {
width: 100%;
.date-picker {
box-sizing: border-box;
width: 100%;
}
.date-picker-wrapper {
.date-picker {
width: 100%;
box-sizing: border-box;
margin: 0;
}
margin: 0;
}
}
}
.report-filters:only-child {
margin-left: auto;
}
}
}
.admin-report.activity-metrics {
table {
table-layout: auto;
}
}
.admin-report.users-by-type {
margin-top: 1em;
}
.admin-report.users-by-type,
.admin-report.users-by-trust-level {
margin-bottom: 1em;
flex: 1;
.report-header {
border-bottom: 1px solid $primary-medium;
padding-bottom: 0.5em;
border-bottom: 1px solid #e9e9e9;
}
}
.admin-report.moderators-activity {
tbody tr td.username,
thead tr th.username {
text-align: left;
}
}
.admin-report.trending-search {
tbody tr td.term,
thead tr th.term {
text-align: left;
}
}
.admin-report.top-traffic-sources {
tbody tr td.domain,
thead tr th.domain {
text-align: left;
}
}
.admin-report.post-edits {
.report-table {
table-layout: auto;
tbody tr td,
thead tr th {
text-align: left;
}
thead tr th.edit_reason,
tbody tr td.edit_reason {
width: 100%;
}
}
}
.admin-report.flags-status {
.admin-report-table {
table-layout: auto;
tbody tr td,
thead tr th {
text-align: left;
}
tbody tr td.response_time,
thead tr th.response_time {
text-align: center;
}
.rtl .admin-report {
.filters {
margin-left: 0;
margin-right: 1em;
}
.trend {
margin-left: unset;
margin-right: auto;
}
}

View File

@ -1,70 +1,21 @@
.admin-report-counters-list {
display: flex;
flex: 1;
flex-direction: column;
.counters-header {
display: grid;
flex: 1;
grid-template-columns: 33% repeat(auto-fit, minmax(20px, 1fr));
border: 1px solid $primary-low;
border-bottom: 0;
padding: 0.25em;
font-weight: 700;
text-align: right;
}
.conditional-loading-section.is-loading {
padding: 0.5em;
margin: 0;
flex-direction: row;
justify-content: flex-start;
.title {
font-weight: normal;
font-size: $font-down-1;
}
.spinner {
margin: 0 0 0 0.5em;
height: 5px;
width: 5px;
}
}
}
.admin-report.counters {
&:last-child .admin-report-counters {
border-bottom: 1px solid $primary-low;
}
.admin-report {
.admin-report-counters {
display: grid;
flex: 1;
grid-template-columns: 33% repeat(auto-fit, minmax(20px, 1fr));
grid-template-rows: repeat(auto-fit, minmax(32px, 1fr));
border: 1px solid $primary-low;
align-items: center;
border-bottom: 0;
.cell {
padding: 0.25em;
text-align: right;
white-space: nowrap;
padding: 8px 21px 8px 8px; // accounting for negative right caret margin
&:nth-of-type(2) {
padding: 8px 12px 8px;
}
i {
margin-right: -12px; // align on caret
@media screen and (max-width: 650px) {
margin-right: -9px;
}
}
&.title {
text-align: left;
padding: 8px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
.d-icon {
color: $primary-low-mid;
@ -78,13 +29,8 @@
}
@media screen and (max-width: 400px) {
&.title {
padding: 8px 0 8px 4px;
font-size: $font-down-1;
.d-icon {
display: none;
}
&.title .d-icon {
display: none;
}
}
@ -102,28 +48,18 @@
}
}
}
}
.no-data {
margin: 0;
padding: 8px;
display: flex;
flex-direction: row;
align-items: center;
font-size: $font-0;
border: 0;
background: $primary-low;
color: $primary-medium;
.d-icon {
font-size: $font-up-1;
margin: 0 0.25em 0 0;
color: $primary-low-mid;
.rtl .counters-list .counters-header .counters-cell {
text-align: left;
}
.rtl .counters-list {
.cell {
text-align: left;
&.title {
text-align: right;
}
}
.alert-error {
text-align: left;
padding: 0.5em;
margin: 0;
border: 0;
}
}

View File

@ -1,113 +1,193 @@
.admin-report-table {
@media screen and (max-width: 650px) {
table {
tbody tr td {
font-size: $font-down-1;
&.two-columns {
.table .admin-report-table-cell:first-child,
.table .admin-report-table-header:first-child {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
text-align: left;
width: 80%;
}
.table .admin-report-table-cell:last-child,
.table .admin-report-table-header:last-child {
display: inline-block;
width: 80%;
text-align: right;
}
}
.table {
margin: 0;
border: 1px solid $primary-low;
table-layout: fixed;
tbody {
border-top: 0;
}
}
.table .admin-report-table-header {
.sort-btn {
outline: none;
background: none;
padding: 3px 8px;
overflow: hidden;
text-overflow: ellipsis;
}
&.is-current-sort {
.d-icon {
color: $tertiary;
}
.sort-btn:hover {
color: $primary-medium;
background: $primary-low;
}
}
&:not(.is-current-sort) .sort-btn {
background: none;
&:hover {
color: $primary-medium;
background: $primary-low;
}
}
}
.admin-report-table-cell {
&.user .username {
margin-left: 0.25em;
}
}
.total-row {
background: $primary-very-low;
td {
font-weight: 700;
text-align: left;
}
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 0.5em;
margin-top: 0.25em;
button {
margin-left: 0.5em;
&.current {
&.is-current {
color: $tertiary;
}
}
}
}
&.two-columns {
.report-table tbody tr td:first-child,
.report-table thead tr th:first-child {
text-align: left;
.admin-report.top-referred-topics {
.admin-report-table-header.topic_title {
width: 80%;
}
}
.admin-report.trending-search {
.admin-report-table-header.ctr,
.admin-report-table-header.unique_searches,
.admin-report-table-cell.ctr,
.admin-report-table-cell.unique_searches {
text-align: center;
width: 20%;
}
.admin-report-table-cell.term {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.admin-report.moderators-activity {
.admin-report-table-header.seconds,
.admin-report-table-header.number,
.admin-report-table-cell.seconds,
.admin-report-table-cell.number {
text-align: center;
}
.admin-report-table-header.user {
width: 20%;
}
}
.admin-report.post-edits {
.admin-report-table-header.user {
width: 20%;
}
.admin-report-table-cell.post,
.admin-report-table-cell.edit_reason {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.admin-report.flags-status {
.admin-report-table-header.response_time,
.admin-report-table-cell.response_time {
text-align: center;
}
}
.rtl {
.admin-report-table {
&.two-columns {
.table .admin-report-table-cell:first-child,
.table .admin-report-table-header:first-child {
text-align: right;
}
.table .admin-report-table-cell:last-child,
.table .admin-report-table-header:last-child {
text-align: left;
}
}
.report-table {
table-layout: auto;
.total-row {
td {
text-align: right;
}
}
}
.report-table {
table-layout: fixed;
border: 1px solid $primary-low;
margin-top: 0;
margin-bottom: 10px;
tbody {
border: none;
.total-row {
td {
font-weight: 700;
text-align: left;
}
}
tr {
td {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
text-align: center;
padding: 8px;
&.user {
text-align: left;
.username {
margin-left: 3px;
}
}
}
}
.admin-report-table-cell {
&.user .username {
margin-left: 0;
margin-right: 0.25em;
}
}
thead {
border: 1px solid $primary-low;
.admin-report.trending-search {
.admin-report-table-header.term,
.admin-report-table-cell.term {
text-align: right;
}
}
.admin-report-table-header {
.sort-button {
outline: none;
background: none;
padding: 3px 7px;
overflow: hidden;
text-overflow: ellipsis;
}
.pagination {
button {
margin-left: 0;
margin-right: 0.5em;
}
}
&.is-current-sort {
.d-icon {
color: $tertiary;
}
.sort-button:hover {
color: $primary-medium;
background: $primary-low;
}
}
&:not(.is-current-sort) .sort-button {
background: none;
&:hover {
color: $primary-medium;
background: $primary-low;
}
}
}
tr {
th {
text-align: center;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.admin-report.moderators-activity {
.admin-report-table-header.user,
.admin-report-table-cell.user {
text-align: right;
}
}
}

View File

@ -333,6 +333,10 @@
}
}
.permalink-form .select-kit {
width: 150px;
}
.permalink-title {
margin-bottom: 10px;
}

View File

@ -7,28 +7,33 @@
.dashboard-next {
.section-top {
margin-bottom: 1em;
margin-bottom: 0.5em;
}
.navigation {
display: flex;
margin: 0 0 1em 0;
margin: 0 0 2.5em 0;
border-bottom: 1px solid $primary-low-mid;
.navigation-item {
display: inline;
background: $secondary;
padding: 0.5em 1em;
&:hover {
background: $primary-very-low;
}
}
.navigation-link {
font-weight: 700;
display: block;
font-weight: bold;
padding: 0.6em 1em 0.5em 1em;
}
}
@mixin active-navigation-item {
border-radius: 3px 3px 0 0;
border: 1px solid $primary-low;
border-bottom: 10px solid $secondary;
.navigation-link {
border-bottom: 0.4em solid $tertiary;
}
}
&.dashboard-next-moderation .navigation-item.moderation {
@ -57,11 +62,11 @@
max-width: 100%;
&:last-child {
margin-left: 1em;
margin-left: 0.5em;
}
&:first-child {
margin-right: 1em;
margin-right: 0.5em;
}
@include breakpoint(medium) {
@ -108,26 +113,31 @@
.section-body {
padding: 1em 0 0;
> p {
margin-top: 0;
}
}
}
.charts {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
.admin-report .header {
border: 0;
padding: 0;
margin-bottom: 1em;
}
.chart {
max-width: calc(100% * 1 / 3.2);
width: 100%;
flex-grow: 1;
flex-basis: 100%;
display: flex;
margin-bottom: 1em;
.charts {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 1em;
grid-row-gap: 1em;
.admin-report {
grid-column: span 4;
}
@include breakpoint(medium) {
.chart {
max-width: 100%;
.admin-report {
grid-column: span 12;
}
}
@ -158,6 +168,7 @@
display: flex;
flex-wrap: wrap;
justify-content: space-between;
border-right: 1px solid $primary-low;
.backups,
.uploads {
@ -189,7 +200,6 @@
}
}
.last-dashboard-update {
border-left: 1px solid $primary-low;
text-align: center;
display: flex;
justify-content: center;
@ -202,6 +212,10 @@
}
}
.top-referred-topics {
margin-bottom: 1.5em;
}
.top-referred-topics,
.trending-search {
th:first-of-type {
@ -209,12 +223,6 @@
}
}
.top-referred-topics {
.dashboard-table table {
table-layout: auto;
}
}
.section {
.period-chooser .period-chooser-header {
.selected-name,
@ -229,7 +237,7 @@
}
.dashboard-problems {
margin-bottom: 2em;
margin-bottom: 2.5em;
.d-icon-exclamation-triangle {
color: $danger;
@ -241,29 +249,82 @@
}
}
.admin-report-table {
margin-bottom: 1em;
.counters-list {
display: flex;
flex: 1;
flex-direction: column;
&.is-disabled {
background: $primary-low;
padding: 1em;
.counters-header {
display: grid;
flex: 1;
grid-template-columns: 33% repeat(auto-fit, minmax(20px, 1fr));
border: 1px solid $primary-low;
border-bottom: 0;
font-weight: 700;
text-align: right;
align-items: center;
padding: 0.65em 0.25em;
}
@media screen and (max-width: 650px) {
table {
tbody tr td {
font-size: $font-down-1;
}
.admin-report .main {
border: 1px solid $primary-low;
&:hover {
background-color: $primary-very-low;
}
}
&.is-loading {
height: 150px;
.admin-report:not(:last-child) {
.main {
border-bottom: 0;
}
.conditional-loading-section.is-loading {
border-bottom: 0;
}
}
.admin-report .conditional-loading-section.is-loading {
display: flex;
flex-direction: row;
padding: 0.5em 0.25em;
align-items: flex-start;
justify-content: flex-start;
border: 1px solid $primary-low;
.title {
font-size: $font-0;
}
.spinner {
margin: 0;
width: 8px;
height: 8px;
margin-left: 0.5em;
}
}
.admin-report .main .report-alert {
display: flex;
flex-direction: row;
padding: 0.5em 0.25em;
align-items: center;
border: 0;
&:hover {
background-color: $primary-very-low;
}
.d-icon {
font-size: $font-up-1;
margin: 0 0.25em 0 0;
color: $primary-low-mid;
}
}
}
.activity-metrics {
margin-bottom: 1em;
margin-bottom: 1.5em;
}
.user-metrics {
@ -363,21 +424,32 @@
}
}
.community-health.section {
margin-bottom: 1em;
.users-by-trust-level,
.users-by-type {
margin-bottom: 1.5em;
}
.dashboard-next.moderation {
.community-health.section {
margin-bottom: 1.5em;
}
.dashboard-next-moderation {
.admin-dashboard-moderation-top {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 1em;
grid-row-gap: 1em;
}
.section-body {
margin-bottom: 1em;
}
.main-section {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 1em;
grid-row-gap: 1em;
> * {
grid-column: span 12;
@ -387,6 +459,7 @@
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-column-gap: 1em;
grid-row-gap: 1em;
}
}

View File

@ -13,6 +13,7 @@
.list-controls {
clear: both;
margin-bottom: 5px;
.combo-box .combo-box-header {
background: $primary-low;
color: $primary;
@ -35,6 +36,9 @@
&.category-notifications-button,
&.tag-notifications-button {
float: right;
button {
display: inline-block;
}
}
}
}

View File

@ -1,31 +1,31 @@
.reorder-categories {
thead {
border-bottom: 1px solid $primary-low;
th {
padding-bottom: 0.5em;
text-align: left;
}
}
input {
width: 4em;
}
.th-pos {
width: calc(4em + 150px);
}
tbody tr {
background-color: transparent;
transition: background 0s ease;
&.highlighted {
background-color: rgba($highlight, 0.4);
&.done {
background-color: transparent;
transition-duration: 1s;
}
@include breakpoint(mobile) {
width: 2em;
}
&:first-child td {
padding-top: 7px;
}
}
tbody {
border-bottom: 1px solid blend-primary-secondary(50%);
}
table {
width: 100%;
padding-bottom: 150px;
td {
padding: 0.5em 0.5em 0.5em 0;
@include breakpoint(mobile, min-width) {
min-width: 15em;
}
}
}
.badge-wrapper span.badge-category {
max-width: 20em;
@include breakpoint(mobile) {
max-width: 30vw;
}
}
}
.category-admin-menu ul {
width: 320px;
}

View File

@ -437,4 +437,10 @@ div.ac-wrap {
.md-table {
overflow-y: auto;
margin: 1em 0;
.mobile-view & {
table {
width: 100%;
}
}
}

View File

@ -1,63 +1,8 @@
.edit-topic-timer-modal {
.modal-body {
max-height: none;
overflow: visible !important; /* inline JS styles */
}
.select-kit {
width: 50%;
}
input.date-picker,
input[type="time"] {
width: 150px;
text-align: left;
}
.radios {
margin-bottom: 10px;
}
label {
vertical-align: middle;
display: inline-block;
padding-right: 5px;
input {
vertical-align: middle;
}
}
.btn.pull-right {
margin-right: 10px;
}
.future-date-input {
input {
margin: 0;
}
.alert-info {
margin: 0 -15px -15px -15px;
}
.pika-single {
position: relative !important;
}
.topic-status-info {
border: none;
padding: 0;
h3 {
font-weight: normal;
font-size: $font-up-1;
}
}
}
}
// mobile styles
.mobile-view .edit-topic-timer-modal {
.control-group {
display: flex;
align-items: center;
@ -66,20 +11,55 @@
margin-right: 5px;
}
}
.select-kit {
width: 50%;
}
input.date-picker,
input[type="time"] {
width: 150px;
text-align: left;
}
.radios {
margin-bottom: 10px;
}
label {
vertical-align: middle;
display: inline-block;
padding-right: 5px;
input {
vertical-align: middle;
}
}
.btn.pull-right {
margin-right: 10px;
}
.future-date-input {
input {
margin: 0;
}
.alert-info {
margin: 0 -15px -15px -15px;
}
.btn-clear {
display: none;
}
.topic-status-info {
border: none;
padding: 0;
h3 {
font-weight: normal;
font-size: $font-up-1;
}
}
}
.pika-single {
position: absolute !important; /* inline JS styles */
}
}
// mobile styles
.mobile-view .edit-topic-timer-modal {
.select-kit.combo-box {
flex: 1;
}
.future-date-input-selector-header .btn-clear {
display: none;
}
.modal-body {
overflow: visible !important; /* inline styles from JS */
}
.pika-single {
position: absolute !important; /* inline styles from JS */
}
}

View File

@ -78,6 +78,7 @@
float: left;
background-color: transparent;
display: inline-flex;
align-items: center;
padding: 0.25em 0.5em;
width: 50%;
box-sizing: border-box;
@ -122,9 +123,6 @@
font-weight: normal;
font-size: $font-down-1;
}
.box + b.topics-count {
padding-top: 2px;
}
span.badge-category {
max-width: 90px;
@ -275,6 +273,9 @@
a {
padding: 0;
> div {
overflow: hidden; // clears the text from wrapping below icons
}
}
p {

View File

@ -29,6 +29,14 @@
}
}
.tag-show-heading {
display: inline-flex;
align-items: center;
.d-icon {
margin: 0 0.25em;
}
}
.topic-header-extra .discourse-tag {
-webkit-animation: fadein 0.7s;
animation: fadein 0.7s;
@ -201,12 +209,6 @@ header .discourse-tag {
width: 500px;
}
.admin-tag {
position: relative;
float: right;
margin-right: 8px;
}
.tag-sort-options {
margin-bottom: 20px;
a {

View File

@ -140,10 +140,12 @@
}
.clearfix > .topic-meta-data > .names {
span.user-title {
background-color: dark-light-choose($highlight-low, $highlight-medium);
color: dark-light-choose($primary-high, $secondary-low);
padding-left: 4px;
padding-right: 4px;
a {
background-color: dark-light-choose($highlight-low, $highlight-medium);
padding-left: 4px;
padding-right: 4px;
}
}
}
}

View File

@ -75,7 +75,6 @@
.d-editor-button-bar {
display: flex;
align-items: center;
border-bottom: none;
min-height: 30px;
padding-left: 3px;
border-bottom: 1px solid $primary-low;

View File

@ -50,6 +50,8 @@
}
.subcategories {
margin-top: 0.25em;
clear: both;
.badge-notification.new-posts {
padding: 0;
margin: 0 10px 0 0;

View File

@ -8,7 +8,7 @@
.list-controls {
.nav {
float: left;
margin-bottom: 15px;
margin-bottom: 10px;
}
.btn {
@ -223,8 +223,7 @@ button.dismiss-read {
.category-logo {
max-height: 150px;
float: left;
margin-bottom: 15px;
margin-right: 15px;
margin: 0.25em 1em 0.5em 0;
}
/* Tablet (portrait) ----------- */

View File

@ -22,9 +22,11 @@
@import "mobile/ring";
@import "mobile/group";
@import "mobile/groups";
@import "mobile/dashboard_next";
@import "mobile/admin_reports";
@import "mobile/admin_report";
@import "mobile/admin_report_table";
@import "mobile/admin_report_counters";
// Import all component-specific files
@import "mobile/components/*";

View File

@ -9,27 +9,9 @@
}
}
.admin-report.top-referred-topics {
.admin-report-table {
.report-table {
table-layout: fixed;
thead tr th.topic_title,
tbody tr td.topic_title {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 80%;
}
}
}
}
.admin-report.trending-search {
.admin-report-table {
.report-table {
table-layout: fixed;
thead tr th.term,
tbody tr td.term {
width: 50%;

View File

@ -0,0 +1,5 @@
.counters-list {
.counters-header .counters-cell {
font-weight: normal;
}
}

View File

@ -1,14 +1,28 @@
.admin-report-table {
.report-table {
table-layout: fixed;
.table {
font-size: $font-down-1;
}
thead tr th {
font-weight: normal;
font-size: $font-down-2;
white-space: unset;
.sort-button {
display: none;
}
.table .admin-report-table-header {
font-weight: 500;
border-right: 1px solid $primary-low;
padding: auto;
.title {
writing-mode: vertical-rl;
text-orientation: mixed;
text-align: right;
transform: rotate(180deg);
}
.sort-btn {
display: none;
}
}
.table tbody tr td {
&.user .username {
display: none;
}
}
}

View File

@ -1,24 +1,24 @@
.admin-reports {
.admin-report {
.report-body {
.body {
flex-direction: column;
.report-filters {
.filters {
order: 0;
margin: 0;
width: 100%;
}
.alert {
.main {
order: 2;
}
.report-alert {
margin: 0;
order: 1;
flex: 1;
padding: 1em;
}
.admin-report-table,
.admin-report-chart {
order: 2;
}
}
}
}

View File

@ -158,6 +158,7 @@
flex: 1;
margin-left: 5px;
margin-bottom: 5px;
z-index: z("base");
}
}

View File

@ -0,0 +1,5 @@
.dashboard-next {
.activity-metrics .counters-list {
font-size: $font-down-1;
}
}

View File

@ -442,7 +442,7 @@ span.highlighted {
}
.user-title {
order: 4;
margin-right: auto;
flex-basis: 100%;
}
span {
margin-right: 4px;
@ -456,7 +456,6 @@ span.highlighted {
.user-title {
color: #aaa;
padding-top: 2px;
overflow: hidden;
margin-right: 50px;
}

View File

@ -23,8 +23,8 @@ class Admin::ReportsController < Admin::AdminController
raise Discourse::NotFound unless report_type =~ /^[a-z0-9\_]+$/
start_date = (params[:start_date].present? ? params[:start_date].to_date : 30.days.ago).beginning_of_day
end_date = (params[:end_date].present? ? params[:end_date].to_date : start_date + 30.days).end_of_day
start_date = (params[:start_date].present? ? Time.parse(params[:start_date]).to_date : 1.days.ago).beginning_of_day
end_date = (params[:end_date].present? ? Time.parse(params[:end_date]).to_date : start_date + 30.days).end_of_day
if params.has_key?(:category_id) && params[:category_id].to_i > 0
category_id = params[:category_id].to_i

View File

@ -33,7 +33,7 @@ class Admin::ScreenedIpAddressesController < Admin::AdminController
def update
if @screened_ip_address.update_attributes(allowed_params)
render json: success_json
render_serialized(@screened_ip_address, ScreenedIpAddressSerializer)
else
render_json_error(@screened_ip_address)
end

View File

@ -182,11 +182,13 @@ class Admin::ThemesController < Admin::AdminController
log_theme_change(original_json, @theme)
format.json { render json: @theme, status: :ok }
else
format.json {
format.json do
error = @theme.errors.full_messages.join(", ").presence
error = I18n.t("themes.bad_color_scheme") if @theme.errors[:color_scheme].present?
error ||= I18n.t("themes.other_error")
error = @theme.errors[:color_scheme] ? I18n.t("themes.bad_color_scheme") : I18n.t("themes.other_error")
render json: { errors: [ error ] }, status: :unprocessable_entity
}
end
end
end
end

View File

@ -22,7 +22,7 @@ class ApplicationController < ActionController::Base
include GlobalPath
include Hijack
attr_reader :theme_id
attr_reader :theme_ids
serialization_scope :guardian
@ -62,8 +62,8 @@ class ApplicationController < ActionController::Base
after_action :remember_theme_id
def remember_theme_id
if @theme_id
Stylesheet::Watcher.theme_id = @theme_id if defined? Stylesheet::Watcher
if @theme_ids.present?
Stylesheet::Watcher.theme_id = @theme_ids.first if defined? Stylesheet::Watcher
end
end
end
@ -129,6 +129,14 @@ class ApplicationController < ActionController::Base
)
end
rescue_from ActiveRecord::RecordInvalid do |e|
if request.format && request.format.json?
render_json_error e, type: :record_invalid, status: 422
else
raise e
end
end
# If they hit the rate limiter
rescue_from RateLimiter::LimitExceeded do |e|
render_rate_limit_error(e)
@ -173,7 +181,16 @@ class ApplicationController < ActionController::Base
end
end
rescue_from Discourse::NotFound, PluginDisabled, ActionController::RoutingError do
rescue_from Discourse::NotFound do |e|
rescue_discourse_actions(
:not_found,
e.status,
check_permalinks: e.check_permalinks,
original_path: e.original_path
)
end
rescue_from PluginDisabled, ActionController::RoutingError do
rescue_discourse_actions(:not_found, 404)
end
@ -194,12 +211,34 @@ class ApplicationController < ActionController::Base
render_json_error I18n.t('read_only_mode_enabled'), type: :read_only, status: 503
end
def redirect_with_client_support(url, options)
if request.xhr?
response.headers['Discourse-Xhr-Redirect'] = 'true'
render plain: url
else
redirect_to url, options
end
end
def rescue_discourse_actions(type, status_code, opts = nil)
opts ||= {}
show_json_errors = (request.format && request.format.json?) ||
(request.xhr?) ||
((params[:external_id] || '').ends_with? '.json')
if type == :not_found && opts[:check_permalinks]
url = opts[:original_path] || request.fullpath
permalink = Permalink.find_by_url(url)
# there are some cases where we have a permalink but no url
# cause category / topic was deleted
if permalink.present? && permalink.target_url
# permalink present, redirect to that URL
redirect_with_client_support permalink.target_url, status: :moved_permanently
return
end
end
message = opts[:custom_message_translated] || I18n.t(opts[:custom_message] || type)
if show_json_errors
@ -331,28 +370,33 @@ class ApplicationController < ActionController::Base
resolve_safe_mode
return if request.env[NO_CUSTOM]
theme_id = request[:preview_theme_id]&.to_i
theme_ids = []
if preview_theme_id = request[:preview_theme_id]&.to_i
theme_ids << preview_theme_id
end
user_option = current_user&.user_option
unless theme_id
if theme_ids.blank?
ids, seq = cookies[:theme_ids]&.split("|")
ids = ids&.split(",")&.map(&:to_i)
if ids && ids.size > 0 && seq && seq.to_i == user_option&.theme_key_seq.to_i
theme_id = ids.first
if ids.present? && seq && seq.to_i == user_option&.theme_key_seq.to_i
theme_ids = ids if guardian.allow_themes?(ids)
end
end
theme_id ||= user_option&.theme_ids&.first
theme_ids = user_option&.theme_ids || [] if theme_ids.blank?
if theme_id && !guardian.allow_themes?(theme_id)
theme_id = nil
unless guardian.allow_themes?(theme_ids)
theme_ids = []
end
theme_id ||= SiteSetting.default_theme_id
theme_id = nil if theme_id.blank? || theme_id == -1
if theme_ids.blank? && SiteSetting.default_theme_id != -1
theme_ids << SiteSetting.default_theme_id
end
@theme_id = request.env[:resolved_theme_id] = theme_id
@theme_ids = request.env[:resolved_theme_ids] = theme_ids
end
def guardian
@ -439,25 +483,6 @@ class ApplicationController < ActionController::Base
request.session_options[:skip] = true
end
def permalink_redirect_or_not_found
url = request.fullpath
permalink = Permalink.find_by_url(url)
if permalink.present?
# permalink present, redirect to that URL
if permalink.external_url
redirect_to permalink.external_url, status: :moved_permanently
elsif permalink.target_url
redirect_to "#{Discourse::base_uri}#{permalink.target_url}", status: :moved_permanently
else
raise Discourse::NotFound
end
else
# redirect to 404
raise Discourse::NotFound
end
end
def secure_session
SecureSession.new(session["secure_session_id"] ||= SecureRandom.hex)
end
@ -502,10 +527,10 @@ class ApplicationController < ActionController::Base
target = view_context.mobile_view? ? :mobile : :desktop
data =
if @theme_id
if @theme_ids.present?
{
top: Theme.lookup_field(@theme_id, target, "after_header"),
footer: Theme.lookup_field(@theme_id, target, "footer")
top: Theme.lookup_field(@theme_ids, target, "after_header"),
footer: Theme.lookup_field(@theme_ids, target, "footer")
}
else
{}
@ -685,7 +710,7 @@ class ApplicationController < ActionController::Base
@slug = params[:slug].class == String ? params[:slug] : ''
@slug = (params[:id].class == String ? params[:id] : '') if @slug.blank?
@slug.tr!('-', ' ')
@hide_google = true if SiteSetting.login_required
@hide_search = true if SiteSetting.login_required
render_to_string status: status, layout: layout, formats: [:html], template: '/exceptions/not_found'
end

View File

@ -48,11 +48,13 @@ class CategoriesController < ApplicationController
if style == "categories_and_latest_topics".freeze
@topic_list = TopicQuery.new(current_user, topic_options).list_latest
@topic_list.more_topics_url = url_for(public_send("latest_path"))
elsif style == "categories_and_top_topics".freeze
@topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
@topic_list.more_topics_url = url_for(public_send("top_path"))
end
if @topic_list.present?
if @topic_list.present? && @topic_list.topics.present?
store_preloaded(
@topic_list.preload_key,
MultiJson.dump(TopicListSerializer.new(@topic_list, scope: guardian))

View File

@ -112,7 +112,7 @@ class EmbedController < ApplicationController
end
response.headers['X-Frame-Options'] = "ALLOWALL"
rescue URI::InvalidURIError
rescue URI::Error
raise Discourse::InvalidAccess.new('invalid referer host')
end

View File

@ -1,6 +1,6 @@
class ExceptionsController < ApplicationController
skip_before_action :check_xhr, :preload_json
before_action :hide_google
before_action :hide_search
def not_found
# centralize all rendering of 404 into app controller
@ -14,8 +14,8 @@ class ExceptionsController < ApplicationController
private
def hide_google
@hide_google = true if SiteSetting.login_required
def hide_search
@hide_search = true if SiteSetting.login_required
end
end

View File

@ -98,7 +98,13 @@ class GroupsController < ApplicationController
type_filters: type_filters
},
total_rows_groups: count,
load_more_groups: groups_path(page: page + 1, type: type),
load_more_groups: groups_path(
page: page + 1,
type: type,
order: order,
asc: params[:asc],
filter: filter
),
)
end

View File

@ -344,7 +344,7 @@ class ListController < ApplicationController
parent_category_id = nil
if parent_slug_or_id.present?
parent_category_id = Category.query_parent_category(parent_slug_or_id)
permalink_redirect_or_not_found && (return) if parent_category_id.blank? && !id
raise Discourse::NotFound.new("category not found", check_permalinks: true) if parent_category_id.blank? && !id
end
@category = Category.query_category(slug_or_id, parent_category_id)
@ -355,7 +355,7 @@ class ListController < ApplicationController
(redirect_to category.url, status: 301) && return if category
end
permalink_redirect_or_not_found && (return) if !@category
raise Discourse::NotFound.new("category not found", check_permalinks: true) if !@category
@description_meta = @category.description_text
raise Discourse::NotFound unless guardian.can_see?(@category)
@ -371,7 +371,12 @@ class ListController < ApplicationController
params[:tags] = [params[:tag_id].parameterize] if params[:tag_id].present? && guardian.can_tag_pms?
TopicQuery.public_valid_options.each do |key|
options[key] = params[key]
if params.key?(key)
val = options[key] = params[key]
if !TopicQuery.validate?(key, val)
raise Discourse::InvalidParameters.new key
end
end
end
# hacky columns get special handling

View File

@ -19,10 +19,12 @@ class MetadataController < ApplicationController
end
file_info = get_file_info(logo)
display = request.user_agent =~ /iPad|iPhone/ ? 'browser' : 'standalone'
manifest = {
name: SiteSetting.title,
short_name: SiteSetting.title,
display: 'standalone',
display: display,
orientation: 'any',
start_url: Discourse.base_uri.present? ? "#{Discourse.base_uri}/" : '.',
background_color: "##{ColorScheme.hex_for_name('secondary')}",

View File

@ -8,9 +8,7 @@ class PermalinksController < ApplicationController
raise Discourse::NotFound unless permalink
if permalink.external_url
redirect_to permalink.external_url, status: :moved_permanently
elsif permalink.target_url
if permalink.target_url
redirect_to permalink.target_url, status: :moved_permanently
else
raise Discourse::NotFound

View File

@ -653,6 +653,11 @@ class PostsController < ApplicationController
result[:is_warning] = false
end
if params[:no_bump] == "true"
raise Discourse::InvalidParameters.new(:no_bump) unless guardian.can_skip_bump?
result[:no_bump] = true
end
if params[:shared_draft] == 'true'
raise Discourse::InvalidParameters.new(:shared_draft) unless guardian.can_create_shared_draft?

View File

@ -4,13 +4,7 @@ class RobotsTxtController < ApplicationController
# NOTE: order is important!
DISALLOWED_PATHS ||= %w{
/auth/cas
/auth/facebook/callback
/auth/twitter/callback
/auth/google/callback
/auth/yahoo/callback
/auth/github/callback
/auth/cas/callback
/auth/
/assets/browser-update*.js
/users/
/u/

View File

@ -88,7 +88,7 @@ class StaticController < ApplicationController
destination = uri.path
destination = "#{uri.path}?#{uri.query}" if uri.path =~ /new-topic/ || uri.path =~ /new-message/ || uri.path =~ /user-api-key/
end
rescue URI::InvalidURIError
rescue URI::Error
# Do nothing if the URI is invalid
end
end

View File

@ -29,7 +29,7 @@ class StylesheetsController < ApplicationController
# we hold of re-compilation till someone asks for asset
if target.include?("theme")
split_target, theme_id = target.split(/_(-?[0-9]+)/)
theme = Theme.find(theme_id) if theme_id
theme = Theme.find_by(id: theme_id) if theme_id.present?
else
split_target, color_scheme_id = target.split(/_(-?[0-9]+)/)
theme = Theme.find_by(color_scheme_id: color_scheme_id)

View File

@ -90,7 +90,7 @@ class TagsController < ::ApplicationController
canonical_url "#{Discourse.base_url_no_prefix}#{public_send(path_name, *(params.slice(:parent_category, :category, :tag_id).values.map { |t| t.force_encoding("UTF-8") }))}"
if @list.topics.size == 0 && params[:tag_id] != 'none' && !Tag.where(name: @tag_id).exists?
permalink_redirect_or_not_found
raise Discourse::NotFound.new("tag not found", check_permalinks: true)
else
respond_with_list(@list)
end

View File

@ -1,27 +1,26 @@
class ThemesController < ::ApplicationController
def assets
theme_id = params[:id].to_i
theme_ids = params[:ids].to_s.split("-").map(&:to_i)
if params[:id] == "default"
theme_id = nil
if params[:ids] == "default"
theme_ids = nil
else
raise Discourse::NotFound unless Theme.where(id: theme_id).exists?
raise Discourse::NotFound unless guardian.allow_themes?(theme_ids)
end
object = [:mobile, :desktop, :desktop_theme, :mobile_theme].map do |target|
link = Stylesheet::Manager.stylesheet_link_tag(target, 'all', params[:id])
if link
href = link.split(/["']/)[1]
if Rails.env.development?
href << (href.include?("?") ? "&" : "?")
href << SecureRandom.hex
end
{
target: target,
url: href
}
targets = view_context.mobile_view? ? [:mobile, :mobile_theme] : [:desktop, :desktop_theme]
targets << :admin if guardian.is_staff?
object = targets.map do |target|
Stylesheet::Manager.stylesheet_data(target, theme_ids).map do |hash|
next hash unless Rails.env.development?
dup_hash = hash.dup
dup_hash[:new_href] << (dup_hash[:new_href].include?("?") ? "&" : "?")
dup_hash[:new_href] << SecureRandom.hex
dup_hash
end
end.compact
end.flatten
render json: object.as_json
end

View File

@ -33,7 +33,8 @@ class TopicsController < ApplicationController
:move_to_inbox,
:convert_topic,
:bookmark,
:publish
:publish,
:reset_bump_date
]
before_action :consider_user_for_promotion, only: :show
@ -135,8 +136,12 @@ class TopicsController < ApplicationController
end
if ex.obj && Topic === ex.obj && guardian.can_see_topic_if_not_deleted?(ex.obj)
rescue_discourse_actions(:not_found, 410)
return
raise Discourse::NotFound.new(
"topic was deleted",
status: 410,
check_permalinks: true,
original_path: ex.obj.relative_url
)
end
raise ex
@ -716,6 +721,17 @@ class TopicsController < ApplicationController
render_json_error(ex)
end
def reset_bump_date
params.require(:id)
guardian.ensure_can_update_bumped_at!
topic = Topic.find_by(id: params[:id])
raise Discourse::NotFound.new unless topic
topic.reset_bumped_at
render body: nil
end
private
def topic_params

View File

@ -179,14 +179,15 @@ class UserAvatarsController < ApplicationController
send_file path, disposition: nil
end
protected
# consider removal of hacks some time in 2019
def get_optimized_image(upload, size)
OptimizedImage.create_for(
upload,
size,
size,
filename: upload.original_filename,
allow_animation: SiteSetting.allow_animated_avatars,
)
return if !upload
upload.get_optimized_image(size, size, allow_animation: SiteSetting.allow_animated_avatars)
# TODO decide if we want to detach here
end
end

View File

@ -60,7 +60,7 @@ class UserBadgesController < ApplicationController
if params[:reason].present?
path = begin
URI.parse(params[:reason]).path
rescue URI::InvalidURIError
rescue URI::Error
end
route = Rails.application.routes.recognize_path(path) if path

View File

@ -41,7 +41,7 @@ class Users::OmniauthCallbacksController < ApplicationController
if origin.present?
parsed = begin
URI.parse(origin)
rescue URI::InvalidURIError
rescue URI::Error
end
if parsed

View File

@ -581,7 +581,6 @@ class UsersController < ApplicationController
email_token_user = EmailToken.confirmable(token)&.user
totp_enabled = email_token_user&.totp_enabled?
backup_enabled = email_token_user&.backup_codes_enabled?
second_factor_token = params[:second_factor_token]
second_factor_method = params[:second_factor_method].to_i
confirm_email = false
@ -1079,7 +1078,7 @@ class UsersController < ApplicationController
# Using Discourse.authenticators rather than Discourse.enabled_authenticators so users can
# revoke permissions even if the admin has temporarily disabled that type of login
authenticator = Discourse.authenticators.find { |authenticator| authenticator.name == provider_name }
authenticator = Discourse.authenticators.find { |a| a.name == provider_name }
raise Discourse::NotFound if authenticator.nil? || !authenticator.can_revoke?
skip_remote = params.permit(:skip_remote)
@ -1088,9 +1087,9 @@ class UsersController < ApplicationController
hijack do
result = authenticator.revoke(user, skip_remote: skip_remote)
if result
return render json: success_json
render json: success_json
else
return render json: {
render json: {
success: false,
message: I18n.t("associated_accounts.revoke_failed", provider_name: provider_name)
}

View File

@ -177,10 +177,8 @@ module ApplicationHelper
["ar", "ur", "fa_IR", "he"].include? I18n.locale.to_s
end
def user_locale
locale = current_user.locale if current_user && SiteSetting.allow_user_locale
# changing back to default shoves a blank string there
locale.present? ? locale : SiteSetting.default_locale
def html_lang
SiteSetting.default_locale.sub("_", "-")
end
# Creates open graph and twitter card meta data
@ -350,11 +348,11 @@ module ApplicationHelper
end
end
def theme_id
def theme_ids
if customization_disabled?
nil
else
request.env[:resolved_theme_id]
request.env[:resolved_theme_ids]
end
end
@ -378,17 +376,17 @@ module ApplicationHelper
end
def theme_lookup(name)
lookup = Theme.lookup_field(theme_id, mobile_view? ? :mobile : :desktop, name)
lookup = Theme.lookup_field(theme_ids, mobile_view? ? :mobile : :desktop, name)
lookup.html_safe if lookup
end
def discourse_stylesheet_link_tag(name, opts = {})
if opts.key?(:theme_id)
id = opts[:theme_id] unless customization_disabled?
if opts.key?(:theme_ids)
ids = opts[:theme_ids] unless customization_disabled?
else
id = theme_id
ids = theme_ids
end
Stylesheet::Manager.stylesheet_link_tag(name, 'all', id)
Stylesheet::Manager.stylesheet_link_tag(name, 'all', ids)
end
end

View File

@ -106,7 +106,7 @@ module UserNotificationsHelper
def url_for_email(href)
URI(href).host.present? ? href : UrlHelper.absolute("#{Discourse.base_uri}#{href}")
rescue URI::InvalidURIError, URI::InvalidComponentError
rescue URI::Error
href
end

View File

@ -119,6 +119,11 @@ module Jobs
RailsMultisite::ConnectionManagement.all_dbs
end
logster_env = {}
Logster.add_to_env(logster_env, :current_db, 'default')
Logster.add_to_env(logster_env, :job, self.class.to_s)
Thread.current[Logster::Logger::LOGSTER_ENV] = logster_env
exceptions = []
dbs.each do |db|
begin
@ -129,6 +134,7 @@ module Jobs
I18n.locale = SiteSetting.default_locale || "en"
I18n.ensure_all_loaded!
begin
Logster.add_to_env(logster_env, :db, db)
execute(opts)
rescue => e
exception[:ex] = e
@ -140,6 +146,7 @@ module Jobs
exception[:other] = { problem_db: db }
ensure
total_db_time += Instrumenter.stats.duration_ms
Thread.current[Logster::Logger::LOGSTER_ENV] = nil
end
end
@ -147,6 +154,8 @@ module Jobs
end
end
Thread.current[Logster::Logger::LOGSTER_ENV] = nil
if exceptions.length > 0
exceptions.each do |exception_hash|
Discourse.handle_job_exception(exception_hash[:ex], error_context(opts, exception_hash[:code], exception_hash[:other]))

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