Version bump
This commit is contained in:
commit
5771b29d19
@ -46,6 +46,7 @@ cache:
|
||||
- vendor/bundle
|
||||
|
||||
before_install:
|
||||
- wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh
|
||||
- nvm install node
|
||||
- node --version
|
||||
- gem install bundler
|
||||
|
||||
2
Gemfile
2
Gemfile
@ -36,7 +36,7 @@ gem 'redis-namespace'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.8.3'
|
||||
|
||||
gem 'onebox', '1.8.69'
|
||||
gem 'onebox', '1.8.71'
|
||||
|
||||
gem 'http_accept_language', '~>2.0.5', require: false
|
||||
|
||||
|
||||
36
Gemfile.lock
36
Gemfile.lock
@ -131,7 +131,7 @@ GEM
|
||||
exifr (1.3.4)
|
||||
fabrication (2.20.1)
|
||||
fakeweb (1.3.0)
|
||||
faraday (0.12.2)
|
||||
faraday (0.15.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-http-cache (1.3.1)
|
||||
faraday (~> 0.8)
|
||||
@ -152,7 +152,7 @@ GEM
|
||||
activesupport (>= 4.2.0)
|
||||
guess_html_encoding (0.0.11)
|
||||
hashdiff (0.3.7)
|
||||
hashie (3.5.7)
|
||||
hashie (3.6.0)
|
||||
highline (1.7.10)
|
||||
hiredis (0.6.1)
|
||||
hkdf (0.3.0)
|
||||
@ -168,7 +168,7 @@ GEM
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jwt (1.5.6)
|
||||
jwt (2.1.0)
|
||||
kgio (2.11.2)
|
||||
kramdown (1.17.0)
|
||||
libv8 (6.7.288.46.1)
|
||||
@ -184,7 +184,7 @@ GEM
|
||||
logstash-event (1.2.02)
|
||||
logstash-logger (0.26.1)
|
||||
logstash-event (~> 1.2)
|
||||
logster (1.3.1)
|
||||
logster (1.3.4)
|
||||
loofah (2.2.3)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
@ -222,25 +222,25 @@ GEM
|
||||
nokogumbo (1.5.0)
|
||||
nokogiri
|
||||
oauth (0.5.4)
|
||||
oauth2 (1.4.0)
|
||||
faraday (>= 0.8, < 0.13)
|
||||
jwt (~> 1.0)
|
||||
oauth2 (1.4.1)
|
||||
faraday (>= 0.8, < 0.16.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
octokit (4.9.0)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
oj (3.6.2)
|
||||
omniauth (1.8.1)
|
||||
hashie (>= 3.4.6, < 3.6.0)
|
||||
omniauth (1.9.0)
|
||||
hashie (>= 3.4.6, < 3.7.0)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-facebook (5.0.0)
|
||||
omniauth-oauth2 (~> 1.2)
|
||||
omniauth-github (1.3.0)
|
||||
omniauth (~> 1.5)
|
||||
omniauth-oauth2 (>= 1.4.0, < 2.0)
|
||||
omniauth-google-oauth2 (0.5.3)
|
||||
jwt (>= 1.5)
|
||||
omniauth-google-oauth2 (0.6.0)
|
||||
jwt (>= 2.0)
|
||||
omniauth (>= 1.1.1)
|
||||
omniauth-oauth2 (>= 1.5)
|
||||
omniauth-instagram (1.3.0)
|
||||
@ -249,16 +249,16 @@ GEM
|
||||
omniauth-oauth (1.1.0)
|
||||
oauth
|
||||
omniauth (~> 1.0)
|
||||
omniauth-oauth2 (1.5.0)
|
||||
omniauth-oauth2 (1.6.0)
|
||||
oauth2 (~> 1.1)
|
||||
omniauth (~> 1.2)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-openid (1.0.1)
|
||||
omniauth (~> 1.0)
|
||||
rack-openid (~> 1.3.1)
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.8.69)
|
||||
onebox (1.8.71)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
multi_json (~> 1.11)
|
||||
@ -357,7 +357,7 @@ GEM
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-support (3.7.1)
|
||||
rtlit (0.0.5)
|
||||
rubocop (0.60.0)
|
||||
rubocop (0.61.1)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5, != 2.5.1.1)
|
||||
@ -426,7 +426,7 @@ GEM
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
unicode-display_width (1.4.0)
|
||||
unicode-display_width (1.4.1)
|
||||
unicorn (5.4.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
@ -512,7 +512,7 @@ DEPENDENCIES
|
||||
omniauth-oauth2
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
onebox (= 1.8.69)
|
||||
onebox (= 1.8.71)
|
||||
openid-redis-store
|
||||
pg
|
||||
pry-nav
|
||||
@ -557,4 +557,4 @@ DEPENDENCIES
|
||||
webpush
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.1
|
||||
1.17.2
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
export default Ember.Component.extend({
|
||||
tagName: "",
|
||||
|
||||
buffer: "",
|
||||
editing: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set("editing", false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
edit() {
|
||||
this.set("buffer", this.get("value"));
|
||||
this.toggleProperty("editing");
|
||||
},
|
||||
|
||||
save() {
|
||||
// Action has to toggle 'editing' property.
|
||||
this.action(this.get("buffer"));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,40 @@
|
||||
import { setting } from "discourse/lib/computed";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ["admin-report-storage-stats"],
|
||||
|
||||
backupLocation: setting("backup_location"),
|
||||
backupStats: Ember.computed.alias("model.data.backups"),
|
||||
uploadStats: Ember.computed.alias("model.data.uploads"),
|
||||
|
||||
@computed("backupStats")
|
||||
showBackupStats(stats) {
|
||||
return stats && this.currentUser.admin;
|
||||
},
|
||||
|
||||
@computed("backupLocation")
|
||||
backupLocationName(backupLocation) {
|
||||
return I18n.t(`admin.backups.location.${backupLocation}`);
|
||||
},
|
||||
|
||||
@computed("backupStats.used_bytes")
|
||||
usedBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
|
||||
@computed("backupStats.free_bytes")
|
||||
freeBackupSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
|
||||
@computed("uploadStats.used_bytes")
|
||||
usedUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
},
|
||||
|
||||
@computed("uploadStats.free_bytes")
|
||||
freeUploadSpace(bytes) {
|
||||
return I18n.toHumanSize(bytes);
|
||||
}
|
||||
});
|
||||
@ -106,7 +106,12 @@ export default Ember.Component.extend({
|
||||
pages(data, perPage, page) {
|
||||
if (!data || data.length <= perPage) return [];
|
||||
|
||||
let pages = [...Array(Math.ceil(data.length / perPage)).keys()].map(v => {
|
||||
const pagesIndexes = [];
|
||||
for (let i = 0; i < Math.ceil(data.length / perPage); i++) {
|
||||
pagesIndexes.push(i);
|
||||
}
|
||||
|
||||
let pages = pagesIndexes.map(v => {
|
||||
return {
|
||||
page: v + 1,
|
||||
index: v,
|
||||
|
||||
@ -16,24 +16,12 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
|
||||
isLoading: false,
|
||||
dashboardFetchedAt: null,
|
||||
exceptionController: Ember.inject.controller("exception"),
|
||||
diskSpace: Ember.computed.alias("model.attributes.disk_space"),
|
||||
logSearchQueriesEnabled: setting("log_search_queries"),
|
||||
lastBackupTakenAt: Ember.computed.alias(
|
||||
"model.attributes.last_backup_taken_at"
|
||||
),
|
||||
shouldDisplayDurability: Ember.computed.and("diskSpace"),
|
||||
basePath: Discourse.BaseUri,
|
||||
|
||||
@computed
|
||||
activityMetrics() {
|
||||
return [
|
||||
"page_view_total_reqs",
|
||||
"visits",
|
||||
"time_to_first_response",
|
||||
"likes",
|
||||
"flags",
|
||||
"user_to_user_private_messages_with_replies"
|
||||
];
|
||||
@computed("siteSettings.dashboard_general_tab_activity_metrics")
|
||||
activityMetrics(metrics) {
|
||||
return (metrics || "").split("|").filter(m => m);
|
||||
},
|
||||
|
||||
@computed
|
||||
@ -65,7 +53,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
|
||||
trendingSearchFilters() {
|
||||
return {
|
||||
startDate: moment()
|
||||
.subtract(6, "days")
|
||||
.subtract(1, "month")
|
||||
.startOf("day"),
|
||||
endDate: this.get("today")
|
||||
};
|
||||
@ -87,6 +75,7 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
|
||||
|
||||
usersByTypeReport: staticReport("users_by_type"),
|
||||
usersByTrustLevelReport: staticReport("users_by_trust_level"),
|
||||
storageReport: staticReport("storage_report"),
|
||||
|
||||
fetchDashboard() {
|
||||
if (this.get("isLoading")) return;
|
||||
@ -129,13 +118,6 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
|
||||
.format("LLL");
|
||||
},
|
||||
|
||||
@computed("lastBackupTakenAt")
|
||||
backupTimestamp(lastBackupTakenAt) {
|
||||
return moment(lastBackupTakenAt)
|
||||
.tz(moment.tz.guess())
|
||||
.format("LLL");
|
||||
},
|
||||
|
||||
_reportsForPeriodURL(period) {
|
||||
return Discourse.getURL(`/admin?period=${period}`);
|
||||
}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
const { get } = Ember;
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
filter: null,
|
||||
|
||||
@computed("model.[]", "filter")
|
||||
filterReports(reports, filter) {
|
||||
if (filter) {
|
||||
filter = filter.toLowerCase();
|
||||
return reports.filter(report => {
|
||||
return (
|
||||
(get(report, "title") || "").toLowerCase().indexOf(filter) > -1 ||
|
||||
(get(report, "description") || "").toLowerCase().indexOf(filter) > -1
|
||||
);
|
||||
});
|
||||
}
|
||||
return reports;
|
||||
},
|
||||
|
||||
actions: {
|
||||
filterReports(filter) {
|
||||
Ember.run.debounce(this, this._performFiltering, filter, 250);
|
||||
}
|
||||
},
|
||||
|
||||
_performFiltering(filter) {
|
||||
this.set("filter", filter);
|
||||
}
|
||||
});
|
||||
@ -4,7 +4,6 @@ import AdminUser from "admin/models/admin-user";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
const ATTRIBUTES = [
|
||||
"disk_space",
|
||||
"admins",
|
||||
"moderators",
|
||||
"silenced",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Ember.Controller.extend(GrantBadgeController, {
|
||||
adminUser: Ember.inject.controller(),
|
||||
@ -70,9 +71,8 @@ export default Ember.Controller.extend(GrantBadgeController, {
|
||||
}
|
||||
});
|
||||
},
|
||||
function() {
|
||||
// Failure
|
||||
bootbox.alert(I18n.t("generic_error"));
|
||||
function(error) {
|
||||
popupAjaxError(error);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
@ -7,9 +7,6 @@ import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend(CanCheckEmails, {
|
||||
adminTools: Ember.inject.service(),
|
||||
editingUsername: false,
|
||||
editingName: false,
|
||||
editingTitle: false,
|
||||
originalPrimaryGroupId: null,
|
||||
customGroupIdsBuffer: null,
|
||||
availableGroups: null,
|
||||
@ -244,17 +241,12 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
this.get("adminTools").showSilenceModal(this.get("model"));
|
||||
},
|
||||
|
||||
toggleUsernameEdit() {
|
||||
this.set("userUsernameValue", this.get("model.username"));
|
||||
this.toggleProperty("editingUsername");
|
||||
},
|
||||
|
||||
saveUsername() {
|
||||
saveUsername(newUsername) {
|
||||
const oldUsername = this.get("model.username");
|
||||
this.set("model.username", this.get("userUsernameValue"));
|
||||
this.set("model.username", newUsername);
|
||||
|
||||
return ajax(`/users/${oldUsername.toLowerCase()}/preferences/username`, {
|
||||
data: { new_username: this.get("userUsernameValue") },
|
||||
data: { new_username: newUsername },
|
||||
type: "PUT"
|
||||
})
|
||||
.catch(e => {
|
||||
@ -264,19 +256,14 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
.finally(() => this.toggleProperty("editingUsername"));
|
||||
},
|
||||
|
||||
toggleNameEdit() {
|
||||
this.set("userNameValue", this.get("model.name"));
|
||||
this.toggleProperty("editingName");
|
||||
},
|
||||
|
||||
saveName() {
|
||||
saveName(newName) {
|
||||
const oldName = this.get("model.name");
|
||||
this.set("model.name", this.get("userNameValue"));
|
||||
this.set("model.name", newName);
|
||||
|
||||
return ajax(
|
||||
userPath(`${this.get("model.username").toLowerCase()}.json`),
|
||||
{
|
||||
data: { name: this.get("userNameValue") },
|
||||
data: { name: newName },
|
||||
type: "PUT"
|
||||
}
|
||||
)
|
||||
@ -287,24 +274,19 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||
.finally(() => this.toggleProperty("editingName"));
|
||||
},
|
||||
|
||||
toggleTitleEdit() {
|
||||
this.set("userTitleValue", this.get("model.title"));
|
||||
this.toggleProperty("editingTitle");
|
||||
},
|
||||
saveTitle(newTitle) {
|
||||
const oldTitle = this.get("model.title");
|
||||
|
||||
saveTitle() {
|
||||
const prevTitle = this.get("userTitleValue");
|
||||
|
||||
this.set("model.title", this.get("userTitleValue"));
|
||||
this.set("model.title", newTitle);
|
||||
return ajax(
|
||||
userPath(`${this.get("model.username").toLowerCase()}.json`),
|
||||
{
|
||||
data: { title: this.get("userTitleValue") },
|
||||
data: { title: newTitle },
|
||||
type: "PUT"
|
||||
}
|
||||
)
|
||||
.catch(e => {
|
||||
this.set("model.title", prevTitle);
|
||||
this.set("model.title", oldTitle);
|
||||
popupAjaxError(e);
|
||||
})
|
||||
.finally(() => this.toggleProperty("editingTitle"));
|
||||
|
||||
@ -2,8 +2,9 @@ import debounce from "discourse/lib/debounce";
|
||||
import { i18n } from "discourse/lib/computed";
|
||||
import AdminUser from "admin/models/admin-user";
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
import CanCheckEmails from "discourse/mixins/can-check-emails";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
export default Ember.Controller.extend(CanCheckEmails, {
|
||||
query: null,
|
||||
queryParams: ["order", "ascending"],
|
||||
order: null,
|
||||
|
||||
@ -17,6 +17,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
checkPrivate: Ember.computed.match("uploadUrl", /^git/),
|
||||
localFile: null,
|
||||
uploadUrl: null,
|
||||
urlPlaceholder: "https://github.com/discourse/sample_theme",
|
||||
|
||||
@computed("loading", "remote", "uploadUrl", "local", "localFile")
|
||||
importDisabled(isLoading, isRemote, uploadUrl, isLocal, localFile) {
|
||||
@ -25,6 +26,10 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
|
||||
@observes("privateChecked")
|
||||
privateWasChecked() {
|
||||
this.get("privateChecked")
|
||||
? this.set("urlPlaceholder", "git@github.com:discourse/sample_theme.git")
|
||||
: this.set("urlPlaceholder", "https://github.com/discourse/sample_theme");
|
||||
|
||||
const checked = this.get("privateChecked");
|
||||
if (checked && !this._keyLoading) {
|
||||
this._keyLoading = true;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
const GENERAL_ATTRIBUTES = ["disk_space", "updated_at", "last_backup_taken_at"];
|
||||
const GENERAL_ATTRIBUTES = ["updated_at"];
|
||||
|
||||
const AdminDashboardNext = Discourse.Model.extend({});
|
||||
|
||||
|
||||
@ -272,6 +272,7 @@ const Report = Discourse.Model.extend({
|
||||
if (type === "seconds") return this._secondsLabel(value);
|
||||
if (type === "link") return this._linkLabel(label.properties, row);
|
||||
if (type === "percent") return this._percentLabel(value);
|
||||
if (type === "bytes") return this._bytesLabel(value);
|
||||
if (type === "number") {
|
||||
return this._numberLabel(value, opts);
|
||||
}
|
||||
@ -381,6 +382,13 @@ const Report = Discourse.Model.extend({
|
||||
};
|
||||
},
|
||||
|
||||
_bytesLabel(value) {
|
||||
return {
|
||||
value,
|
||||
formatedValue: I18n.toHumanSize(value)
|
||||
};
|
||||
},
|
||||
|
||||
_dateLabel(value, date, format = "LL") {
|
||||
return {
|
||||
value,
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return ajax("/admin/reports").then(json => json);
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.setProperties({ model: model.reports, filter: null });
|
||||
}
|
||||
});
|
||||
@ -1,13 +1,5 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return ajax("/admin/reports").then(json => {
|
||||
return json;
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.setProperties({ model: model.reports });
|
||||
beforeModel() {
|
||||
this.transitionTo("admin.dashboardNextReports");
|
||||
}
|
||||
});
|
||||
|
||||
@ -12,6 +12,10 @@ export default function() {
|
||||
path: "/dashboard/security",
|
||||
resetNamespace: true
|
||||
});
|
||||
this.route("admin.dashboardNextReports", {
|
||||
path: "/dashboard/reports",
|
||||
resetNamespace: true
|
||||
});
|
||||
});
|
||||
|
||||
this.route(
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if site.isReadOnly}}
|
||||
{{d-button class="btn-default" icon="eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.disable.title" label="admin.backups.read_only.disable.label"}}
|
||||
{{d-button class="btn-default" icon="far-eye" 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="eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
|
||||
{{d-button class="btn-default" icon="far-eye" action="toggleReadOnlyMode" disabled=status.isOperationRunning title="admin.backups.read_only.enable.title" label="admin.backups.read_only.enable.label"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<table class="grid">
|
||||
@ -31,10 +31,10 @@
|
||||
title="admin.backups.operations.download.title"
|
||||
label="admin.backups.operations.download.label"}}
|
||||
{{#if status.isOperationRunning}}
|
||||
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}}
|
||||
{{d-button icon="far-trash-alt" action="destroyBackup" actionParam=backup class="btn-danger" disabled="true" title="admin.backups.operations.is_running"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=status.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{else}}
|
||||
{{d-button icon="trash-o" action="destroyBackup" actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}}
|
||||
{{d-button icon="far-trash-alt" action="destroyBackup" actionParam=backup class="btn-danger" title="admin.backups.operations.destroy.title"}}
|
||||
{{d-button icon="play" action="startRestore" actionParam=backup disabled=status.restoreDisabled class="btn-default" title=restoreTitle label="admin.backups.operations.restore.label"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
<div class='field'>{{i18n name}}</div>
|
||||
<div class='value'>
|
||||
{{#if editing}}
|
||||
{{text-field value=buffer autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "edit"}}>{{value}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if editing}}
|
||||
{{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"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -0,0 +1,33 @@
|
||||
{{#if showBackupStats}}
|
||||
<div class="backups">
|
||||
<h3 class="storage-stats-title">
|
||||
<a href="{{get-url '/admin/backups'}}">{{d-icon "archive"}} {{i18n "admin.dashboard.backups"}}</a>
|
||||
</h3>
|
||||
<p>
|
||||
{{#if backupStats.free_bytes}}
|
||||
{{i18n "admin.dashboard.space_used_and_free" usedSize=usedBackupSpace freeSize=freeBackupSpace}}
|
||||
{{else}}
|
||||
{{i18n "admin.dashboard.space_used" usedSize=usedBackupSpace}}
|
||||
{{/if}}
|
||||
|
||||
<br>
|
||||
{{i18n "admin.dashboard.backup_count" count=backupStats.count location=backupLocationName}}
|
||||
|
||||
{{#if backupStats.last_backup_taken_at}}
|
||||
<br>
|
||||
{{{i18n "admin.dashboard.lastest_backup" date=(format-date backupStats.last_backup_taken_at leaveAgo="true")}}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="uploads">
|
||||
<h3 class="storage-stats-title">{{d-icon "upload"}} {{i18n "admin.dashboard.uploads"}}</h3>
|
||||
<p>
|
||||
{{#if uploadStats.free_bytes}}
|
||||
{{i18n "admin.dashboard.space_used_and_free" usedSize=usedUploadSpace freeSize=freeUploadSpace}}
|
||||
{{else}}
|
||||
{{i18n "admin.dashboard.space_used" usedSize=usedUploadSpace}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
@ -6,7 +6,7 @@
|
||||
<ul class="breadcrumb">
|
||||
{{#if showAllReportsLink}}
|
||||
<li class="item all-reports">
|
||||
{{#link-to "adminReports" class="report-url"}}
|
||||
{{#link-to "admin.dashboardNextReports" class="report-url"}}
|
||||
{{i18n "admin.dashboard.all_reports"}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
@ -112,17 +112,15 @@
|
||||
{{#if showFilteringUI}}
|
||||
<div class="filters">
|
||||
{{#if showModes}}
|
||||
<ul class="modes">
|
||||
<div class="modes">
|
||||
{{#each displayedModes as |displayedMode|}}
|
||||
<li class="mode">
|
||||
{{d-button
|
||||
action="changeMode"
|
||||
actionParam=displayedMode.mode
|
||||
class=displayedMode.cssClass
|
||||
icon=displayedMode.icon}}
|
||||
</li>
|
||||
{{d-button
|
||||
action="changeMode"
|
||||
actionParam=displayedMode.mode
|
||||
class=displayedMode.cssClass
|
||||
icon=displayedMode.icon}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showDatesOptions}}
|
||||
|
||||
@ -26,6 +26,11 @@
|
||||
{{i18n "admin.dashboard.security_tab"}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li class="navigation-item reports">
|
||||
{{#link-to "admin.dashboardNextReports" class="navigation-link"}}
|
||||
{{i18n "admin.dashboard.reports_tab"}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{outlet}}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<div class="period-section">
|
||||
<div class="section-title">
|
||||
<h2>
|
||||
<a href="{{get-url '/admin/reports'}}">
|
||||
<a href="{{get-url '/admin/dashboard/reports'}}">
|
||||
{{i18n "admin.dashboard.community_health"}}
|
||||
</a>
|
||||
</h2>
|
||||
@ -56,40 +56,38 @@
|
||||
|
||||
<div class="section-columns">
|
||||
<div class="section-column">
|
||||
<div class="admin-report activity-metrics">
|
||||
<div class="header">
|
||||
<ul class="breadcrumb">
|
||||
<li class="item report">
|
||||
{{#link-to "adminReports" class="report-url"}}
|
||||
{{i18n "admin.dashboard.activity_metrics"}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="report-body">
|
||||
<div class="counters-list">
|
||||
<div class="counters-header">
|
||||
<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>
|
||||
{{#if activityMetrics.length}}
|
||||
<div class="admin-report activity-metrics">
|
||||
<div class="header">
|
||||
<ul class="breadcrumb">
|
||||
<li class="item report">
|
||||
{{#link-to "adminReports" class="report-url"}}
|
||||
{{i18n "admin.dashboard.activity_metrics"}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="report-body">
|
||||
<div class="counters-list">
|
||||
<div class="counters-header">
|
||||
<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|}}
|
||||
{{admin-report
|
||||
showHeader=false
|
||||
filters=activityMetricsFilters
|
||||
forcedModes="counters"
|
||||
dataSourceName=metric}}
|
||||
{{/each}}
|
||||
{{#each activityMetrics as |metric|}}
|
||||
{{admin-report
|
||||
showHeader=false
|
||||
filters=activityMetricsFilters
|
||||
forcedModes="counters"
|
||||
dataSourceName=metric}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#link-to "adminReports"}}
|
||||
{{i18n "admin.dashboard.all_reports"}}
|
||||
{{/link-to}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="user-metrics">
|
||||
{{#conditional-loading-section isLoading=isLoading}}
|
||||
@ -103,51 +101,26 @@
|
||||
{{/conditional-loading-section}}
|
||||
</div>
|
||||
|
||||
{{#conditional-loading-section isLoading=isLoading title=(i18n "admin.dashboard.backups")}}
|
||||
<div class="misc">
|
||||
<div class="misc">
|
||||
{{admin-report
|
||||
forcedModes="storage-stats"
|
||||
dataSourceName="storage_stats"
|
||||
showHeader=false}}
|
||||
|
||||
{{#if shouldDisplayDurability}}
|
||||
<div class="durability">
|
||||
{{#if currentUser.admin}}
|
||||
<div class="backups">
|
||||
<h3 class="durability-title">
|
||||
<a href="{{get-url '/admin/backups'}}">{{d-icon "archive"}} {{i18n "admin.dashboard.backups"}}</a>
|
||||
</h3>
|
||||
<p>
|
||||
{{diskSpace.backups_used}} ({{i18n "admin.dashboard.space_free" size=diskSpace.backups_free}})
|
||||
|
||||
{{#if lastBackupTakenAt}}
|
||||
<br />
|
||||
{{{i18n "admin.dashboard.lastest_backup" date=backupTimestamp}}}
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="uploads">
|
||||
<h3 class="durability-title">{{d-icon "upload"}} {{i18n "admin.dashboard.uploads"}}</h3>
|
||||
<p>
|
||||
{{diskSpace.uploads_used}} ({{i18n "admin.dashboard.space_free" size=diskSpace.uploads_free}})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="last-dashboard-update">
|
||||
<div>
|
||||
<div class="last-dashboard-update">
|
||||
<div>
|
||||
<h4>{{i18n "admin.dashboard.last_updated"}} </h4>
|
||||
<p>{{updatedTimestamp}}</p>
|
||||
<a rel="noopener" target="_blank" href="https://meta.discourse.org/tags/release-notes" class="btn btn-default">
|
||||
{{i18n "admin.dashboard.whats_new_in_discourse"}}
|
||||
</a>
|
||||
</div>
|
||||
<a rel="noopener" target="_blank" href="https://meta.discourse.org/tags/release-notes" class="btn btn-default">
|
||||
{{i18n "admin.dashboard.whats_new_in_discourse"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{i18n 'admin.dashboard.find_old'}} {{#link-to 'admin.dashboard'}}{{i18n "admin.dashboard.old_link"}}{{/link-to}}
|
||||
</p>
|
||||
{{/conditional-loading-section}}
|
||||
<p>
|
||||
{{i18n 'admin.dashboard.find_old'}} {{#link-to 'admin.dashboard'}}{{i18n "admin.dashboard.old_link"}}{{/link-to}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section-column">
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="moderators-activity section">
|
||||
<div class="section-title">
|
||||
<h2>
|
||||
<a href="{{get-url '/admin/reports/moderators_activity'}}">
|
||||
<a href="{{get-url '/admin/dashboard/reports/moderators_activity'}}">
|
||||
{{i18n "admin.dashboard.moderators_activity"}}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
{{#conditional-loading-spinner condition=isLoading}}
|
||||
<div class="reports-index section">
|
||||
<div class="section-title">
|
||||
<h2>{{i18n "admin.reports.title"}}</h2>
|
||||
{{input
|
||||
class="filter-reports-input"
|
||||
input=(action "filterReports" value="target.value")
|
||||
placeholder=(i18n "admin.dashboard.filter_reports")
|
||||
autofocus=true}}
|
||||
</div>
|
||||
|
||||
<ul class="reports-list">
|
||||
{{#each filterReports as |report|}}
|
||||
<li class="report">
|
||||
{{#link-to 'adminReports.show' report.type}}
|
||||
<h3 class="report-title">{{report.title}}</h3>
|
||||
|
||||
{{#if report.description}}
|
||||
<p class="report-description">
|
||||
{{report.description}}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/conditional-loading-spinner}}
|
||||
@ -15,7 +15,7 @@
|
||||
{{#if remote}}
|
||||
<div class="inputs">
|
||||
<div class='repo'>
|
||||
{{input value=uploadUrl placeholder="https://github.com/discourse/sample_theme"}}
|
||||
{{input value=uploadUrl placeholder=urlPlaceholder}}
|
||||
<span class="description">{{i18n 'admin.customize.theme.import_web_tip'}}</span>
|
||||
</div>
|
||||
<div class='branch'>
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
<h3>{{i18n "admin.reports.title"}}</h3>
|
||||
|
||||
<ul class="reports-list">
|
||||
{{#each model as |report|}}
|
||||
<li class="report">
|
||||
{{#link-to 'adminReports.show' report.type}}
|
||||
<h4 class="report-title">{{report.title}}</h4>
|
||||
{{/link-to}}
|
||||
|
||||
{{#if report.description}}
|
||||
<p class="report-description">
|
||||
{{report.description}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
@ -10,8 +10,7 @@
|
||||
<thead>
|
||||
<th class="col heading term">{{i18n 'admin.logs.search_logs.term'}}</th>
|
||||
<th class="col heading">{{i18n 'admin.logs.search_logs.searches'}}</th>
|
||||
<th class="col heading">{{i18n 'admin.logs.search_logs.click_through'}}</th>
|
||||
<th class="col heading" title="{{i18n 'admin.logs.search_logs.unique_title'}}">{{i18n 'admin.logs.search_logs.unique'}}</th>
|
||||
<th class="col heading">{{i18n 'admin.logs.search_logs.click_through_rate'}}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each model as |item|}}
|
||||
@ -20,8 +19,7 @@
|
||||
{{#link-to 'adminSearchLogs.term' item.term}}{{item.term}}{{/link-to}}
|
||||
</td>
|
||||
<td class="col"><div class="label">{{i18n 'admin.logs.search_logs.searches'}}</div>{{item.searches}}</td>
|
||||
<td class="col"><div class="label">{{i18n 'admin.logs.search_logs.click_through'}}</div>{{item.click_through}}</td>
|
||||
<td class="col"><div class="label">{{i18n 'admin.logs.search_logs.unique'}}</div>{{item.unique_searches}}</td>
|
||||
<td class="col"><div class="label">{{i18n 'admin.logs.search_logs.click_through_rate'}}</div>{{item.ctr}}%</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
|
||||
@ -19,41 +19,17 @@
|
||||
</div>
|
||||
|
||||
<div class='display-row username'>
|
||||
<div class='field'>{{i18n 'user.username.title'}}</div>
|
||||
<div class='value'>
|
||||
{{#if editingUsername}}
|
||||
{{text-field value=userUsernameValue autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "toggleUsernameEdit"}}>{{model.username}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if editingUsername}}
|
||||
{{d-button class="btn-default" action="saveUsername" label="admin.user_fields.save"}}
|
||||
<a href {{action "toggleUsernameEdit"}}>{{i18n 'cancel'}}</a>
|
||||
{{else}}
|
||||
{{d-button class="btn-default" action="toggleUsernameEdit" icon="pencil"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{admin-editable-field name='user.username.title'
|
||||
value=model.username
|
||||
action=(action 'saveUsername')
|
||||
editing=editingUsername}}
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'user.name.title'}}</div>
|
||||
<div class='value'>
|
||||
{{#if editingName}}
|
||||
{{text-field value=userNameValue autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "toggleNameEdit"}}>{{model.name}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if editingName}}
|
||||
{{d-button class="btn-default" action="saveName" label="admin.user_fields.save"}}
|
||||
<a href {{action "toggleNameEdit"}}>{{i18n 'cancel'}}</a>
|
||||
{{else}}
|
||||
{{d-button class="btn-default" action="toggleNameEdit" icon="pencil"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{admin-editable-field name='user.name.title'
|
||||
value=model.name
|
||||
action=(action 'saveName')
|
||||
editing=editingName}}
|
||||
</div>
|
||||
|
||||
{{plugin-outlet name="admin-user-below-names" args=(hash user=model) tagName='' connectorTagName=''}}
|
||||
@ -130,22 +106,10 @@
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n 'user.title.title'}}</div>
|
||||
<div class='value'>
|
||||
{{#if editingTitle}}
|
||||
{{text-field value=userTitleValue autofocus="autofocus"}}
|
||||
{{else}}
|
||||
<span {{action "toggleTitleEdit"}}>{{model.title}} </span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if editingTitle}}
|
||||
{{d-button class="btn-default" action="saveTitle" label="admin.user_fields.save"}}
|
||||
<a href {{action "toggleTitleEdit"}}>{{i18n 'cancel'}}</a>
|
||||
{{else}}
|
||||
{{d-button class="btn-default" action="toggleTitleEdit" icon="pencil"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{admin-editable-field name='user.title.title'
|
||||
value=model.title
|
||||
action=(action 'saveTitle')
|
||||
editing=editingTitle}}
|
||||
</div>
|
||||
|
||||
<div class='display-row last-ip'>
|
||||
|
||||
@ -7,9 +7,9 @@
|
||||
|
||||
<div class="admin-title">
|
||||
<h2>{{title}}</h2>
|
||||
{{#unless showEmails}}
|
||||
{{#if canCheckEmails}}
|
||||
<button {{action "showEmails"}} class="show-emails btn btn-default">{{i18n 'admin.users.show_emails'}}</button>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='username controls'>
|
||||
{{text-field value=listFilter placeholder=searchHint}}
|
||||
|
||||
@ -28,7 +28,8 @@ export default Em.Component.extend(UploadMixin, {
|
||||
return {
|
||||
type: "PUT",
|
||||
dataType: "xml",
|
||||
autoUpload: false
|
||||
autoUpload: false,
|
||||
multipart: false
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import debounce from "discourse/lib/debounce";
|
||||
import { searchForTerm } from "discourse/lib/search";
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
loading: null,
|
||||
noResults: null,
|
||||
messages: null,
|
||||
|
||||
@observes("messageTitle")
|
||||
messageTitleChanged() {
|
||||
this.setProperties({
|
||||
loading: true,
|
||||
noResults: true,
|
||||
selectedTopicId: null
|
||||
});
|
||||
this.search(this.get("messageTitle"));
|
||||
},
|
||||
|
||||
@observes("messages")
|
||||
messagesChanged() {
|
||||
const messages = this.get("messages");
|
||||
if (messages) {
|
||||
this.set("noResults", messages.length === 0);
|
||||
}
|
||||
this.set("loading", false);
|
||||
},
|
||||
|
||||
search: debounce(function(title) {
|
||||
const currentTopicId = this.get("currentTopicId");
|
||||
|
||||
if (Em.isEmpty(title)) {
|
||||
this.setProperties({ messages: null, loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
searchForTerm(title, {
|
||||
typeFilter: "private_messages",
|
||||
searchForId: true
|
||||
}).then(results => {
|
||||
if (results && results.posts && results.posts.length > 0) {
|
||||
this.set(
|
||||
"messages",
|
||||
results.posts
|
||||
.mapBy("topic")
|
||||
.filter(t => t.get("id") !== currentTopicId)
|
||||
);
|
||||
} else {
|
||||
this.setProperties({ messages: null, loading: false });
|
||||
}
|
||||
});
|
||||
}, 300),
|
||||
|
||||
actions: {
|
||||
chooseMessage(message) {
|
||||
const messageId = Em.get(message, "id");
|
||||
this.set("selectedTopicId", messageId);
|
||||
Ember.run.next(() =>
|
||||
$(`#choose-message-${messageId}`).prop("checked", "true")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -25,5 +25,22 @@ export default Em.Component.extend(UploadMixin, {
|
||||
|
||||
uploadDone() {
|
||||
bootbox.alert(I18n.t("user.invited.bulk_invite.success"));
|
||||
}
|
||||
},
|
||||
|
||||
uploadOptions() {
|
||||
return { autoUpload: false };
|
||||
},
|
||||
|
||||
_init: function() {
|
||||
const $upload = this.$();
|
||||
|
||||
$upload.on("fileuploadadd", (e, data) => {
|
||||
bootbox.confirm(
|
||||
I18n.t("user.invited.bulk_invite.confirmation_message"),
|
||||
I18n.t("cancel"),
|
||||
I18n.t("go_ahead"),
|
||||
result => (result ? data.submit() : data.abort())
|
||||
);
|
||||
});
|
||||
}.on("didInsertElement")
|
||||
});
|
||||
|
||||
@ -15,6 +15,11 @@ export default Ember.Component.extend({
|
||||
return publicExit && userIsGroupUser;
|
||||
},
|
||||
|
||||
@computed("model.allow_membership_requests", "userIsGroupUser")
|
||||
canRequestMembership(allowMembershipRequests, userIsGroupUser) {
|
||||
return allowMembershipRequests && !userIsGroupUser;
|
||||
},
|
||||
|
||||
@computed("model.is_group_user")
|
||||
userIsGroupUser(isGroupUser) {
|
||||
return !!isGroupUser;
|
||||
|
||||
@ -60,7 +60,7 @@ export default Ember.Component.extend({
|
||||
// on Desktop, shows the button at the beginning of the selection
|
||||
// on Mobile, shows the button at the end of the selection
|
||||
const isMobileDevice = this.site.isMobileDevice;
|
||||
const { isIOS, isAndroid, isSafari, isOpera } = this.capabilities;
|
||||
const { isIOS, isAndroid, isSafari, isOpera, isIE11 } = this.capabilities;
|
||||
const showAtEnd = isMobileDevice || isIOS || isAndroid || isOpera;
|
||||
|
||||
// Don't mess with the original range as it results in weird behaviours
|
||||
@ -88,7 +88,10 @@ export default Ember.Component.extend({
|
||||
const parent = markerElement.parentNode;
|
||||
parent.removeChild(markerElement);
|
||||
// merge back all text nodes so they don't get messed up
|
||||
parent.normalize();
|
||||
if (!isIE11) {
|
||||
// Skip this fix in IE11 - .normalize causes the selection to change
|
||||
parent.normalize();
|
||||
}
|
||||
|
||||
// work around Safari that would sometimes lose the selection
|
||||
if (isSafari) {
|
||||
|
||||
@ -786,7 +786,14 @@ export default Ember.Controller.extend({
|
||||
// or get a draft sequence number
|
||||
if (!opts.draft || opts.draftSequence === undefined) {
|
||||
return Draft.get(opts.draftKey)
|
||||
.then(data => self.confirmDraftAbandon(data))
|
||||
.then(data => {
|
||||
if (opts.skipDraftCheck) {
|
||||
data.draft = undefined;
|
||||
return data;
|
||||
}
|
||||
|
||||
return self.confirmDraftAbandon(data);
|
||||
})
|
||||
.then(data => {
|
||||
opts.draft = opts.draft || data.draft;
|
||||
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { movePosts, mergeTopic } from "discourse/models/topic";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
topicController: Ember.inject.controller("topic"),
|
||||
|
||||
saving: false,
|
||||
selectedTopicId: null,
|
||||
|
||||
selectedPostsCount: Ember.computed.alias(
|
||||
"topicController.selectedPostsCount"
|
||||
),
|
||||
|
||||
@computed("saving", "selectedTopicId")
|
||||
buttonDisabled(saving, selectedTopicId) {
|
||||
return saving || Ember.isEmpty(selectedTopicId);
|
||||
},
|
||||
|
||||
@computed("saving")
|
||||
buttonTitle(saving) {
|
||||
return saving ? I18n.t("saving") : I18n.t("topic.merge_topic.title");
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.set("modal.modalClass", "split-modal");
|
||||
},
|
||||
|
||||
actions: {
|
||||
movePostsToExistingTopic() {
|
||||
const topicId = this.get("model.id");
|
||||
|
||||
this.set("saving", true);
|
||||
|
||||
let promise = this.get("topicController.selectedAllPosts")
|
||||
? mergeTopic(topicId, this.get("selectedTopicId"))
|
||||
: movePosts(topicId, {
|
||||
destination_topic_id: this.get("selectedTopicId"),
|
||||
post_ids: this.get("topicController.selectedPostIds")
|
||||
});
|
||||
|
||||
promise
|
||||
.then(result => {
|
||||
this.send("closeModal");
|
||||
this.get("topicController").send("toggleMultiSelect");
|
||||
Ember.run.next(() => DiscourseURL.routeTo(result.url));
|
||||
})
|
||||
.catch(() => {
|
||||
this.flash(I18n.t("topic.merge_topic.error"));
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("saving", false);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,155 @@
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { movePosts, mergeTopic } from "discourse/models/topic";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
topicName: null,
|
||||
saving: false,
|
||||
categoryId: null,
|
||||
tags: null,
|
||||
canAddTags: Ember.computed.alias("site.can_create_tag"),
|
||||
canTagMessages: Ember.computed.alias("site.can_tag_pms"),
|
||||
selectedTopicId: null,
|
||||
newTopic: Ember.computed.equal("selection", "new_topic"),
|
||||
existingTopic: Ember.computed.equal("selection", "existing_topic"),
|
||||
newMessage: Ember.computed.equal("selection", "new_message"),
|
||||
existingMessage: Ember.computed.equal("selection", "existing_message"),
|
||||
moveTypes: ["newTopic", "existingTopic", "newMessage", "existingMessage"],
|
||||
participants: null,
|
||||
|
||||
topicController: Ember.inject.controller("topic"),
|
||||
selectedPostsCount: Ember.computed.alias(
|
||||
"topicController.selectedPostsCount"
|
||||
),
|
||||
selectedAllPosts: Ember.computed.alias("topicController.selectedAllPosts"),
|
||||
selectedPosts: Ember.computed.alias("topicController.selectedPosts"),
|
||||
|
||||
@computed("saving", "selectedTopicId", "topicName")
|
||||
buttonDisabled(saving, selectedTopicId, topicName) {
|
||||
return (
|
||||
saving || (Ember.isEmpty(selectedTopicId) && Ember.isEmpty(topicName))
|
||||
);
|
||||
},
|
||||
|
||||
@computed(
|
||||
"saving",
|
||||
"newTopic",
|
||||
"existingTopic",
|
||||
"newMessage",
|
||||
"existingMessage"
|
||||
)
|
||||
buttonTitle(saving, newTopic, existingTopic, newMessage, existingMessage) {
|
||||
if (newTopic) {
|
||||
return I18n.t("topic.split_topic.title");
|
||||
} else if (existingTopic) {
|
||||
return I18n.t("topic.merge_topic.title");
|
||||
} else if (newMessage) {
|
||||
return I18n.t("topic.move_to_new_message.title");
|
||||
} else if (existingMessage) {
|
||||
return I18n.t("topic.move_to_existing_message.title");
|
||||
} else {
|
||||
return I18n.t("saving");
|
||||
}
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
"modal.modalClass": "move-to-modal",
|
||||
saving: false,
|
||||
selection: "new_topic",
|
||||
categoryId: null,
|
||||
topicName: "",
|
||||
tags: null,
|
||||
participants: null
|
||||
});
|
||||
|
||||
const isPrivateMessage = this.get("model.isPrivateMessage");
|
||||
const canSplitTopic = this.get("canSplitTopic");
|
||||
if (isPrivateMessage) {
|
||||
this.set("selection", canSplitTopic ? "new_message" : "existing_message");
|
||||
} else if (!canSplitTopic) {
|
||||
this.set("selection", "existing_topic");
|
||||
}
|
||||
},
|
||||
|
||||
@computed("selectedAllPosts", "selectedPosts", "selectedPosts.[]")
|
||||
canSplitTopic(selectedAllPosts, selectedPosts) {
|
||||
return (
|
||||
!selectedAllPosts &&
|
||||
selectedPosts.length > 0 &&
|
||||
selectedPosts.sort((a, b) => a.post_number - b.post_number)[0]
|
||||
.post_type === this.site.get("post_types.regular")
|
||||
);
|
||||
},
|
||||
|
||||
actions: {
|
||||
performMove() {
|
||||
this.get("moveTypes").forEach(type => {
|
||||
if (this.get(type)) {
|
||||
this.send("movePostsTo", type);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
movePostsTo(type) {
|
||||
this.set("saving", true);
|
||||
const topicId = this.get("model.id");
|
||||
let mergeOptions, moveOptions;
|
||||
|
||||
if (type === "existingTopic") {
|
||||
mergeOptions = { destination_topic_id: this.get("selectedTopicId") };
|
||||
moveOptions = Object.assign(
|
||||
{ post_ids: this.get("topicController.selectedPostIds") },
|
||||
mergeOptions
|
||||
);
|
||||
} else if (type === "existingMessage") {
|
||||
mergeOptions = {
|
||||
destination_topic_id: this.get("selectedTopicId"),
|
||||
participants: this.get("participants"),
|
||||
archetype: "private_message"
|
||||
};
|
||||
moveOptions = Object.assign(
|
||||
{ post_ids: this.get("topicController.selectedPostIds") },
|
||||
mergeOptions
|
||||
);
|
||||
} else if (type === "newTopic") {
|
||||
mergeOptions = {};
|
||||
moveOptions = {
|
||||
title: this.get("topicName"),
|
||||
post_ids: this.get("topicController.selectedPostIds"),
|
||||
category_id: this.get("categoryId"),
|
||||
tags: this.get("tags")
|
||||
};
|
||||
} else {
|
||||
mergeOptions = {};
|
||||
moveOptions = {
|
||||
title: this.get("topicName"),
|
||||
post_ids: this.get("topicController.selectedPostIds"),
|
||||
tags: this.get("tags"),
|
||||
archetype: "private_message"
|
||||
};
|
||||
}
|
||||
|
||||
const promise = this.get("topicController.selectedAllPosts")
|
||||
? mergeTopic(topicId, mergeOptions)
|
||||
: movePosts(topicId, moveOptions);
|
||||
|
||||
promise
|
||||
.then(result => {
|
||||
this.send("closeModal");
|
||||
this.get("topicController").send("toggleMultiSelect");
|
||||
DiscourseURL.routeTo(result.url);
|
||||
})
|
||||
.catch(xhr => {
|
||||
this.flash(extractError(xhr, I18n.t("topic.move_to.error")));
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("saving", false);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,66 +0,0 @@
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import { movePosts } from "discourse/models/topic";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { default as computed } from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
topicName: null,
|
||||
saving: false,
|
||||
categoryId: null,
|
||||
tags: null,
|
||||
canAddTags: Ember.computed.alias("site.can_create_tag"),
|
||||
|
||||
topicController: Ember.inject.controller("topic"),
|
||||
selectedPostsCount: Ember.computed.alias(
|
||||
"topicController.selectedPostsCount"
|
||||
),
|
||||
|
||||
@computed("saving", "topicName")
|
||||
buttonDisabled(saving, topicName) {
|
||||
return saving || Ember.isEmpty(topicName);
|
||||
},
|
||||
|
||||
@computed("saving")
|
||||
buttonTitle(saving) {
|
||||
return saving ? I18n.t("saving") : I18n.t("topic.split_topic.action");
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.setProperties({
|
||||
"modal.modalClass": "split-modal",
|
||||
saving: false,
|
||||
categoryId: null,
|
||||
topicName: "",
|
||||
tags: null
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
movePostsToNewTopic() {
|
||||
this.set("saving", true);
|
||||
|
||||
const options = {
|
||||
title: this.get("topicName"),
|
||||
post_ids: this.get("topicController.selectedPostIds"),
|
||||
category_id: this.get("categoryId"),
|
||||
tags: this.get("tags")
|
||||
};
|
||||
|
||||
movePosts(this.get("model.id"), options)
|
||||
.then(result => {
|
||||
this.send("closeModal");
|
||||
this.get("topicController").send("toggleMultiSelect");
|
||||
Ember.run.next(() => DiscourseURL.routeTo(result.url));
|
||||
})
|
||||
.catch(xhr => {
|
||||
this.flash(extractError(xhr, I18n.t("topic.split_topic.error")));
|
||||
})
|
||||
.finally(() => {
|
||||
this.set("saving", false);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1113,22 +1113,6 @@ export default Ember.Controller.extend(BufferedContent, {
|
||||
);
|
||||
},
|
||||
|
||||
@computed(
|
||||
"canMergeTopic",
|
||||
"selectedAllPosts",
|
||||
"selectedPosts",
|
||||
"selectedPosts.[]"
|
||||
)
|
||||
canSplitTopic(canMergeTopic, selectedAllPosts, selectedPosts) {
|
||||
return (
|
||||
canMergeTopic &&
|
||||
!selectedAllPosts &&
|
||||
selectedPosts.length > 0 &&
|
||||
selectedPosts.sort((a, b) => a.post_number - b.post_number)[0]
|
||||
.post_type === 1
|
||||
);
|
||||
},
|
||||
|
||||
@computed("model.details.can_move_posts", "selectedPostsCount")
|
||||
canMergeTopic(canMovePosts, selectedPostsCount) {
|
||||
return canMovePosts && selectedPostsCount > 0;
|
||||
|
||||
@ -5,6 +5,12 @@ import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
var get = Em.get,
|
||||
escapeExpression = Handlebars.Utils.escapeExpression;
|
||||
|
||||
let _renderer = defaultCategoryLinkRenderer;
|
||||
|
||||
export function replaceCategoryLinkRenderer(fn) {
|
||||
_renderer = fn;
|
||||
}
|
||||
|
||||
function categoryStripe(color, classes) {
|
||||
var style = color ? "style='background-color: #" + color + ";'" : "";
|
||||
return "<span class='" + classes + "' " + style + "></span>";
|
||||
@ -32,6 +38,43 @@ export function categoryBadgeHTML(category, opts) {
|
||||
)
|
||||
return "";
|
||||
|
||||
return _renderer(category, opts);
|
||||
}
|
||||
|
||||
export function categoryLinkHTML(category, options) {
|
||||
var categoryOptions = {};
|
||||
|
||||
// TODO: This is a compatibility layer with the old helper structure.
|
||||
// Can be removed once we migrate to `registerUnbound` fully
|
||||
if (options && options.hash) {
|
||||
options = options.hash;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
if (options.allowUncategorized) {
|
||||
categoryOptions.allowUncategorized = true;
|
||||
}
|
||||
if (options.link !== undefined) {
|
||||
categoryOptions.link = options.link;
|
||||
}
|
||||
if (options.extraClasses) {
|
||||
categoryOptions.extraClasses = options.extraClasses;
|
||||
}
|
||||
if (options.hideParent) {
|
||||
categoryOptions.hideParent = true;
|
||||
}
|
||||
if (options.categoryStyle) {
|
||||
categoryOptions.categoryStyle = options.categoryStyle;
|
||||
}
|
||||
}
|
||||
return new Handlebars.SafeString(
|
||||
categoryBadgeHTML(category, categoryOptions)
|
||||
);
|
||||
}
|
||||
|
||||
registerUnbound("category-link", categoryLinkHTML);
|
||||
|
||||
function defaultCategoryLinkRenderer(category, opts) {
|
||||
let description = get(category, "description_text");
|
||||
let restricted = get(category, "read_restricted");
|
||||
let url = opts.url
|
||||
@ -103,36 +146,3 @@ export function categoryBadgeHTML(category, opts) {
|
||||
extraClasses = categoryStyle ? categoryStyle + extraClasses : extraClasses;
|
||||
return `<${tagName} class="badge-wrapper ${extraClasses}" ${href}>${html}</${tagName}>`;
|
||||
}
|
||||
|
||||
export function categoryLinkHTML(category, options) {
|
||||
var categoryOptions = {};
|
||||
|
||||
// TODO: This is a compatibility layer with the old helper structure.
|
||||
// Can be removed once we migrate to `registerUnbound` fully
|
||||
if (options && options.hash) {
|
||||
options = options.hash;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
if (options.allowUncategorized) {
|
||||
categoryOptions.allowUncategorized = true;
|
||||
}
|
||||
if (options.link !== undefined) {
|
||||
categoryOptions.link = options.link;
|
||||
}
|
||||
if (options.extraClasses) {
|
||||
categoryOptions.extraClasses = options.extraClasses;
|
||||
}
|
||||
if (options.hideParent) {
|
||||
categoryOptions.hideParent = true;
|
||||
}
|
||||
if (options.categoryStyle) {
|
||||
categoryOptions.categoryStyle = options.categoryStyle;
|
||||
}
|
||||
}
|
||||
return new Handlebars.SafeString(
|
||||
categoryBadgeHTML(category, categoryOptions)
|
||||
);
|
||||
}
|
||||
|
||||
registerUnbound("category-link", categoryLinkHTML);
|
||||
|
||||
@ -2,32 +2,63 @@ const OBSERVER_OPTIONS = {
|
||||
rootMargin: "50%" // load images slightly before they're visible
|
||||
};
|
||||
|
||||
// Min size in pixels for consideration for lazy loading
|
||||
const MINIMUM_SIZE = 150;
|
||||
|
||||
const hiddenData = new WeakMap();
|
||||
|
||||
const LOADING_DATA =
|
||||
"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
|
||||
|
||||
// We hide an image by replacing it with a transparent gif
|
||||
function hide(image) {
|
||||
image.classList.add("d-lazyload");
|
||||
image.classList.add("d-lazyload-hidden");
|
||||
image.setAttribute("data-src", image.getAttribute("src"));
|
||||
image.setAttribute(
|
||||
"src",
|
||||
"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
|
||||
);
|
||||
|
||||
hiddenData.set(image, {
|
||||
src: image.src,
|
||||
srcset: image.srcset,
|
||||
width: image.width,
|
||||
height: image.height
|
||||
});
|
||||
image.removeAttribute("srcset");
|
||||
|
||||
image.src = image.dataset.smallUpload || LOADING_DATA;
|
||||
image.removeAttribute("data-small-upload");
|
||||
}
|
||||
|
||||
// Restore an image from the `data-src` attribute
|
||||
// Restore an image when onscreen
|
||||
function show(image) {
|
||||
let dataSrc = image.getAttribute("data-src");
|
||||
if (dataSrc) {
|
||||
image.setAttribute("src", dataSrc);
|
||||
let imageData = hiddenData.get(image);
|
||||
|
||||
if (imageData) {
|
||||
const copyImg = new Image();
|
||||
copyImg.onload = () => {
|
||||
image.src = copyImg.src;
|
||||
if (copyImg.srcset) {
|
||||
image.srcset = copyImg.srcset;
|
||||
}
|
||||
image.classList.remove("d-lazyload-hidden");
|
||||
image.parentNode.removeChild(copyImg);
|
||||
copyImg.onload = null;
|
||||
};
|
||||
|
||||
copyImg.src = imageData.src;
|
||||
copyImg.srcset = imageData.srcset || copyImg.srcset;
|
||||
|
||||
copyImg.style.position = "absolute";
|
||||
copyImg.style.top = 0;
|
||||
copyImg.style.left = 0;
|
||||
copyImg.style.width = imageData.width;
|
||||
copyImg.style.height = imageData.height;
|
||||
|
||||
image.parentNode.appendChild(copyImg);
|
||||
} else {
|
||||
image.classList.remove("d-lazyload-hidden");
|
||||
}
|
||||
}
|
||||
|
||||
export function setupLazyLoading(api) {
|
||||
// Old IE don't support this API
|
||||
if (!("IntersectionObserver" in window)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
const { target } = entry;
|
||||
@ -35,15 +66,19 @@ export function setupLazyLoading(api) {
|
||||
if (entry.isIntersecting) {
|
||||
show(target);
|
||||
observer.unobserve(target);
|
||||
} else {
|
||||
// The Observer is triggered when entries are added. This allows
|
||||
// us to hide things that start off screen.
|
||||
hide(target);
|
||||
}
|
||||
});
|
||||
}, OBSERVER_OPTIONS);
|
||||
|
||||
api.decorateCooked($post => {
|
||||
$(".lightbox img", $post).each((_, $img) => observer.observe($img));
|
||||
});
|
||||
api.decorateCooked(
|
||||
$post => {
|
||||
$("img", $post).each((_, img) => {
|
||||
if (img.width >= MINIMUM_SIZE && img.height >= MINIMUM_SIZE) {
|
||||
hide(img);
|
||||
observer.observe(img);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ onlyStream: true }
|
||||
);
|
||||
}
|
||||
|
||||
@ -46,9 +46,7 @@ export default function($elem) {
|
||||
const href = item.el.data("download-href") || item.src;
|
||||
let src = [
|
||||
escapeExpression(item.el.attr("title")),
|
||||
$("span.informations", item.el)
|
||||
.text()
|
||||
.replace("x", "×")
|
||||
$("span.informations", item.el).text()
|
||||
];
|
||||
if (
|
||||
!Discourse.SiteSettings.prevent_anons_from_downloading_files ||
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
registerIconRenderer,
|
||||
replaceIcon
|
||||
} from "discourse-common/lib/icon-library";
|
||||
import { replaceCategoryLinkRenderer } from "discourse/helpers/category-link";
|
||||
import { addNavItem } from "discourse/models/nav-item";
|
||||
import { replaceFormatter } from "discourse/lib/utilities";
|
||||
import { modifySelectKit } from "select-kit/mixins/plugin-api";
|
||||
@ -39,7 +40,7 @@ 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.25";
|
||||
const PLUGIN_API_VERSION = "0.8.26";
|
||||
|
||||
class PluginApi {
|
||||
constructor(version, container) {
|
||||
@ -775,6 +776,20 @@ class PluginApi {
|
||||
addComposerUploadHandler(extensions, method) {
|
||||
addComposerUploadHandler(extensions, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a renderer that overrides the display of category links.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* function testReplaceRenderer(category, opts) {
|
||||
* return "Hello World";
|
||||
* }
|
||||
* api.replaceCategoryLinkRenderer(categoryIconsRenderer);
|
||||
**/
|
||||
replaceCategoryLinkRenderer(fn) {
|
||||
replaceCategoryLinkRenderer(fn);
|
||||
}
|
||||
}
|
||||
|
||||
let _pluginv01;
|
||||
|
||||
@ -158,7 +158,7 @@ const DiscourseURL = Ember.Object.extend({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.host !== document.location.host) {
|
||||
if (a.host && a.host !== document.location.host) {
|
||||
document.location = a.href;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -121,6 +121,10 @@ export default Ember.Mixin.create({
|
||||
this.set("isFixed", true);
|
||||
return this._show($target.text().replace(/^@/, ""), $target);
|
||||
});
|
||||
|
||||
this.appEvents.on(`topic-header:trigger-${id}`, (username, $target) => {
|
||||
return this._show(username, $target);
|
||||
});
|
||||
},
|
||||
|
||||
_positionCard(target) {
|
||||
|
||||
@ -96,7 +96,9 @@ export default Em.Mixin.create({
|
||||
});
|
||||
|
||||
$upload.on("fileuploadfail", (e, data) => {
|
||||
displayErrorForUpload(data);
|
||||
if (!data || (data && data.errorThrown !== "abort")) {
|
||||
displayErrorForUpload(data);
|
||||
}
|
||||
reset();
|
||||
});
|
||||
}.on("didInsertElement"),
|
||||
|
||||
@ -752,11 +752,10 @@ export function movePosts(topicId, data) {
|
||||
);
|
||||
}
|
||||
|
||||
export function mergeTopic(topicId, destinationTopicId) {
|
||||
return ajax("/t/" + topicId + "/merge-topic", {
|
||||
type: "POST",
|
||||
data: { destination_topic_id: destinationTopicId }
|
||||
}).then(moveResult);
|
||||
export function mergeTopic(topicId, data) {
|
||||
return ajax("/t/" + topicId + "/merge-topic", { type: "POST", data }).then(
|
||||
moveResult
|
||||
);
|
||||
}
|
||||
|
||||
export default Topic;
|
||||
|
||||
@ -716,6 +716,16 @@ User.reopenClass(Singleton, {
|
||||
// TODO: Use app.register and junk Singleton
|
||||
createCurrent() {
|
||||
const userJson = PreloadStore.get("currentUser");
|
||||
|
||||
if (userJson && userJson.primary_group_id) {
|
||||
const primaryGroup = userJson.groups.find(
|
||||
group => group.id === userJson.primary_group_id
|
||||
);
|
||||
if (primaryGroup) {
|
||||
userJson.primary_group_name = primaryGroup.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (userJson) {
|
||||
const store = Discourse.__container__.lookup("service:store");
|
||||
return store.createRecord("user", userJson);
|
||||
|
||||
@ -30,6 +30,7 @@ export default {
|
||||
return p.toString() === "[object SafariRemoteNotification]";
|
||||
})(!window["safari"] || safari.pushNotification);
|
||||
caps.isChrome = !!window.chrome && !caps.isOpera;
|
||||
caps.isIE11 = !!ua.match(/Trident.*rv\:11\./);
|
||||
|
||||
caps.canPasteImages = caps.isChrome || caps.isFirefox;
|
||||
}
|
||||
|
||||
@ -181,6 +181,7 @@ export default function() {
|
||||
this.route("privacy", { path: "/privacy" });
|
||||
this.route("guidelines", { path: "/guidelines" });
|
||||
this.route("rules", { path: "/rules" });
|
||||
this.route("conduct", { path: "/conduct" });
|
||||
|
||||
this.route("new-topic", { path: "/new-topic" });
|
||||
this.route("new-message", { path: "/new-message" });
|
||||
|
||||
3
app/assets/javascripts/discourse/routes/conduct.js.es6
Normal file
3
app/assets/javascripts/discourse/routes/conduct.js.es6
Normal file
@ -0,0 +1,3 @@
|
||||
import staticRouteBuilder from "discourse/lib/static-route-builder";
|
||||
|
||||
export default staticRouteBuilder("conduct");
|
||||
@ -114,17 +114,13 @@ const TopicRoute = Discourse.Route.extend({
|
||||
this.controllerFor("raw_email").loadRawEmail(model.get("id"));
|
||||
},
|
||||
|
||||
mergeTopic() {
|
||||
showModal("merge-topic", {
|
||||
moveToTopic() {
|
||||
showModal("move-to-topic", {
|
||||
model: this.modelFor("topic"),
|
||||
title: "topic.merge_topic.title"
|
||||
title: "topic.move_to.title"
|
||||
});
|
||||
},
|
||||
|
||||
splitTopic() {
|
||||
showModal("split-topic", { model: this.modelFor("topic") });
|
||||
},
|
||||
|
||||
changeOwner() {
|
||||
showModal("change-owner", {
|
||||
model: this.modelFor("topic"),
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
<div class='ac-message'>
|
||||
{{{i18n 'login.sent_activation_email_again' currentEmail=email}}}
|
||||
{{#if email}}
|
||||
{{{i18n 'login.sent_activation_email_again' currentEmail=email}}}
|
||||
{{else}}
|
||||
{{i18n 'login.sent_activation_email_again_generic'}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
<label for='choose-message-title'>{{i18n 'choose_message.title.search'}}</label>
|
||||
|
||||
{{text-field value=messageTitle placeholderKey="choose_message.title.placeholder" id="choose-message-title"}}
|
||||
|
||||
{{#if loading}}
|
||||
<p>{{i18n 'loading'}}</p>
|
||||
{{else}}
|
||||
{{#if noResults}}
|
||||
<p>{{i18n 'choose_message.none_found'}}</p>
|
||||
{{else}}
|
||||
{{#each messages as |m|}}
|
||||
<div class='controls existing-message'>
|
||||
<label class='radio'>
|
||||
<input type='radio' id="choose-message-{{unbound m.id}}" name='choose_message_id' {{action "chooseMessage" m}}>
|
||||
<span class="message-title">
|
||||
{{m.title}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
@ -10,7 +10,7 @@
|
||||
icon="user-times"
|
||||
label="groups.leave"
|
||||
disabled=updatingMembership}}
|
||||
{{else if model.allow_membership_requests}}
|
||||
{{else if canRequestMembership}}
|
||||
{{d-button action="showRequestMembershipForm"
|
||||
class="group-index-request"
|
||||
disabled=loading
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
{{#d-modal-body id='move-selected'}}
|
||||
<p>{{{i18n 'topic.merge_topic.instructions' count=selectedPostsCount}}}</p>
|
||||
|
||||
<form>
|
||||
{{choose-topic currentTopicId=model.id selectedTopicId=selectedTopicId}}
|
||||
</form>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{#d-button class="btn-primary" disabled=buttonDisabled action="movePostsToExistingTopic"}}
|
||||
{{d-icon 'sign-out'}} {{buttonTitle}}
|
||||
{{/d-button}}
|
||||
</div>
|
||||
@ -0,0 +1,112 @@
|
||||
{{#d-modal-body id='move-selected'}}
|
||||
|
||||
{{#if model.isPrivateMessage}}
|
||||
<div class="radios">
|
||||
{{#if canSplitTopic}}
|
||||
<label class="radio-label" for="move-to-new-message">
|
||||
{{radio-button id='move-to-new-message' name="move-to-entity" value="new_message" selection=selection}}
|
||||
<b>{{i18n 'topic.move_to_new_message.radio_label'}}</b>
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<label class="radio-label" for="move-to-existing-message">
|
||||
{{radio-button id='move-to-existing-message' name="move-to-entity" value="existing_message" selection=selection}}
|
||||
<b>{{i18n 'topic.move_to_existing_message.radio_label'}}</b>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{{#if canSplitTopic}}
|
||||
{{#if newMessage}}
|
||||
<p>{{{i18n 'topic.move_to_new_message.instructions' count=selectedPostsCount}}}</p>
|
||||
<form>
|
||||
<label>{{i18n 'topic.move_to_new_message.message_title'}}</label>
|
||||
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
|
||||
|
||||
{{#if canTagMessages}}
|
||||
<label>{{i18n 'tagging.tags'}}</label>
|
||||
{{tag-chooser tags=tags filterable=true}}
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if existingMessage}}
|
||||
<p>{{{i18n 'topic.move_to_existing_message.instructions' count=selectedPostsCount}}}</p>
|
||||
<form>
|
||||
{{choose-message currentTopicId=model.id selectedTopicId=selectedTopicId}}
|
||||
|
||||
<label>{{i18n 'topic.move_to_new_message.participants'}}</label>
|
||||
{{user-selector usernames=participants class="participant-selector"}}
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
{{else}}
|
||||
|
||||
<div class="radios">
|
||||
{{#if canSplitTopic}}
|
||||
<label class="radio-label" for="move-to-new-topic">
|
||||
{{radio-button id='move-to-new-topic' name="move-to-entity" value="new_topic" selection=selection}}
|
||||
<b>{{i18n 'topic.split_topic.radio_label'}}</b>
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<label class="radio-label" for="move-to-existing-topic">
|
||||
{{radio-button id='move-to-existing-topic' name="move-to-entity" value="existing_topic" selection=selection}}
|
||||
<b>{{i18n 'topic.merge_topic.radio_label'}}</b>
|
||||
</label>
|
||||
|
||||
{{#if canSplitTopic}}
|
||||
<label class="radio-label" for="move-to-new-message">
|
||||
{{radio-button id='move-to-new-message' name="move-to-entity" value="new_message" selection=selection}}
|
||||
<b>{{i18n 'topic.move_to_new_message.radio_label'}}</b>
|
||||
</label>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if existingTopic}}
|
||||
<p>{{{i18n 'topic.merge_topic.instructions' count=selectedPostsCount}}}</p>
|
||||
<form>
|
||||
{{choose-topic currentTopicId=model.id selectedTopicId=selectedTopicId}}
|
||||
</form>
|
||||
{{/if}}
|
||||
|
||||
{{#if canSplitTopic}}
|
||||
{{#if newTopic}}
|
||||
<p>{{{i18n 'topic.split_topic.instructions' count=selectedPostsCount}}}</p>
|
||||
<form>
|
||||
<label>{{i18n 'topic.split_topic.topic_name'}}</label>
|
||||
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
|
||||
|
||||
<label>{{i18n 'categories.category'}}</label>
|
||||
{{category-chooser value=categoryId class="small"}}
|
||||
{{#if canAddTags}}
|
||||
<label>{{i18n 'tagging.tags'}}</label>
|
||||
{{tag-chooser tags=tags filterable=true categoryId=categoryId}}
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canSplitTopic}}
|
||||
{{#if newMessage}}
|
||||
<p>{{{i18n 'topic.move_to_new_message.instructions' count=selectedPostsCount}}}</p>
|
||||
<form>
|
||||
<label>{{i18n 'topic.move_to_new_message.message_title'}}</label>
|
||||
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
|
||||
|
||||
{{#if canTagMessages}}
|
||||
<label>{{i18n 'tagging.tags'}}</label>
|
||||
{{tag-chooser tags=tags filterable=true}}
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{#d-button class="btn-primary" disabled=buttonDisabled action=(action "performMove")}}
|
||||
{{d-icon 'sign-out'}} {{buttonTitle}}
|
||||
{{/d-button}}
|
||||
</div>
|
||||
@ -1,21 +0,0 @@
|
||||
{{#d-modal-body id="move-selected" title="topic.split_topic.title"}}
|
||||
{{{i18n 'topic.split_topic.instructions' count=selectedPostsCount}}}
|
||||
|
||||
<form>
|
||||
<label>{{i18n 'topic.split_topic.topic_name'}}</label>
|
||||
{{text-field value=topicName placeholderKey="composer.title_placeholder" elementId='split-topic-name'}}
|
||||
|
||||
<label>{{i18n 'categories.category'}}</label>
|
||||
{{category-chooser value=categoryId class="small"}}
|
||||
{{#if canAddTags}}
|
||||
<label>{{i18n 'tagging.tags'}}</label>
|
||||
{{tag-chooser tags=tags filterable=true categoryId=categoryId}}
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{#d-button class="btn-primary" disabled=buttonDisabled action="movePostsToNewTopic"}}
|
||||
{{d-icon 'sign-out'}} {{buttonTitle}}
|
||||
{{/d-button}}
|
||||
</div>
|
||||
@ -12,12 +12,8 @@
|
||||
{{d-button action="deleteSelected" icon="trash-o" label="topic.multi_select.delete" class="btn-danger"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canSplitTopic}}
|
||||
{{d-button action="splitTopic" icon="sign-out" label="topic.split_topic.action"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canMergeTopic}}
|
||||
{{d-button action="mergeTopic" icon="sign-out" label="topic.merge_topic.action"}}
|
||||
{{d-button action=(route-action "moveToTopic") icon="sign-out" label="topic.move_to.action" class="move-to-topic"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if canChangeOwner}}
|
||||
|
||||
@ -5,6 +5,54 @@ import DiscourseURL from "discourse/lib/url";
|
||||
import RawHtml from "discourse/widgets/raw-html";
|
||||
import renderTags from "discourse/lib/render-tags";
|
||||
import { topicFeaturedLinkNode } from "discourse/lib/render-topic-featured-link";
|
||||
import { avatarImg } from "discourse/widgets/post";
|
||||
|
||||
createWidget("topic-header-participant", {
|
||||
tagName: "span",
|
||||
|
||||
buildClasses(attrs) {
|
||||
return `trigger-${attrs.type}-card`;
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
const { user, group } = attrs;
|
||||
let content, url;
|
||||
|
||||
if (attrs.type === "user") {
|
||||
content = avatarImg("tiny", {
|
||||
template: user.avatar_template,
|
||||
username: user.username
|
||||
});
|
||||
url = user.get("path");
|
||||
} else {
|
||||
content = [iconNode("users")];
|
||||
url = Discourse.getURL(`/groups/${group.name}`);
|
||||
content.push(h("span", group.name));
|
||||
}
|
||||
|
||||
return h(
|
||||
"a.icon",
|
||||
{
|
||||
attributes: {
|
||||
href: url,
|
||||
"data-auto-route": true,
|
||||
title: attrs.username
|
||||
}
|
||||
},
|
||||
content
|
||||
);
|
||||
},
|
||||
|
||||
click(e) {
|
||||
const $target = $(e.target);
|
||||
this.appEvents.trigger(
|
||||
`topic-header:trigger-${this.attrs.type}-card`,
|
||||
this.attrs.username,
|
||||
$target
|
||||
);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
export default createWidget("header-topic-info", {
|
||||
tagName: "div.extra-info-wrapper",
|
||||
@ -73,6 +121,58 @@ export default createWidget("header-topic-info", {
|
||||
extra.push(new RawHtml({ html: tags }));
|
||||
}
|
||||
|
||||
if (showPM) {
|
||||
const maxHeaderParticipants = extra.length > 0 ? 5 : 10;
|
||||
const participants = [];
|
||||
const topicDetails = topic.get("details");
|
||||
const totalParticipants =
|
||||
topicDetails.allowed_users.length +
|
||||
topicDetails.allowed_groups.length;
|
||||
|
||||
topicDetails.allowed_users.some(user => {
|
||||
if (participants.length >= maxHeaderParticipants) {
|
||||
return true;
|
||||
}
|
||||
|
||||
participants.push(
|
||||
this.attach("topic-header-participant", {
|
||||
type: "user",
|
||||
user,
|
||||
username: user.username
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
topicDetails.allowed_groups.some(group => {
|
||||
if (participants.length >= maxHeaderParticipants) {
|
||||
return true;
|
||||
}
|
||||
|
||||
participants.push(
|
||||
this.attach("topic-header-participant", {
|
||||
type: "group",
|
||||
group,
|
||||
username: group.name
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
if (totalParticipants > maxHeaderParticipants) {
|
||||
const remaining = totalParticipants - maxHeaderParticipants;
|
||||
participants.push(
|
||||
this.attach("link", {
|
||||
className: "more-participants",
|
||||
action: "jumpToTopPost",
|
||||
href,
|
||||
attributes: { "data-topic-id": topic.get("id") },
|
||||
contents: () => `+${remaining}`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
extra.push(h("div.topic-header-participants", participants));
|
||||
}
|
||||
|
||||
extra = extra.concat(applyDecorators(this, "after-tags", attrs, state));
|
||||
|
||||
if (this.siteSettings.topic_featured_link_enabled) {
|
||||
|
||||
@ -267,7 +267,7 @@ registerButton("delete", attrs => {
|
||||
return {
|
||||
id: "delete_topic",
|
||||
action: "deletePost",
|
||||
title: "topic.actions.delete",
|
||||
title: "post.controls.delete_topic",
|
||||
icon: "trash-o",
|
||||
className: "delete"
|
||||
};
|
||||
|
||||
@ -211,15 +211,13 @@ export default createWidget("topic-admin-menu", {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.currentUser.get("staff")) {
|
||||
buttons.push({
|
||||
className: "topic-admin-reset-bump-date",
|
||||
buttonClass: "btn-default",
|
||||
action: "resetBumpDate",
|
||||
icon: "anchor",
|
||||
label: "actions.reset_bump_date"
|
||||
});
|
||||
}
|
||||
buttons.push({
|
||||
className: "topic-admin-reset-bump-date",
|
||||
buttonClass: "btn-default",
|
||||
action: "resetBumpDate",
|
||||
icon: "anchor",
|
||||
label: "actions.reset_bump_date"
|
||||
});
|
||||
|
||||
if (!isPrivateMessage) {
|
||||
buttons.push({
|
||||
|
||||
@ -344,6 +344,11 @@ export function setup(helper) {
|
||||
helper.registerPlugin(md => {
|
||||
const ruler = md.block.bbcode.ruler;
|
||||
|
||||
ruler.push("excerpt", {
|
||||
tag: "excerpt",
|
||||
wrap: "div.excerpt"
|
||||
});
|
||||
|
||||
ruler.push("code", {
|
||||
tag: "code",
|
||||
replace: function(state, tagInfo, content) {
|
||||
|
||||
@ -44,7 +44,7 @@ function addHashtag(buffer, matches, state) {
|
||||
export function setup(helper) {
|
||||
helper.registerPlugin(md => {
|
||||
const rule = {
|
||||
matcher: /#([\w-:]{1,101})/,
|
||||
matcher: /#([\u00C0-\u1FFF\u2C00-\uD7FF\w-:]{1,101})/,
|
||||
onMatch: addHashtag
|
||||
};
|
||||
|
||||
|
||||
@ -193,8 +193,11 @@ export default DropdownSelectBoxComponent.extend({
|
||||
}
|
||||
|
||||
const currentUser = Discourse.User.current();
|
||||
const showToggleTopicBump =
|
||||
currentUser &&
|
||||
(currentUser.get("staff") || currentUser.trust_level === 4);
|
||||
|
||||
if (action === REPLY && currentUser && currentUser.get("staff")) {
|
||||
if (action === REPLY && showToggleTopicBump) {
|
||||
items.push({
|
||||
name: I18n.t("composer.composer_actions.toggle_topic_bump.label"),
|
||||
description: I18n.t("composer.composer_actions.toggle_topic_bump.desc"),
|
||||
@ -298,6 +301,7 @@ export default DropdownSelectBoxComponent.extend({
|
||||
options.action = action;
|
||||
options.categoryId = this.get("composerModel.categoryId");
|
||||
options.topicTitle = this.get("composerModel.title");
|
||||
options.skipDraftCheck = true;
|
||||
this._openComposer(options);
|
||||
},
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ export default DropdownSelectBoxComponent.extend({
|
||||
classNames: ["period-chooser"],
|
||||
rowComponent: "period-chooser/period-chooser-row",
|
||||
headerComponent: "period-chooser/period-chooser-header",
|
||||
content: Ember.computed.alias("site.periods"),
|
||||
content: Ember.computed.oneWay("site.periods"),
|
||||
value: Ember.computed.alias("period"),
|
||||
isHidden: Ember.computed.alias("showPeriods"),
|
||||
|
||||
|
||||
@ -25,10 +25,6 @@ export default Ember.Component.extend(
|
||||
"isExpanded",
|
||||
"isDisabled",
|
||||
"isHidden",
|
||||
"isAbove",
|
||||
"isBelow",
|
||||
"isLeftAligned",
|
||||
"isRightAligned",
|
||||
"hasSelection",
|
||||
"hasReachedMaximum",
|
||||
"hasReachedMinimum"
|
||||
|
||||
@ -63,10 +63,9 @@ export default Ember.Mixin.create({
|
||||
return this.$(this.filterInputSelector);
|
||||
},
|
||||
|
||||
@on("didRender")
|
||||
_adjustPosition() {
|
||||
this._applyFixedPosition();
|
||||
this._applyDirection();
|
||||
this._applyFixedPosition();
|
||||
this._positionWrapper();
|
||||
},
|
||||
|
||||
@ -124,15 +123,27 @@ export default Ember.Mixin.create({
|
||||
});
|
||||
this.focusFilterOrHeader();
|
||||
this.autoHighlight();
|
||||
this._boundaryActionHandler("onExpand", this);
|
||||
|
||||
Ember.run.next(() => {
|
||||
this._boundaryActionHandler("onExpand", this);
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
if (!this.isDestroying && !this.isDestroyed) {
|
||||
this._adjustPosition();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
collapse() {
|
||||
this.set("isExpanded", false);
|
||||
|
||||
Ember.run.next(() => {
|
||||
Ember.run.schedule("afterRender", () => this._removeFixedPosition());
|
||||
this._boundaryActionHandler("onCollapse", this);
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
if (!this.isDestroying && !this.isDestroyed) {
|
||||
this._removeFixedPosition();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -181,38 +192,61 @@ export default Ember.Mixin.create({
|
||||
: windowWidth;
|
||||
const bodyWidth = this._computedStyle(this.$body()[0], "width");
|
||||
|
||||
let marginToEdge;
|
||||
let spaceToLeftEdge;
|
||||
if (this.$scrollableParent().length) {
|
||||
marginToEdge =
|
||||
spaceToLeftEdge =
|
||||
this.$().offset().left - this.$scrollableParent().offset().left;
|
||||
} else {
|
||||
marginToEdge = this.get("element").getBoundingClientRect().left;
|
||||
spaceToLeftEdge = this.get("element").getBoundingClientRect().left;
|
||||
}
|
||||
|
||||
const enoughMarginToOppositeEdge =
|
||||
parentWidth - marginToEdge - bodyWidth + this.get("horizontalOffset") >
|
||||
0;
|
||||
if (enoughMarginToOppositeEdge) {
|
||||
this.setProperties({ isLeftAligned: true, isRightAligned: false });
|
||||
options.left = this.get("horizontalOffset");
|
||||
options.right = "unset";
|
||||
let isLeftAligned = true;
|
||||
const spaceToRightEdge = parentWidth - spaceToLeftEdge;
|
||||
const elementWidth = this.get("element").getBoundingClientRect().width;
|
||||
if (spaceToRightEdge > spaceToLeftEdge + elementWidth) {
|
||||
isLeftAligned = false;
|
||||
}
|
||||
|
||||
if (isLeftAligned) {
|
||||
this.$()
|
||||
.addClass("is-left-aligned")
|
||||
.removeClass("is-right-aligned");
|
||||
|
||||
if (this._isRTL()) {
|
||||
options.right = this.get("horizontalOffset");
|
||||
} else {
|
||||
options.left =
|
||||
-bodyWidth + elementWidth - this.get("horizontalOffset");
|
||||
}
|
||||
} else {
|
||||
this.setProperties({ isLeftAligned: false, isRightAligned: true });
|
||||
options.left = "unset";
|
||||
options.right = this.get("horizontalOffset");
|
||||
this.$()
|
||||
.addClass("is-right-aligned")
|
||||
.removeClass("is-left-aligned");
|
||||
|
||||
if (this._isRTL()) {
|
||||
options.right =
|
||||
-bodyWidth + elementWidth - this.get("horizontalOffset");
|
||||
} else {
|
||||
options.left = this.get("horizontalOffset");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fullHeight =
|
||||
this.get("verticalOffset") + bodyHeight + componentHeight;
|
||||
const hasBelowSpace = $(window).height() - offsetBottom - fullHeight > 0;
|
||||
const hasAboveSpace = offsetTop - fullHeight - discourseHeaderHeight > 0;
|
||||
const hasBelowSpace = $(window).height() - offsetBottom - fullHeight >= -1;
|
||||
const hasAboveSpace = offsetTop - fullHeight - discourseHeaderHeight >= -1;
|
||||
const headerHeight = this._computedStyle(this.$header()[0], "height");
|
||||
|
||||
if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) {
|
||||
this.setProperties({ isBelow: true, isAbove: false });
|
||||
this.$()
|
||||
.addClass("is-below")
|
||||
.removeClass("is-above");
|
||||
options.top = headerHeight + this.get("verticalOffset");
|
||||
} else {
|
||||
this.setProperties({ isBelow: false, isAbove: true });
|
||||
this.$()
|
||||
.addClass("is-above")
|
||||
.removeClass("is-below");
|
||||
options.bottom = headerHeight + this.get("verticalOffset");
|
||||
}
|
||||
|
||||
|
||||
@ -151,19 +151,32 @@ export function createPreviewComponent(width, height, obj) {
|
||||
);
|
||||
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 45, 55);
|
||||
|
||||
const headerFontSize = headerHeight / 44;
|
||||
|
||||
ctx.font = `${headerFontSize}em FontAwesome`;
|
||||
ctx.fillText(
|
||||
"\uf0c9",
|
||||
width - avatarSize * 2 - headerMargin * 0.5,
|
||||
avatarSize
|
||||
const pathScale = headerHeight / 1200;
|
||||
// search icon SVG path
|
||||
const searchIcon = new Path2D(
|
||||
"M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
|
||||
);
|
||||
ctx.fillText(
|
||||
"\uf002",
|
||||
// hamburger icon
|
||||
const hamburgerIcon = new Path2D(
|
||||
"M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"
|
||||
);
|
||||
ctx.save(); // Save the previous state for translation and scale
|
||||
ctx.translate(
|
||||
width - avatarSize * 3 - headerMargin * 0.5,
|
||||
avatarSize
|
||||
avatarSize / 2
|
||||
);
|
||||
// need to scale paths otherwise they're too large
|
||||
ctx.scale(pathScale, pathScale);
|
||||
ctx.fill(searchIcon);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.translate(
|
||||
width - avatarSize * 2 - headerMargin * 0.5,
|
||||
avatarSize / 2
|
||||
);
|
||||
ctx.scale(pathScale, pathScale);
|
||||
ctx.fill(hamburgerIcon);
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
drawPills(colors, headerHeight, opts) {
|
||||
@ -176,37 +189,41 @@ export function createPreviewComponent(width, height, obj) {
|
||||
const headerMargin = headerHeight * 0.2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = darkLightDiff(
|
||||
colors.primary,
|
||||
colors.secondary,
|
||||
90,
|
||||
-65
|
||||
);
|
||||
ctx.strokeStyle = colors.primary;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.rect(
|
||||
headerMargin,
|
||||
headerHeight + headerMargin,
|
||||
categoriesSize,
|
||||
badgeHeight
|
||||
);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
const fontSize = Math.round(badgeHeight * 0.5);
|
||||
|
||||
ctx.font = `${fontSize}px 'Arial'`;
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.fillText(
|
||||
"all categories",
|
||||
headerMargin * 1.5,
|
||||
headerHeight + headerMargin * 1.42 + fontSize
|
||||
headerHeight + headerMargin * 1.4 + fontSize
|
||||
);
|
||||
|
||||
ctx.font = "0.9em 'FontAwesome'";
|
||||
ctx.fillStyle = colors.primary;
|
||||
ctx.fillText(
|
||||
"\uf0da",
|
||||
categoriesSize - headerMargin / 4,
|
||||
headerHeight + headerMargin * 1.6 + fontSize
|
||||
const pathScale = badgeHeight / 1000;
|
||||
// caret icon
|
||||
const caretIcon = new Path2D(
|
||||
"M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
|
||||
);
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(
|
||||
categoriesSize - headerMargin / 4,
|
||||
headerHeight + headerMargin + badgeHeight / 4
|
||||
);
|
||||
ctx.scale(pathScale, pathScale);
|
||||
ctx.fill(caretIcon);
|
||||
ctx.restore();
|
||||
|
||||
const text = opts.categories ? "Categories" : "Latest";
|
||||
|
||||
const activeWidth = categoriesSize * (opts.categories ? 0.8 : 0.55);
|
||||
|
||||
@ -470,7 +470,7 @@ $mobile-breakpoint: 700px;
|
||||
nav {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
@ -892,7 +892,7 @@ table#user-badges {
|
||||
|
||||
.value-input {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
border-color: $primary-low;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
@ -919,7 +919,7 @@ table#user-badges {
|
||||
margin-left: -0.25em;
|
||||
margin-top: -0.125em;
|
||||
.new-value-input {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
.value-input,
|
||||
.new-value-input {
|
||||
|
||||
@ -132,7 +132,7 @@
|
||||
|
||||
.mode {
|
||||
display: inline-flex;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
|
||||
.mode-btn.is-current {
|
||||
color: $tertiary;
|
||||
@ -184,3 +184,9 @@
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.admin-report.storage-stats {
|
||||
.main {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.admin-report {
|
||||
.admin-report-counters {
|
||||
display: grid;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
grid-template-columns: 33% repeat(auto-fit, minmax(20px, 1fr));
|
||||
grid-template-rows: repeat(auto-fit, minmax(32px, 1fr));
|
||||
align-items: center;
|
||||
|
||||
@ -13,20 +13,6 @@
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.reports-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
|
||||
.report {
|
||||
padding-bottom: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
.report-description {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-container {
|
||||
display: flex;
|
||||
|
||||
@ -41,7 +27,7 @@
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-left: 2em;
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
@include active-navigation-item;
|
||||
}
|
||||
|
||||
&.dashboard-next-reports .navigation-item.reports {
|
||||
@include active-navigation-item;
|
||||
}
|
||||
|
||||
&.general .navigation-item.general {
|
||||
@include active-navigation-item;
|
||||
}
|
||||
@ -191,7 +195,7 @@
|
||||
display: flex;
|
||||
border: 1px solid $primary-low;
|
||||
|
||||
.durability,
|
||||
.storage-stats,
|
||||
.last-dashboard-update {
|
||||
flex: 1 1 50%;
|
||||
box-sizing: border-box;
|
||||
@ -199,7 +203,7 @@
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.durability {
|
||||
.storage-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
@ -213,15 +217,11 @@
|
||||
.uploads p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.durability-title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
flex-wrap: wrap;
|
||||
.durability,
|
||||
.storage-stats,
|
||||
.last-dashboard-update {
|
||||
flex: 1 1 100%;
|
||||
text-align: left;
|
||||
@ -286,7 +286,7 @@
|
||||
|
||||
.counters-list {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
|
||||
.counters-header {
|
||||
@ -530,3 +530,33 @@
|
||||
grid-row-gap: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-next-reports {
|
||||
.reports-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
list-style-type: none;
|
||||
margin: 0 -1.5%;
|
||||
}
|
||||
.report {
|
||||
margin: 1.5%;
|
||||
border: 1px solid $primary-low;
|
||||
flex: 1 1 28%;
|
||||
transition: box-shadow 0.25s;
|
||||
min-width: 225px;
|
||||
max-width: 550px;
|
||||
a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 1em;
|
||||
.report-description {
|
||||
color: $primary-high;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: shadow("card");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,22 +133,23 @@
|
||||
|
||||
.link-bottom-line {
|
||||
font-size: $font-down-1;
|
||||
a.badge-wrapper.box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
a.badge-wrapper.box,
|
||||
a.discourse-tag.box {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.discourse-tag.simple:after,
|
||||
.discourse-tag.box {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-featured-link {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
span.badge-category {
|
||||
.category-name {
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-excerpt {
|
||||
font-size: $font-down-1;
|
||||
margin-top: 5px;
|
||||
|
||||
@ -216,7 +216,7 @@
|
||||
}
|
||||
.category-chooser {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1 0 auto;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +60,6 @@
|
||||
// mobile styles
|
||||
.mobile-view .edit-topic-timer-modal {
|
||||
.select-kit.combo-box {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ sup img.emoji {
|
||||
.emoji-picker .categories-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-right: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
|
||||
@ -61,7 +61,7 @@ sup img.emoji {
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ sup img.emoji {
|
||||
.emoji-picker .filter input {
|
||||
height: 24px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
padding-right: 24px;
|
||||
@ -247,7 +247,7 @@ sup img.emoji {
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 4px;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.emoji-picker .filter .clear-filter {
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
box-shadow: shadow("header");
|
||||
|
||||
> .wrap {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.contents {
|
||||
@ -298,3 +297,39 @@
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-header-participants {
|
||||
&:not(:first-child) {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
> span {
|
||||
margin: 0 2px;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.trigger-group-card {
|
||||
height: 16px;
|
||||
margin: 0 4px;
|
||||
padding: 1px 4px;
|
||||
border: 1px solid $primary-low;
|
||||
border-radius: 0.25em;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
color: $primary-high;
|
||||
|
||||
.d-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more-participants {
|
||||
display: inline-block;
|
||||
color: $header_primary-high;
|
||||
line-height: 20px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
.lightbox-wrapper .lightbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
background: $primary-low;
|
||||
&:hover .meta {
|
||||
opacity: 0.9;
|
||||
@ -9,10 +10,13 @@
|
||||
}
|
||||
|
||||
.d-lazyload-hidden {
|
||||
opacity: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.onebox img.d-lazyload-hidden {
|
||||
border: 1px solid $primary-low;
|
||||
}
|
||||
|
||||
.cooked img.d-lazyload {
|
||||
transition: opacity 0.4s 0.75s ease;
|
||||
}
|
||||
|
||||
@ -567,7 +567,7 @@
|
||||
|
||||
.left,
|
||||
.right {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
margin-bottom: 1em;
|
||||
|
||||
.search-query {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
margin: 0 0.5em 0 0;
|
||||
}
|
||||
|
||||
|
||||
@ -124,14 +124,9 @@ $tag-color: $primary-medium;
|
||||
}
|
||||
|
||||
.topic-list-item .discourse-tags {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
font-weight: normal;
|
||||
font-size: $font-down-1;
|
||||
|
||||
.discourse-tag.box {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.categories-list .topic-list-latest .discourse-tags {
|
||||
@ -153,15 +148,16 @@ $tag-color: $primary-medium;
|
||||
}
|
||||
|
||||
.discourse-tag.bullet {
|
||||
margin-right: 0.25em;
|
||||
margin-right: 0.5em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
&:before {
|
||||
background: $primary-low-mid;
|
||||
margin-right: 5px;
|
||||
position: relative;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +396,7 @@ aside.quote {
|
||||
|
||||
.remove-invited {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
@ -419,7 +419,8 @@ aside.quote {
|
||||
.avatar-flair-preview,
|
||||
.user-card-avatar,
|
||||
.topic-map .poster,
|
||||
.user-profile-avatar {
|
||||
.user-profile-avatar,
|
||||
.user-image {
|
||||
.avatar-flair {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -433,7 +434,8 @@ aside.quote {
|
||||
}
|
||||
.topic-avatar .avatar-flair,
|
||||
.avatar-flair-preview .avatar-flair,
|
||||
.collapsed-info .user-profile-avatar .avatar-flair {
|
||||
.collapsed-info .user-profile-avatar .avatar-flair,
|
||||
.user-image .avatar-flair {
|
||||
background-size: 20px 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
@ -4,14 +4,12 @@
|
||||
color: $primary;
|
||||
border: 1px solid $primary-low;
|
||||
line-height: $line-height-large;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: $secondary;
|
||||
margin: 0 0 3px;
|
||||
|
||||
.fa {
|
||||
padding-right: 3px;
|
||||
font-size: 1.4em;
|
||||
vertical-align: bottom;
|
||||
.d-icon {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
img {
|
||||
|
||||
@ -156,7 +156,7 @@
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
a {
|
||||
.btn {
|
||||
margin-bottom: 10px;
|
||||
line-height: $line-height-medium;
|
||||
}
|
||||
|
||||
@ -51,6 +51,7 @@
|
||||
}
|
||||
.badge-category-parent-bg,
|
||||
.badge-category-bg {
|
||||
flex: 0 0 auto;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 5px;
|
||||
|
||||
@ -11,6 +11,11 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
// Fixes Edge bug with SVG elements not triggering click event
|
||||
svg > use {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Stacked Icons
|
||||
// Usage:
|
||||
// <span class="fa-stack">
|
||||
|
||||
@ -45,18 +45,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-flair {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -8px;
|
||||
background-size: 18px 18px;
|
||||
border-radius: 12px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: 333px;
|
||||
@media screen and (max-width: $small-width) {
|
||||
|
||||
@ -230,7 +230,7 @@ $highlight-medium: dark-light-diff($highlight, $secondary, 50%, -55%);
|
||||
$highlight-high: dark-light-diff($highlight, $secondary, -50%, -10%);
|
||||
|
||||
//danger
|
||||
$danger-low: dark-light-diff($danger, $secondary, 85%, -85%);
|
||||
$danger-low: dark-light-diff($danger, $secondary, 85%, -64%);
|
||||
$danger-medium: dark-light-diff($danger, $secondary, 30%, -35%);
|
||||
|
||||
//success
|
||||
|
||||
@ -21,6 +21,8 @@
|
||||
}
|
||||
|
||||
.select-kit-header {
|
||||
height: 30px;
|
||||
|
||||
.selected-name {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
@ -28,6 +30,7 @@
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
.select-kit-filter {
|
||||
border: 0;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@
|
||||
min-width: 50px;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
|
||||
.filter-input,
|
||||
.filter-input:focus {
|
||||
@ -112,7 +112,7 @@
|
||||
.selected-color {
|
||||
.selected-color-wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@
|
||||
padding: 2px 4px;
|
||||
line-height: $line-height-medium;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
display: flex;
|
||||
|
||||
.period-title {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.date-section {
|
||||
|
||||
@ -24,6 +24,8 @@
|
||||
z-index: z("dropdown");
|
||||
|
||||
.select-kit-body {
|
||||
-webkit-animation: fadein 0.25s;
|
||||
animation: fadein 0.25s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
left: 0;
|
||||
@ -212,7 +214,7 @@
|
||||
background: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
outline: none;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
|
||||
@ -109,7 +109,7 @@
|
||||
flex-flow: row wrap;
|
||||
|
||||
div.column {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
flex-direction: row;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
}
|
||||
}
|
||||
.topic-stats {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
text-align: right;
|
||||
color: dark-light-choose($primary-medium, $secondary-high);
|
||||
}
|
||||
|
||||
@ -113,19 +113,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
.split-modal {
|
||||
.move-to-modal {
|
||||
.modal-body {
|
||||
position: relative;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
#move-selected {
|
||||
width: 475px;
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
margin-right: 10px;
|
||||
.radios {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.radio-label {
|
||||
display: inline-block;
|
||||
padding-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@ -142,9 +151,19 @@
|
||||
width: 95%;
|
||||
margin-top: 20px;
|
||||
#split-topic-name,
|
||||
#choose-topic-title {
|
||||
#choose-topic-title,
|
||||
#choose-message-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.participant-selector {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.ac-wrap {
|
||||
width: 100%;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -517,9 +517,6 @@ video {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.topic-header-extra {
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* default docked header CSS for all topics, including those without categories */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user