Version bump
@ -114,3 +114,10 @@ Layout/AlignHash:
|
||||
|
||||
Bundler/OrderedGems:
|
||||
Enabled: false
|
||||
|
||||
Style/SingleLineMethods:
|
||||
Enabled: true
|
||||
|
||||
Style/Semicolon:
|
||||
Enabled: true
|
||||
AllowAsExpressionSeparator: true
|
||||
|
||||
25
Gemfile
@ -13,13 +13,15 @@ if rails_master?
|
||||
gem 'rails', git: 'https://github.com/rails/rails.git'
|
||||
gem 'seed-fu', git: 'https://github.com/SamSaffron/seed-fu.git', branch: 'discourse'
|
||||
else
|
||||
gem 'actionmailer', '5.2'
|
||||
gem 'actionpack', '5.2'
|
||||
gem 'actionview', '5.2'
|
||||
gem 'activemodel', '5.2'
|
||||
gem 'activerecord', '5.2'
|
||||
gem 'activesupport', '5.2'
|
||||
gem 'railties', '5.2'
|
||||
# until rubygems gives us optional dependencies we are stuck with this
|
||||
# bundle update actionmailer actionpack actionview activemodel activerecord activesupport railties
|
||||
gem 'actionmailer', '5.2.2'
|
||||
gem 'actionpack', '5.2.2'
|
||||
gem 'actionview', '5.2.2'
|
||||
gem 'activemodel', '5.2.2'
|
||||
gem 'activerecord', '5.2.2'
|
||||
gem 'activesupport', '5.2.2'
|
||||
gem 'railties', '5.2.2'
|
||||
gem 'sprockets-rails'
|
||||
gem 'seed-fu'
|
||||
end
|
||||
@ -34,7 +36,7 @@ gem 'redis-namespace'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.8.3'
|
||||
|
||||
gem 'onebox', '1.8.68'
|
||||
gem 'onebox', '1.8.69'
|
||||
|
||||
gem 'http_accept_language', '~>2.0.5', require: false
|
||||
|
||||
@ -43,7 +45,12 @@ gem 'ember-source', '2.13.3'
|
||||
gem 'ember-handlebars-template', '0.7.5'
|
||||
gem 'barber'
|
||||
|
||||
gem 'message_bus'
|
||||
# message bus 2.2.0 should be very stable
|
||||
# we trimmed some of the internal API surface down so we went with
|
||||
# a pre release here to make we don't do a full release prior to
|
||||
# baking here. Remove 2.2.0.pre no later than Jan 2019 and move back
|
||||
# to the standard releases
|
||||
gem 'message_bus', '2.2.0.pre.1'
|
||||
|
||||
gem 'rails_multisite'
|
||||
|
||||
|
||||
86
Gemfile.lock
@ -1,37 +1,37 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (5.2.0)
|
||||
actionpack (= 5.2.0)
|
||||
actionview (= 5.2.0)
|
||||
activejob (= 5.2.0)
|
||||
actionmailer (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activejob (= 5.2.2)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.0)
|
||||
actionview (= 5.2.0)
|
||||
activesupport (= 5.2.0)
|
||||
actionpack (5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.0)
|
||||
activesupport (= 5.2.0)
|
||||
actionview (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_model_serializers (0.8.4)
|
||||
activemodel (>= 3.0)
|
||||
activejob (5.2.0)
|
||||
activesupport (= 5.2.0)
|
||||
activejob (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.0)
|
||||
activesupport (= 5.2.0)
|
||||
activerecord (5.2.0)
|
||||
activemodel (= 5.2.0)
|
||||
activesupport (= 5.2.0)
|
||||
activemodel (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activerecord (5.2.2)
|
||||
activemodel (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
arel (>= 9.0)
|
||||
activesupport (5.2.0)
|
||||
activesupport (5.2.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
@ -83,7 +83,7 @@ GEM
|
||||
open4 (~> 1.3)
|
||||
coderay (1.1.2)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
concurrent-ruby (1.1.3)
|
||||
connection_pool (2.2.2)
|
||||
cork (0.3.0)
|
||||
colored2 (~> 3.1)
|
||||
@ -158,7 +158,7 @@ GEM
|
||||
hkdf (0.3.0)
|
||||
htmlentities (4.3.4)
|
||||
http_accept_language (2.0.5)
|
||||
i18n (1.0.1)
|
||||
i18n (1.1.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_size (1.5.0)
|
||||
in_threads (1.5.0)
|
||||
@ -193,11 +193,11 @@ GEM
|
||||
mini_mime (>= 0.1.1)
|
||||
maxminddb (0.1.21)
|
||||
memory_profiler (0.9.12)
|
||||
message_bus (2.1.6)
|
||||
message_bus (2.2.0.pre.1)
|
||||
rack (>= 1.1.3)
|
||||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
mini_mime (1.0.0)
|
||||
mini_mime (1.0.1)
|
||||
mini_portile2 (2.3.0)
|
||||
mini_racer (0.2.3)
|
||||
libv8 (>= 6.3)
|
||||
@ -258,7 +258,7 @@ GEM
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.8.68)
|
||||
onebox (1.8.69)
|
||||
htmlentities (~> 4.3)
|
||||
moneta (~> 1.0)
|
||||
multi_json (~> 1.11)
|
||||
@ -270,7 +270,7 @@ GEM
|
||||
redis
|
||||
ruby-openid
|
||||
parallel (1.12.1)
|
||||
parser (2.5.1.0)
|
||||
parser (2.5.3.0)
|
||||
ast (~> 2.4.0)
|
||||
pg (1.1.3)
|
||||
powerpack (0.1.2)
|
||||
@ -287,14 +287,14 @@ GEM
|
||||
puma (3.11.4)
|
||||
r2 (0.2.7)
|
||||
rack (2.0.6)
|
||||
rack-mini-profiler (1.0.0)
|
||||
rack-mini-profiler (1.0.1)
|
||||
rack (>= 1.2.0)
|
||||
rack-openid (1.3.1)
|
||||
rack (>= 1.1.0)
|
||||
ruby-openid (>= 2.1.8)
|
||||
rack-protection (2.0.3)
|
||||
rack
|
||||
rack-test (1.0.0)
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
@ -304,15 +304,15 @@ GEM
|
||||
rails_multisite (2.0.4)
|
||||
activerecord (> 4.2, < 6)
|
||||
railties (> 4.2, < 6)
|
||||
railties (5.2.0)
|
||||
actionpack (= 5.2.0)
|
||||
activesupport (= 5.2.0)
|
||||
railties (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.19.0)
|
||||
rake (12.3.1)
|
||||
rake (12.3.2)
|
||||
rake-compiler (1.0.4)
|
||||
rake
|
||||
rb-fsevent (0.10.3)
|
||||
@ -357,17 +357,17 @@ GEM
|
||||
rspec-support (~> 3.7.0)
|
||||
rspec-support (3.7.1)
|
||||
rtlit (0.0.5)
|
||||
rubocop (0.57.2)
|
||||
rubocop (0.60.0)
|
||||
jaro_winkler (~> 1.5.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5)
|
||||
parser (>= 2.5, != 2.5.1.1)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
unicode-display_width (~> 1.4.0)
|
||||
ruby-openid (2.7.0)
|
||||
ruby-prof (0.17.0)
|
||||
ruby-progressbar (1.9.0)
|
||||
ruby-progressbar (1.10.0)
|
||||
ruby-readability (0.7.0)
|
||||
guess_html_encoding (>= 0.0.4)
|
||||
nokogiri (>= 1.6.0)
|
||||
@ -443,13 +443,13 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
actionmailer (= 5.2)
|
||||
actionpack (= 5.2)
|
||||
actionview (= 5.2)
|
||||
actionmailer (= 5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
active_model_serializers (~> 0.8.3)
|
||||
activemodel (= 5.2)
|
||||
activerecord (= 5.2)
|
||||
activesupport (= 5.2)
|
||||
activemodel (= 5.2.2)
|
||||
activerecord (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
annotate
|
||||
aws-sdk-s3
|
||||
barber
|
||||
@ -491,7 +491,7 @@ DEPENDENCIES
|
||||
mail (= 2.7.1.rc1)
|
||||
maxminddb
|
||||
memory_profiler
|
||||
message_bus
|
||||
message_bus (= 2.2.0.pre.1)
|
||||
mini_mime
|
||||
mini_racer
|
||||
mini_scheduler
|
||||
@ -512,7 +512,7 @@ DEPENDENCIES
|
||||
omniauth-oauth2
|
||||
omniauth-openid
|
||||
omniauth-twitter
|
||||
onebox (= 1.8.68)
|
||||
onebox (= 1.8.69)
|
||||
openid-redis-store
|
||||
pg
|
||||
pry-nav
|
||||
@ -522,7 +522,7 @@ DEPENDENCIES
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails_multisite
|
||||
railties (= 5.2)
|
||||
railties (= 5.2.2)
|
||||
rake
|
||||
rb-fsevent
|
||||
rb-inotify (~> 0.9)
|
||||
|
||||
|
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1000 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.9 KiB |
@ -6,58 +6,79 @@ export default Ember.Component.extend({
|
||||
limit: 8,
|
||||
total: 0,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.resizeHandler = () =>
|
||||
Ember.run.debounce(this, this._scheduleChartRendering, 500);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
$(window).on("resize.chart", this.resizeHandler);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
$(window).off("resize.chart", this.resizeHandler);
|
||||
|
||||
this._resetChart();
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
Ember.run.debounce(this, this._scheduleChartRendering, 100);
|
||||
},
|
||||
|
||||
_scheduleChartRendering() {
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
const $chartCanvas = this.$(".chart-canvas");
|
||||
if (!$chartCanvas || !$chartCanvas.length) return;
|
||||
this._renderChart(this.get("model"), this.$(".chart-canvas"));
|
||||
});
|
||||
},
|
||||
|
||||
const context = $chartCanvas[0].getContext("2d");
|
||||
const model = this.get("model");
|
||||
const chartData = Ember.makeArray(
|
||||
model.get("chartData") || model.get("data")
|
||||
);
|
||||
const prevChartData = Ember.makeArray(
|
||||
model.get("prevChartData") || model.get("prev_data")
|
||||
);
|
||||
_renderChart(model, $chartCanvas) {
|
||||
if (!$chartCanvas || !$chartCanvas.length) return;
|
||||
|
||||
const labels = chartData.map(d => d.x);
|
||||
const context = $chartCanvas[0].getContext("2d");
|
||||
const chartData = Ember.makeArray(
|
||||
model.get("chartData") || model.get("data")
|
||||
);
|
||||
const prevChartData = Ember.makeArray(
|
||||
model.get("prevChartData") || model.get("prev_data")
|
||||
);
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
data: chartData.map(d => Math.round(parseFloat(d.y))),
|
||||
backgroundColor: prevChartData.length
|
||||
? "transparent"
|
||||
: model.secondary_color,
|
||||
borderColor: model.primary_color
|
||||
}
|
||||
]
|
||||
};
|
||||
const labels = chartData.map(d => d.x);
|
||||
|
||||
if (prevChartData.length) {
|
||||
data.datasets.push({
|
||||
data: prevChartData.map(d => Math.round(parseFloat(d.y))),
|
||||
borderColor: model.primary_color,
|
||||
borderDash: [5, 5],
|
||||
backgroundColor: "transparent",
|
||||
borderWidth: 1,
|
||||
pointRadius: 0
|
||||
});
|
||||
}
|
||||
const data = {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
data: chartData.map(d => Math.round(parseFloat(d.y))),
|
||||
backgroundColor: prevChartData.length
|
||||
? "transparent"
|
||||
: model.secondary_color,
|
||||
borderColor: model.primary_color
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
loadScript("/javascripts/Chart.min.js").then(() => {
|
||||
this._resetChart();
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
if (prevChartData.length) {
|
||||
data.datasets.push({
|
||||
data: prevChartData.map(d => Math.round(parseFloat(d.y))),
|
||||
borderColor: model.primary_color,
|
||||
borderDash: [5, 5],
|
||||
backgroundColor: "transparent",
|
||||
borderWidth: 1,
|
||||
pointRadius: 0
|
||||
});
|
||||
}
|
||||
|
||||
loadScript("/javascripts/Chart.min.js").then(() => {
|
||||
this._resetChart();
|
||||
this._chart = new window.Chart(context, this._buildChartConfig(data));
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -154,7 +154,7 @@ export default Ember.Controller.extend({
|
||||
|
||||
@computed("maximized")
|
||||
maximizeIcon(maximized) {
|
||||
return maximized ? "compress" : "expand";
|
||||
return maximized ? "discourse-compress" : "discourse-expand";
|
||||
},
|
||||
|
||||
@computed("model.isSaving")
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
email: null,
|
||||
text: null,
|
||||
elided: null,
|
||||
format: null,
|
||||
loading: null,
|
||||
|
||||
actions: {
|
||||
run() {
|
||||
this.set("loading", true);
|
||||
|
||||
ajax("/admin/email/advanced-test", {
|
||||
type: "POST",
|
||||
data: { email: this.get("email") }
|
||||
})
|
||||
.then(data => {
|
||||
this.setProperties({
|
||||
text: data.text,
|
||||
elided: data.elided,
|
||||
format: data.format
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => this.set("loading", false));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -4,7 +4,7 @@ export default Ember.Controller.extend({
|
||||
categoryNameKey: null,
|
||||
adminSiteSettings: Ember.inject.controller(),
|
||||
|
||||
@computed("adminSiteSettings.model", "categoryNameKey")
|
||||
@computed("adminSiteSettings.visibleSiteSettings", "categoryNameKey")
|
||||
category(categories, nameKey) {
|
||||
return (categories || []).findBy("nameKey", nameKey);
|
||||
},
|
||||
|
||||
@ -2,6 +2,8 @@ import debounce from "discourse/lib/debounce";
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
filter: null,
|
||||
allSiteSettings: Ember.computed.alias("model"),
|
||||
visibleSiteSettings: null,
|
||||
onlyOverridden: false,
|
||||
|
||||
filterContentNow(category) {
|
||||
@ -14,7 +16,7 @@ export default Ember.Controller.extend({
|
||||
}
|
||||
|
||||
if ((!filter || 0 === filter.length) && !this.get("onlyOverridden")) {
|
||||
this.set("model", this.get("allSiteSettings"));
|
||||
this.set("visibleSiteSettings", this.get("allSiteSettings"));
|
||||
this.transitionToRoute("adminSiteSettings");
|
||||
return;
|
||||
}
|
||||
@ -62,7 +64,7 @@ export default Ember.Controller.extend({
|
||||
all.hasMore = matches.length > 30;
|
||||
all.count = all.hasMore ? "30+" : matches.length;
|
||||
|
||||
this.set("model", matchesGroupedByCategory);
|
||||
this.set("visibleSiteSettings", matchesGroupedByCategory);
|
||||
this.transitionToRoute(
|
||||
"adminSiteSettingsCategory",
|
||||
category || "all_results"
|
||||
|
||||
@ -9,6 +9,11 @@ export default Ember.Controller.extend({
|
||||
defaultEventTypes: Ember.computed.alias("adminWebHooks.defaultEventTypes"),
|
||||
contentTypes: Ember.computed.alias("adminWebHooks.contentTypes"),
|
||||
|
||||
@computed
|
||||
showTagsFilter() {
|
||||
return this.siteSettings.tagging_enabled;
|
||||
},
|
||||
|
||||
@computed("model.isSaving", "saved", "saveButtonDisabled")
|
||||
savingStatus(isSaving, saved, saveButtonDisabled) {
|
||||
if (isSaving) {
|
||||
|
||||
@ -98,6 +98,7 @@ const AdminUser = Discourse.User.extend({
|
||||
},
|
||||
|
||||
deleteAllPosts() {
|
||||
let deletedPosts = 0;
|
||||
const user = this,
|
||||
message = I18n.messageFormat("admin.user.delete_all_posts_confirm_MF", {
|
||||
POSTS: user.get("post_count"),
|
||||
@ -114,13 +115,52 @@ const AdminUser = Discourse.User.extend({
|
||||
`${iconHTML("exclamation-triangle")} ` +
|
||||
I18n.t("admin.user.delete_all_posts"),
|
||||
class: "btn btn-danger",
|
||||
callback: function() {
|
||||
ajax("/admin/users/" + user.get("id") + "/delete_all_posts", {
|
||||
type: "PUT"
|
||||
}).then(() => user.set("post_count", 0));
|
||||
callback: () => {
|
||||
openProgressModal();
|
||||
performDelete();
|
||||
}
|
||||
}
|
||||
];
|
||||
],
|
||||
openProgressModal = () => {
|
||||
bootbox.dialog(
|
||||
`<p>${I18n.t(
|
||||
"admin.user.delete_posts_progress"
|
||||
)}</p><div class='progress-bar'><span></span></div>`,
|
||||
[],
|
||||
{ classes: "delete-posts-progress" }
|
||||
);
|
||||
},
|
||||
performDelete = () => {
|
||||
let deletedPercentage = 0;
|
||||
return ajax(`/admin/users/${user.get("id")}/delete_posts_batch`, {
|
||||
type: "PUT"
|
||||
})
|
||||
.then(({ posts_deleted }) => {
|
||||
if (posts_deleted === 0) {
|
||||
user.set("post_count", 0);
|
||||
bootbox.hideAll();
|
||||
} else {
|
||||
deletedPosts += posts_deleted;
|
||||
deletedPercentage = Math.floor(
|
||||
(deletedPosts * 100) / user.get("post_count")
|
||||
);
|
||||
$(".delete-posts-progress .progress-bar > span").css({
|
||||
width: `${deletedPercentage}%`
|
||||
});
|
||||
performDelete();
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
bootbox.hideAll();
|
||||
let error;
|
||||
AdminUser.find(user.get("id")).then(u => user.setProperties(u));
|
||||
if (e.responseJSON && e.responseJSON.errors) {
|
||||
error = e.responseJSON.errors[0];
|
||||
}
|
||||
error = error || I18n.t("admin.user.delete_posts_failed");
|
||||
bootbox.alert(error);
|
||||
});
|
||||
};
|
||||
bootbox.dialog(message, buttons, { classes: "delete-all-posts" });
|
||||
},
|
||||
|
||||
|
||||
@ -136,7 +136,7 @@ export default Post.extend({
|
||||
label: I18n.t("yes_value"),
|
||||
class: "btn-danger",
|
||||
callback() {
|
||||
Post.deleteMany(replies.map(r => r.id))
|
||||
Post.deleteMany(replies.map(r => r.id), { deferFlags: true })
|
||||
.then(action)
|
||||
.then(resolve)
|
||||
.catch(error => {
|
||||
|
||||
@ -276,9 +276,13 @@ const Report = Discourse.Model.extend({
|
||||
return this._numberLabel(value, opts);
|
||||
}
|
||||
if (type === "date") {
|
||||
const date = moment(value, "YYYY-MM-DD");
|
||||
const date = moment(value);
|
||||
if (date.isValid()) return this._dateLabel(value, date);
|
||||
}
|
||||
if (type === "precise_date") {
|
||||
const date = moment(value);
|
||||
if (date.isValid()) return this._dateLabel(value, date, "LLL");
|
||||
}
|
||||
if (type === "text") return this._textLabel(value);
|
||||
|
||||
return {
|
||||
@ -377,10 +381,10 @@ const Report = Discourse.Model.extend({
|
||||
};
|
||||
},
|
||||
|
||||
_dateLabel(value, date) {
|
||||
_dateLabel(value, date, format = "LL") {
|
||||
return {
|
||||
value,
|
||||
formatedValue: value ? date.format("LL") : "—"
|
||||
formatedValue: value ? date.format(format) : "—"
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@ -63,6 +63,7 @@ export default RestModel.extend({
|
||||
createProperties() {
|
||||
const types = this.get("web_hook_event_types");
|
||||
const categoryIds = this.get("categories").map(c => c.id);
|
||||
const tagNames = this.get("tag_names");
|
||||
|
||||
// Hack as {{group-selector}} accepts a comma-separated string as data source, but
|
||||
// we use an array to populate the datasource above.
|
||||
@ -81,6 +82,7 @@ export default RestModel.extend({
|
||||
? [null]
|
||||
: types.map(type => type.id),
|
||||
category_ids: Ember.isEmpty(categoryIds) ? [null] : categoryIds,
|
||||
tag_names: Ember.isEmpty(tagNames) ? [null] : tagNames,
|
||||
group_ids:
|
||||
Ember.isEmpty(groupNames) || Ember.isEmpty(groupNames[0])
|
||||
? [null]
|
||||
|
||||
@ -8,6 +8,10 @@ export default function() {
|
||||
path: "/dashboard/moderation",
|
||||
resetNamespace: true
|
||||
});
|
||||
this.route("admin.dashboardNextSecurity", {
|
||||
path: "/dashboard/security",
|
||||
resetNamespace: true
|
||||
});
|
||||
});
|
||||
|
||||
this.route(
|
||||
@ -31,6 +35,7 @@ export default function() {
|
||||
this.route("received");
|
||||
this.route("rejected");
|
||||
this.route("previewDigest", { path: "/preview-digest" });
|
||||
this.route("advancedTest", { path: "/advanced-test" });
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -6,7 +6,8 @@ export default Discourse.Route.extend({
|
||||
beforeModel() {
|
||||
this.replaceWith(
|
||||
"adminSiteSettingsCategory",
|
||||
this.modelFor("adminSiteSettings")[0].nameKey
|
||||
this.controllerFor("adminSiteSettings").get("visibleSiteSettings")[0]
|
||||
.nameKey
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -10,9 +10,10 @@ export default Discourse.Route.extend({
|
||||
},
|
||||
|
||||
afterModel(siteSettings) {
|
||||
this.controllerFor("adminSiteSettings").set(
|
||||
"allSiteSettings",
|
||||
siteSettings
|
||||
);
|
||||
const controller = this.controllerFor("adminSiteSettings");
|
||||
|
||||
if (!controller.get("visibleSiteSettings")) {
|
||||
controller.set("visibleSiteSettings", siteSettings);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -19,6 +19,7 @@ export default Discourse.Route.extend({
|
||||
}
|
||||
|
||||
model.set("category_ids", model.get("category_ids"));
|
||||
model.set("tag_names", model.get("tag_names"));
|
||||
model.set("group_ids", model.get("group_ids"));
|
||||
controller.setProperties({ model, saved: false });
|
||||
},
|
||||
|
||||
@ -21,6 +21,11 @@
|
||||
{{i18n "admin.dashboard.moderation_tab"}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
<li class="navigation-item security">
|
||||
{{#link-to "admin.dashboardNextSecurity" class="navigation-link"}}
|
||||
{{i18n "admin.dashboard.security_tab"}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{{outlet}}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
<div class="sections">
|
||||
{{plugin-outlet name="admin-dashboard-security-top"}}
|
||||
|
||||
<div class="main-section">
|
||||
{{admin-report
|
||||
dataSourceName="suspicious_logins"
|
||||
filters=lastWeekfilters}}
|
||||
|
||||
{{admin-report
|
||||
dataSourceName="staff_logins"
|
||||
filters=lastWeekfilters}}
|
||||
|
||||
{{plugin-outlet name="admin-dashboard-security-bottom"}}
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,25 @@
|
||||
<p>{{i18n 'admin.email.advanced_test.desc'}}</p>
|
||||
|
||||
<div class='email-advanced-test'>
|
||||
<label for="email">{{i18n 'admin.email.advanced_test.email'}}</label>
|
||||
{{textarea name="email" value=email class="email-body"}}
|
||||
<button class='btn' {{action "run"}}>{{i18n 'admin.email.advanced_test.run'}}</button>
|
||||
</div>
|
||||
|
||||
{{#conditional-loading-spinner condition=loading}}
|
||||
|
||||
{{#if format}}
|
||||
<hr/>
|
||||
<div class="text">
|
||||
<h3>{{i18n 'admin.email.advanced_test.text'}}</h3>
|
||||
<pre class="full-reason">{{{text}}}</pre>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<div class="elided">
|
||||
<h3>{{i18n 'admin.email.advanced_test.elided'}}</h3>
|
||||
<pre class="full-reason">{{{elided}}}</pre>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{/conditional-loading-spinner}}
|
||||
@ -1,6 +1,7 @@
|
||||
{{#admin-nav}}
|
||||
{{nav-item route='adminEmail.index' label='admin.email.settings'}}
|
||||
{{nav-item route='adminEmail.previewDigest' label='admin.email.preview_digest'}}
|
||||
{{nav-item route='adminEmail.advancedTest' label='admin.email.advanced_test.title'}}
|
||||
{{nav-item route='adminCustomizeEmailTemplates' label='admin.email.templates'}}
|
||||
{{nav-item route='adminEmail.sent' label='admin.email.sent'}}
|
||||
{{nav-item route='adminEmail.skipped' label='admin.email.skipped'}}
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</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_searches'}}</div>{{item.unique_searches}}</td>
|
||||
<td class="col"><div class="label">{{i18n 'admin.logs.search_logs.unique'}}</div>{{item.unique_searches}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
<div class="admin-nav pull-left">
|
||||
<ul class="nav nav-stacked">
|
||||
{{#each model as |category|}}
|
||||
{{#each visibleSiteSettings as |category|}}
|
||||
<li class="{{category.nameKey}}">
|
||||
{{#link-to 'adminSiteSettingsCategory' category.nameKey class=category.nameKey}}
|
||||
{{category.name}}
|
||||
|
||||
@ -51,6 +51,13 @@
|
||||
{{category-selector categories=model.categories}}
|
||||
<div class="instructions">{{i18n 'admin.web_hooks.categories_filter_instructions'}}</div>
|
||||
</div>
|
||||
{{#if showTagsFilter}}
|
||||
<div class="filter">
|
||||
<label>{{d-icon 'circle' class='tracking'}}{{i18n 'admin.web_hooks.tags_filter'}}</label>
|
||||
{{tag-chooser tags=model.tag_names everyTag=true}}
|
||||
<div class="instructions">{{i18n 'admin.web_hooks.tags_filter_instructions'}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="filter">
|
||||
<label>{{d-icon 'circle' class='tracking'}}{{i18n 'admin.web_hooks.groups_filter'}}</label>
|
||||
{{group-selector groupNames=model.groupsFilterInName groupFinder=model.groupFinder}}
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
export default function deprecated(msg) {
|
||||
console.warn(`DEPRECATION: ${msg}`); // eslint-disable-line no-console
|
||||
export default function deprecated(msg, opts = {}) {
|
||||
msg = ["Deprecation notice:", msg];
|
||||
if (opts.since) {
|
||||
msg.push(`(deprecated since Discourse ${opts.since})`);
|
||||
}
|
||||
if (opts.dropFrom) {
|
||||
msg.push(`(removal in Discourse ${opts.dropFrom})`);
|
||||
}
|
||||
msg = msg.join(" ");
|
||||
|
||||
if (opts.raiseError) {
|
||||
throw msg;
|
||||
}
|
||||
console.warn(msg); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
@ -470,6 +470,7 @@ const fa4Replacements = {
|
||||
"vimeo-square": "fab-vimeo-square",
|
||||
vine: "fab-vine",
|
||||
vk: "fab-vk",
|
||||
vkontakte: "fab-vk",
|
||||
"volume-control-phone": "phone-volume",
|
||||
warning: "exclamation-triangle",
|
||||
wechat: "fab-weixin",
|
||||
|
||||
@ -12,8 +12,5 @@ export default Ember.Component.extend({
|
||||
return this.get("categories").any(c => {
|
||||
return !Ember.isEmpty(c.get("uploaded_logo.url"));
|
||||
});
|
||||
return this.get("categories").any(
|
||||
c => !Ember.isEmpty(c.get("uploaded_logo.url"))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -12,9 +12,11 @@ export default Ember.Component.extend({
|
||||
|
||||
@computed("composeState")
|
||||
fullscreenTitle(composeState) {
|
||||
return composeState === "fullscreen"
|
||||
? "composer.exit_fullscreen"
|
||||
: "composer.enter_fullscreen";
|
||||
return composeState === "draft"
|
||||
? "composer.open"
|
||||
: composeState === "fullscreen"
|
||||
? "composer.exit_fullscreen"
|
||||
: "composer.enter_fullscreen";
|
||||
},
|
||||
|
||||
@computed("composeState")
|
||||
@ -26,6 +28,10 @@ export default Ember.Component.extend({
|
||||
|
||||
@computed("composeState")
|
||||
fullscreenIcon(composeState) {
|
||||
return composeState === "fullscreen" ? "compress" : "expand";
|
||||
return composeState === "draft"
|
||||
? "chevron-up"
|
||||
: composeState === "fullscreen"
|
||||
? "discourse-compress"
|
||||
: "discourse-expand";
|
||||
}
|
||||
});
|
||||
|
||||
@ -410,10 +410,17 @@ export default Ember.Component.extend({
|
||||
},
|
||||
|
||||
onKeyUp(text, cp) {
|
||||
const matches = /(?:^|[^a-z])(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/gi.exec(
|
||||
text.substring(0, cp)
|
||||
);
|
||||
// Regular expressions used to extract emoji name from text.
|
||||
// The space version requires a ' ' (space) before the emoji name
|
||||
// (i.e. ' :smile'), while the other one does not and is used
|
||||
// when enable_inline_emoji_translation is true.
|
||||
const noSpaceColonEmoji = /(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/gi;
|
||||
const spaceColonEmoji = /(?:^|[^a-z])(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/gi;
|
||||
|
||||
const regex = self.siteSettings.enable_inline_emoji_translation
|
||||
? noSpaceColonEmoji
|
||||
: spaceColonEmoji;
|
||||
const matches = regex.exec(text.substring(0, cp));
|
||||
if (matches && matches[1]) {
|
||||
return [matches[1]];
|
||||
}
|
||||
@ -676,8 +683,13 @@ export default Ember.Component.extend({
|
||||
// Replace value (side effect: cursor at the end).
|
||||
this.set("value", val.replace(oldVal, newVal));
|
||||
|
||||
// Restore cursor.
|
||||
this._selectText(newSelection.start, newSelection.end - newSelection.start);
|
||||
if ($("textarea.d-editor-input").is(":focus")) {
|
||||
// Restore cursor.
|
||||
this._selectText(
|
||||
newSelection.start,
|
||||
newSelection.end - newSelection.start
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_addBlock(sel, text) {
|
||||
|
||||
@ -12,6 +12,8 @@ export default Ember.Component.extend({
|
||||
|
||||
const connectorClass = this.get("connector.connectorClass");
|
||||
connectorClass.setupComponent.call(this, args, this);
|
||||
|
||||
this.set("actions", connectorClass.actions);
|
||||
},
|
||||
|
||||
@observes("args")
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import MountWidget from "discourse/components/mount-widget";
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
import Docking from "discourse/mixins/docking";
|
||||
import PanEvents, {
|
||||
SWIPE_VELOCITY,
|
||||
SWIPE_DISTANCE_THRESHOLD,
|
||||
SWIPE_VELOCITY_THRESHOLD
|
||||
} from "discourse/mixins/pan-events";
|
||||
|
||||
const _flagProperties = [];
|
||||
function addFlagProperty(prop) {
|
||||
@ -9,10 +14,20 @@ function addFlagProperty(prop) {
|
||||
|
||||
const PANEL_BODY_MARGIN = 30;
|
||||
|
||||
const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
//android supports pulling in from the screen edges
|
||||
const SCREEN_EDGE_MARGIN = 30;
|
||||
const SCREEN_OFFSET = 300;
|
||||
|
||||
const SiteHeaderComponent = MountWidget.extend(Docking, PanEvents, {
|
||||
widget: "header",
|
||||
docAt: null,
|
||||
dockedHeader: null,
|
||||
_animate: false,
|
||||
_isPanning: false,
|
||||
_panMenuOrigin: "right",
|
||||
_panMenuOffset: 0,
|
||||
_scheduledMovingAnimation: null,
|
||||
_scheduledRemoveAnimate: null,
|
||||
_topic: null,
|
||||
|
||||
@observes(
|
||||
@ -23,6 +38,157 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
this.queueRerender();
|
||||
},
|
||||
|
||||
_animateOpening($panel) {
|
||||
$panel.css({ right: "", left: "" });
|
||||
this._panMenuOffset = 0;
|
||||
},
|
||||
|
||||
_animateClosing($panel, menuOrigin, windowWidth) {
|
||||
$panel.css(menuOrigin, -windowWidth);
|
||||
this._animate = true;
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
this.eventDispatched("dom:clean", "header");
|
||||
this._panMenuOffset = 0;
|
||||
});
|
||||
},
|
||||
|
||||
_isRTL() {
|
||||
return $("html").css("direction") === "rtl";
|
||||
},
|
||||
|
||||
_leftMenuClass() {
|
||||
return this._isRTL() ? ".user-menu" : ".hamburger-panel";
|
||||
},
|
||||
|
||||
_leftMenuAction() {
|
||||
return this._isRTL() ? "toggleUserMenu" : "toggleHamburger";
|
||||
},
|
||||
|
||||
_rightMenuAction() {
|
||||
return this._isRTL() ? "toggleHamburger" : "toggleUserMenu";
|
||||
},
|
||||
|
||||
_handlePanDone(offset, event) {
|
||||
const $window = $(window);
|
||||
const windowWidth = parseInt($window.width());
|
||||
const $menuPanels = $(".menu-panel");
|
||||
const menuOrigin = this._panMenuOrigin;
|
||||
this._shouldMenuClose(event, menuOrigin)
|
||||
? (offset += SWIPE_VELOCITY)
|
||||
: (offset -= SWIPE_VELOCITY);
|
||||
$menuPanels.each((idx, panel) => {
|
||||
const $panel = $(panel);
|
||||
const $headerCloak = $(".header-cloak");
|
||||
$panel.css(menuOrigin, -offset);
|
||||
$headerCloak.css("opacity", Math.min(0.5, (300 - offset) / 600));
|
||||
if (offset > windowWidth) {
|
||||
this._animateClosing($panel, menuOrigin, windowWidth);
|
||||
} else if (offset <= 0) {
|
||||
this._animateOpening($panel);
|
||||
} else {
|
||||
//continue to open or close menu
|
||||
this._scheduledMovingAnimation = window.requestAnimationFrame(() =>
|
||||
this._handlePanDone(offset, event)
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_shouldMenuClose(e, menuOrigin) {
|
||||
// menu should close after a pan either:
|
||||
// if a user moved the panel closed past a threshold and away and is NOT swiping back open
|
||||
// if a user swiped to close fast enough regardless of distance
|
||||
if (menuOrigin === "right") {
|
||||
return (
|
||||
(e.deltaX > SWIPE_DISTANCE_THRESHOLD &&
|
||||
e.velocityX > -SWIPE_VELOCITY_THRESHOLD) ||
|
||||
e.velocityX > 0
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
(e.deltaX < -SWIPE_DISTANCE_THRESHOLD &&
|
||||
e.velocityX < SWIPE_VELOCITY_THRESHOLD) ||
|
||||
e.velocityX < 0
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
panStart(e) {
|
||||
const center = e.center;
|
||||
const $centeredElement = $(document.elementFromPoint(center.x, center.y));
|
||||
const $window = $(window);
|
||||
const windowWidth = parseInt($window.width());
|
||||
if (
|
||||
($centeredElement.hasClass("panel-body") ||
|
||||
$centeredElement.hasClass("header-cloak") ||
|
||||
$centeredElement.parents(".panel-body").length) &&
|
||||
(e.direction === "left" || e.direction === "right")
|
||||
) {
|
||||
e.originalEvent.preventDefault();
|
||||
this._isPanning = true;
|
||||
} else if (
|
||||
center.x < SCREEN_EDGE_MARGIN &&
|
||||
!this.$(".menu-panel").length &&
|
||||
e.direction === "right"
|
||||
) {
|
||||
this._animate = false;
|
||||
this._panMenuOrigin = "left";
|
||||
this._panMenuOffset = -SCREEN_OFFSET;
|
||||
this._isPanning = true;
|
||||
$("header.d-header").removeClass("scroll-down scroll-up");
|
||||
this.eventDispatched(this._leftMenuAction(), "header");
|
||||
} else if (
|
||||
windowWidth - center.x < SCREEN_EDGE_MARGIN &&
|
||||
!this.$(".menu-panel").length &&
|
||||
e.direction === "left"
|
||||
) {
|
||||
this._animate = false;
|
||||
this._panMenuOrigin = "right";
|
||||
this._panMenuOffset = -SCREEN_OFFSET;
|
||||
this._isPanning = true;
|
||||
$("header.d-header").removeClass("scroll-down scroll-up");
|
||||
this.eventDispatched(this._rightMenuAction(), "header");
|
||||
} else {
|
||||
this._isPanning = false;
|
||||
}
|
||||
},
|
||||
|
||||
panEnd(e) {
|
||||
if (!this._isPanning) {
|
||||
return;
|
||||
}
|
||||
this._isPanning = false;
|
||||
$(".menu-panel").each((idx, panel) => {
|
||||
const $panel = $(panel);
|
||||
let offset = $panel.css("right");
|
||||
if (this._panMenuOrigin === "left") {
|
||||
offset = $panel.css("left");
|
||||
}
|
||||
offset = Math.abs(parseInt(offset, 10));
|
||||
this._handlePanDone(offset, e);
|
||||
});
|
||||
},
|
||||
|
||||
panMove(e) {
|
||||
if (!this._isPanning) {
|
||||
return;
|
||||
}
|
||||
const $menuPanels = $(".menu-panel");
|
||||
$menuPanels.each((idx, panel) => {
|
||||
const $panel = $(panel);
|
||||
const $headerCloak = $(".header-cloak");
|
||||
if (this._panMenuOrigin === "right") {
|
||||
const pxClosed = Math.min(0, -e.deltaX + this._panMenuOffset);
|
||||
$panel.css("right", pxClosed);
|
||||
$headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
|
||||
} else {
|
||||
const pxClosed = Math.min(0, e.deltaX + this._panMenuOffset);
|
||||
$panel.css("left", pxClosed);
|
||||
$headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
dockCheck(info) {
|
||||
const $header = $("header.d-header");
|
||||
|
||||
@ -47,6 +213,7 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
},
|
||||
|
||||
setTopic(topic) {
|
||||
this.eventDispatched("dom:clean", "header");
|
||||
this._topic = topic;
|
||||
this.queueRerender();
|
||||
},
|
||||
@ -74,6 +241,14 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
this.eventDispatched("dom:clean", "header");
|
||||
}
|
||||
});
|
||||
|
||||
if (this.site.mobileView) {
|
||||
$("body")
|
||||
.on("pointerdown", e => this._panStart(e))
|
||||
.on("pointermove", e => this._panMove(e))
|
||||
.on("pointerup", e => this._panMove(e))
|
||||
.on("pointercancel", e => this._panMove(e));
|
||||
}
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
@ -84,6 +259,16 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
this.appEvents.off("header:show-topic");
|
||||
this.appEvents.off("header:hide-topic");
|
||||
this.appEvents.off("dom:clean");
|
||||
|
||||
if (this.site.mobileView) {
|
||||
$("body")
|
||||
.off("pointerdown")
|
||||
.off("pointerup")
|
||||
.off("pointermove")
|
||||
.off("pointercancel");
|
||||
}
|
||||
Ember.run.cancel(this._scheduledRemoveAnimate);
|
||||
window.cancelAnimationFrame(this._scheduledMovingAnimation);
|
||||
},
|
||||
|
||||
buildArgs() {
|
||||
@ -100,6 +285,9 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
afterRender() {
|
||||
const $menuPanels = $(".menu-panel");
|
||||
if ($menuPanels.length === 0) {
|
||||
if (this.site.mobileView) {
|
||||
this._animate = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -112,15 +300,29 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
|
||||
$menuPanels.each((idx, panel) => {
|
||||
const $panel = $(panel);
|
||||
const $headerCloak = $(".header-cloak");
|
||||
let width = parseInt($panel.attr("data-max-width") || 300);
|
||||
if (windowWidth - width < 50) {
|
||||
width = windowWidth - 50;
|
||||
}
|
||||
if (this._panMenuOffset) {
|
||||
this._panMenuOffset = -width;
|
||||
}
|
||||
|
||||
$panel
|
||||
.removeClass("drop-down")
|
||||
.removeClass("slide-in")
|
||||
.addClass(viewMode);
|
||||
$panel.removeClass("drop-down slide-in").addClass(viewMode);
|
||||
if (this._animate || this._panMenuOffset !== 0) {
|
||||
$headerCloak.css("opacity", 0);
|
||||
if (
|
||||
this.site.mobileView &&
|
||||
$panel.parent(this._leftMenuClass()).length > 0
|
||||
) {
|
||||
this._panMenuOrigin = "left";
|
||||
$panel.css("left", -windowWidth);
|
||||
} else {
|
||||
this._panMenuOrigin = "right";
|
||||
$panel.css("right", -windowWidth);
|
||||
}
|
||||
}
|
||||
|
||||
const $panelBody = $(".panel-body", $panel);
|
||||
// 2 pixel fudge allows for firefox subpixel sizing stuff causing scrollbar
|
||||
@ -150,7 +352,8 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
|
||||
if (
|
||||
contentHeight + (offsetTop - scrollTop) + PANEL_BODY_MARGIN >
|
||||
fullHeight
|
||||
fullHeight ||
|
||||
this.site.mobileView
|
||||
) {
|
||||
contentHeight =
|
||||
fullHeight - (offsetTop - scrollTop) - PANEL_BODY_MARGIN;
|
||||
@ -160,11 +363,19 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
}
|
||||
$("body").addClass("drop-down-mode");
|
||||
} else {
|
||||
const menuTop = headerHeight();
|
||||
if (this.site.mobileView) {
|
||||
$headerCloak.show();
|
||||
}
|
||||
|
||||
const menuTop = this.site.mobileView ? 0 : headerHeight();
|
||||
|
||||
let height;
|
||||
const winHeight = $(window).height() - 16;
|
||||
if (menuTop + contentHeight < winHeight) {
|
||||
const winHeightOffset = 16;
|
||||
let initialWinHeight = window.innerHeight
|
||||
? window.innerHeight
|
||||
: $(window).height();
|
||||
const winHeight = initialWinHeight - winHeightOffset;
|
||||
if (menuTop + contentHeight < winHeight && !this.site.mobileView) {
|
||||
height = contentHeight + "px";
|
||||
} else {
|
||||
height = winHeight - menuTop;
|
||||
@ -180,6 +391,17 @@ const SiteHeaderComponent = MountWidget.extend(Docking, {
|
||||
}
|
||||
|
||||
$panel.width(width);
|
||||
if (this._animate) {
|
||||
$panel.addClass("animate");
|
||||
$headerCloak.addClass("animate");
|
||||
this._scheduledRemoveAnimate = Ember.run.later(() => {
|
||||
$panel.removeClass("animate");
|
||||
$headerCloak.removeClass("animate");
|
||||
}, 200);
|
||||
}
|
||||
$panel.css({ right: "", left: "" });
|
||||
$headerCloak.css("opacity", 0.5);
|
||||
this._animate = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -96,13 +96,6 @@ export default Ember.Component.extend(
|
||||
return classes.join(" ");
|
||||
},
|
||||
|
||||
titleColSpan: function() {
|
||||
return !this.get("hideCategory") &&
|
||||
this.get("topic.isPinnedUncategorized")
|
||||
? 2
|
||||
: 1;
|
||||
}.property("topic.isPinnedUncategorized"),
|
||||
|
||||
hasLikes: function() {
|
||||
return this.get("topic.like_count") > 0;
|
||||
},
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { observes } from "ember-addons/ember-computed-decorators";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import PanEvents from "discourse/mixins/pan-events";
|
||||
import PanEvents, {
|
||||
SWIPE_VELOCITY,
|
||||
SWIPE_DISTANCE_THRESHOLD,
|
||||
SWIPE_VELOCITY_THRESHOLD
|
||||
} from "discourse/mixins/pan-events";
|
||||
|
||||
export default Ember.Component.extend(PanEvents, {
|
||||
composerOpen: null,
|
||||
@ -117,10 +121,13 @@ export default Ember.Component.extend(PanEvents, {
|
||||
}
|
||||
},
|
||||
|
||||
_panOpenClose(offset, velocity, direction) {
|
||||
_handlePanDone(offset, event) {
|
||||
const $timelineContainer = $(".timeline-container");
|
||||
const maxOffset = parseInt($timelineContainer.css("height"));
|
||||
direction === "close" ? (offset += velocity) : (offset -= velocity);
|
||||
const maxOffset = parseInt($timelineContainer.css("height"), 10);
|
||||
|
||||
this._shouldPanClose(event)
|
||||
? (offset += SWIPE_VELOCITY)
|
||||
: (offset -= SWIPE_VELOCITY);
|
||||
|
||||
$timelineContainer.css("bottom", -offset);
|
||||
if (offset > maxOffset) {
|
||||
@ -128,43 +135,43 @@ export default Ember.Component.extend(PanEvents, {
|
||||
} else if (offset <= 0) {
|
||||
$timelineContainer.css("bottom", "");
|
||||
} else {
|
||||
Ember.run.later(
|
||||
() => this._panOpenClose(offset, velocity, direction),
|
||||
20
|
||||
);
|
||||
Ember.run.later(() => this._handlePanDone(offset, event), 20);
|
||||
}
|
||||
},
|
||||
|
||||
_shouldPanClose(e) {
|
||||
return (e.deltaY > 200 && e.velocityY > -0.15) || e.velocityY > 0.15;
|
||||
return (
|
||||
(e.deltaY > SWIPE_DISTANCE_THRESHOLD &&
|
||||
e.velocityY > -SWIPE_VELOCITY_THRESHOLD) ||
|
||||
e.velocityY > SWIPE_VELOCITY_THRESHOLD
|
||||
);
|
||||
},
|
||||
|
||||
panStart(e) {
|
||||
e.originalEvent.preventDefault();
|
||||
const center = e.center;
|
||||
const $centeredElement = $(document.elementFromPoint(center.x, center.y));
|
||||
if ($centeredElement.parents(".timeline-scrollarea-wrapper").length) {
|
||||
this.set("isPanning", false);
|
||||
this.isPanning = false;
|
||||
} else if (e.direction === "up" || e.direction === "down") {
|
||||
this.set("isPanning", true);
|
||||
this.isPanning = true;
|
||||
}
|
||||
},
|
||||
|
||||
panEnd(e) {
|
||||
if (!this.get("isPanning")) {
|
||||
if (!this.isPanning) {
|
||||
return;
|
||||
}
|
||||
this.set("isPanning", false);
|
||||
if (this._shouldPanClose(e)) {
|
||||
this._panOpenClose(e.deltaY, 40, "close");
|
||||
} else {
|
||||
this._panOpenClose(e.deltaY, 40, "open");
|
||||
}
|
||||
e.originalEvent.preventDefault();
|
||||
this.isPanning = false;
|
||||
this._handlePanDone(e.deltaY, e);
|
||||
},
|
||||
|
||||
panMove(e) {
|
||||
if (!this.get("isPanning")) {
|
||||
if (!this.isPanning) {
|
||||
return;
|
||||
}
|
||||
e.originalEvent.preventDefault();
|
||||
$(".timeline-container").css("bottom", Math.min(0, -e.deltaY));
|
||||
},
|
||||
|
||||
|
||||
@ -62,6 +62,12 @@ function loadDraft(store, opts) {
|
||||
|
||||
const _popupMenuOptionsCallbacks = [];
|
||||
|
||||
let _checkDraftPopup = !Ember.testing;
|
||||
|
||||
export function toggleCheckDraftPopup(enabled) {
|
||||
_checkDraftPopup = enabled;
|
||||
}
|
||||
|
||||
export function clearPopupMenuOptionsCallback() {
|
||||
_popupMenuOptionsCallbacks.length = 0;
|
||||
}
|
||||
@ -770,23 +776,30 @@ export default Ember.Controller.extend({
|
||||
.then(resolve, reject);
|
||||
}
|
||||
|
||||
// we need a draft sequence for the composer to work
|
||||
if (opts.draftSequence === undefined) {
|
||||
return Draft.get(opts.draftKey)
|
||||
.then(function(data) {
|
||||
opts.draftSequence = data.draft_sequence;
|
||||
opts.draft = data.draft;
|
||||
self._setModel(composerModel, opts);
|
||||
})
|
||||
.then(resolve, reject);
|
||||
}
|
||||
|
||||
if (composerModel) {
|
||||
if (composerModel.get("action") !== opts.action) {
|
||||
composerModel.setProperties({ unlistTopic: false, whisper: false });
|
||||
}
|
||||
}
|
||||
|
||||
// check if there is another draft saved on server
|
||||
// or get a draft sequence number
|
||||
if (!opts.draft || opts.draftSequence === undefined) {
|
||||
return Draft.get(opts.draftKey)
|
||||
.then(data => self.confirmDraftAbandon(data))
|
||||
.then(data => {
|
||||
opts.draft = opts.draft || data.draft;
|
||||
|
||||
// we need a draft sequence for the composer to work
|
||||
if (opts.draft_sequence === undefined) {
|
||||
opts.draftSequence = data.draft_sequence;
|
||||
}
|
||||
|
||||
self._setModel(composerModel, opts);
|
||||
})
|
||||
.then(resolve, reject);
|
||||
}
|
||||
|
||||
self._setModel(composerModel, opts);
|
||||
resolve();
|
||||
});
|
||||
@ -865,6 +878,41 @@ export default Ember.Controller.extend({
|
||||
}
|
||||
},
|
||||
|
||||
confirmDraftAbandon(data) {
|
||||
if (!data.draft) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// do not show abandon dialog if old draft is clean
|
||||
const draft = JSON.parse(data.draft);
|
||||
if (draft.reply === draft.originalText) {
|
||||
data.draft = null;
|
||||
return data;
|
||||
}
|
||||
|
||||
if (_checkDraftPopup) {
|
||||
return new Ember.RSVP.Promise(resolve => {
|
||||
bootbox.dialog(I18n.t("drafts.abandon.confirm"), [
|
||||
{
|
||||
label: I18n.t("drafts.abandon.no_value"),
|
||||
callback: () => resolve(data)
|
||||
},
|
||||
{
|
||||
label: I18n.t("drafts.abandon.yes_value"),
|
||||
class: "btn-danger",
|
||||
callback: () => {
|
||||
data.draft = null;
|
||||
resolve(data);
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
data.draft = null;
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
cancelComposer() {
|
||||
return new Ember.RSVP.Promise(resolve => {
|
||||
if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
|
||||
|
||||
@ -111,7 +111,7 @@ const controllerOpts = {
|
||||
return (
|
||||
(this.isFilterPage(this.get("model.filter"), "new") ||
|
||||
this.isFilterPage(this.get("model.filter"), "unread")) &&
|
||||
this.get("model.topics.length") >= 30
|
||||
this.get("model.topics.length") >= 15
|
||||
);
|
||||
}.property("model.filter", "model.topics.length"),
|
||||
|
||||
|
||||
@ -243,7 +243,7 @@ export default Ember.Controller.extend(
|
||||
},
|
||||
|
||||
connectAccount(method) {
|
||||
method.doLogin();
|
||||
method.doLogin(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||
import lightbox from "discourse/lib/lightbox";
|
||||
import { setupLazyLoading } from "discourse/lib/lazy-load-images";
|
||||
import { setTextDirections } from "discourse/lib/text-direction";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
@ -14,6 +15,8 @@ export default {
|
||||
api.decorateCooked(setTextDirections);
|
||||
}
|
||||
|
||||
setupLazyLoading(api);
|
||||
|
||||
api.decorateCooked($elem => {
|
||||
const players = $("audio", $elem);
|
||||
if (players.length) {
|
||||
|
||||
49
app/assets/javascripts/discourse/lib/lazy-load-images.js.es6
Normal file
@ -0,0 +1,49 @@
|
||||
const OBSERVER_OPTIONS = {
|
||||
rootMargin: "50%" // load images slightly before they're visible
|
||||
};
|
||||
|
||||
// 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=="
|
||||
);
|
||||
}
|
||||
|
||||
// Restore an image from the `data-src` attribute
|
||||
function show(image) {
|
||||
let dataSrc = image.getAttribute("data-src");
|
||||
if (dataSrc) {
|
||||
image.setAttribute("src", dataSrc);
|
||||
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;
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
@ -3,8 +3,9 @@ import { escapeExpression } from "discourse/lib/utilities";
|
||||
const fadeSpeed = 300;
|
||||
const tooltipID = "#discourse-tooltip";
|
||||
|
||||
export function showTooltip($this) {
|
||||
const $parent = $this.offsetParent();
|
||||
export function showTooltip(e) {
|
||||
const $this = $(e.currentTarget),
|
||||
$parent = $this.offsetParent();
|
||||
// html tooltip are risky try your best to sanitize anything
|
||||
// displayed as html to avoid XSS attacks
|
||||
const content = $this.attr("data-tooltip")
|
||||
@ -77,9 +78,7 @@ export function hideTooltip() {
|
||||
|
||||
export function registerTooltip(jqueryContext) {
|
||||
if (jqueryContext.length) {
|
||||
jqueryContext
|
||||
.off("click")
|
||||
.on("click", event => showTooltip($(event.currentTarget)));
|
||||
jqueryContext.off("click").on("click", event => showTooltip(event));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
/**
|
||||
Pan events is a mixin that allows components to detect and respond to swipe gestures
|
||||
It fires callbacks for panStart, panEnd, panMove with the pan state, and the original event.
|
||||
**/
|
||||
export const SWIPE_VELOCITY = 40;
|
||||
export const SWIPE_DISTANCE_THRESHOLD = 50;
|
||||
export const SWIPE_VELOCITY_THRESHOLD = 0.1;
|
||||
export default Ember.Mixin.create({
|
||||
//velocity is pixels per ms
|
||||
|
||||
@ -7,11 +14,23 @@ export default Ember.Mixin.create({
|
||||
this._super();
|
||||
|
||||
if (this.site.mobileView) {
|
||||
this.$()
|
||||
.on("pointerdown", e => this._panStart(e))
|
||||
.on("pointermove", e => this._panMove(e))
|
||||
.on("pointerup", e => this._panMove(e))
|
||||
.on("pointercancel", e => this._panMove(e));
|
||||
if ("onpointerdown" in document.documentElement) {
|
||||
this.$()
|
||||
.on("pointerdown", e => this._panStart(e))
|
||||
.on("pointermove", e => this._panMove(e, e))
|
||||
.on("pointerup", e => this._panMove(e, e))
|
||||
.on("pointercancel", e => this._panMove(e, e));
|
||||
} else if ("ontouchstart" in document.documentElement) {
|
||||
this.$()
|
||||
.on("touchstart", e => this._panStart(e.touches[0]))
|
||||
.on("touchmove", e => {
|
||||
const touchEvent = e.touches[0];
|
||||
touchEvent.type = "pointermove";
|
||||
this._panMove(touchEvent, e);
|
||||
})
|
||||
.on("touchend", e => this._panMove({ type: "pointerup" }, e))
|
||||
.on("touchcancel", e => this._panMove({ type: "pointercancel" }, e));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -23,7 +42,11 @@ export default Ember.Mixin.create({
|
||||
.off("pointerdown")
|
||||
.off("pointerup")
|
||||
.off("pointermove")
|
||||
.off("pointercancel");
|
||||
.off("pointercancel")
|
||||
.off("touchstart")
|
||||
.off("touchmove")
|
||||
.off("touchend")
|
||||
.off("touchcancel");
|
||||
}
|
||||
},
|
||||
|
||||
@ -44,6 +67,9 @@ export default Ember.Mixin.create({
|
||||
}
|
||||
const newTimestamp = new Date().getTime();
|
||||
const timeDiffSeconds = newTimestamp - oldState.timestamp;
|
||||
if (timeDiffSeconds === 0) {
|
||||
return oldState;
|
||||
}
|
||||
//calculate delta x, y, distance from START location
|
||||
const deltaX = e.clientX - oldState.startLocation.x;
|
||||
const deltaY = e.clientY - oldState.startLocation.y;
|
||||
@ -93,7 +119,7 @@ export default Ember.Mixin.create({
|
||||
this.set("_panState", newState);
|
||||
},
|
||||
|
||||
_panMove(e) {
|
||||
_panMove(e, originalEvent) {
|
||||
if (!this.get("_panState")) {
|
||||
this._panStart(e);
|
||||
return;
|
||||
@ -104,6 +130,7 @@ export default Ember.Mixin.create({
|
||||
return;
|
||||
}
|
||||
this.set("_panState", newState);
|
||||
newState.originalEvent = originalEvent;
|
||||
if (previousState.start && "panStart" in this) {
|
||||
this.panStart(newState);
|
||||
} else if (
|
||||
|
||||
@ -24,7 +24,7 @@ const LoginMethod = Ember.Object.extend({
|
||||
);
|
||||
},
|
||||
|
||||
doLogin() {
|
||||
doLogin(reconnect = false) {
|
||||
const name = this.get("name");
|
||||
const customLogin = this.get("customLogin");
|
||||
|
||||
@ -33,6 +33,10 @@ const LoginMethod = Ember.Object.extend({
|
||||
} else {
|
||||
let authUrl = this.get("custom_url") || Discourse.getURL("/auth/" + name);
|
||||
|
||||
if (reconnect) {
|
||||
authUrl += "?reconnect=true";
|
||||
}
|
||||
|
||||
if (this.get("full_screen_login")) {
|
||||
document.cookie = "fsl=true";
|
||||
window.location = authUrl;
|
||||
@ -45,7 +49,8 @@ const LoginMethod = Ember.Object.extend({
|
||||
const width = this.get("frame_width") || 800;
|
||||
|
||||
if (name === "facebook") {
|
||||
authUrl = authUrl + "?display=popup";
|
||||
authUrl += authUrl.includes("?") ? "&" : "?";
|
||||
authUrl += "display=popup";
|
||||
}
|
||||
|
||||
const w = window.open(
|
||||
|
||||
@ -369,10 +369,10 @@ Post.reopenClass({
|
||||
});
|
||||
},
|
||||
|
||||
deleteMany(post_ids) {
|
||||
deleteMany(post_ids, { deferFlags = false } = {}) {
|
||||
return ajax("/posts/destroy_many", {
|
||||
type: "DELETE",
|
||||
data: { post_ids }
|
||||
data: { post_ids, defer_flags: deferFlags }
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ export default Ember.Object.extend({
|
||||
return this.forceName;
|
||||
}
|
||||
|
||||
return I18n.t(this.name);
|
||||
return this.name ? I18n.t(this.name) : "";
|
||||
}.property(),
|
||||
|
||||
sortIcon: function() {
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import buildStaticRoute from "discourse/routes/build-static-route";
|
||||
import { defaultHomepage } from "discourse/lib/utilities";
|
||||
|
||||
const LoginRoute = buildStaticRoute("login");
|
||||
|
||||
LoginRoute.reopen({
|
||||
beforeModel() {
|
||||
if (!this.siteSettings.login_required) {
|
||||
this.replaceWith("discovery.latest").then(e => {
|
||||
this.replaceWith(`/${defaultHomepage()}`).then(e => {
|
||||
Ember.run.next(() => e.send("showLogin"));
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
{{
|
||||
category-drop
|
||||
{{category-drop
|
||||
category=firstCategory
|
||||
categories=parentCategoriesSorted
|
||||
countSubcategories=true
|
||||
}}
|
||||
countSubcategories=true}}
|
||||
|
||||
{{#if childCategories}}
|
||||
{{category-drop
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{{#each categories as |c|}}
|
||||
<div class='category-box category-box-{{unbound c.slug}}' style={{border-color c.color}}>
|
||||
<div class='category category-box category-box-{{unbound c.slug}}' style={{border-color c.color}}>
|
||||
<div class='category-box-inner'>
|
||||
<div class='category-box-heading'>
|
||||
<a href={{c.url}}>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{{#each categories as |c|}}
|
||||
<div class='category-box category-box-{{unbound c.slug}}' style={{border-color c.color}} data-url={{c.url}}>
|
||||
<div class='category category-box category-box-{{unbound c.slug}}' style={{border-color c.color}} data-url={{c.url}}>
|
||||
<div class='category-box-inner'>
|
||||
<div class="category-logo">
|
||||
{{#if c.uploaded_logo.url}}
|
||||
@ -30,10 +30,10 @@
|
||||
<a class="subcategory" href={{sc.url}}>
|
||||
<span class="subcategory-image-placeholder">
|
||||
{{cdn-img
|
||||
src=c.uploaded_logo.url
|
||||
src=sc.uploaded_logo.url
|
||||
class="logo"
|
||||
width=c.uploaded_logo.width
|
||||
height=c.uploaded_logo.height}}
|
||||
width=sc.uploaded_logo.width
|
||||
height=sc.uploaded_logo.height}}
|
||||
</span>
|
||||
<span class="subcategory-link">{{sc.name}}</span>
|
||||
</a>
|
||||
|
||||
@ -169,7 +169,7 @@
|
||||
<label for="category-minimum-tags">
|
||||
{{i18n 'category.minimum_required_tags'}}
|
||||
</label>
|
||||
{{text-field value=category.minimum_required_tags id="category-minimum-tags" type="number"}}
|
||||
{{text-field value=category.minimum_required_tags id="category-minimum-tags" type="number" min="0"}}
|
||||
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
<div class="user-image">
|
||||
<a href="{{unbound userPath}}" data-user-card="{{unbound user.username}}">{{avatar user imageSize="large"}}</a>
|
||||
<div class="user-image-inner">
|
||||
<a href="{{unbound userPath}}" data-user-card="{{unbound user.username}}">{{avatar user imageSize="large"}}</a>
|
||||
{{#if user.primary_group_name}}
|
||||
{{avatar-flair
|
||||
flairURL=user.primary_group_flair_url
|
||||
flairBgColor=user.primary_group_flair_bg_color
|
||||
flairColor=user.primary_group_flair_color
|
||||
groupName=user.primary_group_name}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-detail">
|
||||
|
||||
@ -179,7 +179,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
{{else}}
|
||||
<div class='saving-text'>
|
||||
{{#if model.createdPost}}
|
||||
{{i18n 'composer.saved'}} <a class='permalink' href="{{unbound createdPost.url}}" {{action "viewNewReply"}}>{{i18n 'composer.view_new_post'}}</a>
|
||||
@ -197,7 +197,7 @@
|
||||
</div>
|
||||
|
||||
{{composer-toggles composeState=model.composeState
|
||||
toggleFullscreen=(action "fullscreenComposer")
|
||||
toggleFullscreen=(action "openIfDraft")
|
||||
toggleComposer=(action "toggle")
|
||||
toggleToolbar=(action "toggleToolbar")}}
|
||||
|
||||
|
||||
@ -1 +1,6 @@
|
||||
<{{tagName}} class="{{class}} {{cold-age-class topic.createdAt startDate=topic.bumpedAt class=""}} activity" title="{{{topic.bumpedAtTitle}}}"><a class="post-activity" href="{{topic.lastPostUrl}}">{{format-date topic.bumpedAt format="tiny" noTitle="true"}}</a></{{tagName}}>
|
||||
<{{tagName}} class="{{class}} {{cold-age-class topic.createdAt startDate=topic.bumpedAt class=""}} activity" title="{{{topic.bumpedAtTitle}}}">
|
||||
<a class="post-activity" href="{{topic.lastPostUrl}}">
|
||||
{{raw-plugin-outlet name="topic-list-before-relative-date"}}
|
||||
{{format-date topic.bumpedAt format="tiny" noTitle="true"}}
|
||||
</a>
|
||||
</{{tagName}}>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
This causes the topic-post-badge to be considered the same word as "text"
|
||||
at the end of the link, preventing it from line wrapping onto its own line.
|
||||
--}}
|
||||
<td class='main-link clearfix' colspan="{{titleColSpan}}">
|
||||
<td class='main-link clearfix' colspan="1">
|
||||
<span class='link-top-line'>
|
||||
{{~raw-plugin-outlet name="topic-list-before-status"}}
|
||||
{{~raw "topic-status" topic=topic}}
|
||||
@ -24,20 +24,20 @@
|
||||
{{~raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
|
||||
{{~/if}}
|
||||
</span>
|
||||
|
||||
<div class="link-bottom-line">
|
||||
{{#unless hideCategory}}
|
||||
{{#unless topic.isPinnedUncategorized}}
|
||||
{{category-link topic.category}}
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
{{discourse-tags topic mode="list" tagsForUser=tagsForUser}}
|
||||
{{#if expandPinned}}
|
||||
{{raw "list/topic-excerpt" topic=topic}}
|
||||
{{/if}}
|
||||
{{raw "list/action-list" topic=topic postNumbers=topic.liked_post_numbers className="likes" icon="heart"}}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{#unless hideCategory}}
|
||||
{{#unless topic.isPinnedUncategorized}}
|
||||
{{raw "list/category-column" category=topic.category}}
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if showPosters}}
|
||||
{{raw "list/posters-column" posters=topic.posters}}
|
||||
{{/if}}
|
||||
|
||||
@ -6,11 +6,8 @@
|
||||
</th>
|
||||
{{/if}}
|
||||
{{raw "topic-list-header-column" order='default' name=listTitle bulkSelectEnabled=bulkSelectEnabled showBulkToggle=toggleInTitle canBulkSelect=canBulkSelect}}
|
||||
{{#unless hideCategory}}
|
||||
{{raw "topic-list-header-column" sortable=sortable order='category' name='category_title'}}
|
||||
{{/unless}}
|
||||
{{#if showPosters}}
|
||||
{{raw "topic-list-header-column" order='posters' name='users'}}
|
||||
{{raw "topic-list-header-column" order='posters'}}
|
||||
{{/if}}
|
||||
{{raw "topic-list-header-column" sortable=sortable number='true' order='posts' name='replies'}}
|
||||
{{#if showParticipants}}
|
||||
|
||||
@ -323,7 +323,32 @@ export default createWidget("hamburger-menu", {
|
||||
});
|
||||
},
|
||||
|
||||
clickOutside() {
|
||||
this.sendWidgetAction("toggleHamburger");
|
||||
clickOutsideMobile(e) {
|
||||
const $centeredElement = $(document.elementFromPoint(e.clientX, e.clientY));
|
||||
if (
|
||||
$centeredElement.parents(".panel").length &&
|
||||
!$centeredElement.hasClass("header-cloak")
|
||||
) {
|
||||
this.sendWidgetAction("toggleHamburger");
|
||||
} else {
|
||||
const $window = $(window);
|
||||
const windowWidth = parseInt($window.width(), 10);
|
||||
const $panel = $(".menu-panel");
|
||||
$panel.addClass("animate");
|
||||
const panelOffsetDirection = this.site.mobileView ? "left" : "right";
|
||||
$panel.css(panelOffsetDirection, -windowWidth);
|
||||
const $headerCloak = $(".header-cloak");
|
||||
$headerCloak.addClass("animate");
|
||||
$headerCloak.css("opacity", 0);
|
||||
Ember.run.later(() => this.sendWidgetAction("toggleHamburger"), 200);
|
||||
}
|
||||
},
|
||||
|
||||
clickOutside(e) {
|
||||
if (this.site.mobileView) {
|
||||
this.clickOutsideMobile(e);
|
||||
} else {
|
||||
this.sendWidgetAction("toggleHamburger");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -253,6 +253,15 @@ createWidget("header-buttons", {
|
||||
}
|
||||
});
|
||||
|
||||
createWidget("header-cloak", {
|
||||
tagName: "div.header-cloak",
|
||||
html() {
|
||||
return "";
|
||||
},
|
||||
click() {},
|
||||
scheduleRerender() {}
|
||||
});
|
||||
|
||||
const forceContextEnabled = ["category", "user", "private_messages"];
|
||||
|
||||
let additionalPanels = [];
|
||||
@ -315,6 +324,9 @@ export default createWidget("header", {
|
||||
} else if (state.userVisible) {
|
||||
panels.push(this.attach("user-menu"));
|
||||
}
|
||||
if (this.site.mobileView) {
|
||||
panels.push(this.attach("header-cloak"));
|
||||
}
|
||||
|
||||
additionalPanels.map(panel => {
|
||||
if (this.state[panel.toggle]) {
|
||||
@ -348,6 +360,7 @@ export default createWidget("header", {
|
||||
this.state.userVisible = false;
|
||||
this.state.hamburgerVisible = false;
|
||||
this.state.searchVisible = false;
|
||||
this.toggleBodyScrolling(false);
|
||||
},
|
||||
|
||||
linkClickedEvent(attrs) {
|
||||
@ -416,10 +429,36 @@ export default createWidget("header", {
|
||||
}
|
||||
|
||||
this.state.userVisible = !this.state.userVisible;
|
||||
this.toggleBodyScrolling(this.state.userVisible);
|
||||
},
|
||||
|
||||
toggleHamburger() {
|
||||
this.state.hamburgerVisible = !this.state.hamburgerVisible;
|
||||
this.toggleBodyScrolling(this.state.hamburgerVisible);
|
||||
},
|
||||
|
||||
toggleBodyScrolling(bool) {
|
||||
if (!this.site.mobileView) return;
|
||||
if (bool) {
|
||||
document.body.addEventListener("touchmove", this.preventDefault, {
|
||||
passive: false
|
||||
});
|
||||
} else {
|
||||
document.body.removeEventListener("touchmove", this.preventDefault, {
|
||||
passive: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
preventDefault(e) {
|
||||
// prevent all scrollin on menu panels, except on overflow
|
||||
const height = window.innerHeight ? window.innerHeight : $(window).height();
|
||||
if (
|
||||
!$(e.target).parents(".menu-panel").length ||
|
||||
$(".menu-panel .panel-body-contents").height() <= height
|
||||
) {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
togglePageSearch() {
|
||||
|
||||
@ -104,6 +104,11 @@ export default class PostCooked {
|
||||
valid = href.indexOf(lc.url) >= 0;
|
||||
}
|
||||
|
||||
// Match server-side behaviour for internal links with query params
|
||||
if (lc.internal && /\?/.test(href)) {
|
||||
valid = href.split("?")[0] === lc.url;
|
||||
}
|
||||
|
||||
// don't display badge counts on category badge & oneboxes (unless when explicitely stated)
|
||||
if (valid && isValidLink($link)) {
|
||||
const title = I18n.t("topic_map.clicks", { count: lc.clicks });
|
||||
|
||||
@ -194,7 +194,31 @@ export default createWidget("user-menu", {
|
||||
});
|
||||
},
|
||||
|
||||
clickOutside() {
|
||||
this.sendWidgetAction("toggleUserMenu");
|
||||
clickOutsideMobile(e) {
|
||||
const $centeredElement = $(document.elementFromPoint(e.clientX, e.clientY));
|
||||
if (
|
||||
$centeredElement.parents(".panel").length &&
|
||||
!$centeredElement.hasClass("header-cloak")
|
||||
) {
|
||||
this.sendWidgetAction("toggleUserMenu");
|
||||
} else {
|
||||
const $window = $(window);
|
||||
const windowWidth = parseInt($window.width(), 10);
|
||||
const $panel = $(".menu-panel");
|
||||
$panel.addClass("animate");
|
||||
$panel.css("right", -windowWidth);
|
||||
const $headerCloak = $(".header-cloak");
|
||||
$headerCloak.addClass("animate");
|
||||
$headerCloak.css("opacity", 0);
|
||||
Ember.run.later(() => this.sendWidgetAction("toggleUserMenu"), 200);
|
||||
}
|
||||
},
|
||||
|
||||
clickOutside(e) {
|
||||
if (this.site.mobileView) {
|
||||
this.clickOutsideMobile(e);
|
||||
} else {
|
||||
this.sendWidgetAction("toggleUserMenu");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -57,7 +57,7 @@ function imageFor(code, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
function getEmojiName(content, pos, state) {
|
||||
function getEmojiName(content, pos, state, inlineEmoji) {
|
||||
if (content.charCodeAt(pos) !== 58) {
|
||||
return;
|
||||
}
|
||||
@ -65,6 +65,7 @@ function getEmojiName(content, pos, state) {
|
||||
if (pos > 0) {
|
||||
let prev = content.charCodeAt(pos - 1);
|
||||
if (
|
||||
!inlineEmoji &&
|
||||
!state.md.utils.isSpace(prev) &&
|
||||
!state.md.utils.isPunctChar(String.fromCharCode(prev))
|
||||
) {
|
||||
@ -173,7 +174,13 @@ function getEmojiTokenByTranslation(content, pos, state) {
|
||||
}
|
||||
}
|
||||
|
||||
function applyEmoji(content, state, emojiUnicodeReplacer, enableShortcuts) {
|
||||
function applyEmoji(
|
||||
content,
|
||||
state,
|
||||
emojiUnicodeReplacer,
|
||||
enableShortcuts,
|
||||
inlineEmoji
|
||||
) {
|
||||
let i;
|
||||
let result = null;
|
||||
let contentToken = null;
|
||||
@ -188,7 +195,7 @@ function applyEmoji(content, state, emojiUnicodeReplacer, enableShortcuts) {
|
||||
|
||||
for (i = 0; i < content.length - 1; i++) {
|
||||
let offset = 0;
|
||||
let emojiName = getEmojiName(content, i, state);
|
||||
const emojiName = getEmojiName(content, i, state, inlineEmoji);
|
||||
let token = null;
|
||||
|
||||
if (emojiName) {
|
||||
@ -235,6 +242,7 @@ export function setup(helper) {
|
||||
helper.registerOptions((opts, siteSettings, state) => {
|
||||
opts.features.emoji = !!siteSettings.enable_emoji;
|
||||
opts.features.emojiShortcuts = !!siteSettings.enable_emoji_shortcuts;
|
||||
opts.features.inlineEmoji = !!siteSettings.enable_inline_emoji_translation;
|
||||
opts.emojiSet = siteSettings.emoji_set || "";
|
||||
opts.customEmoji = state.customEmoji;
|
||||
});
|
||||
@ -246,7 +254,8 @@ export function setup(helper) {
|
||||
c,
|
||||
s,
|
||||
md.options.discourse.emojiUnicodeReplacer,
|
||||
md.options.discourse.features.emojiShortcuts
|
||||
md.options.discourse.features.emojiShortcuts,
|
||||
md.options.discourse.features.inlineEmoji
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@ -183,6 +183,7 @@ const DEFAULT_LIST = [
|
||||
"small",
|
||||
"span[lang]",
|
||||
"span.excerpt",
|
||||
"div.excerpt",
|
||||
"span.hashtag",
|
||||
"span.mention",
|
||||
"strike",
|
||||
|
||||
@ -17,7 +17,6 @@ export default ComboBoxComponent.extend({
|
||||
tagName: "li",
|
||||
categoryStyle: Ember.computed.alias("siteSettings.category_style"),
|
||||
noCategoriesLabel: I18n.t("categories.no_subcategory"),
|
||||
mutateAttributes() {},
|
||||
fullWidthOnMobile: true,
|
||||
caretDownIcon: "caret-right",
|
||||
caretUpIcon: "caret-down",
|
||||
@ -53,14 +52,7 @@ export default ComboBoxComponent.extend({
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
if (this.get("category")) {
|
||||
this.set("value", this.get("category.id"));
|
||||
} else {
|
||||
this.set("value", null);
|
||||
}
|
||||
if (!this.get("categories")) this.set("categories", []);
|
||||
this._super(...arguments);
|
||||
|
||||
this.get("rowComponentOptions").setProperties({
|
||||
hideParentCategory: this.get("subCategory"),
|
||||
@ -73,6 +65,11 @@ export default ComboBoxComponent.extend({
|
||||
});
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
if (!this.get("categories")) this.set("categories", []);
|
||||
this.forceValue(this.get("category.id"));
|
||||
},
|
||||
|
||||
@computed("content")
|
||||
filterable(content) {
|
||||
const contentLength = (content && content.length) || 0;
|
||||
|
||||
@ -115,6 +115,11 @@ export default SelectKitComponent.extend({
|
||||
},
|
||||
mutateContent() {},
|
||||
|
||||
forceValues(values) {
|
||||
this.mutateValues(values);
|
||||
this._compute();
|
||||
},
|
||||
|
||||
filterComputedContent(computedContent, computedValues, filter) {
|
||||
return computedContent.filter(c => {
|
||||
return this._normalize(get(c, "name")).indexOf(filter) > -1;
|
||||
|
||||
@ -56,6 +56,11 @@ export default SelectKitComponent.extend({
|
||||
this.set("value", computedValue);
|
||||
},
|
||||
|
||||
forceValue(value) {
|
||||
this.mutateValue(value);
|
||||
this._compute();
|
||||
},
|
||||
|
||||
_beforeWillComputeValue(value) {
|
||||
if (
|
||||
!isEmpty(this.get("content")) &&
|
||||
|
||||
@ -260,6 +260,7 @@ export default Ember.Mixin.create({
|
||||
display: "inline-block",
|
||||
width,
|
||||
height,
|
||||
"margin-bottom": this.$().css("margin-bottom"),
|
||||
"vertical-align": "middle"
|
||||
});
|
||||
|
||||
|
||||
@ -32,3 +32,4 @@
|
||||
//= require virtual-dom-amd
|
||||
//= require highlight.js
|
||||
//= require htmlparser.js
|
||||
//= require intersection-observer
|
||||
|
||||
@ -39,6 +39,10 @@
|
||||
@include active-navigation-item;
|
||||
}
|
||||
|
||||
&.dashboard-next-security .navigation-item.security {
|
||||
@include active-navigation-item;
|
||||
}
|
||||
|
||||
&.general .navigation-item.general {
|
||||
@include active-navigation-item;
|
||||
}
|
||||
@ -488,14 +492,8 @@
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.dashboard-next-moderation {
|
||||
.admin-dashboard-moderation-top {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: 1em;
|
||||
}
|
||||
|
||||
.dashboard-next-moderation,
|
||||
.dashboard-next-security {
|
||||
.section-body {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
@ -510,6 +508,7 @@
|
||||
grid-column: span 12;
|
||||
}
|
||||
|
||||
.admin-dashboard-security-bottom-outlet,
|
||||
.admin-dashboard-moderation-bottom-outlet {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
@ -518,11 +517,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.admin-report.flags-status {
|
||||
grid-column: span 12;
|
||||
}
|
||||
|
||||
.admin-report.post-edits {
|
||||
.admin-report {
|
||||
grid-column: span 12;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-next-moderation {
|
||||
.admin-dashboard-moderation-top {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
grid-column-gap: 1em;
|
||||
grid-row-gap: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,3 +83,15 @@
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.email-advanced-test {
|
||||
.admin-controls {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.email-body {
|
||||
width: 95%;
|
||||
height: 150px;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,6 +131,14 @@
|
||||
@extend .topic-list-main-link;
|
||||
}
|
||||
|
||||
.link-bottom-line {
|
||||
font-size: $font-down-1;
|
||||
a.badge-wrapper.box {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-featured-link {
|
||||
padding-left: 5px;
|
||||
}
|
||||
@ -144,7 +152,7 @@
|
||||
.topic-excerpt {
|
||||
font-size: $font-down-1;
|
||||
margin-top: 5px;
|
||||
color: dark-light-choose($primary-high, $secondary-high);
|
||||
color: $primary-high;
|
||||
word-wrap: break-word;
|
||||
line-height: $line-height-large;
|
||||
padding-right: 20px;
|
||||
@ -152,14 +160,6 @@
|
||||
.topic-statuses:empty {
|
||||
display: none;
|
||||
}
|
||||
.topic-status {
|
||||
margin-right: 4px;
|
||||
padding: 0;
|
||||
font-size: 1.071em;
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.num {
|
||||
text-align: center;
|
||||
@ -253,14 +253,6 @@ ol.category-breadcrumb {
|
||||
|
||||
.d-icon-thumbtack.unpinned {
|
||||
@include fa-rotate(180deg, 1);
|
||||
color: $primary;
|
||||
/* because it is rotated, right becomes left! */
|
||||
padding-left: 3px;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.topic-statuses .fa {
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
.top-title-buttons {
|
||||
|
||||
@ -55,10 +55,6 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
h3 .fa {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.category-box-inner {
|
||||
@ -143,7 +139,7 @@
|
||||
display: inline-block;
|
||||
margin-right: 0.6em;
|
||||
}
|
||||
.logo {
|
||||
.logo img {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
@ -186,16 +182,34 @@
|
||||
li {
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
.overflow {
|
||||
max-height: 3em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.d-icon {
|
||||
margin-right: 6px;
|
||||
margin-top: 2px;
|
||||
margin-right: 0.15em;
|
||||
width: 0.76em;
|
||||
height: 0.76em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.categories-list .category {
|
||||
h3 .d-icon {
|
||||
color: $primary-medium;
|
||||
height: 0.76em;
|
||||
width: 0.76em;
|
||||
vertical-align: baseline;
|
||||
margin-right: 0.15em;
|
||||
}
|
||||
}
|
||||
|
||||
.category-boxes-with-topics,
|
||||
.category-boxes {
|
||||
.category-box h3 .d-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
}
|
||||
|
||||
.hljs-regexp {
|
||||
color: #009926;
|
||||
color: $success;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
@ -90,24 +90,24 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
.lisp .hljs-title,
|
||||
.clojure .hljs-built_in,
|
||||
.hljs-builtin-name {
|
||||
color: #0086b3;
|
||||
color: $tertiary-high;
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: #999;
|
||||
color: $primary-medium;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
background: #fdd;
|
||||
background: $danger-low;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
background: #dfd;
|
||||
background: $success-low;
|
||||
}
|
||||
|
||||
.diff .hljs-meta {
|
||||
color: #aaa;
|
||||
color: $primary-low;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@ -668,3 +668,25 @@ select {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.topic-statuses {
|
||||
display: inline;
|
||||
float: left;
|
||||
margin-right: 0.15em;
|
||||
.topic-status {
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
color: $primary-medium;
|
||||
.d-icon {
|
||||
height: 0.76em;
|
||||
width: 0.75em;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.15em;
|
||||
}
|
||||
}
|
||||
|
||||
.d-icon-envelope {
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,13 @@ img.emoji {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
small img.emoji,
|
||||
sub img.emoji,
|
||||
sup img.emoji {
|
||||
height: 1.1em;
|
||||
width: 1.1em;
|
||||
}
|
||||
|
||||
.emoji-picker {
|
||||
background-clip: padding-box;
|
||||
z-index: z("modal", "content");
|
||||
|
||||
@ -48,6 +48,7 @@
|
||||
}
|
||||
|
||||
$size: 50px;
|
||||
$icon-size: $size / 1.8;
|
||||
|
||||
.avatar-flair-image {
|
||||
width: $size;
|
||||
@ -61,10 +62,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.d-icon {
|
||||
height: $size / 1.8;
|
||||
width: $size / 1.8;
|
||||
height: $icon-size;
|
||||
width: $icon-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,6 +218,7 @@
|
||||
}
|
||||
|
||||
$size: 40px;
|
||||
$icon-size: $size / 1.8;
|
||||
|
||||
.avatar-flair {
|
||||
background-size: $size;
|
||||
@ -227,10 +228,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.d-icon {
|
||||
height: $size / 1.8;
|
||||
width: $size / 1.8;
|
||||
height: $icon-size;
|
||||
width: $icon-size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
box-shadow: shadow("header");
|
||||
|
||||
> .wrap {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.contents {
|
||||
@ -56,6 +57,8 @@
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
@ -231,21 +234,12 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.topic-statuses {
|
||||
margin-top: -2px;
|
||||
float: left;
|
||||
padding: 0;
|
||||
i {
|
||||
color: $header_primary;
|
||||
.d-icon {
|
||||
color: $header_primary-medium;
|
||||
}
|
||||
.d-icon-envelope {
|
||||
color: $danger;
|
||||
}
|
||||
.d-icon-lock {
|
||||
padding-top: 0.15em;
|
||||
}
|
||||
.unpinned {
|
||||
color: $header_primary;
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 0.25em 0;
|
||||
@ -264,6 +258,9 @@
|
||||
}
|
||||
.badge-wrapper {
|
||||
margin-right: 8px;
|
||||
&.bullet {
|
||||
padding-top: 1px; // alignment hack
|
||||
}
|
||||
}
|
||||
.badge-wrapper.bullet {
|
||||
.badge-category-parent-bg,
|
||||
@ -271,12 +268,22 @@
|
||||
min-width: 5px;
|
||||
}
|
||||
}
|
||||
.badge-wrapper {
|
||||
&.bullet,
|
||||
&.bar,
|
||||
&.none {
|
||||
span.badge-category {
|
||||
color: $header_primary-high;
|
||||
}
|
||||
}
|
||||
}
|
||||
.topic-header-extra {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
flex: 1 0 0%; // unit on flex-basis is required for IE11
|
||||
.discourse-tags {
|
||||
color: $header_primary-high;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@ -1,15 +1,28 @@
|
||||
.lightbox {
|
||||
.lightbox-wrapper .lightbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
background: $primary-low;
|
||||
&:hover .meta {
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
.d-lazyload-hidden {
|
||||
opacity: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cooked img.d-lazyload {
|
||||
transition: opacity 0.4s 0.75s ease;
|
||||
}
|
||||
|
||||
.lightbox-wrapper {
|
||||
display: inline-block;
|
||||
img {
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
}
|
||||
&,
|
||||
* {
|
||||
outline: 0;
|
||||
@ -60,8 +73,9 @@
|
||||
right: 7px;
|
||||
&:before {
|
||||
// ideally, the SVG used here should be in HTML and reference the SVG sprite
|
||||
// the SVG used here is the "expand" icon from FontAwesome 4.7.0
|
||||
content: svg-uri(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="16px" viewBox="0 0 512 512" fill="#{$secondary}"> <path d="M0 180V56c0-13.3 10.7-24 24-24h124c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H64v84c0 6.6-5.4 12-12 12H12c-6.6 0-12-5.4-12-12zM288 44v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12V56c0-13.3-10.7-24-24-24H300c-6.6 0-12 5.4-12 12zm148 276h-40c-6.6 0-12 5.4-12 12v84h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24V332c0-6.6-5.4-12-12-12zM160 468v-40c0-6.6-5.4-12-12-12H64v-84c0-6.6-5.4-12-12-12H12c-6.6 0-12 5.4-12 12v124c0 13.3 10.7 24 24 24h124c6.6 0 12-5.4 12-12z"/></svg>'
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="16px" viewBox="0 0 1792 1792" fill="#{$secondary}"><path d="M883 1056q0 13-10 23l-332 332 144 144q19 19 19 45t-19 45-45 19h-448q-26 0-45-19t-19-45v-448q0-26 19-45t45-19 45 19l144 144 332-332q10-10 23-10t23 10l114 114q10 10 10 23zm781-864v448q0 26-19 45t-45 19-45-19l-144-144-332 332q-10 10-23 10t-23-10l-114-114q-10-10-10-23t10-23l332-332-144-144q-19-19-19-45t19-45 45-19h448q26 0 45 19t19 45z"/></svg>'
|
||||
);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
position: fixed;
|
||||
right: 0;
|
||||
box-shadow: shadow("header");
|
||||
&.animate {
|
||||
transition: right 0.2s ease-out, left 0.2s ease-out;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
position: absolute;
|
||||
@ -10,6 +13,9 @@
|
||||
width: 97%;
|
||||
}
|
||||
}
|
||||
.header-cloak {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-panel.drop-down {
|
||||
position: absolute;
|
||||
@ -42,6 +48,7 @@
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
touch-action: pan-y pinch-zoom;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@ -334,6 +334,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.delete-posts-progress {
|
||||
.progress-bar {
|
||||
height: 15px;
|
||||
position: relative;
|
||||
background: $primary-low-mid;
|
||||
border-radius: 25px;
|
||||
overflow: hidden;
|
||||
margin: 30px 0 20px;
|
||||
span {
|
||||
display: block;
|
||||
width: 0%;
|
||||
height: 100%;
|
||||
background-color: $tertiary;
|
||||
position: relative;
|
||||
transition: width 0.6s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#invite-modal {
|
||||
overflow: visible;
|
||||
|
||||
|
||||
@ -549,6 +549,10 @@ aside.onebox.stackexchange .onebox-body {
|
||||
.retweet {
|
||||
color: dark-light-choose($primary-medium, $secondary-medium);
|
||||
padding-left: 10px;
|
||||
svg {
|
||||
fill: currentColor;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -57,6 +57,9 @@
|
||||
.main-results {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
.topic-statuses {
|
||||
color: $primary-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.main-results + .secondary-results {
|
||||
@ -190,16 +193,5 @@
|
||||
.topic-title {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.topic-statuses {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
color: dark-light-choose($primary-medium, $secondary-medium);
|
||||
margin: 0;
|
||||
|
||||
.fa {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,17 +220,18 @@
|
||||
color: $tertiary-high;
|
||||
}
|
||||
.search-link {
|
||||
.topic-statuses,
|
||||
.topic-title {
|
||||
font-size: $font-up-2;
|
||||
line-height: $line-height-large;
|
||||
line-height: $line-height-medium;
|
||||
}
|
||||
|
||||
.topic-statuses {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
color: dark-light-choose($primary-medium, $secondary-medium);
|
||||
font-size: $font-0;
|
||||
font-size: 1.3em;
|
||||
line-height: $line-height-medium;
|
||||
color: $primary-medium;
|
||||
span {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.blurb {
|
||||
|
||||
@ -80,7 +80,7 @@ $tag-color: $primary-medium;
|
||||
}
|
||||
|
||||
.extra-info-wrapper & {
|
||||
color: $header-primary !important;
|
||||
color: $header-primary_high !important;
|
||||
}
|
||||
|
||||
&.box {
|
||||
@ -124,10 +124,9 @@ $tag-color: $primary-medium;
|
||||
}
|
||||
|
||||
.topic-list-item .discourse-tags {
|
||||
display: block;
|
||||
font-size: $font-down-2;
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
clear: both;
|
||||
font-size: $font-down-1;
|
||||
|
||||
.discourse-tag.box {
|
||||
position: relative;
|
||||
|
||||
@ -167,7 +167,7 @@
|
||||
.post-infos {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,8 +233,9 @@ aside.quote {
|
||||
.quote-controls {
|
||||
float: right;
|
||||
display: flex;
|
||||
.d-icon {
|
||||
margin-left: 0.2em;
|
||||
align-items: center;
|
||||
a {
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,12 +510,6 @@ aside.quote {
|
||||
}
|
||||
}
|
||||
|
||||
// this ensures consistent top margin on topic posts even if the first line of a post
|
||||
// is a top-margin-less element like a list or image.
|
||||
.topic-body .regular {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.post-info {
|
||||
flex: 0 0 auto;
|
||||
margin-right: 0.5em;
|
||||
|
||||
@ -66,6 +66,13 @@
|
||||
a.topic-featured-link {
|
||||
display: inline-block;
|
||||
}
|
||||
.topic-statuses {
|
||||
line-height: 1.2em;
|
||||
margin-right: 0.15em;
|
||||
.d-icon {
|
||||
color: $primary-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
||||
@ -206,15 +206,19 @@
|
||||
}
|
||||
|
||||
&.medium {
|
||||
vertical-align: top;
|
||||
flex: 0 0 32%;
|
||||
flex: 0 0 auto;
|
||||
width: 32%;
|
||||
margin-right: calc(2% - 3px);
|
||||
&:nth-of-type(3n) {
|
||||
margin-right: 0;
|
||||
@media screen and (min-width: 851px) {
|
||||
&:nth-of-type(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@include breakpoint(medium) {
|
||||
flex: 0 0 49%;
|
||||
margin-right: 0;
|
||||
width: 48.5%;
|
||||
&:nth-of-type(2n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
@include breakpoint(mobile) {
|
||||
flex: 0 0 100%;
|
||||
@ -225,9 +229,6 @@
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
@include breakpoint(mobile-small) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
&.large {
|
||||
width: 100%;
|
||||
|
||||
@ -157,8 +157,8 @@
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 10px;
|
||||
line-height: $line-height-medium;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -31,8 +31,9 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
.d-icon {
|
||||
padding-left: 1px; // prevents lock icon from being cut off on the left
|
||||
margin-right: 3px;
|
||||
width: 0.74em;
|
||||
height: 0.74em;
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +42,7 @@
|
||||
&.bullet {
|
||||
margin-right: 12px;
|
||||
span.badge-category {
|
||||
color: $primary;
|
||||
color: $primary-high;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.extra-info-wrapper & {
|
||||
@ -63,6 +64,9 @@
|
||||
width: 5px;
|
||||
}
|
||||
}
|
||||
.d-icon {
|
||||
color: $primary-medium;
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Box
|
||||
@ -110,7 +114,7 @@
|
||||
margin-right: 5px;
|
||||
|
||||
span.badge-category {
|
||||
color: $primary;
|
||||
color: $primary-high;
|
||||
padding: 1px 3px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -135,7 +139,7 @@
|
||||
// ----- No category style
|
||||
|
||||
&.none {
|
||||
color: $primary;
|
||||
color: $primary-high;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,11 +9,6 @@
|
||||
fill: currentColor;
|
||||
flex-shrink: 0; // Prevent the icon from shrinking if it's in a flexbox
|
||||
overflow: visible;
|
||||
|
||||
&.d-icon-lock {
|
||||
height: 0.9em;
|
||||
width: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
// Stacked Icons
|
||||
|
||||
@ -7,6 +7,12 @@
|
||||
.user-image {
|
||||
float: left;
|
||||
padding-right: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.user-image-inner {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
@ -39,6 +45,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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) {
|
||||
|
||||
@ -199,12 +199,17 @@ $primary-very-low: dark-light-diff($primary, $secondary, 97%, -80%);
|
||||
$primary-low: dark-light-diff($primary, $secondary, 90%, -65%);
|
||||
$primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%);
|
||||
$primary-medium: dark-light-diff($primary, $secondary, 50%, -35%);
|
||||
$primary-high: dark-light-diff($primary, $secondary, 30%, -10%);
|
||||
$primary-high: dark-light-diff($primary, $secondary, 30%, -25%);
|
||||
|
||||
//header_primary
|
||||
$header_primary-low: dark-light-diff($header_primary, $secondary, 90%, -65%);
|
||||
$header_primary-medium: dark-light-diff($header_primary, $secondary, 50%, -20%);
|
||||
$header_primary-high: dark-light-diff($header_primary, $secondary, 20%, 20%);
|
||||
$header_primary-medium: dark-light-diff($header_primary, $secondary, 50%, -35%);
|
||||
$header_primary-high: dark-light-diff(
|
||||
$header_primary,
|
||||
$header_background,
|
||||
30%,
|
||||
-25%
|
||||
);
|
||||
|
||||
//secondary
|
||||
$secondary-low: dark-light-diff($secondary, $primary, 70%, -70%);
|
||||
|
||||
@ -62,22 +62,23 @@
|
||||
margin: 10px 0 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
a.last-posted-at,
|
||||
a.last-posted-at:visited {
|
||||
font-size: $font-down-1;
|
||||
color: dark-light-choose($primary-medium, $secondary-high);
|
||||
}
|
||||
|
||||
.topic-statuses .fa {
|
||||
color: dark-light-choose($primary-medium, $secondary-high);
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
.topic-statuses {
|
||||
margin-right: 0.15em;
|
||||
}
|
||||
|
||||
.topic-post-badges .badge.new-posts,
|
||||
.title {
|
||||
margin-right: 5px;
|
||||
@ -93,9 +94,6 @@
|
||||
a[href] {
|
||||
color: $primary;
|
||||
}
|
||||
.fa {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,8 +126,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.discourse-tag {
|
||||
font-size: $font-down-2;
|
||||
.discourse-tags {
|
||||
display: inline-block;
|
||||
.discourse-tag {
|
||||
font-size: $font-down-1;
|
||||
}
|
||||
}
|
||||
|
||||
.topic-featured-link {
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
}
|
||||
.user-image {
|
||||
width: 55px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,25 +13,6 @@ body.widget-dragging {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.topic-statuses {
|
||||
float: left;
|
||||
padding: 0;
|
||||
|
||||
.topic-status {
|
||||
padding: 0 2px 0 0;
|
||||
margin: 0;
|
||||
line-height: $line-height-small;
|
||||
|
||||
.d-icon {
|
||||
font-size: $font-down-1;
|
||||
}
|
||||
}
|
||||
|
||||
.d-icon-envelope {
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
|
||||
.form-vertical {
|
||||
.control-group {
|
||||
margin-bottom: 24px;
|
||||
|
||||