Version bump
This commit is contained in:
commit
f407c88327
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
target: ["PLUGINS", "CORE"]
|
||||
os: [ubuntu-latest]
|
||||
ruby: ["2.6"]
|
||||
postgres: ["10"]
|
||||
postgres: ["12"]
|
||||
redis: ["4.x"]
|
||||
|
||||
services:
|
||||
@ -64,9 +64,17 @@ jobs:
|
||||
- name: Setup packages
|
||||
if: env.BUILD_TYPE != 'LINT'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -yqq install postgresql-client libpq-dev gifsicle jpegoptim optipng jhead
|
||||
wget -qO- https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-pngquant | sudo sh
|
||||
|
||||
- name: Update imagemagick
|
||||
if: env.BUILD_TYPE == 'BACKEND'
|
||||
run: |
|
||||
wget https://raw.githubusercontent.com/discourse/discourse_docker/master/image/base/install-imagemagick
|
||||
chmod +x install-imagemagick
|
||||
sudo ./install-imagemagick
|
||||
|
||||
- name: Setup redis
|
||||
uses: shogo82148/actions-setup-redis@v1
|
||||
if: env.BUILD_TYPE != 'LINT'
|
||||
|
||||
@ -1,2 +1,9 @@
|
||||
inherit_gem:
|
||||
rubocop-discourse: default.yml
|
||||
|
||||
# Still work to do in ensuring we don't link old files
|
||||
Discourse/NoAddReferenceOrAliasesActiveRecordMigration:
|
||||
Enabled: false
|
||||
|
||||
Discourse/NoResetColumnInformationInMigrations:
|
||||
Enabled: true
|
||||
|
||||
@ -29,5 +29,3 @@ Javascript
|
||||
Ruby
|
||||
|
||||
Rails - Copyright (c) 2005-2013 David Heinemeier Hansson, Rails Core Team contributors (MIT)
|
||||
|
||||
Thin - Copyright (c) 2012-2013 Marc-Andre Cournoyer
|
||||
|
||||
4
Gemfile
4
Gemfile
@ -178,7 +178,7 @@ end
|
||||
group :development do
|
||||
gem 'ruby-prof', require: false, platform: :mri
|
||||
gem 'bullet', require: !!ENV['BULLET']
|
||||
gem 'better_errors', platform: :mri
|
||||
gem 'better_errors', platform: :mri, require: !!ENV['BETTER_ERRORS']
|
||||
gem 'binding_of_caller'
|
||||
gem 'yaml-lint'
|
||||
gem 'annotate'
|
||||
@ -250,4 +250,4 @@ gem 'webpush', require: false
|
||||
gem 'colored2', require: false
|
||||
gem 'maxminddb'
|
||||
|
||||
gem 'rails_failover', require: false, git: 'https://github.com/discourse/rails_failover'
|
||||
gem 'rails_failover', require: false
|
||||
|
||||
61
Gemfile.lock
61
Gemfile.lock
@ -1,11 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/discourse/rails_failover
|
||||
revision: 66602aa73785851b81c506f0023d3c2a2e40de0a
|
||||
specs:
|
||||
rails_failover (0.4.0)
|
||||
activerecord (~> 6.0)
|
||||
railties (~> 6.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@ -51,10 +43,10 @@ GEM
|
||||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.0)
|
||||
ast (2.4.1)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.322.0)
|
||||
aws-sdk-core (3.96.1)
|
||||
aws-partitions (1.329.0)
|
||||
aws-sdk-core (3.99.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
@ -66,11 +58,11 @@ GEM
|
||||
aws-sdk-core (~> 3, >= 3.96.1)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-sns (1.23.0)
|
||||
aws-sdk-core (~> 3, >= 3.71.0)
|
||||
aws-sdk-sns (1.25.1)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.1.3)
|
||||
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||
aws-sigv4 (1.2.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
barber (0.12.2)
|
||||
ember-source (>= 1.0, < 3.1)
|
||||
execjs (>= 1.2, < 3)
|
||||
@ -104,7 +96,7 @@ GEM
|
||||
css_parser (1.7.1)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
diff-lcs (1.3)
|
||||
diff-lcs (1.4.1)
|
||||
diffy (3.3.0)
|
||||
discourse-ember-source (3.12.2.0)
|
||||
discourse_image_optim (0.26.2)
|
||||
@ -129,7 +121,7 @@ GEM
|
||||
railties (>= 3.1)
|
||||
ember-source (2.18.2)
|
||||
erubi (1.9.0)
|
||||
excon (0.73.0)
|
||||
excon (0.75.0)
|
||||
execjs (2.7.0)
|
||||
exifr (1.3.6)
|
||||
fabrication (2.21.1)
|
||||
@ -142,7 +134,7 @@ GEM
|
||||
rake-compiler
|
||||
fast_xs (0.8.0)
|
||||
fastimage (2.1.7)
|
||||
ffi (1.13.0)
|
||||
ffi (1.13.1)
|
||||
flamegraph (0.9.5)
|
||||
fspath (3.1.2)
|
||||
gc_tracer (1.5.1)
|
||||
@ -181,8 +173,8 @@ GEM
|
||||
logstash-event (1.2.02)
|
||||
logstash-logger (0.26.1)
|
||||
logstash-event (~> 1.2)
|
||||
logster (2.8.0)
|
||||
loofah (2.5.0)
|
||||
logster (2.9.0)
|
||||
loofah (2.6.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
lru_redux (1.1.0)
|
||||
@ -205,7 +197,7 @@ GEM
|
||||
ffi (~> 1.9)
|
||||
minitest (5.14.1)
|
||||
mocha (1.11.2)
|
||||
mock_redis (0.23.0)
|
||||
mock_redis (0.24.0)
|
||||
msgpack (1.3.3)
|
||||
multi_json (1.14.1)
|
||||
multi_xml (0.6.0)
|
||||
@ -248,7 +240,7 @@ GEM
|
||||
omniauth-twitter (1.4.0)
|
||||
omniauth-oauth (~> 1.1)
|
||||
rack
|
||||
onebox (1.9.28.3)
|
||||
onebox (1.9.29)
|
||||
addressable (~> 2.7.0)
|
||||
htmlentities (~> 4.3)
|
||||
multi_json (~> 1.11)
|
||||
@ -257,11 +249,11 @@ GEM
|
||||
sanitize
|
||||
openssl-signature_algorithm (0.4.0)
|
||||
optimist (3.0.1)
|
||||
parallel (1.19.1)
|
||||
parallel_tests (2.32.0)
|
||||
parallel (1.19.2)
|
||||
parallel_tests (3.0.0)
|
||||
parallel
|
||||
parser (2.7.1.3)
|
||||
ast (~> 2.4.0)
|
||||
parser (2.7.1.4)
|
||||
ast (~> 2.4.1)
|
||||
pg (1.2.3)
|
||||
progress (3.5.2)
|
||||
pry (0.13.1)
|
||||
@ -288,6 +280,9 @@ GEM
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
loofah (~> 2.3)
|
||||
rails_failover (0.5.2)
|
||||
activerecord (~> 6.0)
|
||||
railties (~> 6.0)
|
||||
rails_multisite (2.3.0)
|
||||
activerecord (> 5.0, < 7)
|
||||
railties (> 5.0, < 7)
|
||||
@ -310,7 +305,7 @@ GEM
|
||||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rchardet (1.8.0)
|
||||
redis (4.1.4)
|
||||
redis (4.2.1)
|
||||
redis-namespace (1.7.0)
|
||||
redis (>= 3.0.4)
|
||||
regexp_parser (1.7.1)
|
||||
@ -353,21 +348,21 @@ GEM
|
||||
json-schema (~> 2.2)
|
||||
railties (>= 3.1, < 7.0)
|
||||
rtlit (0.0.5)
|
||||
rubocop (0.85.1)
|
||||
rubocop (0.86.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.0.1)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.7)
|
||||
rexml
|
||||
rubocop-ast (>= 0.0.3)
|
||||
rubocop-ast (>= 0.0.3, < 1.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-ast (0.0.3)
|
||||
parser (>= 2.7.0.1)
|
||||
rubocop-discourse (2.1.2)
|
||||
rubocop-discourse (2.2.0)
|
||||
rubocop (>= 0.69.0)
|
||||
rubocop-rspec (>= 1.39.0)
|
||||
rubocop-rspec (1.39.0)
|
||||
rubocop-rspec (1.40.0)
|
||||
rubocop (>= 0.68.1)
|
||||
ruby-prof (1.4.1)
|
||||
ruby-progressbar (1.10.1)
|
||||
@ -376,7 +371,7 @@ GEM
|
||||
nokogiri (>= 1.6.0)
|
||||
rubyzip (2.3.0)
|
||||
safe_yaml (1.0.5)
|
||||
sanitize (5.1.0)
|
||||
sanitize (5.2.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.8.0)
|
||||
nokogumbo (~> 2.0)
|
||||
@ -526,7 +521,7 @@ DEPENDENCIES
|
||||
rack (= 2.2.2)
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails_failover!
|
||||
rails_failover
|
||||
rails_multisite
|
||||
railties (= 6.0.3.1)
|
||||
rake
|
||||
|
||||
@ -22,6 +22,16 @@ export default Controller.extend({
|
||||
this.model.unshiftObject(arg);
|
||||
},
|
||||
|
||||
copyUrl(pl) {
|
||||
let linkElement = document.querySelector(`#admin-permalink-${pl.id}`);
|
||||
let textArea = document.createElement("textarea");
|
||||
textArea.value = linkElement.textContent;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand("Copy");
|
||||
textArea.remove();
|
||||
},
|
||||
|
||||
destroy: function(record) {
|
||||
return bootbox.confirm(
|
||||
I18n.t("admin.permalink.delete_confirm"),
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
content=permalinkTypes
|
||||
value=permalinkType
|
||||
onChange=(action (mut permalinkType))
|
||||
class="permalink-type"
|
||||
}}
|
||||
|
||||
{{text-field
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
{{#admin-nav}}
|
||||
{{nav-item route="adminCustomizeThemes" label="admin.customize.theme.title"}}
|
||||
{{nav-item route="adminCustomize.colors" label="admin.customize.colors.title"}}
|
||||
{{nav-item route="adminSiteText" label="admin.site_text.title"}}
|
||||
{{nav-item route="adminCustomizeEmailTemplates" label="admin.customize.email_templates.title"}}
|
||||
{{nav-item route="adminCustomizeEmailStyle" label="admin.customize.email_style.title"}}
|
||||
{{nav-item route="adminUserFields" label="admin.user_fields.title"}}
|
||||
{{nav-item route="adminEmojis" label="admin.emoji.title"}}
|
||||
{{nav-item route="adminPermalinks" label="admin.permalink.title"}}
|
||||
{{nav-item route="adminEmbedding" label="admin.embedding.title"}}
|
||||
{{nav-item route="adminCustomizeThemes" label="admin.customize.theme.title" class="admin-customize-themes"}}
|
||||
{{nav-item route="adminCustomize.colors" label="admin.customize.colors.title" class="admin-customize-colors"}}
|
||||
{{nav-item route="adminSiteText" label="admin.site_text.title" class="admin-customize-site-text"}}
|
||||
{{nav-item route="adminCustomizeEmailTemplates" label="admin.customize.email_templates.title" class="admin-customize-email-templates"}}
|
||||
{{nav-item route="adminCustomizeEmailStyle" label="admin.customize.email_style.title" class="admin-customize-email-styles"}}
|
||||
{{nav-item route="adminUserFields" label="admin.user_fields.title" class="admin-customize-user-fields"}}
|
||||
{{nav-item route="adminEmojis" label="admin.emoji.title" class="admin-customize-emojis"}}
|
||||
{{nav-item route="adminPermalinks" label="admin.permalink.title" class="admin-customize-permalinks"}}
|
||||
{{nav-item route="adminEmbedding" label="admin.embedding.title" class="admin-customize-embedding"}}
|
||||
{{/admin-nav}}
|
||||
|
||||
<div class="admin-container">
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<tbody>
|
||||
{{#each model as |pl|}}
|
||||
<tr class="admin-list-item">
|
||||
<td class="col first url">{{pl.url}}</td>
|
||||
<td class="col first url">{{d-button title="admin.permalink.copy_to_clipboard" icon="far-clipboard" action=(action "copyUrl" pl)}} <span id="admin-permalink-{{pl.id}}" title={{pl.url}}>{{pl.url}}</span></td>
|
||||
<td class="col destination">
|
||||
{{#if pl.topic_id}}
|
||||
<a href={{pl.topic_url}}>{{pl.topic_title}}</a>
|
||||
@ -42,7 +42,7 @@
|
||||
<a href={{pl.external_url}}>{{pl.external_url}}</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="col action">
|
||||
<td class="col action" style="text-align: right;">
|
||||
{{d-button action=(action "destroy") actionParam=pl icon="far-trash-alt" class="btn-danger"}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -514,7 +514,7 @@
|
||||
<div class="display-row">
|
||||
<div class="field">{{i18n "admin.groups.custom"}}</div>
|
||||
<div class="value">
|
||||
{{admin-group-selector
|
||||
{{group-chooser
|
||||
content=availableGroups
|
||||
value=customGroupIdsBuffer
|
||||
labelProperty="name"
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import TextArea from "@ember/component/text-area";
|
||||
|
||||
export default TextArea.extend({
|
||||
attributeBindings: ["aria-label"]
|
||||
});
|
||||
@ -12,7 +12,8 @@ export default TextField.extend({
|
||||
"autocapitalize",
|
||||
"autofocus",
|
||||
"maxLength",
|
||||
"dir"
|
||||
"dir",
|
||||
"aria-label"
|
||||
],
|
||||
|
||||
init() {
|
||||
|
||||
@ -180,7 +180,7 @@ export default Component.extend({
|
||||
this.set("docked", isDocked);
|
||||
|
||||
const $replyArea = $("#reply-control .reply-area");
|
||||
if ($replyArea && $replyArea.length > 0 && wrapperDir === "left") {
|
||||
if ($replyArea && $replyArea.length > 0) {
|
||||
$wrapper.css(wrapperDir, `${$replyArea.offset().left}px`);
|
||||
} else {
|
||||
$wrapper.css(wrapperDir, "1em");
|
||||
|
||||
@ -1040,7 +1040,7 @@ export default Controller.extend({
|
||||
const keyPrefix =
|
||||
this.model.action === "edit" ? "post.abandon_edit" : "post.abandon";
|
||||
|
||||
let promise = new Promise(resolve => {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
if (this.get("model.hasMetaData") || this.get("model.replyDirty")) {
|
||||
bootbox.dialog(I18n.t(keyPrefix + ".confirm"), [
|
||||
{
|
||||
@ -1052,8 +1052,10 @@ export default Controller.extend({
|
||||
if (differentDraft) {
|
||||
this.model.clearState();
|
||||
this.close();
|
||||
resolve();
|
||||
}
|
||||
resolve();
|
||||
|
||||
reject();
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -60,6 +60,11 @@ export default Controller.extend(ModalFunctionality, {
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("previousVersion")
|
||||
revertToRevisionText(revision) {
|
||||
return I18n.t("post.revisions.controls.revert", { revision });
|
||||
},
|
||||
|
||||
refresh(postId, postVersion) {
|
||||
this.set("loading", true);
|
||||
|
||||
@ -261,9 +266,10 @@ export default Controller.extend(ModalFunctionality, {
|
||||
this.set("bodyDiff", html);
|
||||
} else {
|
||||
const opts = {
|
||||
features: { editHistory: true },
|
||||
features: { editHistory: true, historyOneboxes: true },
|
||||
whiteListed: {
|
||||
editHistory: { custom: (tag, attr) => attr === "class" }
|
||||
editHistory: { custom: (tag, attr) => attr === "class" },
|
||||
historyOneboxes: ["header", "article", "div[style]"]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
|
||||
this.state === States.existing
|
||||
);
|
||||
}),
|
||||
|
||||
showUnpublish: computed("state", function() {
|
||||
return this.state === States.existing || this.state === States.unpublishing;
|
||||
}),
|
||||
@ -95,7 +96,7 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
|
||||
this.set("state", States.saving);
|
||||
|
||||
return this.publishedPage
|
||||
.update({ slug: this.publishedPage.slug })
|
||||
.update(this.publishedPage.getProperties("slug", "public"))
|
||||
.then(() => {
|
||||
this.set("state", States.existing);
|
||||
this.model.set("publishedPage", this.publishedPage);
|
||||
@ -110,11 +111,17 @@ export default Controller.extend(ModalFunctionality, StateHelpers, {
|
||||
startNew() {
|
||||
this.setProperties({
|
||||
state: States.new,
|
||||
publishedPage: this.store.createRecord("published_page", {
|
||||
id: this.model.id,
|
||||
slug: this.model.slug
|
||||
})
|
||||
publishedPage: this.store.createRecord(
|
||||
"published_page",
|
||||
this.model.getProperties("id", "slug", "public")
|
||||
)
|
||||
});
|
||||
this.checkSlug();
|
||||
},
|
||||
|
||||
@action
|
||||
onChangePublic(isPublic) {
|
||||
this.publishedPage.set("public", isPublic);
|
||||
this.publish();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { later } from "@ember/runloop";
|
||||
import Mobile from "discourse/lib/mobile";
|
||||
import { setResolverOption } from "discourse-common/resolver";
|
||||
import { isAppWebview, postRNWebviewMessage } from "discourse/lib/utilities";
|
||||
|
||||
// Initializes the `Mobile` helper object.
|
||||
export default {
|
||||
@ -16,14 +14,5 @@ export default {
|
||||
site.set("isMobileDevice", Mobile.isMobileDevice);
|
||||
|
||||
setResolverOption("mobileView", Mobile.mobileView);
|
||||
|
||||
if (isAppWebview()) {
|
||||
later(() => {
|
||||
postRNWebviewMessage(
|
||||
"headerBg",
|
||||
$(".d-header").css("background-color")
|
||||
);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { later } from "@ember/runloop";
|
||||
import { isAppWebview, postRNWebviewMessage } from "discourse/lib/utilities";
|
||||
|
||||
// Send bg color to webview so iOS status bar matches site theme
|
||||
export default {
|
||||
name: "webview-background",
|
||||
after: "inject-objects",
|
||||
|
||||
initialize() {
|
||||
if (isAppWebview()) {
|
||||
later(() => {
|
||||
const header = document.querySelectorAll(".d-header")[0];
|
||||
if (header) {
|
||||
const styles = window.getComputedStyle(header);
|
||||
postRNWebviewMessage("headerBg", styles.backgroundColor);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -355,7 +355,8 @@ export default {
|
||||
this.container.lookup("controller:topic").togglePinnedState();
|
||||
},
|
||||
|
||||
goToPost() {
|
||||
goToPost(event) {
|
||||
preventKeyboardEvent(event);
|
||||
this.appEvents.trigger("topic:keyboard-trigger", { type: "jump" });
|
||||
},
|
||||
|
||||
|
||||
@ -30,7 +30,8 @@ const SERVER_SIDE_ONLY = [
|
||||
/^\/admin\/upgrade$/,
|
||||
/^\/logs($|\/)/,
|
||||
/^\/admin\/logs\/watched_words\/action\/[^\/]+\/download$/,
|
||||
/^\/pub\//
|
||||
/^\/pub\//,
|
||||
/^\/invites\//
|
||||
];
|
||||
|
||||
export function rewritePath(path) {
|
||||
|
||||
@ -25,6 +25,10 @@ export function translateSize(size) {
|
||||
}
|
||||
|
||||
export function escapeExpression(string) {
|
||||
if (!string) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
|
||||
@ -71,11 +71,16 @@ const NavItem = EmberObject.extend({
|
||||
return mode + name.replace(" ", "-");
|
||||
},
|
||||
|
||||
@discourseComputed("name", "category", "topicTrackingState.messageCount")
|
||||
count(name, category) {
|
||||
@discourseComputed(
|
||||
"name",
|
||||
"category",
|
||||
"tagId",
|
||||
"topicTrackingState.messageCount"
|
||||
)
|
||||
count(name, category, tagId) {
|
||||
const state = this.topicTrackingState;
|
||||
if (state) {
|
||||
return state.lookupCount(name, category);
|
||||
return state.lookupCount(name, category, tagId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -132,7 +132,9 @@ Site.reopenClass(Singleton, {
|
||||
// The current singleton will retrieve its attributes from the `PreloadStore`.
|
||||
createCurrent() {
|
||||
const store = Discourse.__container__.lookup("service:store");
|
||||
return store.createRecord("site", PreloadStore.get("site"));
|
||||
const siteAttributes = PreloadStore.get("site");
|
||||
siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly");
|
||||
return store.createRecord("site", siteAttributes);
|
||||
},
|
||||
|
||||
create() {
|
||||
|
||||
@ -408,7 +408,7 @@ const TopicTrackingState = EmberObject.extend({
|
||||
return new Set(result);
|
||||
},
|
||||
|
||||
countCategoryByState(fn, categoryId) {
|
||||
countCategoryByState(fn, categoryId, tagId) {
|
||||
const subcategoryIds = this.getSubCategoryIds(categoryId);
|
||||
return _.chain(this.states)
|
||||
.filter(fn)
|
||||
@ -416,17 +416,18 @@ const TopicTrackingState = EmberObject.extend({
|
||||
topic =>
|
||||
topic.archetype !== "private_message" &&
|
||||
!topic.deleted &&
|
||||
(!categoryId || subcategoryIds.has(topic.category_id))
|
||||
(!categoryId || subcategoryIds.has(topic.category_id)) &&
|
||||
(!tagId || (topic.tags && topic.tags.indexOf(tagId) > -1))
|
||||
)
|
||||
.value().length;
|
||||
},
|
||||
|
||||
countNew(categoryId) {
|
||||
return this.countCategoryByState(isNew, categoryId);
|
||||
countNew(categoryId, tagId) {
|
||||
return this.countCategoryByState(isNew, categoryId, tagId);
|
||||
},
|
||||
|
||||
countUnread(categoryId) {
|
||||
return this.countCategoryByState(isUnread, categoryId);
|
||||
countUnread(categoryId, tagId) {
|
||||
return this.countCategoryByState(isUnread, categoryId, tagId);
|
||||
},
|
||||
|
||||
countTags(tags) {
|
||||
@ -462,10 +463,14 @@ const TopicTrackingState = EmberObject.extend({
|
||||
return counts;
|
||||
},
|
||||
|
||||
countCategory(category_id) {
|
||||
countCategory(category_id, tagId) {
|
||||
let sum = 0;
|
||||
Object.values(this.states).forEach(topic => {
|
||||
if (topic.category_id === category_id && !topic.deleted) {
|
||||
if (
|
||||
topic.category_id === category_id &&
|
||||
!topic.deleted &&
|
||||
(!tagId || (topic.tags && topic.tags.indexOf(tagId) > -1))
|
||||
) {
|
||||
sum +=
|
||||
topic.last_read_post_number === null ||
|
||||
topic.last_read_post_number < topic.highest_post_number
|
||||
@ -476,23 +481,24 @@ const TopicTrackingState = EmberObject.extend({
|
||||
return sum;
|
||||
},
|
||||
|
||||
lookupCount(name, category) {
|
||||
lookupCount(name, category, tagId) {
|
||||
if (name === "latest") {
|
||||
return (
|
||||
this.lookupCount("new", category) + this.lookupCount("unread", category)
|
||||
this.lookupCount("new", category, tagId) +
|
||||
this.lookupCount("unread", category, tagId)
|
||||
);
|
||||
}
|
||||
|
||||
let categoryId = category ? get(category, "id") : null;
|
||||
|
||||
if (name === "new") {
|
||||
return this.countNew(categoryId);
|
||||
return this.countNew(categoryId, tagId);
|
||||
} else if (name === "unread") {
|
||||
return this.countUnread(categoryId);
|
||||
return this.countUnread(categoryId, tagId);
|
||||
} else {
|
||||
const categoryName = name.split("/")[1];
|
||||
if (categoryName) {
|
||||
return this.countCategory(categoryId);
|
||||
return this.countCategory(categoryId, tagId);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -430,8 +430,11 @@ const Topic = RestModel.extend({
|
||||
return this.firstPost().then(firstPost => {
|
||||
const toggleBookmarkOnServer = () => {
|
||||
if (bookmark) {
|
||||
return firstPost.toggleBookmark().then(() => {
|
||||
return firstPost.toggleBookmark().then(opts => {
|
||||
this.set("bookmarking", false);
|
||||
if (opts.closedWithoutSaving) {
|
||||
return;
|
||||
}
|
||||
return this.afterTopicBookmarked(firstPost);
|
||||
});
|
||||
} else {
|
||||
|
||||
@ -595,7 +595,7 @@ const User = RestModel.extend({
|
||||
);
|
||||
}
|
||||
|
||||
if (!isEmpty(json.user.groups)) {
|
||||
if (!isEmpty(json.user.groups) && !isEmpty(json.user.group_users)) {
|
||||
const groups = [];
|
||||
|
||||
for (let i = 0; i < json.user.groups.length; i++) {
|
||||
@ -770,6 +770,7 @@ const User = RestModel.extend({
|
||||
this.setProperties({
|
||||
email: result.email,
|
||||
secondary_emails: result.secondary_emails,
|
||||
unconfirmed_emails: result.unconfirmed_emails,
|
||||
associated_accounts: result.associated_accounts
|
||||
});
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
</thead>
|
||||
<tbody aria-labelledby="categories-only-category">
|
||||
{{#each categories as |c|}}
|
||||
{{plugin-outlet name="category-list-above-each-category" connectorTagName="" tagName="" args=(hash category=c)}}
|
||||
<tr data-category-id={{c.id}} data-notification-level={{c.notificationLevelString}} class="{{if c.description_excerpt "has-description" "no-description"}} {{if c.uploaded_logo.url "has-logo" "no-logo"}}">
|
||||
<td class="category {{if c.isMuted "muted"}} {{if noCategoryStyle "no-category-style"}}" style={{unless noCategoryStyle (border-color c.color)}}>
|
||||
{{category-title-link category=c}}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
id="reply-title"
|
||||
maxLength=titleMaxLength
|
||||
placeholderKey=composer.titlePlaceholder
|
||||
aria-label=(I18n composer.titlePlaceholder)
|
||||
disabled=disabled
|
||||
autocomplete="discourse"}}
|
||||
|
||||
|
||||
@ -33,12 +33,13 @@
|
||||
</div>
|
||||
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
{{textarea
|
||||
{{d-textarea
|
||||
autocomplete="discourse"
|
||||
tabindex=tabindex
|
||||
value=value
|
||||
class="d-editor-input"
|
||||
placeholder=placeholderTranslated
|
||||
aria-label=placeholderTranslated
|
||||
disabled=disabled
|
||||
input=change}}
|
||||
{{popup-input-tip validation=validation}}
|
||||
|
||||
@ -11,10 +11,13 @@
|
||||
includeWeekend=includeWeekend
|
||||
includeFarFuture=includeFarFuture
|
||||
includeMidFuture=includeMidFuture
|
||||
includeNow=includeNow
|
||||
clearable=clearable
|
||||
none="topic.auto_update_input.none"
|
||||
onChangeInput=onChangeInput
|
||||
onChange=(action (mut selection))
|
||||
options=(hash
|
||||
none="topic.auto_update_input.none"
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
<div class="name-line">
|
||||
<span class="username"><a href={{this.userPath}} data-user-card={{@user.username}}>{{format-username @user.username}}</a></span>
|
||||
<span class="name">{{this.name}}</span>
|
||||
{{plugin-outlet name="after-user-name" connectorTagName="span" args=(hash user=user)}}
|
||||
</div>
|
||||
<div class="title">{{@user.title}}</div>
|
||||
|
||||
@ -25,3 +26,5 @@
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
|
||||
{{plugin-outlet name="after-user-info" args=(hash user=user)}}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
messageCount=messageCount
|
||||
addLinkLookup=(action "addLinkLookup")}}
|
||||
{{#if model.viewOpenOrFullscreen}}
|
||||
<div class="reply-area {{if canEditTags "with-tags"}}">
|
||||
<div role="form" aria-label={{I18n saveLabel}} class="reply-area {{if canEditTags "with-tags"}}">
|
||||
<div class="composer-fields">
|
||||
{{plugin-outlet name="composer-open" args=(hash model=model)}}
|
||||
<div class="reply-to">
|
||||
@ -100,7 +100,7 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet name="composer-fields" args=(hash model=model)}}
|
||||
{{plugin-outlet name="composer-fields" args=(hash model=model showPreview=showPreview)}}
|
||||
{{/unless}}
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{{#if categories}}
|
||||
<div class="category-list {{if showTopics "with-topics"}}">
|
||||
{{#each categories as |c|}}
|
||||
{{plugin-outlet name="category-list-above-each-category" connectorTagName="" tagName="" args=(hash category=c)}}
|
||||
<div data-category-id={{c.id}} data-notification-level={{c.notificationLevelString}} style={{border-color c.color}} class="category-list-item category {{if c.isMuted "muted"}}">
|
||||
<table class="topic-list">
|
||||
<tbody>
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
{{~#if topic.featured_link~}}
|
||||
{{~topic-featured-link topic~}}
|
||||
{{~/if~}}
|
||||
{{~raw-plugin-outlet name="topic-list-after-title"}}
|
||||
{{~#if topic.unseen~}}
|
||||
<span class="topic-post-badges"> <span class="badge-notification new-topic"></span></span>
|
||||
{{~/if~}}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
{{/if}}
|
||||
|
||||
<div class="control-group bookmark-name-wrap">
|
||||
{{input id="bookmark-name" value=model.name name="bookmark-name" class="bookmark-name" placeholder=(i18n "post.bookmarks.name_placeholder")}}
|
||||
{{input id="bookmark-name" value=model.name name="bookmark-name" class="bookmark-name" enter=(action "saveAndClose") placeholder=(i18n "post.bookmarks.name_placeholder")}}
|
||||
{{d-button icon="cog" action=(action "toggleOptionsPanel") class="bookmark-options-button"}}
|
||||
</div>
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if displayRevert}}
|
||||
{{d-button action=(action "revertToVersion") icon="undo" label="post.revisions.controls.revert" class="btn-danger" disabled=loading}}
|
||||
{{d-button action=(action "revertToVersion") icon="undo" translatedLabel=revertToRevisionText class="btn-danger" disabled=loading}}
|
||||
{{/if}}
|
||||
|
||||
{{#if displayHide}}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
includeWeekend=true
|
||||
includeDateTime=false
|
||||
includeMidFuture=true
|
||||
includeFarFuture=false
|
||||
includeFarFuture=true
|
||||
onChangeInput=(action (mut ignoredUntil))
|
||||
}}
|
||||
<p>{{i18n "user.user_notifications.ignore_duration_note"}}</p>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
includeWeekend=true
|
||||
includeDateTime=false
|
||||
includeMidFuture=true
|
||||
includeFarFuture=false
|
||||
includeFarFuture=true
|
||||
onChangeInput=(action (mut ignoredUntil))
|
||||
}}
|
||||
<p>{{i18n "user.user_notifications.ignore_duration_note"}}</p>
|
||||
|
||||
@ -6,8 +6,29 @@
|
||||
<p class="publish-description">{{i18n "topic.publish_page.description"}}</p>
|
||||
|
||||
<form>
|
||||
<label>{{i18n "topic.publish_page.slug"}}</label>
|
||||
{{text-field value=publishedPage.slug onChange=(action "checkSlug") onChangeImmediate=(action "startCheckSlug") disabled=existing class="publish-slug"}}
|
||||
<div class="controls">
|
||||
<label>{{i18n "topic.publish_page.slug"}}</label>
|
||||
{{text-field
|
||||
value=publishedPage.slug
|
||||
onChange=(action "checkSlug")
|
||||
onChangeImmediate=(action "startCheckSlug")
|
||||
disabled=existing
|
||||
class="publish-slug"
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<label>{{i18n "topic.publish_page.public"}}</label>
|
||||
|
||||
<p class="description">
|
||||
{{input
|
||||
type="checkbox"
|
||||
checked=publishedPage.public
|
||||
click=(action "onChangePublic" value="target.checked")
|
||||
}}
|
||||
{{i18n "topic.publish_page.public_description"}}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="publish-url">
|
||||
@ -40,14 +61,15 @@
|
||||
|
||||
<div class="modal-footer">
|
||||
{{#if showUnpublish}}
|
||||
{{d-button icon="times" label="close" action=(action "closeModal")}}
|
||||
|
||||
{{d-button
|
||||
label="topic.publish_page.unpublish"
|
||||
icon="trash-alt"
|
||||
class="btn-danger"
|
||||
isLoading=unpublishing
|
||||
action=(action "unpublish") }}
|
||||
action=(action "unpublish")
|
||||
}}
|
||||
|
||||
{{d-button class="close-publish-page" icon="times" label="close" action=(action "closeModal")}}
|
||||
{{else if unpublished}}
|
||||
{{d-button label="topic.publish_page.publishing_settings" action=(action "startNew")}}
|
||||
{{else}}
|
||||
|
||||
@ -50,9 +50,11 @@
|
||||
<div class="emails">
|
||||
{{#each emails as |email|}}
|
||||
<div class="row email">
|
||||
{{email-dropdown email=email
|
||||
setPrimaryEmail=(action "setPrimaryEmail")
|
||||
destroyEmail=(action "destroyEmail")}}
|
||||
{{#if model.can_edit_email}}
|
||||
{{email-dropdown email=email
|
||||
setPrimaryEmail=(action "setPrimaryEmail")
|
||||
destroyEmail=(action "destroyEmail")}}
|
||||
{{/if}}
|
||||
|
||||
<div class="email-first">{{email.email}}</div>
|
||||
|
||||
@ -77,9 +79,11 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{#link-to "preferences.email" (query-params new=1) class="pull-right"}}
|
||||
{{d-icon "plus"}} {{i18n "user.email.add_email"}}
|
||||
{{/link-to}}
|
||||
{{#if model.can_edit_email}}
|
||||
{{#link-to "preferences.email" (query-params new=1) class="pull-right"}}
|
||||
{{d-icon "plus"}} {{i18n "user.email.add_email"}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<div class="controls">
|
||||
<span class="static">{{model.email}}</span>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
{{plugin-outlet name="user-preferences-interface-top" args=(hash model=model save=(action "save"))}}
|
||||
|
||||
{{#if showThemeSelector}}
|
||||
<div class="control-group theme">
|
||||
<label class="control-label">{{i18n "user.theme"}}</label>
|
||||
|
||||
@ -90,6 +90,9 @@
|
||||
{{#if model.publishedPage}}
|
||||
<div class="published-page-notice">
|
||||
<div class="details">
|
||||
{{#if model.publishedPage.public}}
|
||||
<span class="is-public">{{i18n "topic.publish_page.public"}}</span>
|
||||
{{/if}}
|
||||
{{i18n "topic.publish_page.topic_published"}}
|
||||
<div>
|
||||
<a href={{model.publishedPage.url}} target="_blank" rel="noopener noreferrer">{{model.publishedPage.url}}</a>
|
||||
|
||||
@ -6,17 +6,18 @@ import { h } from "virtual-dom";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import hbs from "discourse/widgets/hbs-compiler";
|
||||
|
||||
export function avatarAtts(user) {
|
||||
export function smallUserAtts(user) {
|
||||
return {
|
||||
template: user.avatar_template,
|
||||
username: user.username,
|
||||
post_url: user.post_url,
|
||||
url: userPath(user.username_lower)
|
||||
url: userPath(user.username_lower),
|
||||
unknown: user.unknown
|
||||
};
|
||||
}
|
||||
|
||||
createWidget("small-user-list", {
|
||||
tagName: "div.clearfix",
|
||||
tagName: "div.clearfix.small-user-list",
|
||||
|
||||
buildClasses(atts) {
|
||||
return atts.listClassName;
|
||||
@ -30,7 +31,7 @@ createWidget("small-user-list", {
|
||||
atts.addSelf &&
|
||||
!users.some(u => u.username === currentUser.username)
|
||||
) {
|
||||
users = users.concat(avatarAtts(currentUser));
|
||||
users = users.concat(smallUserAtts(currentUser));
|
||||
}
|
||||
|
||||
let description = null;
|
||||
@ -43,7 +44,13 @@ createWidget("small-user-list", {
|
||||
let postUrl;
|
||||
const icons = users.map(u => {
|
||||
postUrl = postUrl || u.post_url;
|
||||
return avatarFor.call(this, "small", u);
|
||||
if (u.unknown) {
|
||||
return h("div.unknown", {
|
||||
attributes: { title: I18n.t("post.unknown_user") }
|
||||
});
|
||||
} else {
|
||||
return avatarFor.call(this, "small", u);
|
||||
}
|
||||
});
|
||||
|
||||
if (postUrl) {
|
||||
|
||||
@ -12,8 +12,13 @@ export default createWidget("emoji", {
|
||||
tagName: "img.emoji",
|
||||
|
||||
buildAttributes(attrs) {
|
||||
let result = { src: emojiUrlFor(attrs.name), alt: `:${attrs.name}:` };
|
||||
if (attrs.title) result.title = attrs.name;
|
||||
let result = {
|
||||
src: emojiUrlFor(attrs.name),
|
||||
alt: `:${attrs.alt || attrs.name}:`
|
||||
};
|
||||
if (attrs.title) {
|
||||
result.title = typeof attrs.title === "string" ? attrs.title : attrs.name;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,6 +6,7 @@ import { h } from "virtual-dom";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||
|
||||
const flatten = array => [].concat.apply([], array);
|
||||
@ -26,14 +27,24 @@ createWidget("priority-faq-link", {
|
||||
},
|
||||
|
||||
click(e) {
|
||||
e.preventDefault();
|
||||
if (this.siteSettings.faq_url === this.attrs.href) {
|
||||
ajax(userPath("read-faq"), { type: "POST" }).then(() => {
|
||||
this.currentUser.set("read_faq", true);
|
||||
DiscourseURL.routeToTag($(e.target).closest("a")[0]);
|
||||
|
||||
if (wantsNewWindow(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
DiscourseURL.routeTo(this.attrs.href);
|
||||
});
|
||||
} else {
|
||||
DiscourseURL.routeToTag($(e.target).closest("a")[0]);
|
||||
if (wantsNewWindow(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
DiscourseURL.routeTo(this.attrs.href);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -267,12 +278,7 @@ export default createWidget("hamburger-menu", {
|
||||
panelContents() {
|
||||
const { currentUser } = this;
|
||||
const results = [];
|
||||
|
||||
let faqUrl = this.siteSettings.faq_url;
|
||||
if (!faqUrl || faqUrl.length === 0) {
|
||||
faqUrl = getURL("/faq");
|
||||
}
|
||||
|
||||
const faqUrl = this.siteSettings.faq_url || getURL("/faq");
|
||||
const prioritizeFaq =
|
||||
this.settings.showFAQ && this.currentUser && !this.currentUser.read_faq;
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ createWidget("post-admin-menu-button", {
|
||||
return this.attach("button", {
|
||||
className: attrs.className,
|
||||
action: attrs.action,
|
||||
url: attrs.url,
|
||||
icon: attrs.icon,
|
||||
label: attrs.label,
|
||||
secondaryAction: attrs.secondaryAction
|
||||
@ -30,7 +31,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
|
||||
if (currentUser.staff) {
|
||||
contents.push({
|
||||
icon: "list",
|
||||
className: "btn-default",
|
||||
className: "popup-menu-button moderation-history",
|
||||
label: "review.moderation_history",
|
||||
url: `/review?topic_id=${attrs.topicId}&status=all`
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { next, run } from "@ember/runloop";
|
||||
import { applyDecorators, createWidget } from "discourse/widgets/widget";
|
||||
import { avatarAtts } from "discourse/widgets/actions-summary";
|
||||
import { smallUserAtts } from "discourse/widgets/actions-summary";
|
||||
import { h } from "virtual-dom";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { Promise } from "rsvp";
|
||||
@ -696,7 +696,7 @@ export default createWidget("post-menu", {
|
||||
post_action_type_id: LIKE_ACTION
|
||||
})
|
||||
.then(users => {
|
||||
state.likedUsers = users.map(avatarAtts);
|
||||
state.likedUsers = users.map(smallUserAtts);
|
||||
state.total = users.totalRows;
|
||||
});
|
||||
},
|
||||
@ -705,7 +705,7 @@ export default createWidget("post-menu", {
|
||||
const { attrs, state } = this;
|
||||
|
||||
return this.store.find("post-reader", { id: attrs.id }).then(users => {
|
||||
state.readers = users.map(avatarAtts);
|
||||
state.readers = users.map(smallUserAtts);
|
||||
state.totalReaders = users.totalRows;
|
||||
});
|
||||
},
|
||||
|
||||
@ -147,6 +147,7 @@ function videoHTML(token, opts) {
|
||||
const preloadType = opts.secureMedia ? "none" : "metadata";
|
||||
const dataOrigSrcAttr = origSrc !== null ? `data-orig-src="${origSrc}"` : "";
|
||||
return `<div class="video-container">
|
||||
<p class="video-description">${opts.alt}</p>
|
||||
<video width="100%" height="100%" preload="${preloadType}" controls>
|
||||
<source src="${src}" ${dataOrigSrcAttr}>
|
||||
<a href="${src}">${src}</a>
|
||||
@ -176,7 +177,8 @@ function renderImageOrPlayableMedia(tokens, idx, options, env, slf) {
|
||||
// see https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
|
||||
// handles |video and |audio alt transformations for image tags
|
||||
const mediaOpts = {
|
||||
secureMedia: options.discourse.limitedSiteSettings.secureMedia
|
||||
secureMedia: options.discourse.limitedSiteSettings.secureMedia,
|
||||
alt: split[0]
|
||||
};
|
||||
if (split[1] === "video") {
|
||||
return videoHTML(token, mediaOpts);
|
||||
|
||||
@ -185,6 +185,7 @@ export const DEFAULT_LIST = [
|
||||
"span.excerpt",
|
||||
"div.excerpt",
|
||||
"div.video-container",
|
||||
"p.video-description",
|
||||
"span.hashtag",
|
||||
"span.mention",
|
||||
"strike",
|
||||
|
||||
@ -18,6 +18,13 @@ function buildTimeframe(opts) {
|
||||
}
|
||||
|
||||
export const TIMEFRAMES = [
|
||||
buildTimeframe({
|
||||
id: "now",
|
||||
format: "h:mm a",
|
||||
enabled: opts => opts.canScheduleNow,
|
||||
when: time => time.add(1, "minute"),
|
||||
icon: "magic"
|
||||
}),
|
||||
buildTimeframe({
|
||||
id: "later_today",
|
||||
format: "h a",
|
||||
@ -214,6 +221,7 @@ export default ComboBoxComponent.extend(DatetimeMixin, {
|
||||
includeFarFuture: this.includeFarFuture,
|
||||
includeDateTime: this.includeDateTime,
|
||||
includeBasedOnLastPost: this.statusType === CLOSE_STATUS_TYPE,
|
||||
canScheduleNow: this.includeNow || false,
|
||||
canScheduleToday: 24 - now.hour() > 6
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import MultiSelectComponent from "select-kit/components/multi-select";
|
||||
|
||||
export default MultiSelectComponent.extend({
|
||||
pluginApiIdentifiers: ["admin-group-selector"],
|
||||
classNames: ["admin-group-selector"],
|
||||
pluginApiIdentifiers: ["group-chooser"],
|
||||
classNames: ["group-chooser"],
|
||||
selectKitOptions: {
|
||||
allowAny: false
|
||||
}
|
||||
@ -45,7 +45,7 @@ export default ComboBox.extend(TagsMixin, {
|
||||
},
|
||||
|
||||
modifyComponentForRow(collection, item) {
|
||||
if (this.getValue(item) === this.selectKit.filter) {
|
||||
if (this.getValue(item) === this.selectKit.filter && !item.count) {
|
||||
return "select-kit/select-kit-row";
|
||||
}
|
||||
|
||||
|
||||
@ -800,15 +800,19 @@ export default Component.extend(
|
||||
name: "applySmallScreenMaxWidth",
|
||||
enabled: window.innerWidth <= 450,
|
||||
phase: "beforeWrite",
|
||||
fn({ state }) {
|
||||
fn: ({ state }) => {
|
||||
if (inModal) {
|
||||
const innerModal = document.querySelector(
|
||||
"#discourse-modal div.modal-inner-container"
|
||||
);
|
||||
|
||||
if (innerModal) {
|
||||
state.styles.popper.width = `${innerModal.clientWidth -
|
||||
20}px`;
|
||||
if (this.multiSelect) {
|
||||
state.styles.popper.width = `${this.element.offsetWidth}px`;
|
||||
} else {
|
||||
state.styles.popper.width = `${innerModal.clientWidth -
|
||||
20}px`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.styles.popper.width = `${window.innerWidth - 20}px`;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{{#unless isHidden}}
|
||||
{{#unless selectKit.isHidden}}
|
||||
{{component selectKit.options.headerComponent
|
||||
tabindex=tabindex
|
||||
value=value
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{{#unless isHidden}}
|
||||
{{#unless selectKit.isHidden}}
|
||||
{{component selectKit.options.headerComponent
|
||||
tabindex=tabindex
|
||||
value=value
|
||||
|
||||
@ -112,13 +112,13 @@
|
||||
border-top: 1px solid $primary-low;
|
||||
}
|
||||
.buttons {
|
||||
float: left;
|
||||
width: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
button {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.saving {
|
||||
padding: 5px 0 0 0;
|
||||
margin-left: 10px;
|
||||
width: 80px;
|
||||
color: $primary;
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -623,7 +623,7 @@
|
||||
}
|
||||
|
||||
.select-kit {
|
||||
width: 150px;
|
||||
width: 200px;
|
||||
}
|
||||
input {
|
||||
margin: 5px 0;
|
||||
|
||||
@ -10,6 +10,7 @@ img.emoji.only-emoji {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
a,
|
||||
.md-table,
|
||||
.poll {
|
||||
img.emoji.only-emoji {
|
||||
|
||||
@ -689,24 +689,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
.publish-page-modal .modal-body {
|
||||
p.publish-description {
|
||||
margin-top: 0;
|
||||
}
|
||||
input.publish-slug {
|
||||
width: 100%;
|
||||
}
|
||||
.publish-page-modal {
|
||||
.modal-body {
|
||||
p.publish-description {
|
||||
margin-top: 0;
|
||||
}
|
||||
input.publish-slug {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.publish-url {
|
||||
margin-bottom: 1em;
|
||||
.example-url,
|
||||
.invalid-slug {
|
||||
font-weight: bold;
|
||||
.publish-url {
|
||||
margin-bottom: 1em;
|
||||
.example-url,
|
||||
.invalid-slug {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.publish-slug:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.publish-slug:disabled {
|
||||
cursor: not-allowed;
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
|
||||
.close-publish-page {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ignore-duration-with-username-modal {
|
||||
.future-date-input {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -703,6 +703,9 @@ aside.onebox.stackexchange .onebox-body {
|
||||
// Force oneboxed videos to 16:9 aspect ratio
|
||||
.onebox.video-onebox,
|
||||
.video-container {
|
||||
background: $primary-very-low;
|
||||
border: 1px solid $primary-low;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
padding: 0 0 56.25% 0;
|
||||
width: 100%;
|
||||
@ -714,6 +717,12 @@ aside.onebox.stackexchange .onebox-body {
|
||||
}
|
||||
}
|
||||
|
||||
.video-description {
|
||||
color: $primary-medium;
|
||||
margin: 1rem;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.onebox-placeholder-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
@ -264,6 +264,16 @@ blockquote {
|
||||
}
|
||||
}
|
||||
|
||||
.small-user-list .unknown {
|
||||
display: inline-block;
|
||||
background-color: $primary-low;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.post-hidden {
|
||||
.topic-avatar,
|
||||
.cooked,
|
||||
|
||||
@ -305,4 +305,13 @@ a.topic-featured-link {
|
||||
2}
|
||||
);
|
||||
align-items: center;
|
||||
|
||||
.is-public {
|
||||
padding: 0.25em 0.5em;
|
||||
font-size: $font-down-2;
|
||||
background: $tertiary;
|
||||
color: $secondary;
|
||||
border-radius: 3px;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
.ignored-user-list-item {
|
||||
border: 1px solid $primary-medium;
|
||||
border-radius: 0.25em;
|
||||
border-radius: 5px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
|
||||
.d-icon {
|
||||
flex: 0 0 100%;
|
||||
overflow: hidden;
|
||||
font-size: $font-up-2;
|
||||
align-self: center;
|
||||
margin-right: 0;
|
||||
|
||||
@ -7,10 +7,6 @@
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
|
||||
&.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -71,6 +67,7 @@
|
||||
.selected-name {
|
||||
text-align: left;
|
||||
flex: 0 1 auto;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
outline: none;
|
||||
|
||||
@ -22,11 +22,14 @@ $avatar_width: 120px;
|
||||
margin-top: 1em;
|
||||
max-width: 100%;
|
||||
li {
|
||||
flex: 1;
|
||||
flex: 1 0 auto;
|
||||
min-width: 0;
|
||||
&:nth-child(2) {
|
||||
border-left: 0.5em solid transparent;
|
||||
}
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
button {
|
||||
@include ellipsis;
|
||||
}
|
||||
|
||||
@ -86,7 +86,3 @@
|
||||
#main-outlet {
|
||||
padding-top: 4.2857em;
|
||||
}
|
||||
|
||||
.search-link .badge-category {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -116,6 +116,10 @@
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.insert-hyperlink-modal .modal-inner-container {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.modal .modal-body.reorder-categories {
|
||||
|
||||
@ -61,7 +61,7 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||
(new_category_ids - previous_category_ids).each do |category_id|
|
||||
skip_user_ids = CategoryUser.where(category_id: category_id).pluck(:user_id)
|
||||
|
||||
User.where.not(id: skip_user_ids).select(:id).find_in_batches do |users|
|
||||
User.real.where(staged: false).where.not(id: skip_user_ids).select(:id).find_in_batches do |users|
|
||||
category_users = []
|
||||
users.each { |user| category_users << { category_id: category_id, user_id: user.id, notification_level: notification_level } }
|
||||
CategoryUser.insert_all!(category_users)
|
||||
@ -88,7 +88,7 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||
(new_tag_ids - previous_tag_ids).each do |tag_id|
|
||||
skip_user_ids = TagUser.where(tag_id: tag_id).pluck(:user_id)
|
||||
|
||||
User.where.not(id: skip_user_ids).select(:id).find_in_batches do |users|
|
||||
User.real.where(staged: false).where.not(id: skip_user_ids).select(:id).find_in_batches do |users|
|
||||
tag_users = []
|
||||
users.each { |user| tag_users << { tag_id: tag_id, user_id: user.id, notification_level: notification_level, created_at: now, updated_at: now } }
|
||||
TagUser.insert_all!(tag_users)
|
||||
@ -135,8 +135,10 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||
|
||||
user_ids = CategoryUser.where(category_id: previous_category_ids - new_category_ids, notification_level: notification_level).distinct.pluck(:user_id)
|
||||
user_ids += User
|
||||
.real
|
||||
.joins("CROSS JOIN categories c")
|
||||
.joins("LEFT JOIN category_users cu ON users.id = cu.user_id AND c.id = cu.category_id")
|
||||
.where(staged: false)
|
||||
.where("c.id IN (?) AND cu.notification_level IS NULL", new_category_ids - previous_category_ids)
|
||||
.distinct
|
||||
.pluck("users.id")
|
||||
@ -159,8 +161,10 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||
|
||||
user_ids = TagUser.where(tag_id: previous_tag_ids - new_tag_ids, notification_level: notification_level).distinct.pluck(:user_id)
|
||||
user_ids += User
|
||||
.real
|
||||
.joins("CROSS JOIN tags t")
|
||||
.joins("LEFT JOIN tag_users tu ON users.id = tu.user_id AND t.id = tu.tag_id")
|
||||
.where(staged: false)
|
||||
.where("t.id IN (?) AND tu.notification_level IS NULL", new_tag_ids - previous_tag_ids)
|
||||
.distinct
|
||||
.pluck("users.id")
|
||||
|
||||
@ -539,6 +539,7 @@ class ApplicationController < ActionController::Base
|
||||
store_preloaded("customHTML", custom_html_json)
|
||||
store_preloaded("banner", banner_json)
|
||||
store_preloaded("customEmoji", custom_emoji)
|
||||
store_preloaded("isReadOnly", @readonly_mode.to_s)
|
||||
end
|
||||
|
||||
def preload_current_user_data
|
||||
|
||||
@ -9,7 +9,7 @@ class CategoryHashtagsController < ApplicationController
|
||||
ids = category_slugs.map { |category_slug| Category.query_from_hashtag_slug(category_slug).try(:id) }
|
||||
|
||||
valid_categories = Category.secured(guardian).where(id: ids).map do |category|
|
||||
{ slug: category.hashtag_slug, url: category.url_with_id }
|
||||
{ slug: category.hashtag_slug, url: category.url }
|
||||
end.compact
|
||||
|
||||
render json: { valid: valid_categories }
|
||||
|
||||
@ -144,6 +144,10 @@ class ListController < ApplicationController
|
||||
|
||||
def self.generate_message_route(action)
|
||||
define_method("#{action}") do
|
||||
if action == :private_messages_tag && !guardian.can_tag_pms?
|
||||
raise Discourse::NotFound
|
||||
end
|
||||
|
||||
list_opts = build_topic_list_options
|
||||
target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) }, [:user_stat, :user_option])
|
||||
guardian.ensure_can_see_private_messages!(target_user.id)
|
||||
|
||||
@ -15,6 +15,16 @@ class PostActionUsersController < ApplicationController
|
||||
post = finder.first
|
||||
guardian.ensure_can_see!(post)
|
||||
|
||||
unknown_user_ids = Set.new
|
||||
if current_user.present?
|
||||
result = DB.query_single(<<~SQL, user_id: current_user.id)
|
||||
SELECT mu.muted_user_id AS id FROM muted_users AS mu WHERE mu.user_id = :user_id
|
||||
UNION
|
||||
SELECT iu.ignored_user_id AS id FROM ignored_users AS iu WHERE iu.user_id = :user_id
|
||||
SQL
|
||||
unknown_user_ids.merge(result)
|
||||
end
|
||||
|
||||
post_actions = post.post_actions.where(post_action_type_id: post_action_type_id)
|
||||
.includes(:user)
|
||||
.offset(page * page_size)
|
||||
@ -29,7 +39,13 @@ class PostActionUsersController < ApplicationController
|
||||
action_type = PostActionType.types.key(post_action_type_id)
|
||||
total_count = post["#{action_type}_count"].to_i
|
||||
|
||||
data = { post_action_users: serialize_data(post_actions.to_a, PostActionUserSerializer) }
|
||||
data = {
|
||||
post_action_users: serialize_data(
|
||||
post_actions.to_a,
|
||||
PostActionUserSerializer,
|
||||
unknown_user_ids: unknown_user_ids
|
||||
)
|
||||
}
|
||||
|
||||
if total_count > page_size
|
||||
data[:total_rows_post_action_users] = total_count
|
||||
|
||||
@ -5,6 +5,7 @@ class PublishedPagesController < ApplicationController
|
||||
skip_before_action :preload_json
|
||||
skip_before_action :check_xhr, :verify_authenticity_token, only: [:show]
|
||||
before_action :ensure_publish_enabled
|
||||
before_action :redirect_to_login_if_required, except: [:show]
|
||||
|
||||
def show
|
||||
params.require(:slug)
|
||||
@ -12,7 +13,22 @@ class PublishedPagesController < ApplicationController
|
||||
pp = PublishedPage.find_by(slug: params[:slug])
|
||||
raise Discourse::NotFound unless pp
|
||||
|
||||
guardian.ensure_can_see!(pp.topic)
|
||||
return if enforce_login_required!
|
||||
|
||||
if !pp.public
|
||||
begin
|
||||
guardian.ensure_can_see!(pp.topic)
|
||||
rescue Discourse::InvalidAccess => e
|
||||
return rescue_discourse_actions(
|
||||
:invalid_access,
|
||||
403,
|
||||
include_ember: false,
|
||||
custom_message: e.custom_message,
|
||||
group: e.group
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@topic = pp.topic
|
||||
@canonical_url = @topic.url
|
||||
|
||||
@ -37,7 +53,15 @@ class PublishedPagesController < ApplicationController
|
||||
end
|
||||
|
||||
def upsert
|
||||
result, pp = PublishedPage.publish!(current_user, fetch_topic, params[:published_page][:slug].strip)
|
||||
pp_params = params.require(:published_page)
|
||||
|
||||
result, pp = PublishedPage.publish!(
|
||||
current_user,
|
||||
fetch_topic,
|
||||
pp_params[:slug].strip,
|
||||
pp_params.permit(:public)
|
||||
)
|
||||
|
||||
json_result(pp, serializer: PublishedPageSerializer) { result }
|
||||
end
|
||||
|
||||
@ -68,4 +92,13 @@ private
|
||||
raise Discourse::NotFound unless SiteSetting.enable_page_publishing?
|
||||
end
|
||||
|
||||
def enforce_login_required!
|
||||
if SiteSetting.login_required? &&
|
||||
!current_user &&
|
||||
!SiteSetting.show_published_pages_login_required? &&
|
||||
redirect_to_login
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -37,10 +37,10 @@ class TagsController < ::ApplicationController
|
||||
ungrouped_tags = ungrouped_tags.where("tags.topic_count > 0") unless show_all_tags
|
||||
|
||||
grouped_tag_counts = TagGroup.visible(guardian).order('name ASC').includes(:tags).map do |tag_group|
|
||||
{ id: tag_group.id, name: tag_group.name, tags: self.class.tag_counts_json(tag_group.tags.where(target_tag_id: nil)) }
|
||||
{ id: tag_group.id, name: tag_group.name, tags: self.class.tag_counts_json(tag_group.tags.where(target_tag_id: nil), show_pm_tags: guardian.can_tag_pms?) }
|
||||
end
|
||||
|
||||
@tags = self.class.tag_counts_json(ungrouped_tags)
|
||||
@tags = self.class.tag_counts_json(ungrouped_tags, show_pm_tags: guardian.can_tag_pms?)
|
||||
@extras = { tag_groups: grouped_tag_counts }
|
||||
else
|
||||
tags = show_all_tags ? Tag.all : Tag.where("tags.topic_count > 0")
|
||||
@ -54,7 +54,7 @@ class TagsController < ::ApplicationController
|
||||
{ id: c.id, tags: self.class.tag_counts_json(c.tags.where(target_tag_id: nil)) }
|
||||
end
|
||||
|
||||
@tags = self.class.tag_counts_json(unrestricted_tags)
|
||||
@tags = self.class.tag_counts_json(unrestricted_tags, show_pm_tags: guardian.can_tag_pms?)
|
||||
@extras = { categories: category_tag_counts }
|
||||
end
|
||||
|
||||
@ -231,7 +231,7 @@ class TagsController < ::ApplicationController
|
||||
filter_params
|
||||
)
|
||||
|
||||
tags = self.class.tag_counts_json(tags_with_counts)
|
||||
tags = self.class.tag_counts_json(tags_with_counts, show_pm_tags: guardian.can_tag_pms?)
|
||||
|
||||
json_response = { results: tags }
|
||||
|
||||
@ -336,17 +336,19 @@ class TagsController < ::ApplicationController
|
||||
raise Discourse::NotFound if DiscourseTagging.hidden_tag_names(guardian).include?(params[:tag_id])
|
||||
end
|
||||
|
||||
def self.tag_counts_json(tags)
|
||||
def self.tag_counts_json(tags, show_pm_tags: true)
|
||||
target_tags = Tag.where(id: tags.map(&:target_tag_id).compact.uniq).select(:id, :name)
|
||||
tags.map do |t|
|
||||
next if t.topic_count == 0 && t.pm_topic_count > 0 && !show_pm_tags
|
||||
|
||||
{
|
||||
id: t.name,
|
||||
text: t.name,
|
||||
count: t.topic_count,
|
||||
pm_count: t.pm_topic_count,
|
||||
pm_count: show_pm_tags ? t.pm_topic_count : 0,
|
||||
target_tag: t.target_tag_id ? target_tags.find { |x| x.id == t.target_tag_id }&.name : nil
|
||||
}
|
||||
end
|
||||
end.compact
|
||||
end
|
||||
|
||||
def set_category_from_params
|
||||
|
||||
@ -195,10 +195,12 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
email, *secondary_emails = user.emails
|
||||
unconfirmed_emails = user.unconfirmed_emails
|
||||
|
||||
render json: {
|
||||
email: email,
|
||||
secondary_emails: secondary_emails,
|
||||
unconfirmed_emails: unconfirmed_emails,
|
||||
associated_accounts: user.associated_accounts
|
||||
}
|
||||
rescue Discourse::InvalidAccess
|
||||
@ -232,7 +234,7 @@ class UsersController < ApplicationController
|
||||
if current_user.staff? && current_user != user
|
||||
StaffActionLogger.new(current_user).log_update_email(user)
|
||||
else
|
||||
UserHistory.create!(action: UserHistory.actions[:update_email], target_user_id: user.id)
|
||||
UserHistory.create!(action: UserHistory.actions[:update_email], acting_user_id: user.id)
|
||||
end
|
||||
end
|
||||
|
||||
@ -264,7 +266,7 @@ class UsersController < ApplicationController
|
||||
if current_user.staff? && current_user != user
|
||||
StaffActionLogger.new(current_user).log_destroy_email(user)
|
||||
else
|
||||
UserHistory.create(action: UserHistory.actions[:destroy_email], target_user_id: user.id)
|
||||
UserHistory.create(action: UserHistory.actions[:destroy_email], acting_user_id: user.id)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1176,6 +1178,7 @@ class UsersController < ApplicationController
|
||||
user = fetch_user_from_params
|
||||
|
||||
if params[:notification_level] == "ignore"
|
||||
@error_message = "ignore_error"
|
||||
guardian.ensure_can_ignore_user!(user)
|
||||
MutedUser.where(user: current_user, muted_user: user).delete_all
|
||||
ignored_user = IgnoredUser.find_by(user: current_user, ignored_user: user)
|
||||
@ -1185,6 +1188,7 @@ class UsersController < ApplicationController
|
||||
IgnoredUser.create!(user: current_user, ignored_user: user, expiring_at: Time.parse(params[:expiring_at]))
|
||||
end
|
||||
elsif params[:notification_level] == "mute"
|
||||
@error_message = "mute_error"
|
||||
guardian.ensure_can_mute_user!(user)
|
||||
IgnoredUser.where(user: current_user, ignored_user: user).delete_all
|
||||
MutedUser.find_or_create_by!(user: current_user, muted_user: user)
|
||||
@ -1194,6 +1198,8 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
render json: success_json
|
||||
rescue Discourse::InvalidAccess => e
|
||||
render_json_error(I18n.t("notification_level.#{@error_message}"))
|
||||
end
|
||||
|
||||
def read_faq
|
||||
|
||||
@ -1,92 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class MigrateUrlSiteSettings < ::Jobs::Onceoff
|
||||
SETTINGS = [
|
||||
['logo_url', 'logo'],
|
||||
['logo_small_url', 'logo_small'],
|
||||
['digest_logo_url', 'digest_logo'],
|
||||
['mobile_logo_url', 'mobile_logo'],
|
||||
['large_icon_url', 'large_icon'],
|
||||
['favicon_url', 'favicon'],
|
||||
['apple_touch_icon_url', 'apple_touch_icon'],
|
||||
['default_opengraph_image_url', 'opengraph_image'],
|
||||
['twitter_summary_large_image_url', 'twitter_summary_large_image'],
|
||||
['push_notifications_icon_url', 'push_notifications_icon'],
|
||||
]
|
||||
|
||||
def execute_onceoff(args)
|
||||
SETTINGS.each do |old_setting, new_setting|
|
||||
upload = SiteSetting.get(new_setting)
|
||||
|
||||
next if upload && upload.id >= Upload::SEEDED_ID_THRESHOLD
|
||||
|
||||
old_url = DB.query_single(
|
||||
"SELECT value FROM site_settings WHERE name = '#{old_setting}'"
|
||||
).first
|
||||
|
||||
next if old_url.blank?
|
||||
|
||||
count = 0
|
||||
file = nil
|
||||
sleep_interval = 5
|
||||
|
||||
loop do
|
||||
url = UrlHelper.absolute_without_cdn(old_url)
|
||||
|
||||
begin
|
||||
file = FileHelper.download(
|
||||
url,
|
||||
max_file_size: [
|
||||
SiteSetting.max_image_size_kb.kilobytes,
|
||||
20.megabytes
|
||||
].max,
|
||||
tmp_file_name: 'tmp_site_setting_logo',
|
||||
skip_rate_limit: true,
|
||||
follow_redirect: true
|
||||
)
|
||||
rescue OpenURI::HTTPError,
|
||||
OpenSSL::SSL::SSLError,
|
||||
Net::OpenTimeout,
|
||||
Net::ReadTimeout,
|
||||
Errno::ECONNREFUSED,
|
||||
EOFError,
|
||||
SocketError,
|
||||
Discourse::InvalidParameters => e
|
||||
|
||||
logger.warn(
|
||||
"Error encountered when trying to download file " +
|
||||
"for #{new_setting}.\n#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
||||
)
|
||||
end
|
||||
|
||||
count += 1
|
||||
break if file || (file.blank? && count >= 3)
|
||||
|
||||
logger.info(
|
||||
"Failed to download upload from #{url} for #{new_setting}. Retrying..."
|
||||
)
|
||||
|
||||
sleep(count * sleep_interval)
|
||||
end
|
||||
|
||||
next if file.blank?
|
||||
|
||||
upload = UploadCreator.new(
|
||||
file,
|
||||
"#{new_setting}",
|
||||
origin: UrlHelper.absolute(old_url),
|
||||
for_site_setting: true
|
||||
).create_for(Discourse.system_user.id)
|
||||
|
||||
SiteSetting.set(new_setting, upload)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def logger
|
||||
Rails.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -41,7 +41,7 @@ module Jobs
|
||||
end
|
||||
|
||||
if !post.user&.staff? && !post.user&.staged?
|
||||
s = post.cooked
|
||||
s = post.raw
|
||||
s << " #{post.topic.title}" if post.post_number == 1
|
||||
if !args[:bypass_bump] && WordWatcher.new(s).should_flag?
|
||||
PostActionCreator.create(
|
||||
|
||||
@ -171,10 +171,13 @@ module Jobs
|
||||
# make sure we actually have a url
|
||||
return false unless src.present?
|
||||
|
||||
# If file is on the forum or CDN domain or already has the
|
||||
# secure media url
|
||||
if Discourse.store.has_been_uploaded?(src) || src =~ /\A\/[^\/]/i || Upload.secure_media_url?(src)
|
||||
return false if src =~ /\/images\/emoji\//
|
||||
local_bases = [
|
||||
Discourse.base_url,
|
||||
Discourse.asset_host,
|
||||
].compact.map { |s| normalize_src(s) }
|
||||
|
||||
if Discourse.store.has_been_uploaded?(src) || normalize_src(src).start_with?(*local_bases) || src =~ /\A\/[^\/]/i
|
||||
return false if !(src =~ /\/uploads\// || Upload.secure_media_url?(src))
|
||||
|
||||
# Someone could hotlink a file from a different site on the same CDN,
|
||||
# so check whether we have it in this database
|
||||
@ -220,7 +223,7 @@ module Jobs
|
||||
uri.normalize!
|
||||
uri.scheme = nil
|
||||
uri.to_s
|
||||
rescue URI::Error
|
||||
rescue URI::Error, Addressable::URI::InvalidURIError
|
||||
src
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class CleanUpDeprecatedUrlSiteSettings < ::Jobs::Scheduled
|
||||
every 1.day
|
||||
|
||||
def execute(args)
|
||||
::Jobs::MigrateUrlSiteSettings::SETTINGS.each do |old_setting, new_setting|
|
||||
if SiteSetting.where("name = ? AND value IS NOT NULL", new_setting).exists?
|
||||
SiteSetting.set(old_setting, nil, warn: false)
|
||||
SiteSetting.find_by(name: old_setting).destroy!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -27,15 +27,6 @@ module Jobs
|
||||
|
||||
# Any URLs in site settings are fair game
|
||||
ignore_urls = [
|
||||
SiteSetting.logo_url(warn: false),
|
||||
SiteSetting.logo_small_url(warn: false),
|
||||
SiteSetting.digest_logo_url(warn: false),
|
||||
SiteSetting.mobile_logo_url(warn: false),
|
||||
SiteSetting.large_icon_url(warn: false),
|
||||
SiteSetting.favicon_url(warn: false),
|
||||
SiteSetting.default_opengraph_image_url(warn: false),
|
||||
SiteSetting.twitter_summary_large_image_url(warn: false),
|
||||
SiteSetting.apple_touch_icon_url(warn: false),
|
||||
*SiteSetting.selectable_avatars.split("\n"),
|
||||
].flatten.map do |url|
|
||||
if url.present?
|
||||
|
||||
@ -8,6 +8,8 @@ module Jobs
|
||||
CLEANUP_GRACE_PERIOD ||= 1.day.ago
|
||||
|
||||
def execute(args)
|
||||
@verbose = true if args && Hash === args && args[:verbose]
|
||||
|
||||
rebuild_problem_topics
|
||||
rebuild_problem_posts
|
||||
rebuild_problem_categories
|
||||
@ -15,11 +17,17 @@ module Jobs
|
||||
rebuild_problem_tags
|
||||
clean_post_search_data
|
||||
clean_topic_search_data
|
||||
|
||||
@verbose = nil
|
||||
end
|
||||
|
||||
def rebuild_problem_categories(limit: 500)
|
||||
category_ids = load_problem_category_ids(limit)
|
||||
|
||||
if @verbose
|
||||
puts "rebuilding #{category_ids.length} categories"
|
||||
end
|
||||
|
||||
category_ids.each do |id|
|
||||
category = Category.find_by(id: id)
|
||||
SearchIndexer.index(category, force: true) if category
|
||||
@ -29,6 +37,10 @@ module Jobs
|
||||
def rebuild_problem_users(limit: 10000)
|
||||
user_ids = load_problem_user_ids(limit)
|
||||
|
||||
if @verbose
|
||||
puts "rebuilding #{user_ids.length} users"
|
||||
end
|
||||
|
||||
user_ids.each do |id|
|
||||
user = User.find_by(id: id)
|
||||
SearchIndexer.index(user, force: true) if user
|
||||
@ -38,19 +50,34 @@ module Jobs
|
||||
def rebuild_problem_topics(limit: 10000)
|
||||
topic_ids = load_problem_topic_ids(limit)
|
||||
|
||||
if @verbose
|
||||
puts "rebuilding #{topic_ids.length} topics"
|
||||
end
|
||||
|
||||
topic_ids.each do |id|
|
||||
topic = Topic.find_by(id: id)
|
||||
SearchIndexer.index(topic, force: true) if topic
|
||||
end
|
||||
end
|
||||
|
||||
def rebuild_problem_posts(limit: 20000, indexer: SearchIndexer)
|
||||
def rebuild_problem_posts(limit: 20000, indexer: SearchIndexer, verbose: false)
|
||||
post_ids = load_problem_post_ids(limit)
|
||||
verbose ||= @verbose
|
||||
|
||||
if verbose
|
||||
puts "rebuilding #{post_ids.length} posts"
|
||||
end
|
||||
|
||||
i = 0
|
||||
post_ids.each do |id|
|
||||
# could be deleted while iterating through batch
|
||||
if post = Post.find_by(id: id)
|
||||
indexer.index(post, force: true)
|
||||
i += 1
|
||||
|
||||
if verbose && i % 1000 == 0
|
||||
puts "#{i} posts reindexed"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -58,6 +85,10 @@ module Jobs
|
||||
def rebuild_problem_tags(limit: 10000)
|
||||
tag_ids = load_problem_tag_ids(limit)
|
||||
|
||||
if @verbose
|
||||
puts "rebuilding #{tag_ids.length} tags"
|
||||
end
|
||||
|
||||
tag_ids.each do |id|
|
||||
tag = Tag.find_by(id: id)
|
||||
SearchIndexer.index(tag, force: true) if tag
|
||||
@ -67,6 +98,8 @@ module Jobs
|
||||
private
|
||||
|
||||
def clean_post_search_data
|
||||
puts "cleaning up post search data" if @verbose
|
||||
|
||||
PostSearchData
|
||||
.joins("LEFT JOIN posts p ON p.id = post_search_data.post_id")
|
||||
.where("p.raw = ''")
|
||||
@ -90,6 +123,8 @@ module Jobs
|
||||
end
|
||||
|
||||
def clean_topic_search_data
|
||||
puts "cleaning up topic search data" if @verbose
|
||||
|
||||
DB.exec(<<~SQL, deleted_at: CLEANUP_GRACE_PERIOD)
|
||||
DELETE FROM topic_search_data
|
||||
WHERE topic_id IN (
|
||||
|
||||
@ -9,6 +9,6 @@ class SubscriptionMailer < ActionMailer::Base
|
||||
template: "unsubscribe_mailer",
|
||||
site_title: SiteSetting.title,
|
||||
site_domain_name: Discourse.current_hostname,
|
||||
confirm_unsubscribe_link: "#{Discourse.base_url}/unsubscribe/#{unsubscribe_key}"
|
||||
confirm_unsubscribe_link: email_unsubscribe_url(unsubscribe_key, host: Discourse.base_url)
|
||||
end
|
||||
end
|
||||
|
||||
@ -174,14 +174,6 @@ class UserNotifications < ActionMailer::Base
|
||||
)
|
||||
end
|
||||
|
||||
def short_date(dt)
|
||||
if dt.year == Time.now.year
|
||||
I18n.l(dt, format: :short_no_year)
|
||||
else
|
||||
I18n.l(dt, format: :date_only)
|
||||
end
|
||||
end
|
||||
|
||||
def digest(user, opts = {})
|
||||
build_summary_for(user)
|
||||
min_date = opts[:since] || user.last_emailed_at || user.last_seen_at || 1.month.ago
|
||||
|
||||
@ -721,11 +721,13 @@ class Category < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def url
|
||||
@@url_cache[self.id] ||= "#{Discourse.base_uri}/c/#{slug_path.join('/')}"
|
||||
@@url_cache[self.id] ||= "#{Discourse.base_uri}/c/#{slug_path.join('/')}/#{self.id}"
|
||||
end
|
||||
|
||||
def url_with_id
|
||||
self.parent_category ? "#{url}/#{self.id}" : "#{Discourse.base_uri}/c/#{self.slug}/#{self.id}"
|
||||
Discourse.deprecate("Category#url_with_id is deprecated. Use `Category#url` instead.", output_in_test: true)
|
||||
|
||||
url
|
||||
end
|
||||
|
||||
# If the name changes, try and update the category definition topic too if it's an exact match
|
||||
@ -739,9 +741,10 @@ class Category < ActiveRecord::Base
|
||||
|
||||
def create_category_permalink
|
||||
old_slug = saved_changes.transform_values(&:first)["slug"]
|
||||
|
||||
url = +"#{Discourse.base_uri}/c"
|
||||
url << "/#{parent_category.slug_path.join('/')}" if parent_category_id
|
||||
url << "/#{old_slug}"
|
||||
url << "/#{old_slug}/#{id}"
|
||||
url = Permalink.normalize_url(url)
|
||||
|
||||
if Permalink.where(url: url).exists?
|
||||
|
||||
@ -59,7 +59,7 @@ class CategoryFeaturedTopic < ActiveRecord::Base
|
||||
# no featured topics (all the previous 2x topics are only visible to admins)
|
||||
|
||||
# Add topics, even if they're in secured categories or invisible
|
||||
query = TopicQuery.new(CategoryFeaturedTopic.fake_admin, query_opts)
|
||||
query = TopicQuery.new(Discourse.system_user, query_opts)
|
||||
results = query.list_category_topic_ids(c).uniq
|
||||
|
||||
# Add some topics that are visible to everyone:
|
||||
@ -81,15 +81,6 @@ class CategoryFeaturedTopic < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.fake_admin
|
||||
# fake an admin
|
||||
admin = User.new
|
||||
admin.admin = true
|
||||
admin.id = -1
|
||||
admin
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
||||
@ -19,6 +19,7 @@ module Roleable
|
||||
end
|
||||
|
||||
def grant_moderation!
|
||||
return if moderator
|
||||
set_permission('moderator', true)
|
||||
auto_approve_user
|
||||
enqueue_staff_welcome_message(:moderator)
|
||||
@ -29,6 +30,7 @@ module Roleable
|
||||
end
|
||||
|
||||
def grant_admin!
|
||||
return if admin
|
||||
set_permission('admin', true)
|
||||
auto_approve_user
|
||||
enqueue_staff_welcome_message(:admin)
|
||||
|
||||
@ -6,8 +6,10 @@ class EmailLog < ActiveRecord::Base
|
||||
admin_login
|
||||
confirm_new_email
|
||||
confirm_old_email
|
||||
confirm_old_email_add
|
||||
forgot_password
|
||||
notify_old_email
|
||||
notify_old_email_add
|
||||
signup
|
||||
signup_after_approval
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ class EmbeddableHost < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def host_must_be_valid
|
||||
if host !~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i &&
|
||||
if host !~ /\A[a-z0-9]+([\-\.]+{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i &&
|
||||
host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(:[0-9]{1,5})?(\/.*)?\Z/ &&
|
||||
host !~ /\A([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.)?localhost(\:[0-9]{1,5})?(\/.*)?\Z/i
|
||||
errors.add(:host, I18n.t('errors.messages.invalid'))
|
||||
|
||||
@ -134,12 +134,6 @@ class GlobalSetting
|
||||
end
|
||||
end
|
||||
|
||||
if hash["replica_host"]
|
||||
if !ENV["ACTIVE_RECORD_RAILS_FAILOVER"]
|
||||
hash["adapter"] = "postgresql_fallback"
|
||||
end
|
||||
end
|
||||
|
||||
hostnames = [ hostname ]
|
||||
hostnames << backup_hostname if backup_hostname.present?
|
||||
|
||||
@ -151,6 +145,7 @@ class GlobalSetting
|
||||
hash["prepared_statements"] = !!self.db_prepared_statements
|
||||
hash["idle_timeout"] = connection_reaper_age if connection_reaper_age.present?
|
||||
hash["reaping_frequency"] = connection_reaper_interval if connection_reaper_interval.present?
|
||||
hash["advisory_locks"] = !!self.db_advisory_locks
|
||||
|
||||
{ "production" => hash }
|
||||
end
|
||||
@ -168,16 +163,10 @@ class GlobalSetting
|
||||
c[:host] = redis_host if redis_host
|
||||
c[:port] = redis_port if redis_port
|
||||
|
||||
if redis_slave_host && redis_slave_port
|
||||
if ENV["REDIS_RAILS_FAILOVER"]
|
||||
c[:replica_host] = redis_slave_host
|
||||
c[:replica_port] = redis_slave_port
|
||||
c[:connector] = RailsFailover::Redis::Connector
|
||||
else
|
||||
c[:slave_host] = redis_slave_host
|
||||
c[:slave_port] = redis_slave_port
|
||||
c[:connector] = DiscourseRedis::Connector
|
||||
end
|
||||
if redis_slave_host && redis_slave_port && defined?(RailsFailover)
|
||||
c[:replica_host] = redis_slave_host
|
||||
c[:replica_port] = redis_slave_port
|
||||
c[:connector] = RailsFailover::Redis::Connector
|
||||
end
|
||||
|
||||
c[:password] = redis_password if redis_password.present?
|
||||
@ -199,15 +188,9 @@ class GlobalSetting
|
||||
c[:port] = message_bus_redis_port if message_bus_redis_port
|
||||
|
||||
if message_bus_redis_slave_host && message_bus_redis_slave_port
|
||||
if ENV["REDIS_RAILS_FAILOVER"]
|
||||
c[:replica_host] = message_bus_redis_slave_host
|
||||
c[:replica_port] = message_bus_redis_slave_port
|
||||
c[:connector] = RailsFailover::Redis::Connector
|
||||
else
|
||||
c[:slave_host] = message_bus_redis_slave_host
|
||||
c[:slave_port] = message_bus_redis_slave_port
|
||||
c[:connector] = DiscourseRedis::Connector
|
||||
end
|
||||
c[:replica_host] = message_bus_redis_slave_host
|
||||
c[:replica_port] = message_bus_redis_slave_port
|
||||
c[:connector] = RailsFailover::Redis::Connector
|
||||
end
|
||||
|
||||
c[:password] = message_bus_redis_password if message_bus_redis_password.present?
|
||||
|
||||
@ -57,7 +57,6 @@ class Group < ActiveRecord::Base
|
||||
|
||||
def remove_review_groups
|
||||
Category.where(review_group_id: self.id).update_all(review_group_id: nil)
|
||||
Category.where(review_group_id: self.id).update_all(review_group_id: nil)
|
||||
end
|
||||
|
||||
validate :name_format_validator
|
||||
|
||||
@ -128,10 +128,14 @@ InviteRedeemer = Struct.new(:invite, :email, :username, :name, :password, :user_
|
||||
end
|
||||
|
||||
def add_user_to_groups
|
||||
guardian = Guardian.new(invite.invited_by)
|
||||
new_group_ids = invite.groups.pluck(:id) - invited_user.group_users.pluck(:group_id)
|
||||
new_group_ids.each do |id|
|
||||
invited_user.group_users.create!(group_id: id)
|
||||
DiscourseEvent.trigger(:user_added_to_group, invited_user, Group.find_by(id: id), automatic: false)
|
||||
group = Group.find_by(id: id)
|
||||
if guardian.can_edit_group?(group)
|
||||
invited_user.group_users.create!(group_id: group.id)
|
||||
DiscourseEvent.trigger(:user_added_to_group, invited_user, group, automatic: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ class Permalink < ActiveRecord::Base
|
||||
return external_url if external_url
|
||||
return "#{Discourse::base_uri}#{post.url}" if post
|
||||
return topic.relative_url if topic
|
||||
return "#{category.url}/#{category.id}" if category
|
||||
return category.url if category
|
||||
return tag.full_url if tag
|
||||
nil
|
||||
end
|
||||
|
||||
@ -38,7 +38,7 @@ class Post < ActiveRecord::Base
|
||||
has_many :topic_links
|
||||
has_many :group_mentions, dependent: :destroy
|
||||
|
||||
has_many :post_uploads
|
||||
has_many :post_uploads, dependent: :delete_all
|
||||
has_many :uploads, through: :post_uploads
|
||||
|
||||
has_one :post_stat
|
||||
@ -255,7 +255,7 @@ class Post < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def self.white_listed_image_classes
|
||||
@white_listed_image_classes ||= ['avatar', 'favicon', 'thumbnail', 'emoji']
|
||||
@white_listed_image_classes ||= ['avatar', 'favicon', 'thumbnail', 'emoji', 'ytp-thumbnail-image']
|
||||
end
|
||||
|
||||
def post_analyzer
|
||||
@ -752,18 +752,16 @@ class Post < ActiveRecord::Base
|
||||
# Enqueue post processing for this post
|
||||
def trigger_post_process(bypass_bump: false, priority: :normal, new_post: false, skip_pull_hotlinked_images: false)
|
||||
args = {
|
||||
post_id: id,
|
||||
bypass_bump: bypass_bump,
|
||||
cooking_options: self.cooking_options,
|
||||
new_post: new_post,
|
||||
post_id: id,
|
||||
skip_pull_hotlinked_images: skip_pull_hotlinked_images,
|
||||
}
|
||||
args[:image_sizes] = image_sizes if image_sizes.present?
|
||||
args[:invalidate_oneboxes] = true if invalidate_oneboxes.present?
|
||||
args[:cooking_options] = self.cooking_options
|
||||
|
||||
if priority && priority != :normal
|
||||
args[:queue] = priority.to_s
|
||||
end
|
||||
args[:image_sizes] = image_sizes if self.image_sizes.present?
|
||||
args[:invalidate_oneboxes] = true if self.invalidate_oneboxes.present?
|
||||
args[:queue] = priority.to_s if priority && priority != :normal
|
||||
|
||||
Jobs.enqueue(:process_post, args)
|
||||
DiscourseEvent.trigger(:after_trigger_post_process, self)
|
||||
|
||||
@ -33,7 +33,10 @@ class PostAnalyzer
|
||||
|
||||
result = Oneboxer.apply(cooked) do |url|
|
||||
@onebox_urls << url
|
||||
Oneboxer.invalidate(url) if opts[:invalidate_oneboxes]
|
||||
if opts[:invalidate_oneboxes]
|
||||
Oneboxer.invalidate(url)
|
||||
InlineOneboxer.invalidate(url)
|
||||
end
|
||||
onebox = Oneboxer.cached_onebox(url)
|
||||
@found_oneboxes = true if onebox.present?
|
||||
onebox
|
||||
|
||||
@ -23,12 +23,13 @@ class PublishedPage < ActiveRecord::Base
|
||||
"#{Discourse.base_url}#{path}"
|
||||
end
|
||||
|
||||
def self.publish!(publisher, topic, slug)
|
||||
def self.publish!(publisher, topic, slug, options = {})
|
||||
pp = nil
|
||||
|
||||
transaction do
|
||||
pp = find_or_initialize_by(topic: topic)
|
||||
pp.slug = slug.strip
|
||||
pp.public = options[:public] || false
|
||||
|
||||
if pp.save
|
||||
StaffActionLogger.new(publisher).log_published_page(topic.id, slug)
|
||||
@ -56,6 +57,7 @@ end
|
||||
# slug :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# public :boolean default(FALSE), not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
||||
@ -155,7 +155,12 @@ class Topic < ActiveRecord::Base
|
||||
message: :has_already_been_used,
|
||||
allow_blank: true,
|
||||
case_sensitive: false,
|
||||
collection: Proc.new { Topic.listable_topics } }
|
||||
collection: Proc.new { |t|
|
||||
SiteSetting.allow_duplicate_topic_titles_category? ?
|
||||
Topic.listable_topics.where("category_id = ?", t.category_id) :
|
||||
Topic.listable_topics
|
||||
}
|
||||
}
|
||||
|
||||
validates :category_id,
|
||||
presence: true,
|
||||
|
||||
@ -67,7 +67,7 @@ class TopicList
|
||||
|
||||
def preload_key
|
||||
if @category
|
||||
"topic_list_#{@category.url.sub(/^\//, '')}/#{@category.id}/l/#{@filter}"
|
||||
"topic_list_#{@category.url.sub(/^\//, '')}/l/#{@filter}"
|
||||
else
|
||||
"topic_list_#{@filter}"
|
||||
end
|
||||
|
||||
@ -24,18 +24,35 @@ class TopicTrackingState
|
||||
def self.publish_new(topic)
|
||||
return unless topic.regular?
|
||||
|
||||
tags, tag_ids = nil
|
||||
if SiteSetting.tagging_enabled
|
||||
topic.tags.pluck(:id, :name).each do |id, name|
|
||||
tags ||= []
|
||||
tag_ids ||= []
|
||||
|
||||
tags << name
|
||||
tag_ids << id
|
||||
end
|
||||
end
|
||||
|
||||
payload = {
|
||||
last_read_post_number: nil,
|
||||
highest_post_number: 1,
|
||||
created_at: topic.created_at,
|
||||
topic_id: topic.id,
|
||||
category_id: topic.category_id,
|
||||
archetype: topic.archetype,
|
||||
}
|
||||
|
||||
if tags
|
||||
payload[:tags] = tags
|
||||
payload[:topic_tag_ids] = tag_ids
|
||||
end
|
||||
|
||||
message = {
|
||||
topic_id: topic.id,
|
||||
message_type: "new_topic",
|
||||
payload: {
|
||||
last_read_post_number: nil,
|
||||
highest_post_number: 1,
|
||||
created_at: topic.created_at,
|
||||
topic_id: topic.id,
|
||||
category_id: topic.category_id,
|
||||
archetype: topic.archetype,
|
||||
topic_tag_ids: topic.tags.pluck(:id)
|
||||
}
|
||||
payload: payload
|
||||
}
|
||||
|
||||
group_ids = topic.category && topic.category.secure_group_ids
|
||||
@ -99,22 +116,31 @@ class TopicTrackingState
|
||||
post.topic.category && post.topic.category.secure_group_ids
|
||||
end
|
||||
|
||||
tags = nil
|
||||
if include_tags_in_report?
|
||||
tags = post.topic.tags.pluck(:name)
|
||||
end
|
||||
|
||||
TopicUser
|
||||
.tracking(post.topic_id)
|
||||
.select([:user_id, :last_read_post_number, :notification_level])
|
||||
.each do |tu|
|
||||
|
||||
payload = {
|
||||
last_read_post_number: tu.last_read_post_number,
|
||||
highest_post_number: post.post_number,
|
||||
created_at: post.created_at,
|
||||
category_id: post.topic.category_id,
|
||||
notification_level: tu.notification_level,
|
||||
archetype: post.topic.archetype
|
||||
}
|
||||
|
||||
payload[:tags] = tags if tags
|
||||
|
||||
message = {
|
||||
topic_id: post.topic_id,
|
||||
message_type: UNREAD_MESSAGE_TYPE,
|
||||
payload: {
|
||||
last_read_post_number: tu.last_read_post_number,
|
||||
highest_post_number: post.post_number,
|
||||
created_at: post.created_at,
|
||||
category_id: post.topic.category_id,
|
||||
notification_level: tu.notification_level,
|
||||
archetype: post.topic.archetype
|
||||
}
|
||||
payload: payload
|
||||
}
|
||||
|
||||
MessageBus.publish(self.unread_channel_key(tu.user_id), message.as_json, group_ids: group_ids)
|
||||
@ -186,7 +212,7 @@ class TopicTrackingState
|
||||
end
|
||||
|
||||
def self.include_tags_in_report?
|
||||
@include_tags_in_report
|
||||
SiteSetting.tagging_enabled && (@include_tags_in_report || SiteSetting.show_filter_by_tag)
|
||||
end
|
||||
|
||||
def self.include_tags_in_report=(v)
|
||||
|
||||
@ -329,7 +329,7 @@ class Upload < ActiveRecord::Base
|
||||
follow_redirect: true
|
||||
)
|
||||
rescue OpenURI::HTTPError
|
||||
retry if (retires += 1) < 1
|
||||
retry if (retries += 1) < 1
|
||||
next
|
||||
end
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user