Version bump

This commit is contained in:
Neil Lalonde 2018-12-14 12:22:27 -05:00
commit eccad71d6c
413 changed files with 6790 additions and 2147 deletions

View File

@ -114,3 +114,10 @@ Layout/AlignHash:
Bundler/OrderedGems:
Enabled: false
Style/SingleLineMethods:
Enabled: true
Style/Semicolon:
Enabled: true
AllowAsExpressionSeparator: true

25
Gemfile
View File

@ -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'

View File

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -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));
});
},

View File

@ -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")

View File

@ -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));
}
}
});

View File

@ -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);
},

View File

@ -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"

View File

@ -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) {

View File

@ -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" });
},

View File

@ -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 => {

View File

@ -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) : "—"
};
},

View File

@ -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]

View File

@ -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" });
}
);

View File

@ -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
);
}
});

View File

@ -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);
}
}
});

View File

@ -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 });
},

View File

@ -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}}

View File

@ -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>

View File

@ -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}}

View File

@ -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'}}

View File

@ -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>

View File

@ -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}}

View File

@ -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}}

View File

@ -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
}

View File

@ -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",

View File

@ -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"))
);
}
});

View File

@ -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";
}
});

View File

@ -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) {

View File

@ -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")

View File

@ -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;
});
}
});

View File

@ -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;
},

View File

@ -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));
},

View File

@ -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")) {

View File

@ -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"),

View File

@ -243,7 +243,7 @@ export default Ember.Controller.extend(
},
connectAccount(method) {
method.doLogin();
method.doLogin(true);
}
}
}

View File

@ -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) {

View 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));
});
}

View File

@ -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));
}
}

View File

@ -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 (

View File

@ -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(

View File

@ -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 }
});
},

View File

@ -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() {

View File

@ -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"));
});
}

View File

@ -1,9 +1,7 @@
{{
category-drop
{{category-drop
category=firstCategory
categories=parentCategoriesSorted
countSubcategories=true
}}
countSubcategories=true}}
{{#if childCategories}}
{{category-drop

View File

@ -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}}>

View File

@ -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>

View File

@ -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}}

View File

@ -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">

View File

@ -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")}}

View File

@ -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}}>

View File

@ -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}}

View File

@ -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}}

View File

@ -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");
}
}
});

View File

@ -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() {

View File

@ -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 });

View File

@ -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");
}
}
});

View File

@ -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
)
)
);

View File

@ -183,6 +183,7 @@ const DEFAULT_LIST = [
"small",
"span[lang]",
"span.excerpt",
"div.excerpt",
"span.hashtag",
"span.mention",
"strike",

View File

@ -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;

View File

@ -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;

View File

@ -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")) &&

View File

@ -260,6 +260,7 @@ export default Ember.Mixin.create({
display: "inline-block",
width,
height,
"margin-bottom": this.$().css("margin-bottom"),
"vertical-align": "middle"
});

View File

@ -32,3 +32,4 @@
//= require virtual-dom-amd
//= require highlight.js
//= require htmlparser.js
//= require intersection-observer

View File

@ -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;
}
}

View File

@ -83,3 +83,15 @@
border-width: 1px;
}
}
.email-advanced-test {
.admin-controls {
display: block;
}
.email-body {
width: 95%;
height: 150px;
font-family: monospace;
}
}

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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;
}
/*

View File

@ -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;
}
}

View File

@ -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");

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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%;

View File

@ -157,8 +157,8 @@
}
a {
padding: 5px 10px;
margin-bottom: 10px;
line-height: $line-height-medium;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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%);

View File

@ -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 {

View File

@ -12,6 +12,7 @@
}
.user-image {
width: 55px;
margin-right: 0;
}
}
}

View File

@ -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;

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