diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index e848bdcf4c..4eb8ad5f07 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -11,24 +11,12 @@ dafd3c3b47f116c6c1dc56cb18df614c11747733
# Rename many `.js.es6` files to `.js`
032205e2029cbf82dc8f05b459fb93adf2503c60
-# Rename admin app es6 -> js
-181758e3248b14ad8b53abe063da8dc6a82d3089
-
# Rename pretty-text from es6 -> js
c15056650647e8650288f973d9038500dc9cf7bb
-# Rename select kit from es6 -> js
-acc5cbdf8ecb9293a0fa9474ee73baf499c02428
-
# Rename wizard from es6 -> js
1ac02422011f89716ab27250d39b0e0212e03892
-# Rename some root files
-11938d58d4b1bea1ff43306450da7b24f05db0a
-
-# The last remaining ES6
-aabeb17aab4e73de5ef56753ab22ef5d416d2932
-
# DEV: enforces block-indentation of ember-template-lint rules
b66b277dc44bcd2122dc21965dab209c30636214
diff --git a/Gemfile b/Gemfile
index a80f23dbc0..da983fa8c7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,13 +18,13 @@ else
# this allows us to include the bits of rails we use without pieces we do not.
#
# To issue a rails update bump the version number here
- gem 'actionmailer', '6.0.3'
- gem 'actionpack', '6.0.3'
- gem 'actionview', '6.0.3'
- gem 'activemodel', '6.0.3'
- gem 'activerecord', '6.0.3'
- gem 'activesupport', '6.0.3'
- gem 'railties', '6.0.3'
+ gem 'actionmailer', '6.0.3.1'
+ gem 'actionpack', '6.0.3.1'
+ gem 'actionview', '6.0.3.1'
+ gem 'activemodel', '6.0.3.1'
+ gem 'activerecord', '6.0.3.1'
+ gem 'activesupport', '6.0.3.1'
+ gem 'railties', '6.0.3.1'
gem 'sprockets-rails'
end
@@ -249,4 +249,4 @@ gem 'webpush', require: false
gem 'colored2', require: false
gem 'maxminddb'
-gem 'rails_failover', require: false
+gem 'rails_failover', require: false, git: 'https://github.com/discourse/rails_failover'
diff --git a/Gemfile.lock b/Gemfile.lock
index 26cce0e8f2..6bf999c7a3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,21 +1,30 @@
+GIT
+ remote: https://github.com/discourse/rails_failover
+ revision: 0e668eba86591c20aa7a43f47b0cd36d4eacaeb6
+ specs:
+ rails_failover (0.4.0)
+ activerecord (~> 6.0)
+ listen (~> 3.2)
+ railties (~> 6.0)
+
GEM
remote: https://rubygems.org/
specs:
- actionmailer (6.0.3)
- actionpack (= 6.0.3)
- actionview (= 6.0.3)
- activejob (= 6.0.3)
+ actionmailer (6.0.3.1)
+ actionpack (= 6.0.3.1)
+ actionview (= 6.0.3.1)
+ activejob (= 6.0.3.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (6.0.3)
- actionview (= 6.0.3)
- activesupport (= 6.0.3)
+ actionpack (6.0.3.1)
+ actionview (= 6.0.3.1)
+ activesupport (= 6.0.3.1)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actionview (6.0.3)
- activesupport (= 6.0.3)
+ actionview (6.0.3.1)
+ activesupport (= 6.0.3.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -24,15 +33,15 @@ GEM
actionview (>= 6.0.a)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
- activejob (6.0.3)
- activesupport (= 6.0.3)
+ activejob (6.0.3.1)
+ activesupport (= 6.0.3.1)
globalid (>= 0.3.6)
- activemodel (6.0.3)
- activesupport (= 6.0.3)
- activerecord (6.0.3)
- activemodel (= 6.0.3)
- activesupport (= 6.0.3)
- activesupport (6.0.3)
+ activemodel (6.0.3.1)
+ activesupport (= 6.0.3.1)
+ activerecord (6.0.3.1)
+ activemodel (= 6.0.3.1)
+ activesupport (= 6.0.3.1)
+ activesupport (6.0.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -45,20 +54,20 @@ GEM
rake (>= 10.4, < 14.0)
ast (2.4.0)
aws-eventstream (1.1.0)
- aws-partitions (1.298.0)
- aws-sdk-core (3.94.0)
+ aws-partitions (1.322.0)
+ aws-sdk-core (3.96.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.30.0)
+ aws-sdk-kms (1.31.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.62.0)
- aws-sdk-core (~> 3, >= 3.83.0)
+ aws-sdk-s3 (1.66.0)
+ aws-sdk-core (~> 3, >= 3.96.1)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sdk-sns (1.22.0)
+ aws-sdk-sns (1.23.0)
aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.1.3)
@@ -82,7 +91,7 @@ GEM
cbor (0.5.9.6)
certified (1.0.0)
chunky_png (1.3.11)
- coderay (1.1.2)
+ coderay (1.1.3)
colored2 (3.1.2)
concurrent-ruby (1.1.6)
connection_pool (2.2.2)
@@ -121,7 +130,7 @@ GEM
railties (>= 3.1)
ember-source (2.18.2)
erubi (1.9.0)
- excon (0.72.0)
+ excon (0.73.0)
execjs (2.7.0)
exifr (1.3.6)
fabrication (2.21.1)
@@ -134,7 +143,7 @@ GEM
rake-compiler
fast_xs (0.8.0)
fastimage (2.1.7)
- ffi (1.12.2)
+ ffi (1.13.0)
flamegraph (0.9.5)
fspath (3.1.2)
gc_tracer (1.5.1)
@@ -183,7 +192,7 @@ GEM
mini_mime (>= 0.1.1)
maxminddb (0.1.22)
memory_profiler (0.9.14)
- message_bus (3.2.0)
+ message_bus (3.3.0)
rack (>= 1.1.3)
method_source (1.0.0)
mini_mime (1.0.2)
@@ -240,7 +249,7 @@ GEM
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
- onebox (1.9.28.1)
+ onebox (1.9.28.3)
addressable (~> 2.7.0)
htmlentities (~> 4.3)
multi_json (~> 1.11)
@@ -252,7 +261,7 @@ GEM
parallel (1.19.1)
parallel_tests (2.32.0)
parallel
- parser (2.7.1.2)
+ parser (2.7.1.3)
ast (~> 2.4.0)
pg (1.2.3)
progress (3.5.2)
@@ -277,14 +286,12 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
- rails_failover (0.2.0)
- redis (~> 4)
rails_multisite (2.1.2)
activerecord (> 5.0, < 7)
railties (> 5.0, < 7)
- railties (6.0.3)
- actionpack (= 6.0.3)
- activesupport (= 6.0.3)
+ railties (6.0.3.1)
+ actionpack (= 6.0.3.1)
+ activesupport (= 6.0.3.1)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@@ -431,14 +438,14 @@ PLATFORMS
ruby
DEPENDENCIES
- actionmailer (= 6.0.3)
- actionpack (= 6.0.3)
- actionview (= 6.0.3)
+ actionmailer (= 6.0.3.1)
+ actionpack (= 6.0.3.1)
+ actionview (= 6.0.3.1)
actionview_precompiler
active_model_serializers (~> 0.8.3)
- activemodel (= 6.0.3)
- activerecord (= 6.0.3)
- activesupport (= 6.0.3)
+ activemodel (= 6.0.3.1)
+ activerecord (= 6.0.3.1)
+ activesupport (= 6.0.3.1)
addressable
annotate
aws-sdk-s3
@@ -514,9 +521,9 @@ DEPENDENCIES
rack (= 2.2.2)
rack-mini-profiler
rack-protection
- rails_failover
+ rails_failover!
rails_multisite
- railties (= 6.0.3)
+ railties (= 6.0.3.1)
rake
rb-fsevent
rb-inotify (~> 0.9)
diff --git a/app/assets/javascripts/admin/models/backup.js b/app/assets/javascripts/admin/models/backup.js
index 2c41e1ef9d..45727a28b2 100644
--- a/app/assets/javascripts/admin/models/backup.js
+++ b/app/assets/javascripts/admin/models/backup.js
@@ -2,6 +2,7 @@ import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import { extractError } from "discourse/lib/ajax-error";
import EmberObject from "@ember/object";
+import MessageBus from "message-bus-client";
const Backup = EmberObject.extend({
destroy() {
@@ -11,7 +12,7 @@ const Backup = EmberObject.extend({
restore() {
return ajax("/admin/backups/" + this.filename + "/restore", {
type: "POST",
- data: { client_id: window.MessageBus.clientId }
+ data: { client_id: MessageBus.clientId }
});
}
});
@@ -38,7 +39,7 @@ Backup.reopenClass({
type: "POST",
data: {
with_uploads: withUploads,
- client_id: window.MessageBus.clientId
+ client_id: MessageBus.clientId
}
}).then(result => {
if (!result.success) {
diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs
index 1353ed0d78..df9a6e196f 100644
--- a/app/assets/javascripts/admin/templates/user-index.hbs
+++ b/app/assets/javascripts/admin/templates/user-index.hbs
@@ -517,6 +517,7 @@
{{admin-group-selector
content=availableGroups
value=customGroupIdsBuffer
+ labelProperty="name"
onChange=(action (mut customGroupIdsBuffer))
}}
diff --git a/app/assets/javascripts/discourse-common/addon/config/environment.js b/app/assets/javascripts/discourse-common/addon/config/environment.js
index 18ce3cc976..8d684bf5a4 100644
--- a/app/assets/javascripts/discourse-common/addon/config/environment.js
+++ b/app/assets/javascripts/discourse-common/addon/config/environment.js
@@ -1,3 +1,9 @@
export const INPUT_DELAY = 250;
-export default { environment: Ember.testing ? "test" : "development" };
+let environment = Ember.testing ? "test" : "development";
+
+export function isTesting() {
+ return environment === "test";
+}
+
+export default { environment };
diff --git a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
index decc668180..ef60571cec 100644
--- a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
+++ b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js
@@ -1,8 +1,6 @@
import I18n from "I18n";
import { h } from "virtual-dom";
import attributeHook from "discourse-common/lib/attribute-hook";
-import deprecated from "discourse-common/lib/deprecated";
-import jQuery from "jquery";
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
let _renderers = [];
@@ -43,474 +41,6 @@ const REPLACEMENTS = {
"notification.membership_request_consolidated": "users"
};
-// TODO: use lib/svg_sprite/fa4-renames.json here
-// Note: these should not be edited manually. They define the fa4-fa5 migration
-const fa4Replacements = {
- "500px": "fab-500px",
- "address-book-o": "far-address-book",
- "address-card-o": "far-address-card",
- adn: "fab-adn",
- amazon: "fab-amazon",
- android: "fab-android",
- angellist: "fab-angellist",
- apple: "fab-apple",
- "area-chart": "chart-area",
- "arrow-circle-o-down": "far-arrow-alt-circle-down",
- "arrow-circle-o-left": "far-arrow-alt-circle-left",
- "arrow-circle-o-right": "far-arrow-alt-circle-right",
- "arrow-circle-o-up": "far-arrow-alt-circle-up",
- arrows: "arrows-alt",
- "arrows-alt": "expand-arrows-alt",
- "arrows-h": "arrows-alt-h",
- "arrows-v": "arrows-alt-v",
- "asl-interpreting": "american-sign-language-interpreting",
- automobile: "car",
- bandcamp: "fab-bandcamp",
- bank: "university",
- "bar-chart": "far-chart-bar",
- "bar-chart-o": "far-chart-bar",
- bathtub: "bath",
- battery: "battery-full",
- "battery-0": "battery-empty",
- "battery-1": "battery-quarter",
- "battery-2": "battery-half",
- "battery-3": "battery-three-quarters",
- "battery-4": "battery-full",
- behance: "fab-behance",
- "behance-square": "fab-behance-square",
- "bell-o": "far-bell",
- "bell-slash-o": "far-bell-slash",
- bitbucket: "fab-bitbucket",
- "bitbucket-square": "fab-bitbucket",
- bitcoin: "fab-btc",
- "black-tie": "fab-black-tie",
- bluetooth: "fab-bluetooth",
- "bluetooth-b": "fab-bluetooth-b",
- "bookmark-o": "far-bookmark",
- btc: "fab-btc",
- "building-o": "far-building",
- buysellads: "fab-buysellads",
- cab: "taxi",
- calendar: "calendar-alt",
- "calendar-check-o": "far-calendar-check",
- "calendar-minus-o": "far-calendar-minus",
- "calendar-o": "far-calendar",
- "calendar-plus-o": "far-calendar-plus",
- "calendar-times-o": "far-calendar-times",
- "caret-square-o-down": "far-caret-square-down",
- "caret-square-o-left": "far-caret-square-left",
- "caret-square-o-right": "far-caret-square-right",
- "caret-square-o-up": "far-caret-square-up",
- cc: "far-closed-captioning",
- "cc-amex": "fab-cc-amex",
- "cc-diners-club": "fab-cc-diners-club",
- "cc-discover": "fab-cc-discover",
- "cc-jcb": "fab-cc-jcb",
- "cc-mastercard": "fab-cc-mastercard",
- "cc-paypal": "fab-cc-paypal",
- "cc-stripe": "fab-cc-stripe",
- "cc-visa": "fab-cc-visa",
- chain: "link",
- "chain-broken": "unlink",
- "check-circle-o": "far-check-circle",
- "check-square-o": "far-check-square",
- chrome: "fab-chrome",
- "circle-o": "far-circle",
- "circle-o-notch": "circle-notch",
- "circle-thin": "far-circle",
- clipboard: "far-clipboard",
- "clock-o": "far-clock",
- clone: "far-clone",
- close: "times",
- "cloud-download": "cloud-download-alt",
- "cloud-upload": "cloud-upload-alt",
- cny: "yen-sign",
- "code-fork": "code-branch",
- codepen: "fab-codepen",
- codiepie: "fab-codiepie",
- "comment-o": "far-comment",
- commenting: "far-comment-dots",
- "commenting-o": "far-comment-dots",
- "comments-o": "far-comments",
- compass: "far-compass",
- connectdevelop: "fab-connectdevelop",
- contao: "fab-contao",
- copyright: "far-copyright",
- "creative-commons": "fab-creative-commons",
- "credit-card": "far-credit-card",
- "credit-card-alt": "credit-card",
- css3: "fab-css3",
- cutlery: "utensils",
- dashboard: "tachometer-alt",
- dashcube: "fab-dashcube",
- deafness: "deaf",
- dedent: "outdent",
- delicious: "fab-delicious",
- deviantart: "fab-deviantart",
- diamond: "far-gem",
- digg: "fab-digg",
- discord: "fab-discord",
- dollar: "dollar-sign",
- "dot-circle-o": "far-dot-circle",
- dribbble: "fab-dribbble",
- "drivers-license": "id-card",
- "drivers-license-o": "far-id-card",
- dropbox: "fab-dropbox",
- drupal: "fab-drupal",
- edge: "fab-edge",
- eercast: "fab-sellcast",
- empire: "fab-empire",
- "envelope-o": "far-envelope",
- "envelope-open-o": "far-envelope-open",
- envira: "fab-envira",
- etsy: "fab-etsy",
- eur: "euro-sign",
- euro: "euro-sign",
- exchange: "exchange-alt",
- expeditedssl: "fab-expeditedssl",
- "external-link": "external-link-alt",
- "external-link-square": "external-link-square-alt",
- eye: "far-eye",
- "eye-slash": "far-eye-slash",
- eyedropper: "eye-dropper",
- fa: "fab-font-awesome",
- facebook: "fab-facebook-f",
- "facebook-f": "fab-facebook-f",
- "facebook-official": "fab-facebook",
- "facebook-square": "fab-facebook-square",
- feed: "rss",
- "file-archive-o": "far-file-archive",
- "file-audio-o": "far-file-audio",
- "file-code-o": "far-file-code",
- "file-excel-o": "far-file-excel",
- "file-image-o": "far-file-image",
- "file-movie-o": "far-file-video",
- "file-o": "far-file",
- "file-pdf-o": "far-file-pdf",
- "file-photo-o": "far-file-image",
- "file-picture-o": "far-file-image",
- "file-powerpoint-o": "far-file-powerpoint",
- "file-sound-o": "far-file-audio",
- "file-text": "file-alt",
- "file-text-o": "far-file-alt",
- "file-video-o": "far-file-video",
- "file-word-o": "far-file-word",
- "file-zip-o": "far-file-archive",
- "files-o": "far-copy",
- firefox: "fab-firefox",
- "first-order": "fab-first-order",
- "flag-o": "far-flag",
- flash: "bolt",
- flickr: "fab-flickr",
- "floppy-o": "far-save",
- "folder-o": "far-folder",
- "folder-open-o": "far-folder-open",
- "font-awesome": "fab-font-awesome",
- fonticons: "fab-fonticons",
- "fort-awesome": "fab-fort-awesome",
- forumbee: "fab-forumbee",
- foursquare: "fab-foursquare",
- "free-code-camp": "fab-free-code-camp",
- "frown-o": "far-frown",
- "futbol-o": "far-futbol",
- gbp: "pound-sign",
- ge: "fab-empire",
- gear: "cog",
- gears: "cogs",
- "get-pocket": "fab-get-pocket",
- gg: "fab-gg",
- "gg-circle": "fab-gg-circle",
- git: "fab-git",
- "git-square": "fab-git-square",
- github: "fab-github",
- "github-alt": "fab-github-alt",
- "github-square": "fab-github-square",
- gitlab: "fab-gitlab",
- gittip: "fab-gratipay",
- glass: "glass-martini",
- glide: "fab-glide",
- "glide-g": "fab-glide-g",
- google: "fab-google",
- "google-plus": "fab-google-plus-g",
- "google-plus-circle": "fab-google-plus",
- "google-plus-official": "fab-google-plus",
- "google-plus-square": "fab-google-plus-square",
- "google-wallet": "fab-google-wallet",
- gratipay: "fab-gratipay",
- grav: "fab-grav",
- group: "users",
- "hacker-news": "fab-hacker-news",
- "hand-grab-o": "far-hand-rock",
- "hand-lizard-o": "far-hand-lizard",
- "hand-o-down": "far-hand-point-down",
- "hand-o-left": "far-hand-point-left",
- "hand-o-right": "far-hand-point-right",
- "hand-o-up": "far-hand-point-up",
- "hand-paper-o": "far-hand-paper",
- "hand-peace-o": "far-hand-peace",
- "hand-pointer-o": "far-hand-pointer",
- "hand-rock-o": "far-hand-rock",
- "hand-scissors-o": "far-hand-scissors",
- "hand-spock-o": "far-hand-spock",
- "hand-stop-o": "far-hand-paper",
- "handshake-o": "far-handshake",
- "hard-of-hearing": "deaf",
- "hdd-o": "far-hdd",
- header: "heading",
- "heart-o": "far-heart",
- "hospital-o": "far-hospital",
- hotel: "bed",
- "hourglass-1": "hourglass-start",
- "hourglass-2": "hourglass-half",
- "hourglass-3": "hourglass-end",
- "hourglass-o": "far-hourglass",
- houzz: "fab-houzz",
- html5: "fab-html5",
- "id-card-o": "far-id-card",
- ils: "shekel-sign",
- image: "far-image",
- imdb: "fab-imdb",
- inr: "rupee-sign",
- instagram: "fab-instagram",
- institution: "university",
- "internet-explorer": "fab-internet-explorer",
- intersex: "transgender",
- ioxhost: "fab-ioxhost",
- joomla: "fab-joomla",
- jpy: "yen-sign",
- jsfiddle: "fab-jsfiddle",
- "keyboard-o": "far-keyboard",
- krw: "won-sign",
- lastfm: "fab-lastfm",
- "lastfm-square": "fab-lastfm-square",
- leanpub: "fab-leanpub",
- legal: "gavel",
- "lemon-o": "far-lemon",
- "level-down": "level-down-alt",
- "level-up": "level-up-alt",
- "life-bouy": "far-life-ring",
- "life-buoy": "far-life-ring",
- "life-ring": "far-life-ring",
- "life-saver": "far-life-ring",
- "lightbulb-o": "far-lightbulb",
- "line-chart": "chart-line",
- linkedin: "fab-linkedin-in",
- "linkedin-square": "fab-linkedin",
- linode: "fab-linode",
- linux: "fab-linux",
- "list-alt": "far-list-alt",
- "long-arrow-down": "long-arrow-alt-down",
- "long-arrow-left": "long-arrow-alt-left",
- "long-arrow-right": "long-arrow-alt-right",
- "long-arrow-up": "long-arrow-alt-up",
- "mail-forward": "share",
- "mail-reply": "reply",
- "mail-reply-all": "reply-all",
- "map-marker": "map-marker-alt",
- "map-o": "far-map",
- maxcdn: "fab-maxcdn",
- meanpath: "fab-font-awesome",
- medium: "fab-medium",
- meetup: "fab-meetup",
- "meh-o": "far-meh",
- "minus-square-o": "far-minus-square",
- mixcloud: "fab-mixcloud",
- mobile: "mobile-alt",
- "mobile-phone": "mobile-alt",
- modx: "fab-modx",
- money: "far-money-bill-alt",
- "moon-o": "far-moon",
- "mortar-board": "graduation-cap",
- navicon: "bars",
- "newspaper-o": "far-newspaper",
- "object-group": "far-object-group",
- "object-ungroup": "far-object-ungroup",
- odnoklassniki: "fab-odnoklassniki",
- "odnoklassniki-square": "fab-odnoklassniki-square",
- opencart: "fab-opencart",
- openid: "fab-openid",
- opera: "fab-opera",
- "optin-monster": "fab-optin-monster",
- pagelines: "fab-pagelines",
- "paper-plane-o": "far-paper-plane",
- paste: "far-clipboard",
- patreon: "fab-patreon",
- "pause-circle-o": "far-pause-circle",
- paypal: "fab-paypal",
- pencil: "pencil-alt",
- "pencil-square": "pen-square",
- "pencil-square-o": "far-edit",
- photo: "far-image",
- "picture-o": "far-image",
- "pie-chart": "chart-pie",
- "pied-piper": "fab-pied-piper",
- "pied-piper-alt": "fab-pied-piper-alt",
- "pied-piper-pp": "fab-pied-piper-pp",
- pinterest: "fab-pinterest",
- "pinterest-p": "fab-pinterest-p",
- "pinterest-square": "fab-pinterest-square",
- "play-circle-o": "far-play-circle",
- "plus-square-o": "far-plus-square",
- "product-hunt": "fab-product-hunt",
- qq: "fab-qq",
- "question-circle-o": "far-question-circle",
- quora: "fab-quora",
- ra: "fab-rebel",
- ravelry: "fab-ravelry",
- rebel: "fab-rebel",
- reddit: "fab-reddit",
- "reddit-alien": "fab-reddit-alien",
- "reddit-square": "fab-reddit-square",
- refresh: "sync",
- registered: "far-registered",
- remove: "times",
- renren: "fab-renren",
- reorder: "bars",
- repeat: "redo",
- resistance: "fab-rebel",
- rmb: "yen-sign",
- "rotate-left": "undo",
- "rotate-right": "redo",
- rouble: "ruble-sign",
- rub: "ruble-sign",
- ruble: "ruble-sign",
- rupee: "rupee-sign",
- s15: "bath",
- safari: "fab-safari",
- scissors: "cut",
- scribd: "fab-scribd",
- sellsy: "fab-sellsy",
- send: "paper-plane",
- "send-o": "far-paper-plane",
- "share-square-o": "far-share-square",
- shekel: "shekel-sign",
- sheqel: "shekel-sign",
- shield: "shield-alt",
- shirtsinbulk: "fab-shirtsinbulk",
- "sign-in": "sign-in-alt",
- "sign-out": "sign-out-alt",
- signing: "sign-language",
- simplybuilt: "fab-simplybuilt",
- skyatlas: "fab-skyatlas",
- skype: "fab-skype",
- slack: "fab-slack",
- sliders: "sliders-h",
- slideshare: "fab-slideshare",
- "smile-o": "far-smile",
- snapchat: "fab-snapchat",
- "snapchat-ghost": "fab-snapchat-ghost",
- "snapchat-square": "fab-snapchat-square",
- "snowflake-o": "far-snowflake",
- "soccer-ball-o": "far-futbol",
- "sort-alpha-asc": "sort-alpha-down",
- "sort-alpha-desc": "sort-alpha-up",
- "sort-amount-asc": "sort-amount-down",
- "sort-amount-desc": "sort-amount-up",
- "sort-asc": "sort-up",
- "sort-desc": "sort-down",
- "sort-numeric-asc": "sort-numeric-down",
- "sort-numeric-desc": "sort-numeric-up",
- soundcloud: "fab-soundcloud",
- spoon: "utensil-spoon",
- spotify: "fab-spotify",
- "square-o": "far-square",
- "stack-exchange": "fab-stack-exchange",
- "stack-overflow": "fab-stack-overflow",
- "star-half-empty": "far-star-half",
- "star-half-full": "far-star-half",
- "star-half-o": "far-star-half",
- "star-o": "far-star",
- steam: "fab-steam",
- "steam-square": "fab-steam-square",
- "sticky-note-o": "far-sticky-note",
- "stop-circle-o": "far-stop-circle",
- stumbleupon: "fab-stumbleupon",
- "stumbleupon-circle": "fab-stumbleupon-circle",
- "sun-o": "far-sun",
- superpowers: "fab-superpowers",
- support: "far-life-ring",
- tablet: "tablet-alt",
- tachometer: "tachometer-alt",
- telegram: "fab-telegram",
- television: "tv",
- "tencent-weibo": "fab-tencent-weibo",
- themeisle: "fab-themeisle",
- thermometer: "thermometer-full",
- "thermometer-0": "thermometer-empty",
- "thermometer-1": "thermometer-quarter",
- "thermometer-2": "thermometer-half",
- "thermometer-3": "thermometer-three-quarters",
- "thermometer-4": "thermometer-full",
- "thumb-tack": "thumbtack",
- "thumbs-o-down": "far-thumbs-down",
- "thumbs-o-up": "far-thumbs-up",
- ticket: "ticket-alt",
- "times-circle-o": "far-times-circle",
- "times-rectangle": "window-close",
- "times-rectangle-o": "far-window-close",
- "toggle-down": "far-caret-square-down",
- "toggle-left": "far-caret-square-left",
- "toggle-right": "far-caret-square-right",
- "toggle-up": "far-caret-square-up",
- trash: "trash-alt",
- "trash-o": "far-trash-alt",
- trello: "fab-trello",
- tripadvisor: "fab-tripadvisor",
- try: "lira-sign",
- tumblr: "fab-tumblr",
- "tumblr-square": "fab-tumblr-square",
- "turkish-lira": "lira-sign",
- twitch: "fab-twitch",
- twitter: "fab-twitter",
- "twitter-square": "fab-twitter-square",
- unsorted: "sort",
- usb: "fab-usb",
- usd: "dollar-sign",
- "user-circle-o": "far-user-circle",
- "user-o": "far-user",
- vcard: "address-card",
- "vcard-o": "far-address-card",
- viacoin: "fab-viacoin",
- viadeo: "fab-viadeo",
- "viadeo-square": "fab-viadeo-square",
- "video-camera": "video",
- vimeo: "fab-vimeo-v",
- "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",
- weibo: "fab-weibo",
- weixin: "fab-weixin",
- whatsapp: "fab-whatsapp",
- "wheelchair-alt": "fab-accessible-icon",
- "wikipedia-w": "fab-wikipedia-w",
- "window-close-o": "far-window-close",
- "window-maximize": "far-window-maximize",
- "window-restore": "far-window-restore",
- windows: "fab-windows",
- won: "won-sign",
- wordpress: "fab-wordpress",
- wpbeginner: "fab-wpbeginner",
- wpexplorer: "fab-wpexplorer",
- wpforms: "fab-wpforms",
- xing: "fab-xing",
- "xing-square": "fab-xing-square",
- "y-combinator": "fab-y-combinator",
- "y-combinator-square": "fab-hacker-news",
- yahoo: "fab-yahoo",
- yc: "fab-y-combinator",
- "yc-square": "fab-hacker-news",
- yelp: "fab-yelp",
- yen: "yen-sign",
- yoast: "fab-yoast",
- youtube: "fab-youtube",
- "youtube-play": "fab-youtube",
- "youtube-square": "fab-youtube-square"
-};
-
export function replaceIcon(source, destination) {
REPLACEMENTS[source] = destination;
}
@@ -584,36 +114,9 @@ function warnIfMissing(id) {
}
}
-const reportedIcons = [];
-
-function warnIfDeprecated(oldId, newId) {
- deprecated(
- `Please replace all occurrences of "${oldId}"" with "${newId}". FontAwesome 4.7 icon names are now deprecated and will be removed in the next release.`
- );
- if (!Discourse.testing && !reportedIcons.includes(oldId)) {
- const errorData = {
- message: `FA icon deprecation: replace "${oldId}"" with "${newId}".`,
- stacktrace: Error().stack
- };
-
- jQuery.ajax(`${Discourse.BaseUri}/logs/report_js_error`, {
- data: errorData,
- type: "POST",
- cache: false
- });
-
- reportedIcons.push(oldId);
- }
-}
-
function handleIconId(icon) {
let id = icon.replacementId || icon.id || "";
- if (fa4Replacements.hasOwnProperty(id)) {
- warnIfDeprecated(id, fa4Replacements[id]);
- id = fa4Replacements[id];
- }
-
// TODO: clean up "thumbtack unpinned" at source instead of here
id = id.replace(" unpinned", "");
diff --git a/app/assets/javascripts/discourse-loader.js b/app/assets/javascripts/discourse-loader.js
index fc829ae12f..f6a28c01cb 100644
--- a/app/assets/javascripts/discourse-loader.js
+++ b/app/assets/javascripts/discourse-loader.js
@@ -1,7 +1,7 @@
var define, requirejs;
(function() {
- var EMBER_MODULES = {};
+ var JS_MODULES = {};
var ALIASES = {
"ember-addons/ember-computed-decorators":
"discourse-common/utils/decorators",
@@ -11,7 +11,7 @@ var define, requirejs;
// In future versions of ember we don't need this
if (typeof Ember !== "undefined") {
- EMBER_MODULES = {
+ JS_MODULES = {
jquery: { default: $ },
"@ember/array": {
default: Ember.Array,
@@ -285,7 +285,7 @@ var define, requirejs;
name = "@ember/object";
}
- var mod = EMBER_MODULES[name] || registry[name];
+ var mod = JS_MODULES[name] || registry[name];
if (!mod) {
throw new Error(
"Could not find module `" + name + "` imported from `" + origin + "`"
@@ -308,8 +308,8 @@ var define, requirejs;
requirejs = require = function(name) {
name = transformForAliases(name);
- if (EMBER_MODULES[name]) {
- return EMBER_MODULES[name];
+ if (JS_MODULES[name]) {
+ return JS_MODULES[name];
}
var mod = registry[name];
diff --git a/app/assets/javascripts/ember-shim.js b/app/assets/javascripts/discourse-shims.js
similarity index 76%
rename from app/assets/javascripts/ember-shim.js
rename to app/assets/javascripts/discourse-shims.js
index 1837ae1b98..f790ed9b05 100644
--- a/app/assets/javascripts/ember-shim.js
+++ b/app/assets/javascripts/discourse-shims.js
@@ -12,3 +12,7 @@ define("ember", ["exports"], function(__exports__) {
__exports__.default = Ember;
});
+
+define("message-bus-client", ["exports"], function(__exports__) {
+ __exports__.default = window.MessageBus;
+});
diff --git a/app/assets/javascripts/discourse/app/components/add-category-tag-classes.js b/app/assets/javascripts/discourse/app/components/add-category-tag-classes.js
index 50d93f3a95..54421ead69 100644
--- a/app/assets/javascripts/discourse/app/components/add-category-tag-classes.js
+++ b/app/assets/javascripts/discourse/app/components/add-category-tag-classes.js
@@ -20,7 +20,10 @@ export default Component.extend({
this._removeClass();
let classes = [];
- if (slug) classes.push(`category-${slug}`);
+ if (slug) {
+ classes.push("category");
+ classes.push(`category-${slug}`);
+ }
if (tags) tags.forEach(t => classes.push(`tag-${t}`));
if (classes.length > 0) $("body").addClass(classes.join(" "));
},
@@ -32,7 +35,7 @@ export default Component.extend({
_removeClass() {
$("body").removeClass((_, css) =>
- (css.match(/\b(?:category|tag)-\S+/g) || []).join(" ")
+ (css.match(/\b(?:category|tag)-\S+|( category )/g) || []).join(" ")
);
},
diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js
index bfdf021bff..184b7f93a6 100644
--- a/app/assets/javascripts/discourse/app/components/d-editor.js
+++ b/app/assets/javascripts/discourse/app/components/d-editor.js
@@ -483,8 +483,15 @@ export default Component.extend({
}
}
- if (translations[full]) {
- return resolve([translations[full]]);
+ // note this will only work for emojis starting with :
+ // eg: :-)
+ const allTranslations = Object.assign(
+ {},
+ translations,
+ this.getWithDefault("site.custom_emoji_translation", {})
+ );
+ if (allTranslations[full]) {
+ return resolve([allTranslations[full]]);
}
const match = term.match(/^:?(.*?):t([2-6])?$/);
diff --git a/app/assets/javascripts/discourse/app/components/edit-category-settings.js b/app/assets/javascripts/discourse/app/components/edit-category-settings.js
index c811ce2db6..5844e45d0c 100644
--- a/app/assets/javascripts/discourse/app/components/edit-category-settings.js
+++ b/app/assets/javascripts/discourse/app/components/edit-category-settings.js
@@ -68,13 +68,6 @@ export default buildCategoryPanel("settings", {
);
},
- @discourseComputed
- availableListFilters() {
- return ["all", "none"].map(p => {
- return { name: I18n.t(`category.list_filters.${p}`), value: p };
- });
- },
-
@discourseComputed
searchPrioritiesOptions() {
const options = [];
diff --git a/app/assets/javascripts/discourse/app/components/group-card-contents.js b/app/assets/javascripts/discourse/app/components/group-card-contents.js
index 2ff80e5b3b..4820500e73 100644
--- a/app/assets/javascripts/discourse/app/components/group-card-contents.js
+++ b/app/assets/javascripts/discourse/app/components/group-card-contents.js
@@ -1,4 +1,4 @@
-import { alias, match, gt, or } from "@ember/object/computed";
+import { alias, match, gt } from "@ember/object/computed";
import Component from "@ember/component";
import { setting } from "discourse/lib/computed";
import discourseComputed from "discourse-common/utils/decorators";
@@ -27,11 +27,6 @@ export default Component.extend(CardContentsBase, CleansUp, {
viewingTopic: match("currentPath", /^topic\./),
showMoreMembers: gt("moreMembersCount", 0),
- hasMembersOrIsMember: or(
- "group.members",
- "group.is_group_owner_display",
- "group.is_group_user"
- ),
group: null,
diff --git a/app/assets/javascripts/discourse/app/components/groups-form-interaction-fields.js b/app/assets/javascripts/discourse/app/components/groups-form-interaction-fields.js
index 9f20ed3101..d241176c38 100644
--- a/app/assets/javascripts/discourse/app/components/groups-form-interaction-fields.js
+++ b/app/assets/javascripts/discourse/app/components/groups-form-interaction-fields.js
@@ -1,5 +1,6 @@
import I18n from "I18n";
import Component from "@ember/component";
+import { or } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
@@ -47,6 +48,21 @@ export default Component.extend({
];
},
+ membersVisibilityLevel: or(
+ "model.members_visibility_level",
+ "visibilityLevelOptions.firstObject.value"
+ ),
+
+ messageableLevel: or(
+ "model.messageable_level",
+ "aliasLevelOptions.firstObject.value"
+ ),
+
+ mentionableLevel: or(
+ "model.mentionable_level",
+ "aliasLevelOptions.firstObject.value"
+ ),
+
@discourseComputed(
"siteSettings.email_in",
"model.automatic",
diff --git a/app/assets/javascripts/discourse/app/components/search-advanced-options.js b/app/assets/javascripts/discourse/app/components/search-advanced-options.js
index a2f6fcfd6a..1be07440d5 100644
--- a/app/assets/javascripts/discourse/app/components/search-advanced-options.js
+++ b/app/assets/javascripts/discourse/app/components/search-advanced-options.js
@@ -21,7 +21,7 @@ const REGEXP_MIN_POST_COUNT_PREFIX = /^min_post_count:/gi;
const REGEXP_POST_TIME_PREFIX = /^(before|after):/gi;
const REGEXP_TAGS_REPLACE = /(^(tags?:|#(?=[a-z0-9\-]+::tag))|::tag\s?$)/gi;
-const REGEXP_IN_MATCH = /^(in|with):(posted|created|watching|tracking|bookmarks|first|pinned|unpinned|wiki|unseen|image)/gi;
+const REGEXP_IN_MATCH = /^(in|with):(posted|created|watching|tracking|bookmarks|first|pinned|wiki|unseen|image)/gi;
const REGEXP_SPECIAL_IN_LIKES_MATCH = /^in:likes/gi;
const REGEXP_SPECIAL_IN_TITLE_MATCH = /^in:title/gi;
const REGEXP_SPECIAL_IN_PERSONAL_MATCH = /^in:personal/gi;
@@ -51,7 +51,6 @@ export default Component.extend({
this.inOptionsForAll = [
{ name: I18n.t("search.advanced.filters.first"), value: "first" },
{ name: I18n.t("search.advanced.filters.pinned"), value: "pinned" },
- { name: I18n.t("search.advanced.filters.unpinned"), value: "unpinned" },
{ name: I18n.t("search.advanced.filters.wiki"), value: "wiki" },
{ name: I18n.t("search.advanced.filters.images"), value: "images" }
];
diff --git a/app/assets/javascripts/discourse/app/components/topic-admin-menu-button.js b/app/assets/javascripts/discourse/app/components/topic-admin-menu-button.js
index 95dbcebcac..3d5ad4d3fc 100644
--- a/app/assets/javascripts/discourse/app/components/topic-admin-menu-button.js
+++ b/app/assets/javascripts/discourse/app/components/topic-admin-menu-button.js
@@ -6,6 +6,6 @@ export default MountWidget.extend({
widget: "topic-admin-menu-button",
buildArgs() {
- return this.getProperties("topic", "fixed", "openUpwards", "rightSide");
+ return this.getProperties("topic", "openUpwards", "rightSide");
}
});
diff --git a/app/assets/javascripts/discourse/app/components/topic-navigation.js b/app/assets/javascripts/discourse/app/components/topic-navigation.js
index 614e144f5f..4ffa6ce1aa 100644
--- a/app/assets/javascripts/discourse/app/components/topic-navigation.js
+++ b/app/assets/javascripts/discourse/app/components/topic-navigation.js
@@ -30,10 +30,7 @@ export default Component.extend(PanEvents, {
let info = this.info;
if (info.get("topicProgressExpanded")) {
- info.setProperties({
- renderTimeline: true,
- renderAdminMenuButton: true
- });
+ info.set("renderTimeline", true);
} else {
let renderTimeline = !this.site.mobileView;
@@ -51,10 +48,7 @@ export default Component.extend(PanEvents, {
}
}
- info.setProperties({
- renderTimeline,
- renderAdminMenuButton: !renderTimeline
- });
+ info.set("renderTimeline", renderTimeline);
}
},
@@ -76,7 +70,6 @@ export default Component.extend(PanEvents, {
if (
!$target.is(".widget-button") &&
!$parents.is(".widget-button") &&
- !$parents.is(".dropdown-menu") &&
!$parents.is("#discourse-modal") &&
!$target.is("#discourse-modal") &&
!$parents.is(".modal-footer") &&
diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js
index f76b948f8c..d030c88c3a 100644
--- a/app/assets/javascripts/discourse/app/controllers/composer.js
+++ b/app/assets/javascripts/discourse/app/controllers/composer.js
@@ -1,7 +1,7 @@
import I18n from "I18n";
import { isEmpty } from "@ember/utils";
import { and, or, alias, reads } from "@ember/object/computed";
-import { debounce } from "@ember/runloop";
+import { cancel, debounce } from "@ember/runloop";
import { inject as service } from "@ember/service";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
@@ -982,6 +982,10 @@ export default Controller.extend({
this.send("clearTopicDraft");
}
+ if (this._saveDraftPromise) {
+ return this._saveDraftPromise.then(() => this.destroyDraft());
+ }
+
return Draft.clear(key, this.get("model.draftSequence")).then(() =>
this.appEvents.trigger("draft:destroyed", key)
);
@@ -1028,6 +1032,10 @@ export default Controller.extend({
cancelComposer(differentDraft = false) {
this.skipAutoSave = true;
+ if (this._saveDraftDebounce) {
+ cancel(this._saveDraftDebounce);
+ }
+
const keyPrefix =
this.model.action === "edit" ? "post.abandon_edit" : "post.abandon";
@@ -1043,8 +1051,8 @@ export default Controller.extend({
if (differentDraft) {
this.model.clearState();
this.close();
- resolve();
}
+ resolve();
}
},
{
@@ -1052,22 +1060,30 @@ export default Controller.extend({
class: "btn-danger",
callback: result => {
if (result) {
- this.destroyDraft().then(() => {
- this.model.clearState();
- this.close();
- resolve();
- });
+ this.destroyDraft()
+ .then(() => {
+ this.model.clearState();
+ this.close();
+ })
+ .finally(() => {
+ resolve();
+ });
+ } else {
+ resolve();
}
}
}
]);
} else {
// it is possible there is some sort of crazy draft with no body ... just give up on it
- this.destroyDraft().then(() => {
- this.model.clearState();
- this.close();
- resolve();
- });
+ this.destroyDraft()
+ .then(() => {
+ this.model.clearState();
+ this.close();
+ })
+ .finally(() => {
+ resolve();
+ });
}
});
@@ -1094,11 +1110,12 @@ export default Controller.extend({
// in test debounce is Ember.run, this will cause
// an infinite loop
if (ENV.environment !== "test") {
- debounce(this, this._saveDraft, 2000);
+ this._saveDraftDebounce = debounce(this, this._saveDraft, 2000);
}
} else {
- model.saveDraft().finally(() => {
+ this._saveDraftPromise = model.saveDraft().finally(() => {
this._lastDraftSaved = Date.now();
+ this._saveDraftPromise = null;
});
}
}
@@ -1119,7 +1136,7 @@ export default Controller.extend({
if (Date.now() - this._lastDraftSaved > 15000) {
this._saveDraft();
} else {
- debounce(this, this._saveDraft, 2000);
+ this._saveDraftDebounce = debounce(this, this._saveDraft, 2000);
}
}
},
diff --git a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
index 2fcc5781c3..c9fb194b80 100644
--- a/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
+++ b/app/assets/javascripts/discourse/app/controllers/preferences/interface.js
@@ -5,7 +5,7 @@ import { setDefaultHomepage } from "discourse/lib/utilities";
import discourseComputed from "discourse-common/utils/decorators";
import { listThemes, setLocalTheme } from "discourse/lib/theme-selector";
import { popupAjaxError } from "discourse/lib/ajax-error";
-import pageReloader from "discourse/helpers/page-reloader";
+import { reload } from "discourse/helpers/page-reloader";
import {
safariHacksDisabled,
isiPad,
@@ -193,7 +193,7 @@ export default Controller.extend({
}
if (this.themeId !== this.currentThemeId) {
- pageReloader.reload();
+ reload();
}
})
.catch(popupAjaxError);
diff --git a/app/assets/javascripts/discourse/app/controllers/review-index.js b/app/assets/javascripts/discourse/app/controllers/review-index.js
index 103edb4f07..28f82624b8 100644
--- a/app/assets/javascripts/discourse/app/controllers/review-index.js
+++ b/app/assets/javascripts/discourse/app/controllers/review-index.js
@@ -57,14 +57,12 @@ export default Controller.extend({
@discourseComputed
sortOrders() {
- return ["priority", "priority_asc", "created_at", "created_at_asc"].map(
- order => {
- return {
- id: order,
- name: I18n.t(`review.filters.orders.${order}`)
- };
- }
- );
+ return ["score", "score_asc", "created_at", "created_at_asc"].map(order => {
+ return {
+ id: order,
+ name: I18n.t(`review.filters.orders.${order}`)
+ };
+ });
},
@discourseComputed
@@ -129,14 +127,14 @@ export default Controller.extend({
priorityStatuses.includes(nextStatus) &&
nextOrder === "created_at"
) {
- nextOrder = "priority";
+ nextOrder = "score";
}
if (
priorityStatuses.includes(currentStatus) &&
- currentOrder === "priority" &&
+ currentOrder === "score" &&
createdAtStatuses.includes(nextStatus) &&
- nextOrder === "priority"
+ nextOrder === "score"
) {
nextOrder = "created_at";
}
diff --git a/app/assets/javascripts/discourse/app/controllers/users.js b/app/assets/javascripts/discourse/app/controllers/users.js
index 89c20efa69..89ddd8f3a1 100644
--- a/app/assets/javascripts/discourse/app/controllers/users.js
+++ b/app/assets/javascripts/discourse/app/controllers/users.js
@@ -2,6 +2,7 @@ import { equal } from "@ember/object/computed";
import Controller, { inject as controller } from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce";
import { observes } from "discourse-common/utils/decorators";
+import { longDate } from "discourse/lib/formatter";
export default Controller.extend({
application: controller(),
@@ -12,9 +13,29 @@ export default Controller.extend({
name: "",
group: null,
exclude_usernames: null,
+ isLoading: false,
showTimeRead: equal("period", "all"),
+ loadUsers(params) {
+ this.set("isLoading", true);
+
+ this.store
+ .find("directoryItem", params)
+ .then(model => {
+ const lastUpdatedAt = model.get("resultSetMeta.last_updated_at");
+ this.setProperties({
+ model,
+ lastUpdatedAt: lastUpdatedAt ? longDate(lastUpdatedAt) : null,
+ period: params.period,
+ nameInput: params.name
+ });
+ })
+ .finally(() => {
+ this.set("isLoading", false);
+ });
+ },
+
@observes("nameInput")
_setName: discourseDebounce(function() {
this.set("name", this.nameInput);
diff --git a/app/assets/javascripts/discourse/app/helpers/page-reloader.js b/app/assets/javascripts/discourse/app/helpers/page-reloader.js
index 6429be3a3c..e2b8382482 100644
--- a/app/assets/javascripts/discourse/app/helpers/page-reloader.js
+++ b/app/assets/javascripts/discourse/app/helpers/page-reloader.js
@@ -1,10 +1,7 @@
-import EmberObject from "@ember/object";
-import Ember from "ember";
+import { isTesting } from "discourse-common/config/environment";
-export default EmberObject.create({
- reload: function() {
- if (!Ember.testing) {
- location.reload();
- }
+export function reload() {
+ if (!isTesting()) {
+ location.reload();
}
-});
+}
diff --git a/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js b/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js
index a2de76d3e9..121891c229 100644
--- a/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js
+++ b/app/assets/javascripts/discourse/app/initializers/topic-footer-buttons.js
@@ -7,7 +7,6 @@ const SHARE_PRIORITY = 1000;
const BOOKMARK_PRIORITY = 900;
const ARCHIVE_PRIORITY = 800;
const FLAG_PRIORITY = 700;
-const EDIT_MESSAGE_PRIORITY = 600;
const DEFER_PRIORITY = 500;
export default {
@@ -166,23 +165,6 @@ export default {
}
});
- registerTopicFooterButton({
- id: "edit-message",
- priority: EDIT_MESSAGE_PRIORITY,
- icon: "pencil-alt",
- label: "topic.edit_message.title",
- title: "topic.edit_message.help",
- action: "editFirstPost",
- classNames: ["edit-message"],
- dependentKeys: ["editFirstPost", "showEditOnFooter"],
- dropdown() {
- return this.site.mobileView && this.get("topic.isPrivateMessage");
- },
- displayed() {
- return this.showEditOnFooter;
- }
- });
-
registerTopicFooterButton({
id: "defer",
icon: "circle",
diff --git a/app/assets/javascripts/discourse/app/lib/autocomplete.js b/app/assets/javascripts/discourse/app/lib/autocomplete.js
index 03fc408a91..eea9f8e417 100644
--- a/app/assets/javascripts/discourse/app/lib/autocomplete.js
+++ b/app/assets/javascripts/discourse/app/lib/autocomplete.js
@@ -274,6 +274,9 @@ export default function(options) {
ul.find("li").click(function() {
selectedOption = ul.find("li").index(this);
completeTerm(autocompleteOptions[selectedOption]);
+ if (!options.single) {
+ me.focus();
+ }
return false;
});
var pos = null;
diff --git a/app/assets/javascripts/discourse/app/lib/category-tag-search.js b/app/assets/javascripts/discourse/app/lib/category-tag-search.js
index 85b17f5aac..9a370f0e21 100644
--- a/app/assets/javascripts/discourse/app/lib/category-tag-search.js
+++ b/app/assets/javascripts/discourse/app/lib/category-tag-search.js
@@ -5,10 +5,11 @@ import { TAG_HASHTAG_POSTFIX } from "discourse/lib/tag-hashtags";
import { SEPARATOR } from "discourse/lib/category-hashtags";
import { Promise } from "rsvp";
import { later, cancel } from "@ember/runloop";
+import { isTesting } from "discourse-common/config/environment";
-var cache = {};
-var cacheTime;
-var oldSearch;
+let cache = {};
+let cacheTime;
+let oldSearch;
function updateCache(term, results) {
cache[term] = results;
@@ -22,7 +23,7 @@ function searchTags(term, categories, limit) {
() => {
resolve(CANCELLED_STATUS);
},
- Ember.testing ? 50 : 5000
+ isTesting() ? 50 : 5000
);
const debouncedSearch = discourseDebounce((q, cats, resultFunc) => {
diff --git a/app/assets/javascripts/discourse/app/lib/text.js b/app/assets/javascripts/discourse/app/lib/text.js
index 692e75b9b5..94d33b18d5 100644
--- a/app/assets/javascripts/discourse/app/lib/text.js
+++ b/app/assets/javascripts/discourse/app/lib/text.js
@@ -18,6 +18,7 @@ function getOpts(opts) {
getURL: getURLWithCDN,
currentUser: Discourse.__container__.lookup("current-user:main"),
censoredRegexp: site.censored_regexp,
+ customEmojiTranslation: site.custom_emoji_translation,
siteSettings,
formatUsername
},
diff --git a/app/assets/javascripts/discourse/app/lib/user-search.js b/app/assets/javascripts/discourse/app/lib/user-search.js
index 86d7d46012..05d1741761 100644
--- a/app/assets/javascripts/discourse/app/lib/user-search.js
+++ b/app/assets/javascripts/discourse/app/lib/user-search.js
@@ -4,6 +4,7 @@ import { userPath } from "discourse/lib/url";
import { emailValid } from "discourse/lib/utilities";
import { Promise } from "rsvp";
import { later, cancel } from "@ember/runloop";
+import { isTesting } from "discourse-common/config/environment";
var cache = {},
cacheKey,
@@ -185,7 +186,7 @@ export default function userSearch(options) {
() => {
resolve(CANCELLED_STATUS);
},
- Ember.testing ? 50 : 5000
+ isTesting() ? 250 : 5000
);
if (skipSearch(term, options.allowEmails)) {
diff --git a/app/assets/javascripts/discourse/app/models/category.js b/app/assets/javascripts/discourse/app/models/category.js
index eaca16a29d..78802cba87 100644
--- a/app/assets/javascripts/discourse/app/models/category.js
+++ b/app/assets/javascripts/discourse/app/models/category.js
@@ -187,8 +187,7 @@ const Category = RestModel.extend({
),
search_priority: this.search_priority,
reviewable_by_group_name: this.reviewable_by_group_name,
- read_only_banner: this.read_only_banner,
- default_list_filter: this.default_list_filter
+ read_only_banner: this.read_only_banner
},
type: id ? "PUT" : "POST"
});
diff --git a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js
index be49ad4b39..65d1c21269 100644
--- a/app/assets/javascripts/discourse/app/models/topic-tracking-state.js
+++ b/app/assets/javascripts/discourse/app/models/topic-tracking-state.js
@@ -404,10 +404,10 @@ const TopicTrackingState = EmberObject.extend({
return new Set(result);
},
- countNew(categoryId) {
+ countCategoryByState(fn, categoryId) {
const subcategoryIds = this.getSubCategoryIds(categoryId);
return _.chain(this.states)
- .filter(isNew)
+ .filter(fn)
.filter(
topic =>
topic.archetype !== "private_message" &&
@@ -417,17 +417,45 @@ const TopicTrackingState = EmberObject.extend({
.value().length;
},
+ countNew(categoryId) {
+ return this.countCategoryByState(isNew, categoryId);
+ },
+
countUnread(categoryId) {
- const subcategoryIds = this.getSubCategoryIds(categoryId);
- return _.chain(this.states)
- .filter(isUnread)
- .filter(
- topic =>
- topic.archetype !== "private_message" &&
- !topic.deleted &&
- (!categoryId || subcategoryIds.has(topic.category_id))
- )
- .value().length;
+ return this.countCategoryByState(isUnread, categoryId);
+ },
+
+ countTags(tags) {
+ let counts = {};
+
+ tags.forEach(tag => {
+ counts[tag] = { unreadCount: 0, newCount: 0 };
+ });
+
+ Object.values(this.states).forEach(topic => {
+ if (
+ topic.archetype !== "private_message" &&
+ !topic.deleted &&
+ topic.tags
+ ) {
+ let newTopic = isNew(topic);
+ let unreadTopic = isUnread(topic);
+ if (isUnread || isNew) {
+ tags.forEach(tag => {
+ if (topic.tags.indexOf(tag) > -1) {
+ if (unreadTopic) {
+ counts[tag].unreadCount++;
+ }
+ if (newTopic) {
+ counts[tag].newCount++;
+ }
+ }
+ });
+ }
+ }
+ });
+
+ return counts;
},
countCategory(category_id) {
diff --git a/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js b/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
new file mode 100644
index 0000000000..494ed5e2e9
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/pre-initializers/discourse-bootstrap.js
@@ -0,0 +1,94 @@
+import PreloadStore from "discourse/lib/preload-store";
+import I18n from "I18n";
+import Session from "discourse/models/session";
+import RSVP from "rsvp";
+import { isTesting } from "discourse-common/config/environment";
+
+export default {
+ name: "discourse-bootstrap",
+
+ // The very first initializer to run
+ initialize(container, app) {
+ // Our test environment has its own bootstrap code
+ if (isTesting()) {
+ return;
+ }
+ const preloadedDataElement = document.getElementById("data-preloaded");
+ const setupData = document.getElementById("data-discourse-setup").dataset;
+
+ if (preloadedDataElement) {
+ const preloaded = JSON.parse(preloadedDataElement.dataset.preloaded);
+
+ Object.keys(preloaded).forEach(function(key) {
+ PreloadStore.store(key, JSON.parse(preloaded[key]));
+
+ if (setupData.debugPreloadedAppData === "true") {
+ /* eslint-disable no-console */
+ console.log(key, PreloadStore.get(key));
+ /* eslint-enable no-console */
+ }
+ });
+ }
+
+ app.CDN = setupData.cdn;
+ app.BaseUrl = setupData.baseUrl;
+ app.BaseUri = setupData.baseUri;
+ app.Environment = setupData.environment;
+ app.SiteSettings = PreloadStore.get("siteSettings");
+ app.ThemeSettings = PreloadStore.get("themeSettings");
+ app.LetterAvatarVersion = setupData.letterAvatarVersion;
+ app.MarkdownItURL = setupData.markdownItUrl;
+ app.ServiceWorkerURL = setupData.serviceWorkerUrl;
+ I18n.defaultLocale = setupData.defaultLocale;
+
+ window.Logster = window.Logster || {};
+ window.Logster.enabled = setupData.enableJsErrorReporting === "true";
+
+ app.set("assetVersion", setupData.assetVersion);
+
+ Session.currentProp(
+ "disableCustomCSS",
+ setupData.disableCustomCss === "true"
+ );
+
+ if (setupData.safeMode) {
+ Session.currentProp("safe_mode", setupData.safeMode);
+ }
+
+ app.HighlightJSPath = setupData.highlightJsPath;
+ app.SvgSpritePath = setupData.svgSpritePath;
+
+ if (app.Environment === "development") {
+ app.SvgIconList = setupData.svgIconList;
+ }
+
+ if (setupData.s3BaseUrl) {
+ app.S3CDN = setupData.s3Cdn;
+ app.S3BaseUrl = setupData.s3BaseUrl;
+ }
+
+ RSVP.configure("onerror", function(e) {
+ // Ignore TransitionAborted exceptions that bubble up
+ if (e && e.message === "TransitionAborted") {
+ return;
+ }
+
+ if (Discourse.Environment === "development") {
+ /* eslint-disable no-console */
+ if (e) {
+ if (e.message || e.stack) {
+ console.log(e.message);
+ console.log(e.stack);
+ } else {
+ console.log("Uncaught promise: ", e);
+ }
+ } else {
+ console.log("A promise failed but was not caught.");
+ }
+ /* eslint-enable no-console */
+ }
+
+ window.onerror(e && e.message, null, null, null, e);
+ });
+ }
+};
diff --git a/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js b/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js
index 30d1fb4e02..9c2a3e4779 100644
--- a/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js
+++ b/app/assets/javascripts/discourse/app/pre-initializers/inject-discourse-objects.js
@@ -9,11 +9,13 @@ import TopicTrackingState, {
import ScreenTrack from "discourse/lib/screen-track";
import Site from "discourse/models/site";
import User from "discourse/models/user";
+import MessageBus from "message-bus-client";
const ALL_TARGETS = ["controller", "component", "route", "model", "adapter"];
export default {
name: "inject-discourse-objects",
+ after: "discourse-bootstrap",
initialize(container, app) {
ALL_TARGETS.forEach(t => app.inject(t, "appEvents", "service:app-events"));
@@ -27,8 +29,7 @@ export default {
ALL_TARGETS.forEach(t => app.inject(t, "store", "service:store"));
}
- const messageBus = window.MessageBus;
- app.register("message-bus:main", messageBus, { instantiate: false });
+ app.register("message-bus:main", MessageBus, { instantiate: false });
ALL_TARGETS.concat("service").forEach(t =>
app.inject(t, "messageBus", "message-bus:main")
@@ -39,7 +40,7 @@ export default {
app.currentUser = currentUser;
const topicTrackingState = TopicTrackingState.create({
- messageBus,
+ messageBus: MessageBus,
currentUser
});
app.register("topic-tracking-state:main", topicTrackingState, {
diff --git a/app/assets/javascripts/discourse/app/routes/review-index.js b/app/assets/javascripts/discourse/app/routes/review-index.js
index b83a95124c..731167878e 100644
--- a/app/assets/javascripts/discourse/app/routes/review-index.js
+++ b/app/assets/javascripts/discourse/app/routes/review-index.js
@@ -7,7 +7,7 @@ export default DiscourseRoute.extend({
if (params.status === "reviewed" || params.status === "all") {
params.sort_order = "created_at";
} else {
- params.sort_order = "priority";
+ params.sort_order = "score";
}
}
diff --git a/app/assets/javascripts/discourse/app/routes/users.js b/app/assets/javascripts/discourse/app/routes/users.js
index a67e287e44..1e3729d022 100644
--- a/app/assets/javascripts/discourse/app/routes/users.js
+++ b/app/assets/javascripts/discourse/app/routes/users.js
@@ -1,6 +1,5 @@
import I18n from "I18n";
import DiscourseRoute from "discourse/routes/discourse";
-import { longDate } from "discourse/lib/formatter";
export default DiscourseRoute.extend({
queryParams: {
@@ -37,20 +36,11 @@ export default DiscourseRoute.extend({
},
model(params) {
- // If we refresh via `refreshModel` set the old model to loading
- this._params = params;
- return this.store.find("directoryItem", params);
+ return params;
},
- setupController(controller, model) {
- const params = this._params;
- const lastUpdatedAt = model.get("resultSetMeta.last_updated_at");
- controller.setProperties({
- model,
- lastUpdatedAt: lastUpdatedAt ? longDate(lastUpdatedAt) : null,
- period: params.period,
- nameInput: params.name
- });
+ setupController(controller, params) {
+ controller.loadUsers(params);
},
actions: {
diff --git a/app/assets/javascripts/discourse/app/templates/badges/index.hbs b/app/assets/javascripts/discourse/app/templates/badges/index.hbs
index 0f64b83f62..6ee39f7ff4 100644
--- a/app/assets/javascripts/discourse/app/templates/badges/index.hbs
+++ b/app/assets/javascripts/discourse/app/templates/badges/index.hbs
@@ -2,6 +2,8 @@
{{i18n "badges.title"}}
+ {{plugin-outlet name="below-badges-title"}}
+
{{#each badgeGroups as |bg|}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs b/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs
index a9efe1f6d5..09509b0557 100644
--- a/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/composer-toggles.hbs
@@ -1,4 +1,6 @@
+ {{plugin-outlet name="before-composer-toggles"}}
+
{{#if site.mobileView}}
{{flat-button
class="toggle-toolbar"
@@ -19,4 +21,4 @@
action=toggleFullscreen
title=fullscreenTitle}}
{{/unless}}
-
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs
index 369acfdd34..0c7cdebded 100644
--- a/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/d-editor.hbs
@@ -10,7 +10,6 @@
onOpen=(action b.action b)
class=b.className
options=(hash
- popupTitle=b.title
icon=b.icon
focusAfterOnChange=false
)
diff --git a/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs b/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs
index 2218fc27fd..ab17a307c3 100644
--- a/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/edit-category-settings.hbs
@@ -196,22 +196,6 @@
{{/unless}}
-
-
-
- {{i18n "category.default_list_filter"}}
-
-
- {{combo-box
- id="category-default-filter"
- valueProperty="value"
- content=availableListFilters
- value=category.default_list_filter
- none="category.list_filters.all"
- }}
-
-
-
{{#if isParentCategory}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/group-card-contents.hbs b/app/assets/javascripts/discourse/app/templates/components/group-card-contents.hbs
index 3c867903a2..6c07de9a81 100644
--- a/app/assets/javascripts/discourse/app/templates/components/group-card-contents.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/group-card-contents.hbs
@@ -49,35 +49,15 @@
{{/if}}
- {{#if hasMembersOrIsMember}}
-
-
-
-
- {{i18n "groups.user_count"}}
-
- {{group.user_count}}
-
-
- {{#if group.is_group_owner_display}}
- {{i18n "groups.index.is_group_owner"}}
- {{else if group.is_group_user}}
- {{i18n "groups.index.is_group_user"}}
- {{/if}}
-
-
-
- {{/if}}
-
{{#if group.members}}
-
+
diff --git a/app/assets/javascripts/discourse/app/templates/components/groups-form-interaction-fields.hbs b/app/assets/javascripts/discourse/app/templates/components/groups-form-interaction-fields.hbs
index 1a2c473824..07cf33c540 100644
--- a/app/assets/javascripts/discourse/app/templates/components/groups-form-interaction-fields.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/groups-form-interaction-fields.hbs
@@ -22,11 +22,12 @@
{{i18n "admin.groups.manage.interaction.members_visibility_levels.title"}}
{{combo-box name="alias"
- valueProperty="value"
- value=model.members_visibility_level
- content=visibilityLevelOptions
- castInteger=true
- class="groups-form-members-visibility-level"}}
+ valueProperty="value"
+ value=membersVisibilityLevel
+ content=visibilityLevelOptions
+ class="groups-form-members-visibility-level"
+ onChange=(action (mut model.members_visibility_level))
+ }}
{{i18n "admin.groups.manage.interaction.members_visibility_levels.description"}}
@@ -41,7 +42,7 @@
{{combo-box
name="alias"
valueProperty="value"
- value=model.mentionable_level
+ value=mentionableLevel
content=aliasLevelOptions
class="groups-form-mentionable-level"
onChange=(action (mut model.mentionable_level))
@@ -54,7 +55,7 @@
{{combo-box
name="alias"
valueProperty="value"
- value=model.messageable_level
+ value=messageableLevel
content=aliasLevelOptions
class="groups-form-messageable-level"
onChange=(action (mut model.messageable_level))
diff --git a/app/assets/javascripts/discourse/app/templates/components/notification-consent-banner.hbs b/app/assets/javascripts/discourse/app/templates/components/notification-consent-banner.hbs
index 463b1b4f17..3c04a03265 100644
--- a/app/assets/javascripts/discourse/app/templates/components/notification-consent-banner.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/notification-consent-banner.hbs
@@ -8,7 +8,6 @@
action=(action "turnon")
label="user.desktop_notifications.enable"
}}
- .
{{d-button
icon="times"
diff --git a/app/assets/javascripts/discourse/app/templates/components/user-card-contents.hbs b/app/assets/javascripts/discourse/app/templates/components/user-card-contents.hbs
index 239aadad35..8218fc3735 100644
--- a/app/assets/javascripts/discourse/app/templates/components/user-card-contents.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/user-card-contents.hbs
@@ -125,7 +125,11 @@
{{d-icon "ban"}}
- {{i18n "user.suspended_notice" date=this.user.suspendedTillDate}}
+ {{#if this.user.suspendedForever}}
+ {{i18n 'user.suspended_permanently'}}
+ {{else}}
+ {{i18n 'user.suspended_notice' date=this.user.suspendedTillDate}}
+ {{/if}}
{{i18n "user.suspended_reason"}}
diff --git a/app/assets/javascripts/discourse/app/templates/discovery.hbs b/app/assets/javascripts/discourse/app/templates/discovery.hbs
index ddec84d234..b8c7f4dc36 100644
--- a/app/assets/javascripts/discourse/app/templates/discovery.hbs
+++ b/app/assets/javascripts/discourse/app/templates/discovery.hbs
@@ -14,6 +14,8 @@
{{conditional-loading-spinner condition=loading}}
+ {{plugin-outlet name="discovery-above"}}
+
@@ -34,4 +36,4 @@
{{plugin-outlet name="discovery-below"}}
-{{/if}}
+{{/if}}
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs
index 2bb1379c80..6c5b479624 100644
--- a/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs
@@ -50,6 +50,7 @@
{{/if}}
{{/if}}
+ {{plugin-outlet name="before-topic-list" args=(hash category=category)}}
{{#if hasTopics}}
{{topic-list
@@ -73,6 +74,8 @@
scrollOnLoad=true
onScroll=discoveryTopicList.saveScrollPosition}}
{{/if}}
+
+ {{plugin-outlet name="after-topic-list" args=(hash category=category)}}
{{/discovery-topics-list}}
@@ -107,4 +110,4 @@
{{/footer-message}}
{{/if}}
-
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/app/templates/topic.hbs b/app/assets/javascripts/discourse/app/templates/topic.hbs
index d5e22f5437..e6968f11b7 100644
--- a/app/assets/javascripts/discourse/app/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/app/templates/topic.hbs
@@ -127,24 +127,6 @@
{{#topic-navigation topic=model jumpToDate=(action "jumpToDate") jumpToIndex=(action "jumpToIndex") as |info|}}
{{#if info.renderTimeline}}
- {{#if info.renderAdminMenuButton}}
- {{topic-admin-menu-button
- topic=model
- fixed="true"
- toggleMultiSelect=(action "toggleMultiSelect")
- deleteTopic=(action "deleteTopic")
- recoverTopic=(action "recoverTopic")
- toggleClosed=(action "toggleClosed")
- toggleArchived=(action "toggleArchived")
- toggleVisibility=(action "toggleVisibility")
- showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
- showFeatureTopic=(route-action "showFeatureTopic")
- showChangeTimestamp=(route-action "showChangeTimestamp")
- resetBumpDate=(action "resetBumpDate")
- convertToPublicTopic=(action "convertToPublicTopic")
- convertToPrivateMessage=(action "convertToPrivateMessage")}}
- {{/if}}
-
{{topic-timeline
topic=model
notificationLevel=model.details.notification_level
@@ -178,24 +160,22 @@
expanded=info.topicProgressExpanded
jumpToPost=(action "jumpToPost")}}
{{plugin-outlet name="before-topic-progress" args=(hash model=model jumpToPost=(action "jumpToPost"))}}
- {{#if info.renderAdminMenuButton}}
- {{topic-admin-menu-button
- topic=model
- openUpwards="true"
- rightSide="true"
- toggleMultiSelect=(action "toggleMultiSelect")
- deleteTopic=(action "deleteTopic")
- recoverTopic=(action "recoverTopic")
- toggleClosed=(action "toggleClosed")
- toggleArchived=(action "toggleArchived")
- toggleVisibility=(action "toggleVisibility")
- showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
- showFeatureTopic=(route-action "showFeatureTopic")
- showChangeTimestamp=(route-action "showChangeTimestamp")
- resetBumpDate=(action "resetBumpDate")
- convertToPublicTopic=(action "convertToPublicTopic")
- convertToPrivateMessage=(action "convertToPrivateMessage")}}
- {{/if}}
+ {{topic-admin-menu-button
+ topic=model
+ openUpwards="true"
+ rightSide="true"
+ toggleMultiSelect=(action "toggleMultiSelect")
+ deleteTopic=(action "deleteTopic")
+ recoverTopic=(action "recoverTopic")
+ toggleClosed=(action "toggleClosed")
+ toggleArchived=(action "toggleArchived")
+ toggleVisibility=(action "toggleVisibility")
+ showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
+ showFeatureTopic=(route-action "showFeatureTopic")
+ showChangeTimestamp=(route-action "showChangeTimestamp")
+ resetBumpDate=(action "resetBumpDate")
+ convertToPublicTopic=(action "convertToPublicTopic")
+ convertToPrivateMessage=(action "convertToPrivateMessage")}}
{{/topic-progress}}
{{/if}}
{{/topic-navigation}}
diff --git a/app/assets/javascripts/discourse/app/templates/users.hbs b/app/assets/javascripts/discourse/app/templates/users.hbs
index 1d3450d0f8..0766928a14 100644
--- a/app/assets/javascripts/discourse/app/templates/users.hbs
+++ b/app/assets/javascripts/discourse/app/templates/users.hbs
@@ -16,7 +16,7 @@
{{text-field value=nameInput placeholderKey="directory.filter_name" class="filter-name no-blur"}}
- {{#conditional-loading-spinner condition=model.loading}}
+ {{#conditional-loading-spinner condition=isLoading}}
{{#if model.length}}
diff --git a/app/assets/javascripts/discourse/app/widgets/header.js b/app/assets/javascripts/discourse/app/widgets/header.js
index 8e27a15a03..2580fff416 100644
--- a/app/assets/javascripts/discourse/app/widgets/header.js
+++ b/app/assets/javascripts/discourse/app/widgets/header.js
@@ -299,17 +299,20 @@ export default createWidget("header", {
html(attrs, state) {
let contents = () => {
- const panels = [
- this.attach("header-buttons", attrs),
- this.attach("header-icons", {
- hamburgerVisible: state.hamburgerVisible,
- userVisible: state.userVisible,
- searchVisible: state.searchVisible,
- ringBackdrop: state.ringBackdrop,
- flagCount: attrs.flagCount,
- user: this.currentUser
- })
- ];
+ const headerIcons = this.attach("header-icons", {
+ hamburgerVisible: state.hamburgerVisible,
+ userVisible: state.userVisible,
+ searchVisible: state.searchVisible,
+ ringBackdrop: state.ringBackdrop,
+ flagCount: attrs.flagCount,
+ user: this.currentUser
+ });
+
+ if (attrs.onlyIcons) {
+ return headerIcons;
+ }
+
+ const panels = [this.attach("header-buttons", attrs), headerIcons];
if (state.searchVisible) {
const contextType = this.searchContextType();
diff --git a/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js b/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js
index fbe2994b3c..072f2166eb 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-admin-menu.js
@@ -1,4 +1,3 @@
-import I18n from "I18n";
import { createWidget } from "discourse/widgets/widget";
import { h } from "virtual-dom";
import { ButtonClass } from "discourse/widgets/button";
@@ -8,6 +7,20 @@ createWidget(
jQuery.extend(ButtonClass, { tagName: "li.btn" })
);
+createWidget("post-admin-menu-button", {
+ tagName: "li",
+
+ html(attrs) {
+ return this.attach("button", {
+ className: attrs.className,
+ action: attrs.action,
+ icon: attrs.icon,
+ label: attrs.label,
+ secondaryAction: attrs.secondaryAction
+ });
+ }
+});
+
export function buildManageButtons(attrs, currentUser, siteSettings) {
if (!currentUser) {
return [];
@@ -27,7 +40,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
const buttonAtts = {
action: "togglePostType",
icon: "shield-alt",
- className: "btn-default toggle-post-type"
+ className: "popup-menu-button toggle-post-type"
};
if (attrs.isModeratorAction) {
@@ -44,14 +57,14 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
icon: "user-shield",
label: "post.controls.remove_post_notice",
action: "removeNotice",
- className: "btn-default remove-notice"
+ className: "popup-menu-button remove-notice"
});
} else {
contents.push({
icon: "user-shield",
label: "post.controls.add_post_notice",
action: "addNotice",
- className: "btn-default add-notice"
+ className: "popup-menu-button add-notice"
});
}
}
@@ -61,7 +74,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
icon: "far-eye",
label: "post.controls.unhide",
action: "unhidePost",
- className: "btn-default unhide-post"
+ className: "popup-menu-button unhide-post"
});
}
@@ -70,7 +83,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
icon: "user",
label: "post.controls.change_owner",
action: "changePostOwner",
- className: "btn-default change-owner"
+ className: "popup-menu-button change-owner"
});
}
@@ -80,7 +93,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
icon: "certificate",
label: "post.controls.grant_badge",
action: "grantBadge",
- className: "btn-default grant-badge"
+ className: "popup-menu-button grant-badge"
});
}
@@ -90,7 +103,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
label: "post.controls.unlock_post",
action: "unlockPost",
title: "post.controls.unlock_post_description",
- className: "btn-default unlock-post"
+ className: "popup-menu-button unlock-post"
});
} else {
contents.push({
@@ -98,7 +111,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
label: "post.controls.lock_post",
action: "lockPost",
title: "post.controls.lock_post_description",
- className: "btn-default lock-post"
+ className: "popup-menu-button lock-post"
});
}
}
@@ -109,14 +122,14 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
action: "toggleWiki",
label: "post.controls.unwiki",
icon: "far-edit",
- className: "btn-default wiki wikied"
+ className: "popup-menu-button wiki wikied"
});
} else {
contents.push({
action: "toggleWiki",
label: "post.controls.wiki",
icon: "far-edit",
- className: "btn-default wiki"
+ className: "popup-menu-button wiki"
});
}
}
@@ -126,7 +139,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
icon: "file",
label: "post.controls.publish_page",
action: "showPagePublish",
- className: "btn-default publish-page"
+ className: "popup-menu-button publish-page"
});
}
@@ -135,7 +148,7 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
icon: "cog",
label: "post.controls.rebake",
action: "rebakePost",
- className: "btn-default rebuild-html"
+ className: "popup-menu-button rebuild-html"
});
}
@@ -147,7 +160,6 @@ export default createWidget("post-admin-menu", {
html() {
const contents = [];
- contents.push(h("h3", I18n.t("admin_title")));
buildManageButtons(this.attrs, this.currentUser, this.siteSettings).forEach(
b => {
@@ -156,7 +168,7 @@ export default createWidget("post-admin-menu", {
}
);
- return contents;
+ return h("ul", contents);
},
clickOutside() {
diff --git a/app/assets/javascripts/discourse/app/widgets/post-stream.js b/app/assets/javascripts/discourse/app/widgets/post-stream.js
index 6406d1c9e8..1ccb12f462 100644
--- a/app/assets/javascripts/discourse/app/widgets/post-stream.js
+++ b/app/assets/javascripts/discourse/app/widgets/post-stream.js
@@ -3,6 +3,7 @@ import { createWidget } from "discourse/widgets/widget";
import transformPost from "discourse/lib/transform-post";
import { Placeholder } from "discourse/lib/posts-with-placeholders";
import { addWidgetCleanCallback } from "discourse/components/mount-widget";
+import { isTesting } from "discourse-common/config/environment";
let transformCallbacks = null;
export function postTransformCallbacks(transformed) {
@@ -19,7 +20,7 @@ export function addPostTransformCallback(callback) {
transformCallbacks.push(callback);
}
-const CLOAKING_ENABLED = !window.inTestEnv;
+const CLOAKING_ENABLED = !isTesting();
const DAY = 1000 * 60 * 60 * 24;
const _dontCloak = {};
diff --git a/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js b/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js
index 8e140d0c07..7c9683069f 100644
--- a/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/topic-admin-menu.js
@@ -1,32 +1,33 @@
-import I18n from "I18n";
import { createWidget, applyDecorators } from "discourse/widgets/widget";
import { h } from "virtual-dom";
createWidget("admin-menu-button", {
+ tagName: "li",
+
+ buildClasses(attrs) {
+ return attrs.className;
+ },
+
html(attrs) {
let className;
if (attrs.buttonClass) {
className = attrs.buttonClass;
}
- return h(
- "li",
- { className: attrs.className },
- this.attach("button", {
- className,
- action: attrs.action,
- url: attrs.url,
- icon: attrs.icon,
- label: attrs.fullLabel || `topic.${attrs.label}`,
- secondaryAction: "hideAdminMenu"
- })
- );
+ return this.attach("button", {
+ className,
+ action: attrs.action,
+ url: attrs.url,
+ icon: attrs.icon,
+ label: attrs.fullLabel || `topic.${attrs.label}`,
+ secondaryAction: "hideAdminMenu"
+ });
}
});
createWidget("topic-admin-menu-button", {
tagName: "span",
- buildKey: () => `topic-admin-menu-button`,
+ buildKey: () => "topic-admin-menu-button",
defaultState() {
return { expanded: false, position: null };
@@ -37,7 +38,6 @@ createWidget("topic-admin-menu-button", {
const menu = this.attach("topic-admin-menu", {
position: state.position,
- fixed: attrs.fixed,
topic: attrs.topic,
openUpwards: attrs.openUpwards,
rightSide: !this.site.mobileView && attrs.rightSide,
@@ -52,8 +52,7 @@ createWidget("topic-admin-menu-button", {
result.push(
this.attach("button", {
className:
- "btn-default toggle-admin-menu" +
- (attrs.fixed ? " show-topic-admin" : "") +
+ "popup-menu-button toggle-admin-menu" +
(attrs.addKeyboardTargetClass ? " keyboard-target-admin-menu" : ""),
title: "topic_admin_menu",
icon: "wrench",
@@ -73,10 +72,6 @@ createWidget("topic-admin-menu-button", {
hideAdminMenu() {
this.state.expanded = false;
this.state.position = null;
-
- if (this.site.mobileView && !this.attrs.rightSide) {
- $(".header-cloak").css("display", "");
- }
},
showAdminMenu(e) {
@@ -89,21 +84,25 @@ createWidget("topic-admin-menu-button", {
$button = $(e.target).closest("button");
}
- const position = $button.position();
+ const position = $button.position(),
+ SPACING = 3,
+ MENU_WIDTH = 217;
const rtl = $("html").hasClass("rtl");
position.outerHeight = $button.outerHeight();
if (rtl) {
- position.left -= 217 - $button.outerWidth();
+ position.left -= MENU_WIDTH - $button.outerWidth();
}
- if (this.attrs.fixed) {
- position.left += $button.width() - 203;
- }
-
- if (this.site.mobileView && !this.attrs.rightSide) {
- $(".header-cloak").css("display", "block");
+ if (this.attrs.openUpwards) {
+ if (rtl) {
+ position.left -= $button[0].offsetWidth + SPACING;
+ } else {
+ position.left += $button[0].offsetWidth + SPACING;
+ }
+ } else {
+ position.top += $button[0].offsetHeight + SPACING;
}
this.state.position = position;
@@ -134,7 +133,7 @@ export default createWidget("topic-admin-menu", {
if (this.currentUser && this.currentUser.get("canManageTopic")) {
this.addActionButton({
className: "topic-admin-multi-select",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "toggleMultiSelect",
icon: "tasks",
label: "actions.multi_select"
@@ -143,7 +142,7 @@ export default createWidget("topic-admin-menu", {
if (details.get("can_delete")) {
this.addActionButton({
className: "topic-admin-delete",
- buttonClass: "btn-danger",
+ buttonClass: "popup-menu-btn-danger",
action: "deleteTopic",
icon: "far-trash-alt",
label: "actions.delete"
@@ -153,7 +152,7 @@ export default createWidget("topic-admin-menu", {
if (topic.get("deleted") && details.get("can_recover")) {
this.addActionButton({
className: "topic-admin-recover",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "recoverTopic",
icon: "undo",
label: "actions.recover"
@@ -163,7 +162,7 @@ export default createWidget("topic-admin-menu", {
if (topic.get("closed")) {
this.addActionButton({
className: "topic-admin-open",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "toggleClosed",
icon: "unlock",
label: "actions.open"
@@ -171,7 +170,7 @@ export default createWidget("topic-admin-menu", {
} else {
this.addActionButton({
className: "topic-admin-close",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "toggleClosed",
icon: "lock",
label: "actions.close"
@@ -180,7 +179,7 @@ export default createWidget("topic-admin-menu", {
this.addActionButton({
className: "topic-admin-status-update",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "showTopicStatusUpdate",
icon: "far-clock",
label: "actions.timed_update"
@@ -189,7 +188,7 @@ export default createWidget("topic-admin-menu", {
if (!isPrivateMessage && (topic.get("visible") || featured)) {
this.addActionButton({
className: "topic-admin-pin",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "showFeatureTopic",
icon: "thumbtack",
label: featured ? "actions.unpin" : "actions.pin"
@@ -199,7 +198,7 @@ export default createWidget("topic-admin-menu", {
if (this.currentUser.get("staff")) {
this.addActionButton({
className: "topic-admin-change-timestamp",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "showChangeTimestamp",
icon: "calendar-alt",
label: "change_timestamp.title"
@@ -208,7 +207,7 @@ export default createWidget("topic-admin-menu", {
this.addActionButton({
className: "topic-admin-reset-bump-date",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "resetBumpDate",
icon: "anchor",
label: "actions.reset_bump_date"
@@ -217,7 +216,7 @@ export default createWidget("topic-admin-menu", {
if (!isPrivateMessage) {
this.addActionButton({
className: "topic-admin-archive",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "toggleArchived",
icon: "folder",
label: topic.get("archived") ? "actions.unarchive" : "actions.archive"
@@ -226,7 +225,7 @@ export default createWidget("topic-admin-menu", {
this.addActionButton({
className: "topic-admin-visible",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: "toggleVisibility",
icon: visible ? "far-eye-slash" : "far-eye",
label: visible ? "actions.invisible" : "actions.visible"
@@ -235,7 +234,7 @@ export default createWidget("topic-admin-menu", {
if (details.get("can_convert_topic")) {
this.addActionButton({
className: "topic-admin-convert",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
action: isPrivateMessage
? "convertToPublicTopic"
: "convertToPrivateMessage",
@@ -249,7 +248,7 @@ export default createWidget("topic-admin-menu", {
if (this.currentUser.get("staff")) {
this.addActionButton({
icon: "list",
- buttonClass: "btn-default",
+ buttonClass: "popup-menu-btn",
fullLabel: "review.moderation_history",
url: `/review?topic_id=${topic.id}&status=all`
});
@@ -259,7 +258,7 @@ export default createWidget("topic-admin-menu", {
buildAttributes(attrs) {
let { top, left, outerHeight } = attrs.position;
- const position = attrs.fixed || this.site.mobileView ? "fixed" : "absolute";
+ const position = this.site.mobileView ? "fixed" : "absolute";
if (attrs.rightSide) {
return;
@@ -275,7 +274,7 @@ export default createWidget("topic-admin-menu", {
}
if (this.site.mobileView) {
- bottom = 0;
+ bottom = 50;
left = 0;
}
@@ -300,26 +299,13 @@ export default createWidget("topic-admin-menu", {
this.attrs,
this.state
);
- return [
- h("div.header", [
- h("h3", I18n.t("topic.actions.title")),
- h(
- "div",
- this.attach("button", {
- action: "clickOutside",
- icon: "times",
- className: "close-button"
- })
- )
- ]),
- h(
- "ul",
- attrs.actionButtons
- .concat(extraButtons)
- .filter(Boolean)
- .map(b => this.attach("admin-menu-button", b))
- )
- ];
+ return h(
+ "ul",
+ attrs.actionButtons
+ .concat(extraButtons)
+ .filter(Boolean)
+ .map(b => this.attach("admin-menu-button", b))
+ );
},
clickOutside() {
diff --git a/app/assets/javascripts/discourse/app/widgets/topic-timeline.js b/app/assets/javascripts/discourse/app/widgets/topic-timeline.js
index 55dcafa553..32f9aa2617 100644
--- a/app/assets/javascripts/discourse/app/widgets/topic-timeline.js
+++ b/app/assets/javascripts/discourse/app/widgets/topic-timeline.js
@@ -359,7 +359,7 @@ createWidget("timeline-footer-controls", {
if (topic.get("details.can_create_post")) {
controls.push(
this.attach("button", {
- className: "btn-default create",
+ className: "btn-default create reply-to-post",
icon: "reply",
title: "topic.reply.help",
action: "replyToPost"
@@ -396,6 +396,15 @@ createWidget("timeline-footer-controls", {
["notificationLevel"]
)
);
+ if (this.site.mobileView) {
+ controls.push(
+ this.attach("topic-admin-menu-button", {
+ topic,
+ addKeyboardTargetClass: true,
+ openUpwards: true
+ })
+ );
+ }
}
return controls;
diff --git a/app/assets/javascripts/ember_jquery.js b/app/assets/javascripts/ember_jquery.js
index be7d57d802..51f31d5ed3 100644
--- a/app/assets/javascripts/ember_jquery.js
+++ b/app/assets/javascripts/ember_jquery.js
@@ -3,4 +3,3 @@
//= require jquery
//= require ember_include
//= require discourse-loader
-//= require ember-shim
diff --git a/app/assets/javascripts/preload-application-data.js b/app/assets/javascripts/preload-application-data.js
deleted file mode 100644
index bb11ecbea4..0000000000
--- a/app/assets/javascripts/preload-application-data.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// discourse-skip-module
-(function() {
- const ps = require("discourse/lib/preload-store").default;
- const preloadedDataElement = document.getElementById("data-preloaded");
- const setupData = document.getElementById("data-discourse-setup").dataset;
- const I18n = require("I18n").default;
-
- if (preloadedDataElement) {
- const preloaded = JSON.parse(preloadedDataElement.dataset.preloaded);
-
- Object.keys(preloaded).forEach(function(key) {
- ps.store(key, JSON.parse(preloaded[key]));
-
- if (setupData.debugPreloadedAppData === "true") {
- /* eslint-disable no-console */
- console.log(key, ps.get(key));
- /* eslint-enable no-console */
- }
- });
- }
-
- window.Logster = window.Logster || {};
- window.Logster.enabled = setupData.enableJsErrorReporting === "true";
-
- Discourse.CDN = setupData.cdn;
- Discourse.BaseUrl = setupData.baseUrl;
- Discourse.BaseUri = setupData.baseUri;
- Discourse.Environment = setupData.environment;
- Discourse.SiteSettings = ps.get("siteSettings");
- Discourse.ThemeSettings = ps.get("themeSettings");
- Discourse.LetterAvatarVersion = setupData.letterAvatarVersion;
- Discourse.MarkdownItURL = setupData.markdownItUrl;
- Discourse.ServiceWorkerURL = setupData.serviceWorkerUrl;
- I18n.defaultLocale = setupData.defaultLocale;
- Discourse.start();
- Discourse.set("assetVersion", setupData.assetVersion);
-
- const Session = require("discourse/models/session").default;
- Session.currentProp(
- "disableCustomCSS",
- setupData.disableCustomCss === "true"
- );
-
- if (setupData.safeMode) {
- Session.currentProp("safe_mode", setupData.safeMode);
- }
-
- Discourse.HighlightJSPath = setupData.highlightJsPath;
- Discourse.SvgSpritePath = setupData.svgSpritePath;
-
- if (Discourse.Environment === "development") {
- Discourse.SvgIconList = setupData.svgIconList;
- }
-
- if (setupData.s3BaseUrl) {
- Discourse.S3CDN = setupData.s3Cdn;
- Discourse.S3BaseUrl = setupData.s3BaseUrl;
- }
-
- // eslint-disable-next-line
- Ember.RSVP.configure("onerror", function(e) {
- // Ignore TransitionAborted exceptions that bubble up
- if (e && e.message === "TransitionAborted") {
- return;
- }
-
- if (Discourse.Environment === "development") {
- /* eslint-disable no-console */
- if (e) {
- if (e.message || e.stack) {
- console.log(e.message);
- console.log(e.stack);
- } else {
- console.log("Uncaught promise: ", e);
- }
- } else {
- console.log("A promise failed but was not caught.");
- }
- /* eslint-enable no-console */
- }
-
- window.onerror(e && e.message, null, null, null, e);
- });
-})();
diff --git a/app/assets/javascripts/pretty-text/addon/emoji.js b/app/assets/javascripts/pretty-text/addon/emoji.js
index 74af3b90cd..5d9fb762c0 100644
--- a/app/assets/javascripts/pretty-text/addon/emoji.js
+++ b/app/assets/javascripts/pretty-text/addon/emoji.js
@@ -98,13 +98,18 @@ export function performEmojiUnescape(string, opts) {
const inlineEmoji = opts.inlineEmoji;
const regexp = unicodeRegexp(inlineEmoji);
+ const allTranslations = Object.assign(
+ {},
+ translations,
+ opts.customEmojiTranslation || {}
+ );
return string.replace(regexp, (m, index) => {
- const isEmoticon = opts.enableEmojiShortcuts && !!translations[m];
+ const isEmoticon = opts.enableEmojiShortcuts && !!allTranslations[m];
const isUnicodeEmoticon = !!replacements[m];
let emojiVal;
if (isEmoticon) {
- emojiVal = translations[m];
+ emojiVal = allTranslations[m];
} else if (isUnicodeEmoticon) {
emojiVal = replacements[m];
} else {
@@ -131,11 +136,16 @@ export function performEmojiUnescape(string, opts) {
export function performEmojiEscape(string, opts) {
const inlineEmoji = opts.inlineEmoji;
const regexp = unicodeRegexp(inlineEmoji);
+ const allTranslations = Object.assign(
+ {},
+ translations,
+ opts.customEmojiTranslation || {}
+ );
return string.replace(regexp, (m, index) => {
if (isReplacableInlineEmoji(string, index, inlineEmoji)) {
- if (!!translations[m]) {
- return opts.emojiShortcuts ? `:${translations[m]}:` : m;
+ if (!!allTranslations[m]) {
+ return opts.emojiShortcuts ? `:${allTranslations[m]}:` : m;
} else if (!!replacements[m]) {
return `:${replacements[m]}:`;
}
diff --git a/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown/emoji.js b/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown/emoji.js
index 7b4bee783d..452bb5046d 100644
--- a/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown/emoji.js
+++ b/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown/emoji.js
@@ -5,15 +5,25 @@ const MAX_NAME_LENGTH = 60;
let translationTree = null;
+export function resetTranslationTree() {
+ translationTree = null;
+}
+
// This allows us to efficiently search for aliases
// We build a data structure that allows us to quickly
// search through our N next chars to see if any match
// one of our alias emojis.
-function buildTranslationTree() {
+function buildTranslationTree(customEmojiTranslation) {
let tree = [];
let lastNode;
- Object.keys(translations).forEach(key => {
+ const allTranslations = Object.assign(
+ {},
+ translations,
+ customEmojiTranslation || {}
+ );
+
+ Object.keys(allTranslations).forEach(key => {
let node = tree;
for (let i = 0; i < key.length; i++) {
@@ -37,7 +47,7 @@ function buildTranslationTree() {
}
}
- lastNode[2] = translations[key];
+ lastNode[2] = allTranslations[key];
});
return tree;
@@ -114,8 +124,14 @@ function getEmojiTokenByName(name, state) {
}
}
-function getEmojiTokenByTranslation(content, pos, state) {
- translationTree = translationTree || buildTranslationTree();
+function getEmojiTokenByTranslation(
+ content,
+ pos,
+ state,
+ customEmojiTranslation
+) {
+ translationTree =
+ translationTree || buildTranslationTree(customEmojiTranslation);
let t = translationTree;
let start = pos;
@@ -175,7 +191,8 @@ function applyEmoji(
state,
emojiUnicodeReplacer,
enableShortcuts,
- inlineEmoji
+ inlineEmoji,
+ customEmojiTranslation
) {
let result = null;
let start = 0;
@@ -201,7 +218,12 @@ function applyEmoji(
if (enableShortcuts && !token) {
// handle aliases (note: we can't do this in inline cause ; is not a split point)
- const info = getEmojiTokenByTranslation(content, i, state);
+ const info = getEmojiTokenByTranslation(
+ content,
+ i,
+ state,
+ customEmojiTranslation
+ );
if (info) {
offset = info.pos - i;
@@ -310,7 +332,8 @@ export function setup(helper) {
s,
md.options.discourse.emojiUnicodeReplacer,
md.options.discourse.features.emojiShortcuts,
- md.options.discourse.features.inlineEmoji
+ md.options.discourse.features.inlineEmoji,
+ md.options.discourse.customEmojiTranslation
)
)
);
diff --git a/app/assets/javascripts/pretty-text/addon/pretty-text.js b/app/assets/javascripts/pretty-text/addon/pretty-text.js
index 7a867d89c9..d24fb4af6f 100644
--- a/app/assets/javascripts/pretty-text/addon/pretty-text.js
+++ b/app/assets/javascripts/pretty-text/addon/pretty-text.js
@@ -30,7 +30,8 @@ export function buildOptions(state) {
previewing,
linkify,
censoredRegexp,
- disableEmojis
+ disableEmojis,
+ customEmojiTranslation
} = state;
let features = {
@@ -68,6 +69,7 @@ export function buildOptions(state) {
emojiUnicodeReplacer,
lookupUploadUrls,
censoredRegexp,
+ customEmojiTranslation,
allowedHrefSchemes: siteSettings.allowed_href_schemes
? siteSettings.allowed_href_schemes.split("|")
: null,
diff --git a/app/assets/javascripts/select-kit/addon/components/categories-admin-dropdown.js b/app/assets/javascripts/select-kit/addon/components/categories-admin-dropdown.js
index d8ea039ffd..2274158410 100644
--- a/app/assets/javascripts/select-kit/addon/components/categories-admin-dropdown.js
+++ b/app/assets/javascripts/select-kit/addon/components/categories-admin-dropdown.js
@@ -36,5 +36,13 @@ export default DropdownSelectBoxComponent.extend({
}
return items;
- })
+ }),
+
+ _onChange(value, item) {
+ if (item.onChange) {
+ item.onChange(value, item);
+ } else if (this.attrs.onChange) {
+ this.attrs.onChange(value, item);
+ }
+ }
});
diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit.js b/app/assets/javascripts/select-kit/addon/components/select-kit.js
index 02acfb5a2e..8ad01cc338 100644
--- a/app/assets/javascripts/select-kit/addon/components/select-kit.js
+++ b/app/assets/javascripts/select-kit/addon/components/select-kit.js
@@ -57,6 +57,8 @@ export default Component.extend(
nameProperty: "name",
singleSelect: false,
multiSelect: false,
+ labelProperty: null,
+ titleProperty: null,
init() {
this._super(...arguments);
@@ -76,6 +78,8 @@ export default Component.extend(
uniqueID: guidFor(this),
valueProperty: this.valueProperty,
nameProperty: this.nameProperty,
+ labelProperty: this.labelProperty,
+ titleProperty: this.titleProperty,
options: EmberObject.create(),
isLoading: false,
diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js
index c3ab6024b0..452e46a2b0 100644
--- a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js
+++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js
@@ -38,13 +38,18 @@ export default Component.extend(UtilsMixin, {
return this.getProperty(this.item, "ariaLabel") || this.title;
}),
- title: computed("item.title", "rowName", function() {
- return this.getProperty(this.item, "title") || this.rowName;
+ title: computed("rowTitle", "item.title", "rowName", function() {
+ return (
+ this.rowTitle || this.getProperty(this.item, "title") || this.rowName
+ );
}),
- label: computed("item.label", "title", "rowName", function() {
+ label: computed("rowLabel", "item.label", "title", "rowName", function() {
const label =
- this.getProperty(this.item, "label") || this.title || this.rowName;
+ this.rowLabel ||
+ this.getProperty(this.item, "label") ||
+ this.title ||
+ this.rowName;
if (
this.selectKit.options.allowAny &&
this.rowValue === this.selectKit.filter &&
@@ -61,7 +66,9 @@ export default Component.extend(UtilsMixin, {
this.setProperties({
rowName: this.getName(this.item),
- rowValue: this.getValue(this.item)
+ rowValue: this.getValue(this.item),
+ rowLabel: this.getProperty(this.item, "labelProperty"),
+ rowTitle: this.getProperty(this.item, "titleProperty")
});
},
diff --git a/app/assets/javascripts/select-kit/addon/components/selected-name.js b/app/assets/javascripts/select-kit/addon/components/selected-name.js
index e1c75a6e5f..fc357bfa35 100644
--- a/app/assets/javascripts/select-kit/addon/components/selected-name.js
+++ b/app/assets/javascripts/select-kit/addon/components/selected-name.js
@@ -22,6 +22,8 @@ export default Component.extend(UtilsMixin, {
// we can't listen on `item.nameProperty` given it's variable
this.setProperties({
+ headerLabel: this.getProperty(this.item, "labelProperty"),
+ headerTitle: this.getProperty(this.item, "titleProperty"),
name: this.getName(this.item),
value:
this.item === this.selectKit.noneItem ? null : this.getValue(this.item)
@@ -38,12 +40,22 @@ export default Component.extend(UtilsMixin, {
return String(this.title).replace("…", "");
}),
- title: computed("item", function() {
- return this._safeProperty("title", this.item) || this.name || "";
+ title: computed("headerTitle", "item", function() {
+ return (
+ this.headerTitle ||
+ this._safeProperty("title", this.item) ||
+ this.name ||
+ ""
+ );
}),
- label: computed("title", "name", function() {
- return this._safeProperty("label", this.item) || this.title || this.name;
+ label: computed("headerLabel", "title", "name", function() {
+ return (
+ this.headerLabel ||
+ this._safeProperty("label", this.item) ||
+ this.title ||
+ this.name
+ );
}),
icons: computed("item.{icon,icons}", function() {
diff --git a/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options.js b/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options.js
index 2fffef964c..287d54824a 100644
--- a/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options.js
+++ b/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options.js
@@ -1,36 +1,16 @@
import I18n from "I18n";
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
-const HEADING_COLLECTION = "HEADING_COLLECTION";
-
export default DropdownSelectBoxComponent.extend({
pluginApiIdentifiers: ["toolbar-popup-menu-options"],
classNames: ["toolbar-popup-menu-options"],
- init() {
- this._super(...arguments);
-
- this.prependCollection(HEADING_COLLECTION);
- },
-
selectKitOptions: {
showFullTitle: false,
filterable: false,
autoFilterable: false
},
- modifyContentForCollection(collection) {
- if (collection === HEADING_COLLECTION) {
- return { title: this.selectKit.options.popupTitle };
- }
- },
-
- modifyComponentForCollection(collection) {
- if (collection === HEADING_COLLECTION) {
- return "toolbar-popup-menu-options/toolbar-popup-menu-options-heading";
- }
- },
-
modifyContent(contents) {
return contents
.map(content => {
diff --git a/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js b/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js
deleted file mode 100644
index 1625cf3e5b..0000000000
--- a/app/assets/javascripts/select-kit/addon/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Component from "@ember/component";
-import { reads } from "@ember/object/computed";
-
-export default Component.extend({
- tagName: "h3",
- layoutName:
- "select-kit/templates/components/toolbar-popup-menu-options/toolbar-popup-menu-options-heading",
- classNames: ["toolbar-popup-menu-options-heading"],
- heading: reads("collection.content.title")
-});
diff --git a/app/assets/javascripts/start-discourse.js b/app/assets/javascripts/start-discourse.js
new file mode 100644
index 0000000000..cabffc4a5e
--- /dev/null
+++ b/app/assets/javascripts/start-discourse.js
@@ -0,0 +1,4 @@
+// discourse-skip-module
+(function() {
+ Discourse.start();
+})();
diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js
index 64119c4940..5efe954acb 100644
--- a/app/assets/javascripts/vendor.js
+++ b/app/assets/javascripts/vendor.js
@@ -29,3 +29,4 @@
//= require virtual-dom
//= require virtual-dom-amd
//= require intersection-observer
+//= require discourse-shims
diff --git a/app/assets/javascripts/wizard/components/homepage-preview.js b/app/assets/javascripts/wizard/components/homepage-preview.js
index c0de35c55a..4e61aac8b1 100644
--- a/app/assets/javascripts/wizard/components/homepage-preview.js
+++ b/app/assets/javascripts/wizard/components/homepage-preview.js
@@ -429,7 +429,7 @@ export default createPreviewComponent(659, 320, {
ctx.font = `${bodyFontSize}em 'Arial'`;
for (let j = 2; j <= 4; j++) {
ctx.fillText(
- j === 5 ? "1h" : Math.floor(Math.random() * 90) + 10,
+ j === 4 ? "1h" : Math.floor(Math.random() * 90) + 10,
cols[j] + margin,
y + rowHeight * 0.6
);
diff --git a/app/assets/javascripts/wizard/models/wizard.js b/app/assets/javascripts/wizard/models/wizard.js
index e907687e11..69d166bf7a 100644
--- a/app/assets/javascripts/wizard/models/wizard.js
+++ b/app/assets/javascripts/wizard/models/wizard.js
@@ -28,7 +28,7 @@ const Wizard = EmberObject.extend({
getCurrentColors(schemeId) {
const colorStep = this.steps.findBy("id", "colors");
if (!colorStep) {
- return;
+ return this.current_color_scheme;
}
const themeChoice = colorStep.get("fieldsById.theme_previews");
diff --git a/app/assets/javascripts/wizard/test/test_helper.js b/app/assets/javascripts/wizard/test/test_helper.js
index c04a59f778..953ba72fda 100644
--- a/app/assets/javascripts/wizard/test/test_helper.js
+++ b/app/assets/javascripts/wizard/test/test_helper.js
@@ -12,7 +12,7 @@
//= require ember-template-compiler
//= require qunit/qunit/qunit
//= require ember-qunit
-//= require ember-shim
+//= require discourse-shims
//= require wizard-application
//= require wizard-vendor
//= require helpers/assertions
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index d887275e94..7fd4fc4f4c 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -487,10 +487,14 @@
.hex-input {
width: 80px;
margin-bottom: 0;
+ margin-right: 6px;
}
.hex {
text-align: center;
}
+ .color-input {
+ display: flex;
+ }
h3 {
margin: 0;
}
diff --git a/app/assets/stylesheets/common/admin/dashboard.scss b/app/assets/stylesheets/common/admin/dashboard.scss
index fbae66ab16..a2ba7db079 100644
--- a/app/assets/stylesheets/common/admin/dashboard.scss
+++ b/app/assets/stylesheets/common/admin/dashboard.scss
@@ -351,21 +351,21 @@
flex-direction: column;
.dashboard-inline-table {
- // and "hides" margin when the item spans 100% width
flex: 1 0 auto;
max-width: 95%;
}
+
.table-cell {
display: flex;
flex: 0 1 auto;
margin: 0 10px 5px 0;
- border: 1px solid $primary-low;
+ padding: 1px;
border-radius: 10px;
+
.label {
display: flex;
align-items: center;
color: $primary;
- background: $primary-very-low;
justify-content: center;
border-radius: 9px 0 0 9px;
padding: 0 5px 0 8px;
@@ -377,8 +377,11 @@
}
.value {
+ background: $secondary;
+ border-radius: 0 9px 9px 0;
padding: 0 8px 0 5px;
}
+
&.user-newuser {
.label {
color: $primary-high;
@@ -386,26 +389,20 @@
}
&.user-basic,
&.user-member {
- border-color: $bronze;
+ background: $bronze;
.label {
- border-color: $bronze;
- background: $bronze;
color: $secondary;
}
}
&.user-regular {
- border-color: $silver;
+ background: $silver;
.label {
- border-color: $silver;
- background: $silver;
color: $secondary;
}
}
&.user-leader {
- border-color: $gold;
+ background: $gold;
.label {
- background: $gold;
- border-color: $gold;
color: $secondary;
}
}
diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss
index 4d02ef28c6..9c8864333a 100644
--- a/app/assets/stylesheets/common/base/compose.scss
+++ b/app/assets/stylesheets/common/base/compose.scss
@@ -166,7 +166,7 @@
}
.whisper {
- margin-right: 0.25em;
+ margin: 0 0.25em;
}
.unlist {
diff --git a/app/assets/stylesheets/common/base/d-popover.scss b/app/assets/stylesheets/common/base/d-popover.scss
index 713432fec3..1e625926ab 100644
--- a/app/assets/stylesheets/common/base/d-popover.scss
+++ b/app/assets/stylesheets/common/base/d-popover.scss
@@ -75,20 +75,19 @@ $d-popover-border: $primary-medium;
color: transparent;
content: "";
position: absolute;
- z-index: calc(z("tooltip") - 100);
+ z-index: z("tooltip") - 100;
}
.d-popover-top-arrow {
border-color: transparent transparent $d-popover-border;
- top: 8px;
- transform: translate(0, -15px);
+ top: -8px;
border-width: 0 8px 8px;
&:after {
border-color: transparent transparent $d-popover-background;
border-style: solid;
border-width: 0 7px 7px;
- bottom: -8px;
+ bottom: -8.5px;
margin-left: -7px;
position: absolute;
content: "";
@@ -97,8 +96,7 @@ $d-popover-border: $primary-medium;
.d-popover-bottom-arrow {
border-color: $d-popover-border transparent transparent;
- top: calc(100% + 16px);
- transform: translate(0, -16px);
+ top: 100%;
border-width: 8px 8px 0;
&:after {
@@ -107,7 +105,7 @@ $d-popover-border: $primary-medium;
border-color: $d-popover-background transparent transparent;
border-style: solid;
border-width: 7px 7px 0;
- bottom: 2px;
+ bottom: 1.5px;
transform: translate(-7px, 0);
}
}
diff --git a/app/assets/stylesheets/common/base/discourse.scss b/app/assets/stylesheets/common/base/discourse.scss
index 831aba61b0..fd6dfef8de 100644
--- a/app/assets/stylesheets/common/base/discourse.scss
+++ b/app/assets/stylesheets/common/base/discourse.scss
@@ -687,14 +687,6 @@ table {
}
}
}
-
- .dropdown-menu {
- width: 120px;
-
- & .icon {
- margin-top: auto;
- }
- }
}
.topic-statuses {
diff --git a/app/assets/stylesheets/common/base/exception.scss b/app/assets/stylesheets/common/base/exception.scss
index 122b1605ed..455023b555 100644
--- a/app/assets/stylesheets/common/base/exception.scss
+++ b/app/assets/stylesheets/common/base/exception.scss
@@ -19,6 +19,7 @@
}
}
.buttons {
+ align-items: center;
display: inline-flex;
margin-top: 15px;
diff --git a/app/assets/stylesheets/common/base/menu-panel.scss b/app/assets/stylesheets/common/base/menu-panel.scss
index 24fa749aeb..b2b219603e 100644
--- a/app/assets/stylesheets/common/base/menu-panel.scss
+++ b/app/assets/stylesheets/common/base/menu-panel.scss
@@ -66,14 +66,20 @@
.menu-panel {
li,
li.heading {
- a.widget-link {
+ a.widget-link,
+ a.categories-link {
padding: 0.25em 0.5em;
display: block;
+ color: $primary;
&:hover,
&:focus {
background-color: $highlight-medium;
outline: none;
}
+
+ .d-icon {
+ color: $primary-medium;
+ }
}
.new {
@@ -83,11 +89,6 @@
}
}
- .categories-link {
- display: block;
- padding: 0.25em 0.5em;
- }
-
li.category-link {
float: left;
background-color: transparent;
@@ -101,6 +102,10 @@
&:hover,
&:focus {
background: transparent;
+
+ .category-name {
+ color: $primary;
+ }
}
}
.badge-notification {
@@ -149,6 +154,9 @@
.quick-access-panel {
width: 100%;
display: table;
+ margin-top: -1px;
+ border-top: 1px solid $primary-low;
+ padding-top: 0.5em;
h3 {
padding: 0 0.4em;
@@ -163,6 +171,7 @@
.icon {
color: $primary-high;
}
+
li {
background-color: $tertiary-low;
@@ -184,6 +193,7 @@
a {
padding: 0;
+
> div {
overflow: hidden; // clears the text from wrapping below icons
overflow-wrap: anywhere;
@@ -204,11 +214,15 @@
}
}
li:not(.show-all) {
- padding: 0.25em 0.5em;
+ padding: 0;
+
+ a {
+ display: flex;
+ padding: 0.25em 0.5em;
+ }
.d-icon {
- float: left;
- margin-right: 5px;
padding-top: 0.2em;
+ margin-right: 0.5em;
}
}
.is-warning {
@@ -246,6 +260,18 @@
}
/* as a big ol' click target, don't let text inside be selected */
@include unselectable;
+
+ &.quick-access-profile {
+ li:not(.show-all) a {
+ color: $primary;
+ .d-icon {
+ color: $primary-medium;
+ }
+
+ // accounts for menu "ears" 4px + border 1px
+ padding: 0.25em calc(0.5em + 4px + 1px);
+ }
+ }
}
.dismiss-link {
@@ -256,13 +282,10 @@
div.menu-links-header {
width: 100%;
- display: table;
- border-collapse: separate;
- border-spacing: 0 0.5em;
.menu-links-row {
- border-bottom: 1px solid dark-light-choose($primary-low, $secondary-medium);
+ box-sizing: border-box;
display: flex;
-
+ z-index: 2;
// Tabs should have "ears".
padding: 0 4px;
@@ -272,9 +295,11 @@ div.menu-links-header {
flex-wrap: wrap;
&.user {
margin-right: auto;
+ flex: 1 1 0;
+ overflow: hidden;
}
&.glyphs {
- flex-wrap: wrap;
+ flex-wrap: nowrap;
text-align: right;
a {
@@ -288,26 +313,17 @@ div.menu-links-header {
// This is to make sure active and inactive tab icons have the same
// size. `box-sizing` does not work and I have no idea why.
border: 1px solid transparent;
- border-bottom: 0;
+ &:not(.active):hover {
+ border-bottom: 0;
+ margin-top: -1px;
+ }
}
a.active {
border: 1px solid dark-light-choose($primary-low, $secondary-medium);
- border-bottom: 0;
+ border-bottom: 1px solid $secondary;
position: relative;
- &:after {
- display: block;
- position: absolute;
- top: 100%;
- left: 0;
- z-index: z("header") + 1; // Higher than .menu-panel
- width: 100%;
- height: 0;
- content: "";
- border-top: 1px solid $secondary;
- }
-
&:focus,
&:hover {
background-color: inherit;
@@ -324,10 +340,10 @@ div.menu-links-header {
padding: 0.3em 0.5em;
}
a.user-activity-link {
+ box-sizing: border-box;
align-items: center;
display: flex;
- margin: -0.5em 0;
- max-width: 130px;
+ max-width: 100%;
// `overflow: hidden` on `.user-activity-link` would hide the `::after`
// pseudo element (used to create the tab-looking effect). Sets `overflow:
@@ -336,7 +352,6 @@ div.menu-links-header {
span.d-label {
display: block;
- max-width: 130px;
@include ellipsis;
}
diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss
index 3f4d85c61f..a15a7cf7c0 100644
--- a/app/assets/stylesheets/common/base/modal.scss
+++ b/app/assets/stylesheets/common/base/modal.scss
@@ -29,9 +29,6 @@
}
.modal-open {
- .dropdown-menu {
- z-index: z("modal", "dropdown");
- }
.popover {
z-index: z("modal", "popover");
}
diff --git a/app/assets/stylesheets/common/base/popup-menu.scss b/app/assets/stylesheets/common/base/popup-menu.scss
new file mode 100644
index 0000000000..2df35b5a4c
--- /dev/null
+++ b/app/assets/stylesheets/common/base/popup-menu.scss
@@ -0,0 +1,59 @@
+.popup-menu {
+ background-color: $secondary;
+ width: 215px;
+ border: 1px solid $primary-low;
+ z-index: z("dropdown");
+ box-shadow: shadow("card");
+
+ ul {
+ margin: 0;
+ list-style: none;
+
+ li {
+ border-bottom: 1px solid rgba($primary-low, 0.5);
+
+ &:last-child {
+ border: none;
+ }
+ }
+ }
+
+ .btn {
+ text-align: left;
+ background: none;
+ width: 100%;
+ padding: 0.75em;
+ border-radius: 0;
+
+ .d-icon {
+ color: $primary-medium;
+ }
+
+ &:hover {
+ color: $primary;
+ background: $tertiary-low;
+
+ .d-icon {
+ color: $primary-medium;
+ }
+ }
+
+ &.popup-menu-btn-danger {
+ .d-icon {
+ color: $danger;
+ }
+
+ .d-button-label {
+ color: $primary;
+ }
+
+ &:hover {
+ .d-icon,
+ .d-button-label {
+ color: $danger;
+ }
+ background: $danger-low;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/common/base/search-menu.scss b/app/assets/stylesheets/common/base/search-menu.scss
index c3b6813e2c..530a13333c 100644
--- a/app/assets/stylesheets/common/base/search-menu.scss
+++ b/app/assets/stylesheets/common/base/search-menu.scss
@@ -68,6 +68,10 @@
display: contents;
}
}
+
+ a.widget-link {
+ color: $tertiary;
+ }
}
.main-results {
diff --git a/app/assets/stylesheets/common/base/topic-admin-menu.scss b/app/assets/stylesheets/common/base/topic-admin-menu.scss
index f310bc76e2..4a698c9c83 100644
--- a/app/assets/stylesheets/common/base/topic-admin-menu.scss
+++ b/app/assets/stylesheets/common/base/topic-admin-menu.scss
@@ -1,43 +1,9 @@
// Styles for the topic admin menu
-.show-topic-admin {
- position: fixed;
- top: 120px;
- right: 10px;
- z-index: z("dropdown");
- height: 35px;
-}
-
-.popup-menu {
- background-color: $secondary;
- width: 215px;
- padding: 10px;
- border: 1px solid $primary-low;
- z-index: z("dropdown");
- box-shadow: shadow("card");
-
- ul {
- list-style: none;
- margin: 10px 0 0 0;
- }
-
- .btn {
- text-align: left;
- }
-
- button {
- width: 100%;
- margin-bottom: 5px;
- }
-
- .header {
- .close-button {
- display: none;
- }
- }
-
+.topic-admin-popup-menu {
@include breakpoint(mobile-extra-large) {
- width: 100%;
+ width: calc(100% - 20px);
+ margin: 0 10px;
padding: 0;
padding-bottom: env(safe-area-inset-bottom);
z-index: z("modal", "popover");
@@ -51,19 +17,6 @@
}
}
animation: slideUp 0.3s;
-
- .header {
- padding: 10px 0 0 10px;
- display: flex;
- justify-content: space-between;
- .close-button {
- display: block;
- background: transparent;
- }
- }
- ul {
- margin-top: 0;
- }
}
.mobile-view & {
diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss
index 48c361c164..8ae08056b6 100644
--- a/app/assets/stylesheets/common/base/topic-post.scss
+++ b/app/assets/stylesheets/common/base/topic-post.scss
@@ -644,18 +644,20 @@ pre {
}
kbd {
- border-radius: 3px;
- box-shadow: shadow("kbd");
+ align-items: center;
background: dark-light-choose(#fafafa, #303030);
border: 1px solid dark-light-choose(#d0d0d0, #505050);
- border-bottom: none;
-
+ border-bottom-width: 2px;
+ border-radius: 3px;
+ box-sizing: border-box;
color: $primary;
- display: inline-block;
+ display: inline-flex;
font-size: $font-down-1;
+ justify-content: center;
line-height: $line-height-large;
- margin: 0 0.1em;
- padding: 0.1em 0.6em;
+ margin: 0 0.15em;
+ min-width: 24px;
+ padding: 0.15em 0.6em;
// don't allow more than 3 nested elements to prevent FF from crashing
// cf. http://what.thedailywtf.com/t/nested-elements/7927
@@ -833,12 +835,6 @@ a.mention-group {
}
}
-.popup-menu {
- h3 {
- margin-top: 0;
- }
-}
-
.suggested-topics {
.topics {
padding-bottom: 15px;
@@ -1053,3 +1049,15 @@ a.mention-group {
}
}
}
+
+.post-admin-menu {
+ display: inline-flex;
+ flex-direction: column;
+ box-sizing: border-box;
+ width: auto;
+ max-width: 320px;
+ position: absolute;
+ text-align: left;
+ bottom: -2px;
+ right: 15px;
+}
diff --git a/app/assets/stylesheets/common/components/bookmark-modal.scss b/app/assets/stylesheets/common/components/bookmark-modal.scss
index bf81e4ddfe..8d65fb4866 100644
--- a/app/assets/stylesheets/common/components/bookmark-modal.scss
+++ b/app/assets/stylesheets/common/components/bookmark-modal.scss
@@ -15,6 +15,7 @@
}
.ember-text-field.bookmark-name {
+ min-width: 220px;
width: 100%;
margin-bottom: 0.5em;
}
diff --git a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss
index 303acbdd65..644b357dde 100644
--- a/app/assets/stylesheets/common/components/keyboard_shortcuts.scss
+++ b/app/assets/stylesheets/common/components/keyboard_shortcuts.scss
@@ -55,7 +55,7 @@
border-radius: 3px;
display: inline-flex;
margin: 0 6px;
- padding: 1px 0 5px;
+ padding: 2px 1px 4px;
}
span:first-child {
@@ -63,19 +63,8 @@
}
kbd {
- align-items: center;
- box-sizing: border-box;
- color: dark-light-choose(#333, #aaa);
- display: inline-flex;
font-family: $base-font-family;
font-weight: bold;
- height: 24px;
- justify-content: center;
- margin: 0 3px;
- min-width: 24px;
- padding: 0 6px;
- vertical-align: bottom;
- white-space: nowrap;
}
}
}
diff --git a/app/assets/stylesheets/common/components/tap-tile.scss b/app/assets/stylesheets/common/components/tap-tile.scss
index e558b7adbf..51f2264fac 100644
--- a/app/assets/stylesheets/common/components/tap-tile.scss
+++ b/app/assets/stylesheets/common/components/tap-tile.scss
@@ -4,14 +4,17 @@
.tap-tile {
color: $primary-high;
- padding: 0.75em;
+ padding: 0.75em 0.25em;
display: flex;
flex-wrap: wrap;
align-items: center;
- border: 1px solid $primary-low;
- margin: 0 0 0.5em;
+ border-bottom: 1px solid $primary-low;
cursor: pointer;
+ &:first-child {
+ border-top: 1px solid $primary-low;
+ }
+
&:hover {
background-color: $tertiary-low;
}
@@ -21,12 +24,12 @@
}
.d-icon {
- color: $primary-medium;
+ color: $primary-high;
margin: 0 0.5em 0 0;
}
.tap-tile-title {
- font-weight: bold;
+ color: $primary;
margin-right: auto;
}
diff --git a/app/assets/stylesheets/common/foundation/variables.scss b/app/assets/stylesheets/common/foundation/variables.scss
index 22da7ac9bc..961a7f181c 100644
--- a/app/assets/stylesheets/common/foundation/variables.scss
+++ b/app/assets/stylesheets/common/foundation/variables.scss
@@ -35,10 +35,10 @@ $bronze: #cd7f32 !default;
// Fonts
// --------------------------------------------------
-$base-font-size-smaller: 14px !default;
-$base-font-size: 15px !default;
-$base-font-size-larger: 17px !default;
-$base-font-size-largest: 19px !default;
+$base-font-size-smaller: 0.875em !default; // eq. to 14px
+$base-font-size: 0.938em !default; // eq. to 15px
+$base-font-size-larger: 1.063em !default; // eq. to 17px
+$base-font-size-largest: 1.118em !default; // eq. to 19px
$base-font-family: Helvetica, Arial, sans-serif !default;
// Font-size defintions, multiplier ^ (step / interval)
diff --git a/app/assets/stylesheets/common/printer-friendly.scss b/app/assets/stylesheets/common/printer-friendly.scss
index e3ae895760..ce0d4687d3 100644
--- a/app/assets/stylesheets/common/printer-friendly.scss
+++ b/app/assets/stylesheets/common/printer-friendly.scss
@@ -13,7 +13,6 @@
#topic-progress-wrapper,
div.nums,
._flyout,
- .show-topic-admin,
#topic-progress,
.quote-controls,
.topic-status-info,
diff --git a/app/assets/stylesheets/common/select-kit/category-drop.scss b/app/assets/stylesheets/common/select-kit/category-drop.scss
index 8529cfd2a2..d7a6b3e0ba 100644
--- a/app/assets/stylesheets/common/select-kit/category-drop.scss
+++ b/app/assets/stylesheets/common/select-kit/category-drop.scss
@@ -60,13 +60,17 @@
}
.topic-count {
- margin-left: 5px;
+ margin-left: 0.5em;
font-weight: normal;
+ color: $primary-medium;
+ }
+
+ .category-name {
+ color: $primary;
}
.badge-wrapper {
margin: 0;
- font-weight: bold;
&:nth-child(2) {
margin-left: 10px;
diff --git a/app/assets/stylesheets/common/select-kit/combo-box.scss b/app/assets/stylesheets/common/select-kit/combo-box.scss
index 863e9fc336..368a481d72 100644
--- a/app/assets/stylesheets/common/select-kit/combo-box.scss
+++ b/app/assets/stylesheets/common/select-kit/combo-box.scss
@@ -8,7 +8,7 @@
.select-kit-row {
margin: 0;
min-height: 1px;
- padding: 6px 10px;
+
&.no-content {
font-weight: normal;
}
@@ -44,7 +44,6 @@
flex-direction: column;
padding: 0;
min-width: 100px;
- max-height: 200px;
.collection-header {
a {
@@ -53,7 +52,7 @@
line-height: $line-height-medium;
font-weight: bold;
display: block;
- padding: 6px 10px;
+ padding: 0.75em;
color: $tertiary;
&:hover {
diff --git a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss
index 5221072afa..97e5477a51 100644
--- a/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss
+++ b/app/assets/stylesheets/common/select-kit/dropdown-select-box.scss
@@ -1,7 +1,5 @@
.select-kit {
&.dropdown-select-box {
- display: -webkit-inline-box;
- display: -ms-inline-flexbox;
display: inline-flex;
min-width: auto;
border: none;
@@ -21,55 +19,27 @@
.select-kit-row {
margin: 0;
- padding: 10px 5px;
.icons {
- display: -webkit-box;
- display: -ms-flexbox;
display: flex;
- flex: 0 0 auto;
- -webkit-box-align: start;
- -ms-flex-align: start;
- align-items: flex-start;
- -webkit-box-pack: center;
- -ms-flex-pack: center;
- justify-content: center;
- -ms-flex-item-align: start;
- align-self: flex-start;
- margin-right: 0.357em;
- margin-top: 2px;
- width: 30px;
+ margin-right: 0.5em;
.d-icon {
font-size: $font-up-2;
- -ms-flex-item-align: center;
align-self: center;
margin-right: 0;
- opacity: 1;
}
}
.texts {
- line-height: $line-height-large;
- -webkit-box-flex: 1;
- -ms-flex: 1;
+ line-height: $line-height-medium;
flex: 1 1 0%;
- -webkit-box-align: start;
- -ms-flex-align: start;
align-items: flex-start;
- display: -webkit-box;
- display: -ms-flexbox;
display: flex;
- -ms-flex-wrap: wrap;
flex-wrap: wrap;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
- -ms-flex-direction: column;
flex-direction: column;
.name {
- -webkit-box-flex: 1;
- -ms-flex: 1;
flex: 1 1 auto;
font-weight: bold;
font-size: $font-0;
@@ -80,8 +50,6 @@
}
.desc {
- -webkit-box-flex: 1;
- -ms-flex: 1;
flex: 1 1 auto;
font-size: $font-down-1;
font-weight: normal;
@@ -98,27 +66,15 @@
}
.dropdown-select-box-header {
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
box-sizing: border-box;
border: 0;
-
- -webkit-box-align: center;
- -ms-flex-align: center;
align-items: center;
- -webkit-box-pack: justify;
- -ms-flex-pack: justify;
justify-content: space-between;
- -webkit-box-orient: horizontal;
- -webkit-box-direction: normal;
- -ms-flex-direction: row;
flex-direction: row;
- display: -webkit-inline-box;
- display: -ms-inline-flexbox;
display: inline-flex;
.d-icon + .d-icon {
- margin-left: 5px;
+ margin-left: 0.25em;
}
&.is-focused {
diff --git a/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss b/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss
index 2f67cf9642..a047c910f7 100644
--- a/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss
+++ b/app/assets/stylesheets/common/select-kit/future-date-input-selector.scss
@@ -10,9 +10,13 @@
}
.future-date-input-selector-datetime {
- color: lighten($primary, 40%);
- margin-left: 5px;
+ color: $primary-medium;
+ margin-left: auto;
white-space: nowrap;
+
+ + svg {
+ margin-left: 0.25em;
+ }
}
.future-date-input-selector-icons {
diff --git a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss
index 27c302e868..b167d54095 100644
--- a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss
+++ b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss
@@ -46,8 +46,14 @@
background: $tertiary-low;
}
+ .discourse-tag {
+ &:hover {
+ color: $primary;
+ }
+ }
+
.discourse-tag-count {
- margin-left: 5px;
+ margin-left: 0.5em;
}
}
diff --git a/app/assets/stylesheets/common/select-kit/notifications-button.scss b/app/assets/stylesheets/common/select-kit/notifications-button.scss
index 40ed042d53..6c7450a54c 100644
--- a/app/assets/stylesheets/common/select-kit/notifications-button.scss
+++ b/app/assets/stylesheets/common/select-kit/notifications-button.scss
@@ -7,8 +7,8 @@
.select-kit-row {
.icons {
- -ms-flex-item-align: start;
align-self: flex-start;
+ margin-right: 0.75em;
}
}
}
diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss
index a9769b5971..ff39b2df69 100644
--- a/app/assets/stylesheets/common/select-kit/select-kit.scss
+++ b/app/assets/stylesheets/common/select-kit/select-kit.scss
@@ -139,13 +139,13 @@
.select-kit-row {
cursor: pointer;
- line-height: $line-height-medium;
outline: none;
display: flex;
flex: 1 0 auto;
box-sizing: border-box;
align-items: center;
justify-content: flex-start;
+ padding: 0.75em;
.name {
margin: 0;
@@ -190,7 +190,7 @@
-webkit-overflow-scrolling: touch;
margin: 0;
padding: 0;
- max-height: 200px;
+ max-height: 250px;
width: 100%;
.validation-message {
diff --git a/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss b/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss
index 1077358f9b..60ba59881b 100644
--- a/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss
+++ b/app/assets/stylesheets/common/select-kit/toolbar-popup-menu-options.scss
@@ -3,52 +3,32 @@
&.toolbar-popup-menu-options {
.select-kit-body {
box-shadow: none;
- padding: 0.5em 0.5em 0.25em 0.5em;
width: 230px;
- }
-
- .toolbar-popup-menu-options-heading {
- width: 100%;
+ border-radius: 0;
}
.select-kit-row {
- margin-bottom: 0.25em;
- padding: 0.5em 0.25em;
- background: $primary-low;
- transition: all 0.25s;
+ padding: 0.75em 0.5em;
+ border-bottom: 1px solid rgba($primary-low, 0.5);
+
+ &:last-child {
+ border: none;
+ }
.texts .name,
.icons .d-icon {
font-size: $font-0;
font-weight: normal;
- color: $primary;
}
.d-icon {
- opacity: 0.7;
- }
-
- &.is-highlighted {
- background: $primary-medium;
-
- .name,
- .d-icon {
- color: $secondary;
- }
+ color: $primary-medium;
}
+ &.is-highlighted,
+ &.is-selected,
&:hover {
- background: $primary-medium;
- color: $secondary;
- }
-
- &.is-selected {
- color: $primary;
- background: $primary-low;
- }
-
- &.is-selected.is-highlighted {
- background: $primary-medium;
+ background: $tertiary-low;
color: $primary;
}
}
diff --git a/app/assets/stylesheets/common/topic-timeline.scss b/app/assets/stylesheets/common/topic-timeline.scss
index 2d13a8a5de..ccbdbcede1 100644
--- a/app/assets/stylesheets/common/topic-timeline.scss
+++ b/app/assets/stylesheets/common/topic-timeline.scss
@@ -33,6 +33,7 @@
&.timeline-fullscreen.show {
max-height: 700px;
transition: max-height 0.4s ease-out;
+
@media screen and (max-height: 425px) {
max-height: 75vh;
}
@@ -58,7 +59,7 @@
right: 0;
border-top: 1px solid dark-light-choose($primary-low, $secondary-low);
box-shadow: shadow("composer");
- padding-top: 20px;
+ padding: 20px 0px;
z-index: z("fullscreen");
@media screen and (max-height: 425px) {
padding-top: 10px;
@@ -119,21 +120,21 @@
.timeline-footer-controls {
display: none;
position: absolute;
- bottom: 10px;
+ bottom: 20px;
left: 10px;
button,
.btn-group {
- float: none;
- display: inline-block;
margin-bottom: 0;
margin-right: 15px;
- position: static;
+ }
+
+ .widget-component-connector {
+ vertical-align: top;
}
}
.timeline-scrollarea-wrapper {
display: table-cell;
- padding-bottom: 20px;
padding-right: 1.5em;
width: 100px;
}
@@ -178,10 +179,6 @@
.timeline-controls {
margin-bottom: 1em;
-
- button {
- margin-right: 0.5em;
- }
}
.timeline-footer-controls {
@@ -189,7 +186,7 @@
transition: opacity 0.2s ease-in;
display: flex;
- button {
+ .reply-to-post {
margin-right: 0.5em;
}
diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss
index 13e1d03da1..d17cef20ff 100644
--- a/app/assets/stylesheets/desktop/topic-list.scss
+++ b/app/assets/stylesheets/desktop/topic-list.scss
@@ -218,15 +218,6 @@ button.dismiss-read {
margin-left: 10px;
}
-.tags-admin-menu {
- .dropdown-menu {
- right: 0;
- top: 30px;
- bottom: auto;
- left: auto;
- }
-}
-
.category-heading {
p {
line-height: $line-height-large;
diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss
index 5adc17d3eb..ed1b906a30 100644
--- a/app/assets/stylesheets/desktop/topic-post.scss
+++ b/app/assets/stylesheets/desktop/topic-post.scss
@@ -53,93 +53,6 @@ section.post-menu-area {
nav.post-controls {
padding: 0;
- // Some buttons can be doubled up, like likes or flags
- .double-button {
- display: inline-flex;
- color: $primary-low-mid;
- margin-right: 0.15em;
- &:hover {
- button {
- background: $primary-low;
- color: $primary-medium;
- }
- }
- button {
- // It looks really confusing when one half a double button has an inner shadow on click.
- &:active {
- box-shadow: none;
- }
- margin-left: 0;
- margin-right: 0;
- &.my-likes,
- &.read-indicator,
- &.regular-likes {
- // Like count on posts
- .d-icon {
- color: $primary-low-mid;
- padding-left: 0.45em;
- }
- }
- &.like {
- // Like button with 0 likes
- &.d-hover {
- background: $love-low;
- .d-icon {
- color: $love;
- }
- }
- }
- &.has-like {
- // Like button after I've liked
- .d-icon {
- color: $love;
- }
- &.d-hover {
- background: $primary-low;
- .d-icon {
- color: $primary-medium;
- }
- }
- }
- &[disabled] {
- // Disabled like button
- cursor: not-allowed;
- }
- &.button-count {
- // Like count button
- &:not(.my-likes) {
- padding-right: 0;
- }
- &.d-hover {
- color: $primary;
- }
- + .toggle-like {
- // Like button when like count is present
- padding-left: 0.45em;
- &.d-hover {
- background: $primary-low;
- }
- }
- }
- }
- }
- a,
- button {
- color: dark-light-choose($primary-low-mid, $secondary-high);
- .d-icon {
- opacity: 1;
- }
- margin-right: 2px;
- display: inline-block;
- }
- a.toggle-likes {
- padding: 8px 0;
- margin-right: -3px;
- }
- span.badge-posts {
- margin-right: 5px;
- transition: all linear 0.15s;
- }
.actions {
text-align: right;
float: right;
@@ -148,103 +61,160 @@ nav.post-controls {
display: none;
overflow: hidden;
}
- }
- .show-replies {
- margin-left: -10px;
- font-size: inherit;
- span.badge-posts {
- color: dark-light-choose($primary-medium, $secondary-high);
+
+ // Some buttons can be doubled up, like likes or flags
+ .double-button {
+ display: inline-flex;
+ color: $primary-low-mid;
+ margin-right: 0.15em;
+ &:hover {
+ button {
+ background: $primary-low;
+ color: $primary-medium;
+ }
+ }
+ button {
+ // It looks really confusing when one half a double button has an inner shadow on click.
+ &:active {
+ box-shadow: none;
+ }
+ margin-left: 0;
+ margin-right: 0;
+ &.my-likes,
+ &.read-indicator,
+ &.regular-likes {
+ // Like count on posts
+ .d-icon {
+ color: $primary-low-mid;
+ padding-left: 0.45em;
+ }
+ }
+ &.like {
+ // Like button with 0 likes
+ &.d-hover {
+ background: $love-low;
+ .d-icon {
+ color: $love;
+ }
+ }
+ }
+ &.has-like {
+ // Like button after I've liked
+ .d-icon {
+ color: $love;
+ }
+ &.d-hover {
+ background: $primary-low;
+ .d-icon {
+ color: $primary-medium;
+ }
+ }
+ }
+ &[disabled] {
+ // Disabled like button
+ cursor: not-allowed;
+ }
+ &.button-count {
+ // Like count button
+ &:not(.my-likes) {
+ padding-right: 0;
+ }
+ &.d-hover {
+ color: $primary;
+ }
+ + .toggle-like {
+ // Like button when like count is present
+ padding-left: 0.45em;
+ &.d-hover {
+ background: $primary-low;
+ }
+ }
+ }
+ }
}
- &:hover {
- background: $primary-low;
- span.badge-posts {
+ a,
+ button {
+ color: dark-light-choose($primary-low-mid, $secondary-high);
+ .d-icon {
+ opacity: 1;
+ }
+ margin-right: 2px;
+ display: inline-block;
+ }
+ a.toggle-likes {
+ padding: 8px 0;
+ margin-right: -3px;
+ }
+ span.badge-posts {
+ margin-right: 5px;
+ transition: all linear 0.15s;
+ }
+
+ button.create {
+ margin-right: 0;
+ color: dark-light-choose($primary-high, $secondary-low);
+ margin-left: 10px;
+ .d-icon {
+ color: dark-light-choose($primary-high, $secondary-low);
+ }
+ }
+ .create .d-icon {
+ margin-right: 5px;
+ }
+ button {
+ font-size: $font-up-1;
+ padding: 8px 10px;
+ vertical-align: top;
+ background: transparent;
+ border: none;
+ margin-left: 3px;
+ &.d-hover,
+ &:focus {
+ background: $primary-low;
color: $primary;
}
+ &:active {
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
+ }
+ &.hidden {
+ display: none;
+ }
+ &.admin {
+ position: relative;
+ }
+ &.delete.d-hover,
+ &.delete:hover,
+ &.delete:focus {
+ background: $danger;
+ color: $secondary;
+ .d-icon {
+ color: $secondary;
+ }
+ }
+ &.bookmark {
+ padding: 8px 11px;
+ &.bookmarked .d-icon {
+ color: $tertiary;
+ }
+ }
+ }
+ }
+
+ .show-replies {
+ font-size: $font-up-1;
+ margin-left: -10px;
+ font-size: inherit;
+ padding: 10px;
+ color: $primary-medium;
+ &:hover {
+ color: $primary;
+ background: $primary-low;
}
.d-icon {
margin-left: 5px;
font-size: $font-down-1;
}
}
- button.create {
- margin-right: 0;
- color: dark-light-choose($primary-high, $secondary-low);
- margin-left: 10px;
- .d-icon {
- color: dark-light-choose($primary-high, $secondary-low);
- }
- }
- .create .d-icon {
- margin-right: 5px;
- }
- button {
- font-size: $font-up-1;
- padding: 8px 10px;
- vertical-align: top;
- background: transparent;
- border: none;
- margin-left: 3px;
- &.d-hover,
- &:focus {
- background: $primary-low;
- color: $primary;
- }
- &:active {
- box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
- }
- &.hidden {
- display: none;
- }
- &.admin {
- position: relative;
- }
- &.delete.d-hover,
- &.delete:hover,
- &.delete:focus {
- background: $danger;
- color: $secondary;
- .d-icon {
- color: $secondary;
- }
- }
- &.bookmark {
- padding: 8px 11px;
- &.bookmarked .d-icon {
- color: $tertiary;
- }
- }
- }
- .post-admin-menu {
- display: inline-flex;
- flex-direction: column;
- box-sizing: border-box;
- background-color: $secondary;
- width: auto;
- max-width: 320px;
- padding: 10px;
- border: 1px solid $primary-low;
- position: absolute;
- text-align: left;
- bottom: -2px;
- right: 15px;
- z-index: z("dropdown");
- h3 {
- margin-top: 0;
- color: $primary;
- font-size: $font-0;
- }
- ul {
- list-style: none;
- margin: 10px 0 0 0;
- }
- li {
- margin-bottom: 5px;
- .d-icon {
- width: 14px;
- margin-right: 10px;
- }
- }
- }
}
pre.copy-codeblocks .copy-cmd:not(.copied) {
@@ -533,10 +503,6 @@ video {
}
}
-.open > .dropdown-menu {
- display: block;
-}
-
.btn-group {
position: relative;
}
@@ -671,72 +637,6 @@ blockquote {
margin-left: 5px;
}
-.dropdown-menu {
- position: absolute;
- bottom: 115%;
- left: 0;
- z-index: z("dropdown");
- display: none;
- float: left;
- width: 550px;
- margin: 1px 0 0;
- list-style: none;
- background-color: $secondary;
- border: 1px solid $primary-low;
- box-shadow: shadow("dropdown");
- background-clip: padding-box;
- span {
- font-size: $font-down-1;
- color: dark-light-choose($primary-medium, $secondary-medium);
- }
- span.title {
- font-weight: bold;
- display: block;
- font-size: $font-0;
- color: $primary;
- }
-}
-
-.dropdown-menu a {
- display: block;
- padding: 9px;
- clear: both;
- font-weight: normal;
- line-height: $line-height-medium;
- color: $primary;
- transition: all linear 0.15s;
- & > div {
- margin-left: 26px;
- }
-}
-
-.dropdown-menu li > a:hover,
-.dropdown-menu .active > a,
-.dropdown-menu .active > a:hover {
- color: $primary;
- text-decoration: none;
- background-color: $highlight-medium;
-}
-
-.dropdown-menu .disabled > a,
-.dropdown-menu .disabled > a:hover {
- text-decoration: none;
- color: $primary;
- background-color: $tertiary-low;
- cursor: default;
-}
-
-.dropdown-menu .icon {
- margin-top: 3px;
- float: left;
- font-size: $font-up-2;
-}
-
-.open > .dropdown-menu {
- display: block;
- clear: both;
-}
-
.selected-posts {
width: 200px;
position: fixed;
diff --git a/app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss b/app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss
index 932ddf346f..6b0ebfcbc8 100644
--- a/app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss
+++ b/app/assets/stylesheets/mobile/components/topic-footer-mobile-dropdown.scss
@@ -2,9 +2,29 @@
&.combo-box {
&.topic-footer-mobile-dropdown {
.select-kit-row {
- .svg-icon-title {
- margin-right: 0.5em;
+ padding: 0.75em 0.5em;
+ border-bottom: 1px solid rgba($primary-low, 0.5);
+
+ .name {
+ line-height: $line-height-medium;
}
+
+ &:last-child {
+ border: none;
+ }
+
+ .d-icon {
+ color: $primary-medium;
+ }
+
+ &.is-highlighted {
+ background: $tertiary-low;
+
+ .name {
+ color: $primary;
+ }
+ }
+
&.bookmarked {
.d-icon {
color: $tertiary;
diff --git a/app/assets/stylesheets/mobile/menu-panel.scss b/app/assets/stylesheets/mobile/menu-panel.scss
index ab6a5e33cc..1966954317 100644
--- a/app/assets/stylesheets/mobile/menu-panel.scss
+++ b/app/assets/stylesheets/mobile/menu-panel.scss
@@ -11,13 +11,17 @@
left: 0;
display: none;
touch-action: pan-y pinch-zoom;
+
&.animate {
transition: opacity 0.2s ease-out;
}
}
-.user-menu .quick-access-profile li:not(.show-all) {
- margin: 0.5em 0 0.75em;
- padding: 0.75em;
- border: 1px solid $primary-low;
+.user-menu .quick-access-panel.quick-access-profile li:not(.show-all) {
+ border-bottom: 1px solid $primary-low;
+
+ a {
+ // accounts for menu "ears" 4px + border 1px
+ padding: 0.75em calc(0.5em + 4px + 1px);
+ }
}
diff --git a/app/assets/stylesheets/mobile/select-kit/dropdown-select-box.scss b/app/assets/stylesheets/mobile/select-kit/dropdown-select-box.scss
index 294b4a8b0e..57ed8327cc 100644
--- a/app/assets/stylesheets/mobile/select-kit/dropdown-select-box.scss
+++ b/app/assets/stylesheets/mobile/select-kit/dropdown-select-box.scss
@@ -1,12 +1,20 @@
.select-kit {
&.dropdown-select-box {
.select-kit-collection {
- max-height: 200px;
overflow-y: auto;
}
+
+ .select-kit-row {
+ .svg-icon-title {
+ width: auto;
+ height: auto;
+ }
+
+ .texts {
+ .name {
+ font-weight: normal;
+ }
+ }
+ }
}
}
-
-.select-kit.combo-box .select-kit-row {
- padding: 10px;
-}
diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss
index 516c864c04..5683d5cfd0 100644
--- a/app/assets/stylesheets/mobile/topic-list.scss
+++ b/app/assets/stylesheets/mobile/topic-list.scss
@@ -424,50 +424,7 @@ tr.category-topic-link {
content: "";
margin-left: 5px;
}
-.dropdown-menu {
- position: relative;
- top: 100%;
- left: 0;
- z-index: z("dropdown");
- display: none;
- float: left;
- width: 280px;
- padding: 4px 0;
- list-style: none;
- background-color: $secondary;
- border: 1px solid dark-light-choose(rgba(0, 0, 0, 0.2), $primary);
- border-radius: 5px;
- box-shadow: shadow("dropdown");
- background-clip: padding-box;
- margin: 1px 0 20px;
- .title {
- font-weight: bold;
- display: block;
- }
-}
-.dropdown-menu a {
- display: block;
- padding: 3px 15px;
- clear: both;
- font-weight: normal;
- line-height: $line-height-medium;
- color: $primary;
-}
-.dropdown-menu li > a:hover,
-.dropdown-menu .active > a,
-.dropdown-menu .active > a:hover {
- color: $tertiary;
- text-decoration: none;
- background-color: $tertiary-low;
-}
-.open > .dropdown-menu {
- display: block;
- clear: both;
-}
-.fade {
- opacity: 0;
- transition: opacity linear 0.15s;
-}
+
.fade.in {
opacity: 1;
}
diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss
index d84b4a727c..7031810bc0 100644
--- a/app/assets/stylesheets/mobile/topic-post.scss
+++ b/app/assets/stylesheets/mobile/topic-post.scss
@@ -31,69 +31,70 @@ span.badge-posts {
color: dark-light-choose($primary-low-mid, $secondary-high);
.actions {
display: flex;
- }
- // Handles the like and flag buttons in the post menu.
- .double-button {
- display: flex;
- flex: 0 1 auto;
- align-items: center;
- button {
- &.like,
- &.read-indicator,
- &.create-flag {
- flex: 1 1 auto;
- }
- &.button-count {
- padding: 8px 4px 8px 8px;
- + .toggle-like,
- + .create-flag {
- padding: 8px 8px 8px 4px;
+
+ // Handles the like and flag buttons in the post menu.
+ .double-button {
+ display: flex;
+ flex: 0 1 auto;
+ align-items: center;
+ button {
+ &.like,
+ &.read-indicator,
+ &.create-flag {
+ flex: 1 1 auto;
}
- &.my-likes,
- &.regular-likes {
- display: flex;
- max-width: unset;
- padding: 8px;
- .d-icon {
- padding-left: 8px;
+ &.button-count {
+ padding: 8px 4px 8px 8px;
+ + .toggle-like,
+ + .create-flag {
+ padding: 8px 8px 8px 4px;
+ }
+ &.my-likes,
+ &.regular-likes {
+ display: flex;
+ max-width: unset;
+ padding: 8px;
+ .d-icon {
+ padding-left: 8px;
+ }
}
}
}
}
- }
- .d-icon {
- opacity: 1;
- }
- button {
- border: none;
- font-size: $font-up-1;
- padding: 10px 8px;
- background: transparent;
- flex: 1 1 auto;
- max-width: 45px;
- &.hidden {
- display: none;
+ .d-icon {
+ opacity: 1;
}
- &.admin {
- position: relative;
- }
- &.expand-post {
- margin: 10px 0 10px 0;
- }
- &.reply {
- .d-icon {
- color: $primary-high;
+ button {
+ border: none;
+ font-size: $font-up-1;
+ padding: 10px 8px;
+ background: transparent;
+ flex: 1 1 auto;
+ max-width: 45px;
+ &.hidden {
+ display: none;
}
- margin-left: auto;
- }
- &.has-like {
- .d-icon {
- color: $love;
+ &.admin {
+ position: relative;
}
- }
- &.bookmarked {
- .d-icon {
- color: $tertiary;
+ &.expand-post {
+ margin: 10px 0 10px 0;
+ }
+ &.reply {
+ .d-icon {
+ color: $primary-high;
+ }
+ margin-left: auto;
+ }
+ &.has-like {
+ .d-icon {
+ color: $love;
+ }
+ }
+ &.bookmarked {
+ .d-icon {
+ color: $tertiary;
+ }
}
}
}
@@ -101,38 +102,11 @@ span.badge-posts {
}
.post-admin-menu {
- display: inline-flex;
- flex-direction: column;
- box-sizing: border-box;
- background-color: $secondary;
- width: auto;
- max-width: 320px;
- padding: 10px;
- border: 1px solid $primary-low;
- position: absolute;
- text-align: left;
bottom: -50px;
left: 135px;
@media screen and (max-width: 374px) {
left: 50px;
}
- z-index: z("dropdown");
- h3 {
- margin-top: 0;
- color: $primary;
- font-size: $font-0;
- }
- ul {
- list-style: none;
- margin: 10px 0 0 0;
- }
- li {
- margin-bottom: 5px;
- .d-icon {
- width: 14px;
- margin-right: 10px;
- }
- }
}
.embedded-posts {
@@ -313,25 +287,6 @@ iframe {
max-width: 100%;
}
-.open > .dropdown-menu {
- display: block;
-}
-
-.dropdown-menu li {
- margin: 5px 0;
- .fa {
- float: left;
- margin-right: 5px;
- padding-top: 1px;
- }
- span {
- color: dark-light-choose($primary-medium, $secondary-medium);
- }
- span.title {
- color: $primary;
- }
-}
-
.btn-group {
margin-top: 25px;
position: relative;
@@ -466,22 +421,6 @@ span.highlighted {
}
}
-.popup-menu,
-#topic-footer-buttons .popup-menu {
- h3 {
- margin-bottom: 0;
- }
-
- ul div li {
- border-bottom: 0.25em solid transparent;
- border-top: 0.25em solid transparent;
-
- button.btn {
- margin-bottom: 0;
- }
- }
-}
-
.post-notice {
margin-bottom: 1em;
&.old {
diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss
index ae82843411..f9e6b44de3 100644
--- a/app/assets/stylesheets/mobile/topic.scss
+++ b/app/assets/stylesheets/mobile/topic.scss
@@ -221,36 +221,23 @@ sub sub {
@media screen and (max-height: 600px) {
.topic-admin-popup-menu {
box-sizing: border-box;
- width: 100%;
- padding: 1em;
- .header {
- padding: 0;
- position: relative;
- .close-button {
- position: absolute;
- display: inline-block;
- right: 0;
- top: -0.1em;
- width: auto;
- background: transparent;
- }
- }
+ padding: 0.25em;
+
ul {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
+
@media screen and (max-width: 550px) {
grid-template-columns: 1fr 1fr;
}
- > div {
- margin-right: 0.5em;
- overflow: hidden;
- &:nth-of-type(2) {
- // move delete further from modal close
- order: 12;
- }
- button {
- @include ellipsis;
- }
+
+ .popup-menu-btn {
+ @include ellipsis;
+ }
+
+ li {
+ border: 0;
+ min-width: 0;
}
}
}
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 2f2b8a9694..e7f8c9750b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -39,12 +39,12 @@ class ApplicationController < ActionController::Base
before_action :redirect_to_login_if_required
before_action :block_if_requires_login
before_action :preload_json
+ before_action :add_noindex_header, if: -> { is_feed_request? || !SiteSetting.allow_index_in_robots_txt }
before_action :check_xhr
after_action :add_readonly_header
after_action :perform_refresh_session
after_action :dont_cache_page
after_action :conditionally_allow_site_embedding
- after_action :add_noindex_header, if: -> { is_feed_request? || !SiteSetting.allow_index_in_robots_txt }
layout :set_layout
@@ -245,12 +245,20 @@ class ApplicationController < ActionController::Base
end
end
- message = opts[:custom_message_translated] || I18n.t(opts[:custom_message] || type)
- error_page_opts = {
- title: opts[:custom_message_translated] || I18n.t(opts[:custom_message] || "page_not_found.title"),
- status: status_code,
- group: opts[:group]
- }
+ if opts[:custom_message_translated]
+ title = message = opts[:custom_message_translated]
+ elsif opts[:custom_message]
+ title = message = I18n.t(opts[:custom_message])
+ else
+ message = I18n.t(type)
+ if status_code == 403
+ title = I18n.t("page_forbidden.title")
+ else
+ title = I18n.t("page_not_found.title")
+ end
+ end
+
+ error_page_opts = { title: title, status: status_code, group: opts[:group] }
if show_json_errors
opts = { type: type, status: status_code }
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index 8fc6ad79bf..8a36480609 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -57,7 +57,7 @@ class CategoriesController < ApplicationController
@topic_list = TopicQuery.new(current_user, topic_options).list_latest
@topic_list.more_topics_url = url_for(public_send("latest_path"))
elsif style == "categories_and_top_topics"
- @topic_list = TopicQuery.new(nil, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
+ @topic_list = TopicQuery.new(current_user, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym)
@topic_list.more_topics_url = url_for(public_send("top_path"))
end
@@ -329,7 +329,6 @@ class CategoriesController < ApplicationController
:required_tag_group_name,
:min_tags_from_required_group,
:read_only_banner,
- :default_list_filter,
custom_fields: [params[:custom_fields].try(:keys)],
permissions: [*p.try(:keys)],
allowed_tags: [],
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index a1bbedc5ce..a21b3af346 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -84,7 +84,7 @@ class InvitesController < ApplicationController
guardian.ensure_can_invite_to_forum!(groups)
group_ids = groups.map(&:id)
- invite_exists = Invite.where(email: params[:email], invited_by_id: current_user.id).first
+ invite_exists = Invite.exists?(email: params[:email], invited_by_id: current_user.id)
if invite_exists && !guardian.can_send_multiple_invites?(current_user)
return render json: failed_json, status: 422
end
@@ -114,7 +114,7 @@ class InvitesController < ApplicationController
group_ids = groups.map(&:id)
- invite_exists = Invite.where(email: params[:email], invited_by_id: current_user.id).first
+ invite_exists = Invite.exists?(email: params[:email], invited_by_id: current_user.id)
if invite_exists && !guardian.can_send_multiple_invites?(current_user)
return render json: failed_json, status: 422
end
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 4830b3b979..4ee3b294af 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -748,7 +748,7 @@ class PostsController < ApplicationController
if recipients
recipients = recipients.split(",").map(&:downcase)
- groups = Group.messageable(current_user).where('lower(name) in (?)', recipients).pluck('name')
+ groups = Group.messageable(current_user).where('lower(name) in (?)', recipients).pluck('lower(name)')
recipients -= groups
emails = recipients.select { |user| user.match(/@/) }
recipients -= emails
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index ddb750f912..22edbcfe00 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -87,11 +87,7 @@ class TopicsController < ApplicationController
raise ex
rescue Discourse::NotLoggedIn => ex
- if !SiteSetting.detailed_404
- raise Discourse::NotFound
- else
- raise ex
- end
+ raise(SiteSetting.detailed_404 ? ex : Discourse::NotFound)
rescue Discourse::InvalidAccess => ex
# If the user can't see the topic, clean up notifications for it.
Notification.remove_for(current_user.id, params[:topic_id]) if current_user
@@ -948,11 +944,7 @@ class TopicsController < ApplicationController
begin
guardian.ensure_can_see!(topic)
rescue Discourse::InvalidAccess => ex
- if !SiteSetting.detailed_404
- raise Discourse::NotFound
- else
- raise ex
- end
+ raise(SiteSetting.detailed_404 ? ex : Discourse::NotFound)
end
url = topic.relative_url
diff --git a/app/jobs/base.rb b/app/jobs/base.rb
index b4e1f2e77b..da1623f816 100644
--- a/app/jobs/base.rb
+++ b/app/jobs/base.rb
@@ -281,8 +281,12 @@ module Jobs
end
end
- def self.enqueue(job_name, opts = {})
- klass = "::Jobs::#{job_name.to_s.camelcase}".constantize
+ def self.enqueue(job, opts = {})
+ if job.instance_of?(Class)
+ klass = job
+ else
+ klass = "::Jobs::#{job.to_s.camelcase}".constantize
+ end
# Unless we want to work on all sites
unless opts.delete(:all_sites)
@@ -319,7 +323,30 @@ module Jobs
klass.new.perform(opts)
end
else
- klass.new.perform(opts)
+ # Run the job synchronously
+ # But never run a job inside another job
+ # That could cause deadlocks during test runs
+ queue = Thread.current[:discourse_nested_job_queue]
+ outermost_job = !queue
+
+ if outermost_job
+ queue = Queue.new
+ Thread.current[:discourse_nested_job_queue] = queue
+ end
+
+ queue.push([klass, opts])
+
+ if outermost_job
+ # responsible for executing the queue
+ begin
+ until queue.empty?
+ queued_klass, queued_opts = queue.pop(true)
+ queued_klass.new.perform(queued_opts)
+ end
+ ensure
+ Thread.current[:discourse_nested_job_queue] = nil
+ end
+ end
end
end
diff --git a/app/jobs/onceoff/migrate_group_flair_images.rb b/app/jobs/onceoff/migrate_group_flair_images.rb
index 2b862cdf17..d613706065 100644
--- a/app/jobs/onceoff/migrate_group_flair_images.rb
+++ b/app/jobs/onceoff/migrate_group_flair_images.rb
@@ -77,7 +77,11 @@ module Jobs
origin: UrlHelper.absolute(old_url)
).create_for(Discourse.system_user.id)
- DB.exec("UPDATE groups SET flair_url = NULL, flair_upload_id = #{upload.id} WHERE id = #{group.id}") if upload.present?
+ if upload.errors.count > 0
+ logger.warn("Failed to create upload for '#{group_name}' group_flair: #{upload.errors.full_messages}")
+ else
+ DB.exec("UPDATE groups SET flair_url = NULL, flair_upload_id = #{upload.id} WHERE id = #{group.id}") if upload&.id.present?
+ end
end
end
diff --git a/app/jobs/onceoff/vacate_legacy_prefix_backups.rb b/app/jobs/onceoff/vacate_legacy_prefix_backups.rb
new file mode 100644
index 0000000000..0791a01c0d
--- /dev/null
+++ b/app/jobs/onceoff/vacate_legacy_prefix_backups.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Jobs
+ class VacateLegacyPrefixBackups < ::Jobs::Onceoff
+ def execute_onceoff(args)
+ args ||= {}
+ BackupRestore::S3BackupStore.create(s3_options: args[:s3_options]).vacate_legacy_prefix if SiteSetting.backup_location == BackupLocationSiteSetting::S3
+ end
+ end
+end
diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb
index 9485fc2fdc..7d9b00d166 100644
--- a/app/jobs/regular/export_csv_file.rb
+++ b/app/jobs/regular/export_csv_file.rb
@@ -231,6 +231,9 @@ module Jobs
if label[:type] == :user
titles[label[:properties][:username]] = label[:title]
header << label[:properties][:username]
+ elsif label[:type] == :topic
+ titles[label[:properties][:id]] = label[:title]
+ header << label[:properties][:id]
else
titles[label[:property]] = label[:title]
header << label[:property]
diff --git a/app/jobs/regular/pull_hotlinked_images.rb b/app/jobs/regular/pull_hotlinked_images.rb
index e7644e8ca8..cb0fee6062 100644
--- a/app/jobs/regular/pull_hotlinked_images.rb
+++ b/app/jobs/regular/pull_hotlinked_images.rb
@@ -59,7 +59,7 @@ module Jobs
if should_download_image?(src, post)
begin
# have we already downloaded that file?
- schemeless_src = remove_scheme(original_src)
+ schemeless_src = normalize_src(original_src)
unless downloaded_images.include?(schemeless_src) || large_images.include?(schemeless_src) || broken_images.include?(schemeless_src)
@@ -75,17 +75,17 @@ module Jobs
if upload.persisted?
downloaded_urls[src] = upload.url
- downloaded_images[remove_scheme(src)] = upload.id
+ downloaded_images[normalize_src(src)] = upload.id
has_downloaded_image = true
else
log(:info, "Failed to pull hotlinked image for post: #{post_id}: #{src} - #{upload.errors.full_messages.join("\n")}")
end
else
- large_images << remove_scheme(original_src)
+ large_images << normalize_src(original_src)
has_new_large_image = true
end
else
- broken_images << remove_scheme(original_src)
+ broken_images << normalize_src(original_src)
has_new_broken_image = true
end
end
@@ -95,8 +95,8 @@ module Jobs
escaped_src = Regexp.escape(original_src)
replace_raw = ->(match, match_src, replacement, _index) {
- if remove_scheme(src) == remove_scheme(match_src)
+ if normalize_src(src) == normalize_src(match_src)
replacement =
if replacement.include?(InlineUploads::PLACEHOLDER)
replacement.sub(InlineUploads::PLACEHOLDER, upload.short_url)
@@ -152,7 +152,10 @@ module Jobs
changes = { raw: raw, edit_reason: I18n.t("upload.edit_reason") }
post.revise(Discourse.system_user, changes, bypass_bump: true, skip_staff_log: true)
elsif has_downloaded_image || has_new_large_image || has_new_broken_image
- post.trigger_post_process(bypass_bump: true)
+ post.trigger_post_process(
+ bypass_bump: true,
+ skip_pull_hotlinked_images: true # Avoid an infinite loop of job scheduling
+ )
end
end
@@ -212,8 +215,13 @@ module Jobs
private
- def remove_scheme(src)
- src.sub(/^https?:/i, "")
+ def normalize_src(src)
+ uri = Addressable::URI.heuristic_parse(src)
+ uri.normalize!
+ uri.scheme = nil
+ uri.to_s
+ rescue URI::Error
+ src
end
end
diff --git a/app/jobs/regular/user_email.rb b/app/jobs/regular/user_email.rb
index afa1105e4f..bee715099d 100644
--- a/app/jobs/regular/user_email.rb
+++ b/app/jobs/regular/user_email.rb
@@ -26,16 +26,17 @@ module Jobs
notification = nil
type = args[:type]
user = User.find_by(id: args[:user_id])
- to_address = args[:to_address].presence || user.try(:email).presence || "no_email_found"
+ to_address = args[:to_address].presence || user&.primary_email&.email.presence || "no_email_found"
set_skip_context(type, args[:user_id], to_address, args[:post_id])
- return skip(SkippedEmailLog.reason_types[:user_email_no_user]) unless user
+ return skip(SkippedEmailLog.reason_types[:user_email_no_user]) if !user
+ return skip(SkippedEmailLog.reason_types[:user_email_no_email]) if to_address == "no_email_found"
if args[:post_id].present?
post = Post.find_by(id: args[:post_id])
- unless post.present?
+ if post.blank?
return skip(SkippedEmailLog.reason_types[:user_email_post_not_found])
end
diff --git a/app/jobs/scheduled/enqueue_digest_emails.rb b/app/jobs/scheduled/enqueue_digest_emails.rb
index 18db50135a..b126dc1553 100644
--- a/app/jobs/scheduled/enqueue_digest_emails.rb
+++ b/app/jobs/scheduled/enqueue_digest_emails.rb
@@ -14,13 +14,15 @@ module Jobs
def target_user_ids
# Users who want to receive digest email within their chosen digest email frequency
- query = User.real
- .not_suspended
+ query = User
+ .real
.activated
+ .not_suspended
.where(staged: false)
- .joins(:user_option, :user_stat)
+ .joins(:user_option, :user_stat, :user_emails)
.where("user_options.email_digests")
.where("user_stats.bounce_score < #{SiteSetting.bounce_score_threshold}")
+ .where("user_emails.primary")
.where("COALESCE(last_emailed_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)")
.where("COALESCE(last_seen_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)")
.where("COALESCE(last_seen_at, '2010-01-01') >= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * #{SiteSetting.suppress_digest_email_after_days})")
diff --git a/app/jobs/scheduled/old_keys_reminder.rb b/app/jobs/scheduled/old_keys_reminder.rb
new file mode 100644
index 0000000000..8689c60cbf
--- /dev/null
+++ b/app/jobs/scheduled/old_keys_reminder.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Jobs
+ class OldKeysReminder < ::Jobs::Scheduled
+ every 1.month
+
+ OLD_CREDENTIALS_PERIOD = 2.years
+
+ def execute(_args)
+ return if SiteSetting.send_old_credential_reminder_days.to_i == 0
+ return if message_exists?
+ return if old_site_settings_keys.blank? && old_api_keys.blank?
+ PostCreator.create!(
+ Discourse.system_user,
+ title: title,
+ raw: body,
+ archetype: Archetype.private_message,
+ target_usernames: admins.map(&:username),
+ validate: false
+ )
+ end
+
+ private
+
+ def old_site_settings_keys
+ @old_site_settings_keys ||= SiteSetting.secret_settings.each_with_object([]) do |secret_name, old_keys|
+ site_setting = SiteSetting.find_by(name: secret_name)
+ next if site_setting&.value.blank?
+ next if site_setting.updated_at + OLD_CREDENTIALS_PERIOD > Time.zone.now
+ old_keys << site_setting
+ end.sort_by { |key| key.updated_at }
+ end
+
+ def old_api_keys
+ @old_api_keys ||= ApiKey.all.order(created_at: :asc).each_with_object([]) do |api_key, old_keys|
+ next if api_key.created_at + OLD_CREDENTIALS_PERIOD > Time.zone.now
+ old_keys << api_key
+ end
+ end
+
+ def admins
+ User.real.admins
+ end
+
+ def message_exists?
+ message = Topic.private_messages.with_deleted.find_by(title: title)
+ message && message.created_at + SiteSetting.send_old_credential_reminder_days.to_i.days > Time.zone.now
+ end
+
+ def title
+ I18n.t('old_keys_reminder.title')
+ end
+
+ def body
+ I18n.t('old_keys_reminder.body', keys: keys_list)
+ end
+
+ def keys_list
+ messages = old_site_settings_keys.map { |key| "#{key.name} - #{key.updated_at.to_date.to_s(:db)}" }
+ old_api_keys.each_with_object(messages) { |key, array| array << "#{[key.description, key.user&.username, key.created_at.to_date.to_s(:db)].compact.join(" - ")}" }
+ messages.join("\n")
+ end
+ end
+end
diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb
index 3125f8f9fe..c90af0f147 100644
--- a/app/models/bookmark.rb
+++ b/app/models/bookmark.rb
@@ -90,7 +90,7 @@ end
# updated_at :datetime not null
# reminder_last_sent_at :datetime
# reminder_set_at :datetime
-# delete_when_reminder_sent :boolean default(FALSE)
+# delete_when_reminder_sent :boolean default(FALSE), not null
#
# Indexes
#
@@ -102,9 +102,3 @@ end
# index_bookmarks_on_user_id (user_id)
# index_bookmarks_on_user_id_and_post_id (user_id,post_id) UNIQUE
#
-# Foreign Keys
-#
-# fk_rails_... (post_id => posts.id)
-# fk_rails_... (topic_id => topics.id)
-# fk_rails_... (user_id => users.id)
-#
diff --git a/app/models/category.rb b/app/models/category.rb
index 6ecfe9d3af..d6375eb714 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -972,6 +972,7 @@ end
# required_tag_group_id :integer
# min_tags_from_required_group :integer default(1), not null
# read_only_banner :string
+# default_list_filter :string(20) default("all")
#
# Indexes
#
diff --git a/app/models/global_setting.rb b/app/models/global_setting.rb
index 890f5ef06c..a31df606eb 100644
--- a/app/models/global_setting.rb
+++ b/app/models/global_setting.rb
@@ -134,7 +134,11 @@ class GlobalSetting
end
end
- hash["adapter"] = "postgresql_fallback" if hash["replica_host"]
+ 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?
@@ -165,15 +169,9 @@ class GlobalSetting
c[:port] = redis_port if redis_port
if redis_slave_host && redis_slave_port
- if ENV["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
+ c[:slave_host] = redis_slave_host
+ c[:slave_port] = redis_slave_port
+ c[:connector] = DiscourseRedis::Connector
end
c[:password] = redis_password if redis_password.present?
@@ -195,15 +193,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["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[:slave_host] = message_bus_redis_slave_host
+ c[:slave_port] = message_bus_redis_slave_port
+ c[:connector] = DiscourseRedis::Connector
end
c[:password] = message_bus_redis_password if message_bus_redis_password.present?
diff --git a/app/models/group.rb b/app/models/group.rb
index 89ce80bbf4..d99fb25721 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -923,7 +923,6 @@ end
# grant_trust_level :integer
# incoming_email :string
# has_messages :boolean default(FALSE), not null
-# flair_url :string
# flair_bg_color :string
# flair_color :string
# bio_raw :text
@@ -934,11 +933,13 @@ end
# visibility_level :integer default(0), not null
# public_exit :boolean default(FALSE), not null
# public_admission :boolean default(FALSE), not null
-# publish_read_state :boolean default(FALSE), not null
# membership_request_template :text
# messageable_level :integer default(0)
# mentionable_level :integer default(0)
+# publish_read_state :boolean default(FALSE), not null
# members_visibility_level :integer default(0), not null
+# flair_icon :string
+# flair_upload_id :integer
#
# Indexes
#
diff --git a/app/models/post.rb b/app/models/post.rb
index e1a3a1f391..37b5deadc9 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -651,7 +651,7 @@ class Post < ActiveRecord::Base
)
if is_first_post?
- topic.update_excerpt(excerpt_for_topic)
+ topic&.update_excerpt(excerpt_for_topic)
end
if invalidate_broken_images
@@ -748,11 +748,12 @@ class Post < ActiveRecord::Base
end
# Enqueue post processing for this post
- def trigger_post_process(bypass_bump: false, priority: :normal, new_post: false)
+ 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,
new_post: new_post,
+ 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?
diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb
index 99c2ad62c0..8e8cbd4b13 100644
--- a/app/models/reviewable.rb
+++ b/app/models/reviewable.rb
@@ -450,7 +450,7 @@ class Reviewable < ActiveRecord::Base
min_score = Reviewable.min_score_for_priority(priority)
order = case sort_order
- when 'priority_asc'
+ when 'score_asc'
'reviewables.score ASC, reviewables.created_at DESC'
when 'created_at'
'reviewables.created_at DESC, reviewables.score DESC'
diff --git a/app/models/skipped_email_log.rb b/app/models/skipped_email_log.rb
index 9ffa2230e9..1a4ec415aa 100644
--- a/app/models/skipped_email_log.rb
+++ b/app/models/skipped_email_log.rb
@@ -37,7 +37,8 @@ class SkippedEmailLog < ActiveRecord::Base
sender_post_deleted: 20,
sender_message_to_invalid: 21,
user_email_access_denied: 22,
- sender_topic_deleted: 23
+ sender_topic_deleted: 23,
+ user_email_no_email: 24,
# you need to add the reason in server.en.yml below the "skipped_email_log" key
# when you add a new enum value
)
diff --git a/app/models/theme.rb b/app/models/theme.rb
index 4f54f9a518..da9d357729 100644
--- a/app/models/theme.rb
+++ b/app/models/theme.rb
@@ -122,6 +122,19 @@ class Theme < ActiveRecord::Base
SvgSprite.expire_cache
end
+ BASE_COMPILER_VERSION = 16
+ def self.compiler_version
+ get_set_cache "compiler_version" do
+ dependencies = [
+ BASE_COMPILER_VERSION,
+ Ember::VERSION,
+ GlobalSetting.cdn_url,
+ Discourse.current_hostname
+ ]
+ Digest::SHA1.hexdigest(dependencies.join)
+ end
+ end
+
def self.get_set_cache(key, &blk)
if @cache.hash.key? key.to_s
return @cache[key]
@@ -248,7 +261,7 @@ class Theme < ActiveRecord::Base
theme_ids = [theme_ids] unless Array === theme_ids
theme_ids = transform_ids(theme_ids)
- cache_key = "#{theme_ids.join(",")}:#{target}:#{field}:#{ThemeField::COMPILER_VERSION}"
+ cache_key = "#{theme_ids.join(",")}:#{target}:#{field}:#{Theme.compiler_version}"
lookup = @cache[cache_key]
return lookup.html_safe if lookup
@@ -262,7 +275,7 @@ class Theme < ActiveRecord::Base
theme_ids = [theme_ids] unless Array === theme_ids
theme_ids = transform_ids(theme_ids)
- get_set_cache("#{theme_ids.join(",")}:modifier:#{modifier_name}:#{ThemeField::COMPILER_VERSION}") do
+ get_set_cache("#{theme_ids.join(",")}:modifier:#{modifier_name}:#{Theme.compiler_version}") do
ThemeModifierSet.resolve_modifier_for_themes(theme_ids, modifier_name)
end
end
@@ -321,7 +334,7 @@ class Theme < ActiveRecord::Base
def self.resolve_baked_field(theme_ids, target, name)
if target == :extra_js
require_rebake = ThemeField.where(theme_id: theme_ids, target_id: Theme.targets[:extra_js]).
- where("compiler_version <> ?", ThemeField::COMPILER_VERSION)
+ where("compiler_version <> ?", Theme.compiler_version)
require_rebake.each { |tf| tf.ensure_baked! }
require_rebake.map(&:theme_id).uniq.each do |theme_id|
Theme.find(theme_id).update_javascript_cache!
diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb
index c2e8d06a72..a15d42ec9e 100644
--- a/app/models/theme_field.rb
+++ b/app/models/theme_field.rb
@@ -60,14 +60,6 @@ class ThemeField < ActiveRecord::Base
validates :name, format: { with: /\A[a-z_][a-z0-9_-]*\z/i },
if: Proc.new { |field| ThemeField.theme_var_type_ids.include?(field.type_id) }
- BASE_COMPILER_VERSION = 16
- DEPENDENT_CONSTANTS = [
- BASE_COMPILER_VERSION,
- Ember::VERSION,
- GlobalSetting.cdn_url
- ]
- COMPILER_VERSION = Digest::SHA1.hexdigest(DEPENDENT_CONSTANTS.join)
-
belongs_to :theme
def process_html(html)
@@ -311,18 +303,18 @@ class ThemeField < ActiveRecord::Base
end
def ensure_baked!
- needs_baking = !self.value_baked || compiler_version != COMPILER_VERSION
+ needs_baking = !self.value_baked || compiler_version != Theme.compiler_version
return unless needs_baking
if basic_html_field? || translation_field?
self.value_baked, self.error = translation_field? ? process_translation : process_html(self.value)
self.error = nil unless self.error.present?
- self.compiler_version = COMPILER_VERSION
+ self.compiler_version = Theme.compiler_version
DB.after_commit { CSP::Extension.clear_theme_extensions_cache! }
elsif extra_js_field?
self.value_baked, self.error = process_extra_js(self.value)
self.error = nil unless self.error.present?
- self.compiler_version = COMPILER_VERSION
+ self.compiler_version = Theme.compiler_version
elsif basic_scss_field?
ensure_scss_compiles!
DB.after_commit { Stylesheet::Manager.clear_theme_cache! }
@@ -332,12 +324,12 @@ class ThemeField < ActiveRecord::Base
DB.after_commit { CSP::Extension.clear_theme_extensions_cache! }
DB.after_commit { SvgSprite.expire_cache }
self.value_baked = "baked"
- self.compiler_version = COMPILER_VERSION
+ self.compiler_version = Theme.compiler_version
elsif svg_sprite_field?
DB.after_commit { SvgSprite.expire_cache }
self.error = nil
self.value_baked = "baked"
- self.compiler_version = COMPILER_VERSION
+ self.compiler_version = Theme.compiler_version
end
if self.will_save_change_to_value_baked? ||
@@ -370,7 +362,7 @@ class ThemeField < ActiveRecord::Base
rescue SassC::SyntaxError => e
self.error = e.message unless self.destroyed?
end
- self.compiler_version = COMPILER_VERSION
+ self.compiler_version = Theme.compiler_version
self.value_baked = Digest::SHA1.hexdigest(result.join(",")) # We don't use the compiled CSS here, we just use it to invalidate the stylesheet cache
end
diff --git a/app/models/theme_modifier_set.rb b/app/models/theme_modifier_set.rb
index 9fae7faf54..781ea42f51 100644
--- a/app/models/theme_modifier_set.rb
+++ b/app/models/theme_modifier_set.rb
@@ -98,12 +98,12 @@ end
#
# Table name: theme_modifier_sets
#
-# id :bigint not null, primary key
-# theme_id :bigint not null
-# serialize_topic_excerpts :boolean
-# csp_extensions :string is an Array
-# svg_icons :string is an Array
-# topic_thumbnail_sizes :string is an Array
+# id :bigint not null, primary key
+# theme_id :bigint not null
+# serialize_topic_excerpts :boolean
+# csp_extensions :string is an Array
+# svg_icons :string is an Array
+# topic_thumbnail_sizes :string is an Array
#
# Indexes
#
diff --git a/app/models/topic_custom_field.rb b/app/models/topic_custom_field.rb
index e922b7b26e..ba5767084e 100644
--- a/app/models/topic_custom_field.rb
+++ b/app/models/topic_custom_field.rb
@@ -17,7 +17,6 @@ end
#
# Indexes
#
-# idx_topic_custom_fields_accepted_answer (topic_id) UNIQUE WHERE ((name)::text = 'accepted_answer_post_id'::text)
# index_topic_custom_fields_on_topic_id_and_name (topic_id,name)
# topic_custom_fields_value_key_idx (value,name) WHERE ((value IS NOT NULL) AND (char_length(value) < 400))
#
diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb
index d8aef83080..7529e98353 100644
--- a/app/models/topic_embed.rb
+++ b/app/models/topic_embed.rb
@@ -116,7 +116,7 @@ class TopicEmbed < ActiveRecord::Base
)
url = fd.resolve
- raise URI::InvalidURIError if url.blank?
+ return if url.blank?
opts = {
tags: %w[div p code pre h1 h2 h3 b em i strong a img ul li ol blockquote],
diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb
index 244ce472b9..dcf1d60400 100644
--- a/app/models/topic_tracking_state.rb
+++ b/app/models/topic_tracking_state.rb
@@ -9,7 +9,6 @@ class TopicTrackingState
include ActiveModel::SerializerSupport
- CHANNEL = "/user-tracking"
UNREAD_MESSAGE_TYPE = "unread"
LATEST_MESSAGE_TYPE = "latest"
MUTED_MESSAGE_TYPE = "muted"
@@ -186,6 +185,14 @@ class TopicTrackingState
).where_clause.send(:predicates)[0]
end
+ def self.include_tags_in_report?
+ @include_tags_in_report
+ end
+
+ def self.include_tags_in_report=(v)
+ @include_tags_in_report = v
+ end
+
def self.report(user, topic_id = nil)
# Sam: this is a hairy report, in particular I need custom joins and fancy conditions
# Dropping to sql_builder so I can make sense of it.
@@ -220,6 +227,18 @@ class TopicTrackingState
muted_tag_ids: tag_ids
)
+ if SiteSetting.tagging_enabled && TopicTrackingState.include_tags_in_report?
+ sql = <<~SQL
+ WITH X AS (#{sql})
+ SELECT *, (
+ SELECT ARRAY_AGG(name) from topic_tags
+ JOIN tags on tags.id = topic_tags.tag_id
+ WHERE topic_id = X.topic_id
+ ) tags
+ FROM X
+ SQL
+ end
+
DB.query(
sql,
user_id: user.id,
@@ -295,18 +314,26 @@ class TopicTrackingState
"(topics.visible #{append}) AND"
end
- tags_filter =
- if opts[:muted_tag_ids].present? && SiteSetting.remove_muted_tags_from_latest == 'always'
- <<~SQL
- NOT ((select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id) && ARRAY[#{opts[:muted_tag_ids].join(',')}]) AND
+ tags_filter = ""
+
+ if (muted_tag_ids = opts[:muted_tag_ids]).present? && ['always', 'only_muted'].include?(SiteSetting.remove_muted_tags_from_latest)
+ existing_tags_sql = "(select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id)"
+ muted_tags_array_sql = "ARRAY[#{opts[:muted_tag_ids].join(',')}]"
+
+ if SiteSetting.remove_muted_tags_from_latest == 'always'
+ tags_filter = <<~SQL
+ NOT (
+ COALESCE(#{existing_tags_sql}, ARRAY[]::int[]) && #{muted_tags_array_sql}
+ ) AND
SQL
- elsif opts[:muted_tag_ids].present? && SiteSetting.remove_muted_tags_from_latest == 'only_muted'
- <<~SQL
- NOT ((select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id) <@ ARRAY[#{opts[:muted_tag_ids].join(',')}]) AND
+ else # only muted
+ tags_filter = <<~SQL
+ NOT (
+ COALESCE(#{existing_tags_sql}, ARRAY[-999]) <@ #{muted_tags_array_sql}
+ ) AND
SQL
- else
- ""
end
+ end
sql = +<<~SQL
SELECT #{select}
diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb
index 06acfc0c4f..d5bb901346 100644
--- a/app/serializers/site_serializer.rb
+++ b/app/serializers/site_serializer.rb
@@ -26,7 +26,8 @@ class SiteSerializer < ApplicationSerializer
:topic_featured_link_allowed_category_ids,
:user_themes,
:censored_regexp,
- :shared_drafts_category_id
+ :shared_drafts_category_id,
+ :custom_emoji_translation
)
has_many :categories, serializer: SiteCategorySerializer, embed: :objects
@@ -154,6 +155,10 @@ class SiteSerializer < ApplicationSerializer
WordWatcher.word_matcher_regexp(:censor)&.source
end
+ def custom_emoji_translation
+ Plugin::CustomEmoji.translations
+ end
+
def shared_drafts_category_id
SiteSetting.shared_drafts_category.to_i
end
diff --git a/app/serializers/wizard_serializer.rb b/app/serializers/wizard_serializer.rb
index d830ad7325..76990716e2 100644
--- a/app/serializers/wizard_serializer.rb
+++ b/app/serializers/wizard_serializer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class WizardSerializer < ApplicationSerializer
- attributes :start, :completed
+ attributes :start, :completed, :current_color_scheme
has_many :steps, serializer: WizardStepSerializer, embed: :objects
@@ -12,4 +12,14 @@ class WizardSerializer < ApplicationSerializer
def completed
object.completed?
end
+
+ def current_color_scheme
+ color_scheme = Theme.where(id: SiteSetting.default_theme_id).first&.color_scheme
+ colors = color_scheme ? color_scheme.colors : ColorScheme.base_colors
+
+ # The frontend expects the color hexs to start with '#'
+ colors_with_hash = {}
+ colors.each { |color, hex| colors_with_hash[color] = "##{hex}" }
+ colors_with_hash
+ end
end
diff --git a/app/services/destroy_task.rb b/app/services/destroy_task.rb
index b76d8f7f57..a366312cb9 100644
--- a/app/services/destroy_task.rb
+++ b/app/services/destroy_task.rb
@@ -90,6 +90,8 @@ class DestroyTask
raise Discourse::InvalidAccess.new("User #{user.username} has #{user.post_count} posts, so can't be deleted.")
rescue NoMethodError
@io.puts "#{user.username} could not be deleted"
+ rescue Discourse::InvalidAccess => e
+ @io.puts "#{user.username} #{e.message}"
end
end
end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 862e00f2db..9cb5efcce9 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -109,7 +109,7 @@
<% end %>
- <%= preload_script "preload-application-data" %>
+ <%= preload_script "start-discourse" %>
<%= yield :data %>
diff --git a/config/application.rb b/config/application.rb
index 808f9e4592..f2cbb14391 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -27,8 +27,8 @@ require_relative '../lib/discourse_plugin_registry'
require_relative '../lib/plugin_gem'
-if ENV['RAILS_FAILOVER']
- require 'rails_failover'
+if ENV['ACTIVE_RECORD_RAILS_FAILOVER']
+ require 'rails_failover/active_record'
end
# Global config
@@ -154,7 +154,7 @@ module Discourse
service-worker.js
google-tag-manager.js
google-universal-analytics.js
- preload-application-data.js
+ start-discourse.js
print-page.js
omniauth-complete.js
activate-account.js
diff --git a/config/initializers/001-redis.rb b/config/initializers/001-redis.rb
index f28c59a3b1..d11303a9d5 100644
--- a/config/initializers/001-redis.rb
+++ b/config/initializers/001-redis.rb
@@ -4,18 +4,3 @@ if Rails.env.development? && ENV['DISCOURSE_FLUSH_REDIS']
puts "Flushing redis (development mode)"
Discourse.redis.flushdb
end
-
-if ENV['RAILS_FAILOVER']
- message_bus_keepalive_interval = MessageBus.keepalive_interval
-
- RailsFailover::Redis.register_master_up_callback do
- MessageBus.keepalive_interval = message_bus_keepalive_interval
- Discourse.clear_readonly!
- Discourse.request_refresh!
- end
-
- RailsFailover::Redis.register_master_down_callback do
- # Disables MessageBus keepalive when Redis is in readonly mode
- MessageBus.keepalive_interval = 0
- end
-end
diff --git a/config/initializers/002-rails_failover.rb b/config/initializers/002-rails_failover.rb
new file mode 100644
index 0000000000..9985ecd520
--- /dev/null
+++ b/config/initializers/002-rails_failover.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+if ENV["ACTIVE_RECORD_RAILS_FAILOVER"]
+ RailsFailover::ActiveRecord.on_failover do
+ Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
+ Sidekiq.pause!("pg_failover") if !Sidekiq.paused?
+ end
+
+ RailsFailover::ActiveRecord.on_fallback do
+ Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
+ Sidekiq.unpause!
+ end
+
+ module Discourse
+ PG_FORCE_READONLY_MODE_KEY ||= 'readonly_mode:postgres_force'
+
+ READONLY_KEYS.push(PG_FORCE_READONLY_MODE_KEY)
+
+ def self.enable_pg_force_readonly_mode
+ Discourse.redis.set(PG_FORCE_READONLY_MODE_KEY, 1)
+ MessageBus.publish(readonly_channel, true)
+ Site.clear_anon_cache!
+ true
+ end
+
+ def self.disable_pg_force_readonly_mode
+ result = Discourse.redis.del(PG_FORCE_READONLY_MODE_KEY)
+ MessageBus.publish(readonly_channel, false)
+ result > 0
+ end
+ end
+
+ RailsFailover::ActiveRecord.register_force_reading_role_callback do
+ Discourse.redis.exists(
+ Discourse::PG_READONLY_MODE_KEY,
+ Discourse::PG_FORCE_READONLY_MODE_KEY
+ )
+ end
+end
diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml
index 85bd88a24f..b7634d3f70 100644
--- a/config/locales/client.ar.yml
+++ b/config/locales/client.ar.yml
@@ -1459,7 +1459,6 @@ ar:
bookmarks: وضعت علية علامة مرجعية
first: اول منشور في الموضوعات
pinned: مثبتة
- unpinned: غير مثبتة
seen: قراءته
unseen: لم أقرأها
wiki: من النوع wiki
@@ -3549,19 +3548,26 @@ ar:
category_id: "رقم التصنيف"
category_title: "قسم"
external_url: "رابط خارجي"
+ destination: "المكان المقصود"
delete_confirm: هل أنت متأكد من حذف هذا الرابط الثابت ؟
form:
label: "جديد :"
add: "أضف"
filter: "بحث ( رابط داخلي أو خارجي )"
reseed:
+ action:
+ label: "استبدال النص ..."
+ title: "استبدل نص الفئات والمواضيع بالترجمات"
modal:
+ title: "استبدال النص"
+ subtitle: "استبدل نص الفئات والمواضيع التي أنشأها النظام بأحدث الترجمات"
categories: "التصنيفات"
topics: "المواضيع"
replace: "غير"
wizard_js:
wizard:
done: "انتهى"
+ finish: "إنهاء"
back: "الى الخلف"
next: "التالي"
step: "%{current} من %{total}"
diff --git a/config/locales/client.be.yml b/config/locales/client.be.yml
index 586baf3d27..a37f2459e5 100644
--- a/config/locales/client.be.yml
+++ b/config/locales/client.be.yml
@@ -221,6 +221,8 @@ be:
title: "тып"
refresh: "абнавіць"
category: "катэгорыя"
+ orders:
+ score: "кошт"
scores:
score: "кошт"
type: "тып"
diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml
index d7a7eadcb5..58905b0a40 100644
--- a/config/locales/client.bg.yml
+++ b/config/locales/client.bg.yml
@@ -392,8 +392,7 @@ bg:
status: "Статус"
category: "Категория"
orders:
- priority: "Приоритет"
- priority_asc: "Приоритет (по обратен ред)"
+ score: "Точки"
created_at: "Създадено на"
created_at_asc: "Създадено на (по обратен ред)"
priority:
@@ -1345,7 +1344,6 @@ bg:
tracking: Аз следя
first: е първата публикация
pinned: са закачени
- unpinned: не са закачени
all_tags: Всички по-горни етикети
post:
time:
diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml
index d190699927..45166458c3 100644
--- a/config/locales/client.bs_BA.yml
+++ b/config/locales/client.bs_BA.yml
@@ -462,8 +462,7 @@ bs_BA:
status: "Status"
category: "Kategorija"
orders:
- priority: "Prioritet"
- priority_asc: "Prioritet (preokrenuti)"
+ score: "bodovi"
created_at: "Napravjeno kod"
created_at_asc: "Napravijeno kod (preokrenuti)"
priority:
@@ -1806,7 +1805,6 @@ bs_BA:
bookmarks: Zabilježene
first: su friške prve objave
pinned: su zakačene
- unpinned: nisu zakačene
seen: Pročitane
unseen: Nepročitane
wiki: su wiki
diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml
index 8db7993d27..419f05e48d 100644
--- a/config/locales/client.ca.yml
+++ b/config/locales/client.ca.yml
@@ -420,8 +420,7 @@ ca:
status: "Estat"
category: "Categoria"
orders:
- priority: "Prioritat"
- priority_asc: "Prioritat (inversa)"
+ score: "Puntuació"
created_at: "Creat"
created_at_asc: "Creat (invers)"
priority:
@@ -1748,7 +1747,6 @@ ca:
bookmarks: He marcat com a preferits
first: són la primera publicació
pinned: estan afixats
- unpinned: no estan afixats
seen: he llegit
unseen: no he llegit
wiki: són wiki
diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml
index ac5ed8e272..e27f0a61cd 100644
--- a/config/locales/client.cs.yml
+++ b/config/locales/client.cs.yml
@@ -1541,7 +1541,6 @@ cs:
bookmarks: Mám v záložkách
first: jsou první příspěvek v tématu
pinned: jsou připnuty
- unpinned: nejsou připnuty
seen: která jsem četl
unseen: jsem nečetl
wiki: jsou wiki
diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml
index 29ff0441cb..ad56db3edd 100644
--- a/config/locales/client.da.yml
+++ b/config/locales/client.da.yml
@@ -420,8 +420,7 @@ da:
status: "Status"
category: "Kategori"
orders:
- priority: "Prioritet"
- priority_asc: "Prioritet (omvendt)"
+ score: "Score"
created_at: "Oprettet kl"
created_at_asc: "Oprettet kl (omvendt)"
priority:
@@ -1718,7 +1717,6 @@ da:
bookmarks: Jeg bogmærkede
first: er det allerførste indlæg
pinned: er fastgjort
- unpinned: er ikke fastgjort
seen: jeg læser
unseen: Jeg har ikke læst
wiki: er wiki
diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml
index 0851c90d14..4554e4b7a2 100644
--- a/config/locales/client.de.yml
+++ b/config/locales/client.de.yml
@@ -446,8 +446,7 @@ de:
status: "Status"
category: "Kategorie"
orders:
- priority: "Priorität"
- priority_asc: "Priorität (umgekehrt)"
+ score: "Wertung"
created_at: "Erstellt am"
created_at_asc: "Erstellt am (umgekehrt)"
priority:
@@ -1793,7 +1792,6 @@ de:
bookmarks: Themen/Beiträge mit Lesezeichen
first: Erste Beiträge in Themen
pinned: Angeheftete Themen
- unpinned: Nicht angeheftete Themen
seen: Gelesene Themen/Beiträge
unseen: Ungelesene Themen/Beiträge
wiki: Wikis
diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml
index e96527ebe6..85c0836b8d 100644
--- a/config/locales/client.el.yml
+++ b/config/locales/client.el.yml
@@ -1250,7 +1250,6 @@ el:
tracking: παρακολουθώ
first: είναι η πρώτη ανάρτηση
pinned: είναι καρφιτσωμένα
- unpinned: δεν είναι καρφιτσωμένα
unseen: δεν διάβασα
wiki: " είναι βίκι"
statuses:
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index a8ff521a7a..3c85e215fb 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -512,8 +512,8 @@ en:
status: "Status"
category: "Category"
orders:
- priority: "Priority"
- priority_asc: "Priority (reverse)"
+ score: "Score"
+ score_asc: "Score (reverse)"
created_at: "Created At"
created_at_asc: "Created At (reverse)"
@@ -2005,7 +2005,6 @@ en:
bookmarks: I bookmarked
first: are the very first post
pinned: are pinned
- unpinned: are not pinned
seen: I read
unseen: I've not read
wiki: are wiki
diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml
index 08c3703d09..8f57fd3760 100644
--- a/config/locales/client.es.yml
+++ b/config/locales/client.es.yml
@@ -443,8 +443,7 @@ es:
status: "Estado"
category: "Categoría"
orders:
- priority: "Prioridad"
- priority_asc: "Prioridad (inverso)"
+ score: "Puntuación"
created_at: "Creado el"
created_at_asc: "Creado el (inverso)"
priority:
@@ -1805,7 +1804,6 @@ es:
bookmarks: he guardado
first: son la primera publicación
pinned: son destacados
- unpinned: son no destacados
seen: he leído
unseen: no he leído
wiki: son tipo wiki
diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml
index 906304e4fe..2e48f786f9 100644
--- a/config/locales/client.et.yml
+++ b/config/locales/client.et.yml
@@ -1326,7 +1326,6 @@ et:
bookmarks: Minu järjehoidjates
first: on teema esimesed postitused
pinned: on esile tõstetud
- unpinned: ei ole esile tõstetud
seen: Minu loetud
unseen: Minu poolt lugemata
wiki: on wiki
diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml
index 3c55bb46ff..a22efc5b04 100644
--- a/config/locales/client.fa_IR.yml
+++ b/config/locales/client.fa_IR.yml
@@ -27,8 +27,10 @@ fa_IR:
millions: "{{number}} میلیون"
dates:
time: "HH:mm"
+ time_with_zone: "HH:mm (z)"
time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
+ long_no_year: "D MMM, HH:mm"
long_no_year_no_time: "MMM D"
full_no_year_no_time: "MMMM Do"
long_with_year: "MMM D, YYYY h:mm a"
@@ -269,11 +271,20 @@ fa_IR:
reminders:
at_desktop: "بار آینده من پشت میزم هستم"
later_today: "امروز"
+ next_business_day: "روز کاری بعدی"
tomorrow: "فردا"
next_week: "هفته بعد"
later_this_week: "این هفته"
+ start_of_next_business_week: "دوشنبه بعدی"
next_month: "ماه بعد"
custom: "درج تاریخ و ساعت"
+ last_custom: "آخرین"
+ none: "هیچ یادآوری لازم نیست"
+ today_with_time: "امروز در %{time}"
+ tomorrow_with_time: "فردا در %{time}"
+ at_time: "در %{date_time}"
+ copy_codeblock:
+ copied: "کپی شد!"
drafts:
resume: "از سر گیری"
remove: "پاک کردن"
@@ -429,8 +440,8 @@ fa_IR:
status: "وضعیت"
category: "دستهبندی"
orders:
- priority: "اولویت"
- priority_asc: "اولویت (برعکس)"
+ score: "امتیاز"
+ score_asc: "امتیاز (معکوس)"
created_at: "ساخته شده"
created_at_asc: "ساخته شده (برعکس)"
priority:
@@ -511,6 +522,7 @@ fa_IR:
days_visited_long: "روزهای بازدید شده"
posts_read: "خواندن"
posts_read_long: "نوشتههای خوانده شده"
+ last_updated: "آخرین به روز رسانی:"
total_rows:
one: "%{count} کاربر"
other: "%{count} کاربر"
@@ -536,6 +548,7 @@ fa_IR:
deny: "رد کردن"
denied: "رد شده"
undone: "بازگرداندن درخواست"
+ handle: "رسیدگی به درخواست عضویت"
manage:
title: "مدیریت"
name: "نام"
@@ -662,6 +675,9 @@ fa_IR:
flair_color_placeholder: "(اختیاری) کد HEX رنگ"
flair_preview_icon: "پیشنمایش آیکن"
flair_preview_image: "پیشنمایش تصویر"
+ flair_type:
+ icon: "یک آیکون را انتخاب کنید"
+ image: "تصویری را بارگذاری کنید"
user_action_groups:
"1": "پسندهای داده شده"
"2": "پسندهای دریافت شده"
@@ -738,6 +754,7 @@ fa_IR:
private_messages: "پیامها"
user_notifications:
filters:
+ filter_by: "فیلتر توسط"
all: "همه"
read: "خواندن"
unread: "خوانده نشده"
@@ -865,6 +882,7 @@ fa_IR:
deleted_posts: "پست های حذف شده"
suspensions: "تعلیقها"
warnings_received: "هشدارها"
+ rejected_posts: "پست های رد شده"
messages:
all: "همه"
inbox: "صندوق دریافت"
@@ -947,6 +965,7 @@ fa_IR:
edit: "کلید امنیتی را ویرایش کنید"
save: "ذخیره"
edit_description: "نام کلید امنیتی"
+ name_required_error: "شما باید یک نام برای کلید امنیتی خود انتخاب کنید."
change_about:
title: "تغییر «دربارهی من»"
error: "در فرآیند تغییر این مقدار خطایی روی داد."
@@ -963,6 +982,7 @@ fa_IR:
success_staff: "یک ایمیل به آدرس کنونی شما ارسال کردیم. لطفا دستورالعمل آن را دنبال کنید."
change_avatar:
title: "عکس نمایه خود را تغییر دهید"
+ gravatar: "{{gravatarName}} ، بر اساس"
letter_based: "سیستم تصویر پرفایل را اختصاص داده است"
uploaded_avatar: "تصویر سفارشی"
uploaded_avatar_empty: "افزودن تصویر سفارشی"
@@ -981,6 +1001,7 @@ fa_IR:
sso_override_instructions: "ایمیل را میتوانید از طریق SSO به روزرسانی کنید"
instructions: "هرگز به عموم نمایش داده نخواهد شد"
ok: "برای تایید ایمیلی برایتان ارسال خواهیم کرد."
+ required: "لطفا یک آدرس ایمیل وارد کنید"
invalid: "لطفا یک آدرس ایمیل معتبر وارد کنید"
authenticated: "ایمیل شما توسط {{provider}} تصدیق شد"
frequency_immediately: "اگر به سرعت چیزی را که برایتان ارسال نکردیم نخوانده باشید، بلافاصله برایتان ایمیل ارسال میکنیم."
@@ -1001,6 +1022,7 @@ fa_IR:
title: "نام"
instructions: "نام و نامخانوادگی شما (اختیاری)"
instructions_required: "نام و نام خانوادگی شما"
+ required: "لطفاً یک نام وارد کنید"
too_short: "نام انتخابی شما خیلی کوتاه است"
ok: "نام انتخابی شما به نطر می رسد خوب است"
username:
@@ -1014,6 +1036,7 @@ fa_IR:
too_long: "نام کاربری انتخابی شما خیلی طولانی است"
checking: "بررسی در دسترس بودن نامکاربری..."
prefilled: "ایمیل منطبق است با این نام کاربری ثبت شده "
+ required: "لطفاً یک نام کاربری وارد کنید"
locale:
title: "زبان رابطکاربری"
instructions: "زبان رابط کاربری. با تازه کردن صفحه تغییر خواهد کرد."
@@ -1021,6 +1044,8 @@ fa_IR:
any: "هر"
password_confirmation:
title: "رمز عبور را مجدد وارد نمایید"
+ invite_code:
+ title: "کد دعوت"
auth_tokens:
title: "ابزارهایی که اخیرا استفاده شدند"
ip: "IP"
@@ -1153,6 +1178,7 @@ fa_IR:
same_as_email: "رمز عبورتان با ایمیل شما برابر است. "
ok: "رمز عبور خوبی است."
instructions: "حداقل %{count} نویسه"
+ required: "لطفاً یک کلمه عبور وارد کنید"
summary:
title: "خلاصه"
stats: "آمار"
@@ -1242,6 +1268,7 @@ fa_IR:
fixed: "بارگذاری برگه"
modal:
close: "بسته"
+ dismiss_error: "رد کردن خطا"
close: "بستن"
assets_changed_confirm: "این وب سایت به روز رسانی شده است. برای دیدن آخرین نسخه تازهسازی میکنید؟"
logout: "شما از سایت خارج شدهاید."
@@ -1270,6 +1297,7 @@ fa_IR:
mute: بیصدا
unmute: صدادار
last_post: ارسال شده
+ local_time: "زمان محلی"
time_read: خواندن
time_read_recently: "اخیراً"
time_read_tooltip: "%{time_read} مجموع زمان خواندن "
@@ -1347,6 +1375,7 @@ fa_IR:
second_factor_backup: "ورود با استفاده از یک کد پشتیبان"
second_factor_backup_title: "پشتیبان دو عامله"
second_factor_backup_description: "لطفا یکی از کدهای پشتیبان را وارد کنید:"
+ security_key_alternative: "روش دیگری را امتحان کنید"
security_key_authenticate: "تأیید اعتبار با کلید امنیتی"
security_key_not_allowed_error: "روند تأیید اعتبار کلید امنیتی به پایان رسیده است یا لغو شده است."
email_placeholder: "ایمیل یا نامکاربری"
@@ -1728,13 +1757,13 @@ fa_IR:
title: مطابقت فقط در عنوان باشد
likes: پسندیدهام
posted: در آن فرستادهام
+ created: من درست کردم
watching: مشاهده میکنم
tracking: پیگیری میکنم
private: در پیامهای من
bookmarks: نشانک زدهام
first: اولین فرسته هستند
pinned: سنجاق شدهاند
- unpinned: سنجاق نشدهاند
seen: خواندم
unseen: نخواندهام
wiki: دانشنامه هستند
@@ -2136,6 +2165,14 @@ fa_IR:
title: "ادغام نوشتههای انتخاب شده"
action: "ادغام نوشتههای انتخاب شده"
error: "خطایی در ادغام نوشتههای انتخاب شده رخ داده است."
+ publish_page:
+ publish: "انتشار"
+ publish_url: "صفحه شما منتشر شده در:"
+ topic_published: "مبحث شما منتشر شده در:"
+ preview_url: "صفحه شما منتشر خواهد شد در:"
+ invalid_slug: "متأسفیم ، شما نمی توانید این صفحه را منتشر کنید."
+ unpublish: "لغو انتشار"
+ publishing_settings: "تنظیمات انتشار"
change_owner:
title: "تغییر دادن مالک"
action: "تغییر مالکیت"
@@ -2279,6 +2316,12 @@ fa_IR:
notify_moderators: "مدیرهای مطلع شده"
notify_user: "یک پیام ارسال شد"
bookmark: "نشانه گذاری کن"
+ like:
+ one: "این را دوست داشت"
+ other: "دوست داشته اند"
+ read:
+ one: "این را بخوان"
+ other: "خوانده اند"
like_capped:
one: "و {{count}} نفر دیگر این را دوست داشتند"
other: "و {{count}} نفر دیگر این را پسندیدهاند."
@@ -2333,7 +2376,9 @@ fa_IR:
button: "HTML"
bookmarks:
created: "ساخته شده"
+ updated: "به روز شده"
name: "نام"
+ set_reminder: "به من یادآوری کن"
category:
can: "can… "
none: "(بدون دستهبندی)"
@@ -2672,6 +2717,15 @@ fa_IR:
log_out: "%{shortcut} خروج"
composing:
title: "نوشتن"
+ bookmarks:
+ enter: "%{shortcut} ذخیره و بستن"
+ later_today: "%{shortcut} بعد از امروز"
+ later_this_week: "%{shortcut} بعد از این هفته"
+ tomorrow: "%{shortcut} فردا"
+ next_week: "%{shortcut} هفته بعد"
+ next_month: "%{shortcut} ماه آینده"
+ next_business_week: "%{shortcut} شروع هفته آینده"
+ next_business_day: "%{shortcut} روز کاری بعدی"
actions:
title: "عملیات"
bookmark_topic: "%{shortcut} باز کردن موضوع نشانهگذاری شده"
@@ -2733,6 +2787,7 @@ fa_IR:
changed: "برچسبهای تغییر یافته:"
tags: "برچسبها"
choose_for_topic: "برچسبهای اختیاری"
+ info: "اطلاعات"
add_synonyms: "افزودن"
delete_tag: "حذف برچسب"
delete_confirm_no_topics: "ایا از حذف این برچسب مطمعن هستید؟"
@@ -2967,6 +3022,7 @@ fa_IR:
user: "کاربر"
title: "API"
created: ساخته شده
+ updated: به روز شده
generate: "تولید کردن"
revoke: "ابطال"
all_users: "همه کاربران"
diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml
index 98d9a4a06b..885fa4871d 100644
--- a/config/locales/client.fi.yml
+++ b/config/locales/client.fi.yml
@@ -276,6 +276,7 @@ fi:
no_timezone: 'Et ole valinnut aikavyöhykettä, joten et voi asettaa muistutuksia. Aseta se profiilisivullasi .'
invalid_custom_datetime: "Antamasi päivämäärä ja kellonaika ei kelpaa, yritä uudelleen."
list_permission_denied: "Et voi nähdä tämän käyttäjän kirjanmerkkejä."
+ delete_when_reminder_sent: "Poista tämä kirjanmerkki, kun muistutusilmoitus lähetetään."
reminders:
at_desktop: "Ensi kerralla kun olen työpöytäympäristössäni"
later_today: "Myöhemmin tänään"
@@ -292,6 +293,8 @@ fi:
tomorrow_with_time: "huomenna klo %{time}"
at_time: "%{date_time}"
existing_reminder: "Olet pyytänyt muistutuksen tästä kirjanmerkistä"
+ copy_codeblock:
+ copied: "kopioitiin!"
drafts:
resume: "Jatka"
remove: "Poista"
@@ -447,8 +450,8 @@ fi:
status: "Tila"
category: "Alue"
orders:
- priority: "Prioriteetti"
- priority_asc: "Prioriteetti (käänteinen)"
+ score: "Arvo"
+ score_asc: "Arvo (käänteinen)"
created_at: "Luotu"
created_at_asc: "Luotu (käänteinen)"
priority:
@@ -676,12 +679,16 @@ fi:
title: "Vaimennettu"
description: "Et saa ilmoituksia ryhmän yksityiskeskusteluista."
flair_url: "Avatarpinssin kuva"
+ flair_upload_description: "Käytä neliönmuotoisia kuvia, joiden koko on vähintään 20px kertaa 20px."
flair_bg_color: "Avatar-pinssin taustaväri"
flair_bg_color_placeholder: "(Ei-pakollinen) värin Hex-arvo"
flair_color: "Avatarpinssin väri"
flair_color_placeholder: "(Ei-pakollinen) värin Hex-arvo"
flair_preview_icon: "Ikonin esikatselu"
flair_preview_image: "Kuvan esikatselu"
+ flair_type:
+ icon: "Valitse ikoni"
+ image: "Lataa kuva"
user_action_groups:
"1": "Annetut tykkäykset"
"2": "Saadut tykkäykset"
@@ -743,6 +750,7 @@ fi:
copied: "kopioitu"
user_fields:
none: "(valitse vaihtoehto)"
+ required: '"%{name}" on pakollinen'
user:
said: "{{username}}:"
profile: "Profiili"
@@ -879,6 +887,7 @@ fi:
api_approved: "Sallittu:"
api_last_used_at: "Viimeksi käytetty:"
theme: "Teema"
+ save_to_change_theme: 'Teema päivittyy kun klikkaat "%{save_text}"'
home: "Oletusnäkymä"
staged: "Esikäyttäjä"
staff_counters:
@@ -1019,6 +1028,7 @@ fi:
no_secondary: "Ei toissijaisia sähköpostiosoitteita"
instructions: "Ei tule julkiseksi."
ok: "Lähetämme sinulle sähköpostin varmistukseksi."
+ required: "Syötä sähköpostiosoitteesi"
invalid: "Sähköpostiosoite ei kelpaa."
authenticated: "{{provider}} on todentanut sähköpostiosoitteesi"
frequency_immediately: "Saat sähköpostia välittömästi, jollet ole jo lukenut asiaa, jota sähköpostiviesti koskee."
@@ -1039,6 +1049,7 @@ fi:
title: "Nimi"
instructions: "koko nimesi (ei pakollinen)"
instructions_required: "Koko nimesi"
+ required: "Syötä nimesi"
too_short: "Nimesi on liian lyhyt"
ok: "Nimesi vaikuttaa hyvältä"
username:
@@ -1052,6 +1063,7 @@ fi:
too_long: "Käyttäjätunnus on liian pitkä"
checking: "Tarkistetaan käyttäjätunnusta..."
prefilled: "Sähköposti vastaa tätä käyttäjänimeä"
+ required: "Syötä käyttäjänimi"
locale:
title: "Käyttöliittymän kieli"
instructions: "Käyttöliittymän kieli. Kieli vaihtuu sivun uudelleen lataamisen yhteydessä."
@@ -1195,6 +1207,7 @@ fi:
same_as_email: "Salasanasi on sama kuin sähköpostisi."
ok: "Salasana vaikuttaa hyvältä."
instructions: "vähintään %{count} merkkiä."
+ required: "Syötä salasana"
summary:
title: "Yhteenveto"
stats: "Tilastot"
@@ -1814,7 +1827,6 @@ fi:
bookmarks: kirjanmerkeistäni
first: jotka ovat ketjun avausviestejä
pinned: jotka on kiinnitettyjä
- unpinned: jotka eivät ole kiinnitettyjä
seen: jotka olen lukenut
unseen: joita en ole lukenut
wiki: ovat wiki-viestejä
diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml
index d2fbd8cb6c..19e6cd5669 100644
--- a/config/locales/client.fr.yml
+++ b/config/locales/client.fr.yml
@@ -33,7 +33,7 @@ fr:
long_no_year: "D MMM, HH:mm"
long_no_year_no_time: "D MMM"
full_no_year_no_time: "Do MMMM"
- long_with_year: "DD MMM YYYY H:mm"
+ long_with_year: "DD MMM YYYY HH:mm"
long_with_year_no_time: "DD MMM YYYY"
full_with_year_no_time: "D MMMM YYYY"
long_date_with_year: "D MMM 'YY LT"
@@ -174,7 +174,7 @@ fr:
cn_north_1: "Chine (Pékin)"
cn_northwest_1: "Chine (Ningxia)"
eu_central_1: "UE (Francfort)"
- eu_north_1: "EU (Stockholm)"
+ eu_north_1: "UE (Stockholm)"
eu_west_1: "UE (Irlande)"
eu_west_2: "UE (Londres)"
eu_west_3: "UE (Paris)"
@@ -187,7 +187,7 @@ fr:
us_west_2: "États-Unis ouest (Oregon)"
edit: "modifier le titre et la catégorie de ce sujet"
expand: "Développer"
- not_implemented: "Cette fonctionnalité n’est pas encore disponible."
+ not_implemented: "Cette fonctionnalité n'est pas encore disponible."
no_value: "Non"
yes_value: "Oui"
submit: "Envoyer"
@@ -212,8 +212,8 @@ fr:
tos: "Conditions générales d'utilisation"
rules: "Règles"
conduct: "Charte"
- mobile_view: "Vue mobile"
- desktop_view: "Vue bureau"
+ mobile_view: "Version mobile"
+ desktop_view: "Version ordinateur"
you: "Vous"
or: "ou"
now: "à l'instant"
@@ -262,7 +262,7 @@ fr:
help:
bookmark: "Cliquer pour mettre un signet sur le premier message de ce sujet"
unbookmark: "Cliquer pour retirer tous les signets de ce sujet"
- unbookmark_with_reminder: "Cliquez pour supprimer tous les signets et rappels de ce sujet. Vous avez un rappel pour %{reminder_at} sur ce sujet."
+ unbookmark_with_reminder: "Cliquer pour supprimer tous les signets et rappels de ce sujet. Vous avez un rappel pour %{reminder_at} sur ce sujet."
bookmarks:
created: "vous avez mis un signet à ce message %{name}"
not_bookmarked: "mettre un signet à ce message"
@@ -446,12 +446,12 @@ fr:
title: "Type"
all: "(tous les types)"
minimum_score: "Score minimum :"
- refresh: "Rafraîchir"
+ refresh: "Actualiser"
status: "État"
category: "Catégorie"
orders:
- priority: "Priorité"
- priority_asc: "Priorité (inverse)"
+ score: "Score"
+ score_asc: "Score (inverse)"
created_at: "Date de création"
created_at_asc: "Date de création (inverse)"
priority:
@@ -510,7 +510,7 @@ fr:
you_replied_to_topic: "Vous avez répondu à ce sujet "
user_mentioned_user: "{{user}} a mentionné {{another_user}} "
user_mentioned_you: "Vous avez été mentionné par {{user}} "
- you_mentioned_user: "Vous avez mentionné {{user}} "
+ you_mentioned_user: "Vous avez mentionné {{another_user}} "
posted_by_user: "Rédigé par {{user}} "
posted_by_you: "Rédigé par vous "
sent_by_user: "Envoyé par {{user}} "
@@ -712,7 +712,7 @@ fr:
reorder:
title: "Réordonner les catégories"
title_long: "Réorganiser la liste des catégories"
- save: "Enregistrer l'ordre"
+ save: "Sauvegarder cette ordre"
apply_all: "Appliquer"
position: "Position"
posts: "Messages"
@@ -787,13 +787,13 @@ fr:
activity_stream: "Activité"
preferences: "Préférences"
feature_topic_on_profile:
- open_search: "Sélectionnez un nouveau sujet"
- title: "Sélectionnez un sujet"
- search_label: "Rechercher des sujet grâce au titre"
+ open_search: "Sélectionner un nouveau sujet"
+ title: "Sélectionner un sujet"
+ search_label: "Rechercher des sujets par titre"
save: "Sauvegarder"
clear:
title: "Vider"
- warning: "Êtes-vous sûr de vouloir effacer ce sujet à la une ?"
+ warning: "Êtes-vous sûr de vouloir masquer votre sujet vedette ?"
use_current_timezone: "Utiliser le fuseau horaire actuel"
profile_hidden: "Le profil public de cet utilisateur est caché."
expand_profile: "Développer"
@@ -825,9 +825,9 @@ fr:
allow_private_messages: "Permettre aux autres utilisateurs de m’envoyer des messages directs"
external_links_in_new_tab: "Ouvrir tous les liens externes dans un nouvel onglet"
enable_quoting: "Proposer de citer le texte sélectionné"
- enable_defer: "Activer le bouton pour reporter de sujets à plus tard en les marquant comme non lus"
+ enable_defer: "Activer le bouton pour reporter des sujets à plus tard en les marquant comme non lus"
change: "modifier"
- featured_topic: "Sujet à la une"
+ featured_topic: "Sujet vedette"
moderator: "{{user}} est un modérateur"
admin: "{{user}} est un administrateur"
moderator_tooltip: "Cet utilisateur est un modérateur"
@@ -999,9 +999,9 @@ fr:
taken: "Désolé, ce nom d'utilisateur est déjà pris."
invalid: "Ce nom d'utilisateur est invalide. Il ne doit être composé que de lettres et de chiffres."
change_email:
- title: "Modifier l'adresse de courriel"
- taken: "Désolé, cette adresse de courriel est indisponible."
- error: "Il y a eu une erreur lors du changement d'adresse de courriel. Cette adresse est peut-être déjà utilisée ?"
+ title: "Modifier l'adresse courriel"
+ taken: "Désolé, cette adresse courriel est indisponible."
+ error: "Il y a eu une erreur lors du changement de l'adresse courriel. Cette adresse est peut-être déjà utilisée ?"
success: "Nous avons envoyé un courriel à cette adresse. Merci de suivre les instructions."
success_staff: "Nous avons envoyé un courriel à votre adresse actuelle. Merci de suivre les instructions."
change_avatar:
@@ -1022,7 +1022,7 @@ fr:
title: "Arrière-plan de la carte de l'utilisateur"
instructions: "Les images d'arrière-plan seront centrées avec une largeur par défaut de 590 pixels."
change_featured_topic:
- title: "Sujet à la une"
+ title: "Sujet vedette"
instructions: "Un lien vers ce sujet sera ajouté sur votre carte d'utilisateur et votre profil."
email:
title: "Courriel"
@@ -1033,8 +1033,8 @@ fr:
instructions: "Jamais visible publiquement."
ok: "Nous vous enverrons un courriel de confirmation"
required: "Veuillez entrer une adresse courriel"
- invalid: "Merci d'entrer une adresse de courriel valide"
- authenticated: "Votre adresse de courriel a été authentifiée par {{provider}}"
+ invalid: "Veuillez entrer une adresse courriel valide"
+ authenticated: "Votre adresse courriel a été authentifiée par {{provider}}"
frequency_immediately: "Nous vous enverrons un courriel immédiatement si vous n'avez pas lu le contenu en question."
frequency:
one: "Nous vous enverrons des courriels seulement si nous ne vous avons pas vu sur le site dans la dernière minute."
@@ -1057,7 +1057,7 @@ fr:
too_short: "Votre nom est trop court"
ok: "Votre nom a l'air correct"
username:
- title: "nom d'utilisateur"
+ title: "Nom d'utilisateur"
instructions: "unique, sans espaces, court"
short_instructions: "Les gens peuvent vous mentionner avec @{{username}}"
available: "Votre nom d'utilisateur est disponible"
@@ -1083,7 +1083,7 @@ fr:
ip: "IP"
details: "Détails"
log_out_all: "Se déconnecter partout"
- active: "utilisé actuellement"
+ active: "actif"
not_you: "Ce n'est pas vous ?"
show_all: "Tout afficher ({{count}})"
show_few: "Afficher moins"
@@ -1303,9 +1303,9 @@ fr:
close: "fermer"
dismiss_error: "Ignorer l'erreur"
close: "Fermer"
- assets_changed_confirm: "Ce site vient d'être mis à jour. Rafraîchir maintenant pour accéder à la nouvelle version ?"
+ assets_changed_confirm: "Ce site vient d'être mis à jour. Actualiser la page pour accéder à la nouvelle version ?"
logout: "Vous avez été déconnecté."
- refresh: "Rafraîchir"
+ refresh: "Actualiser"
home: "Accueil"
read_only_mode:
enabled: "Le site est en mode lecture seule. Vous pouvez continer à naviguer, mais les réponses, J'aime et autre interactions sont désactivées pour l'instant."
@@ -1386,7 +1386,7 @@ fr:
complete_email_found: "Nous avons trouvé un compte correspondant au courriel %{email} . Vous devriez recevoir rapidement un courriel avec les instructions pour réinitialiser votre mot de passe."
complete_username_not_found: "Aucun compte ne correspond au nom d'utilisateur %{username} "
complete_email_not_found: "Aucun compte ne correspond à %{email} "
- help: "Le courriel n'est pas arrivé ? Pensez bien à vérifier dans votre dossier de spam.Vous n'êtes pas sûr de l'adresse électronique que vous avez utilisée ? Saisissez ici une adresse électronique, et nous vous dirons si elle existe ici.
Si vous n'avez plus accès à l'adresse électronique de votre compte, merci de contacter nos responsables serviables.
"
+ help: "Le courriel n'est pas arrivé ? Pensez bien à vérifier dans votre dossier de spam.Vous n'êtes pas sûr de l'adresse courriel que vous avez utilisée ? Saisissez une adresse courriel et nous vous dirons si elle existe ici.
Si vous n'avez plus accès à l'adresse courriel de votre compte, merci de contacter nos responsables serviables.
"
button_ok: "OK"
button_help: "Aide"
email_login:
@@ -1394,7 +1394,7 @@ fr:
button_label: "par courriel"
emoji: "emoji de cadenas"
complete_username: "Si un compte correspond au nom d'utilisateur %{username} , vous devriez recevoir rapidement un courriel avec un lien pour vous connecter."
- complete_email: "Si un compte correspond à l'adresse de courriel %{email} , vous devriez recevoir rapidement un courriel avec un lien pour vous connecter."
+ complete_email: "Si un compte correspond à l'adresse courriel %{email} , vous devriez recevoir rapidement un courriel avec un lien pour vous connecter."
complete_username_found: "Nous avons trouvé un compte correspondant au nom d'utilisateur %{username} , vous devriez recevoir rapidement un courriel avec un lien pour vous connecter."
complete_email_found: "Nous avons trouvé un compte correspond au courriel %{email} , vous devriez recevoir rapidement un courriel avec un lien pour vous connecter."
complete_username_not_found: "Aucun compte ne correspond au nom d'utilisateur %{username} "
@@ -1591,7 +1591,7 @@ fr:
reply: "Répondre"
cancel: "Annuler"
create_topic: "Créer le sujet"
- create_pm: "Message direct"
+ create_pm: "Envoyer le message"
create_whisper: "Murmurer"
create_shared_draft: "Créer un brouillon partagé"
edit_shared_draft: "Modifier le brouillon partagé"
@@ -1623,7 +1623,7 @@ fr:
link_description: "saisir ici la description du lien"
link_dialog_title: "Insérer le lien"
link_optional_text: "titre optionnel"
- link_url_placeholder: "Coller une URL ou tapez pour chercher des sujets"
+ link_url_placeholder: "Collez une URL ou tapez pour chercher des sujets"
quote_title: "Citation"
quote_text: "Citation"
code_title: "Texte préformaté"
@@ -1656,8 +1656,9 @@ fr:
label: "Répondre au message %{postNumber} de %{postUsername}"
desc: Répondre à un message spécifique
reply_as_new_topic:
- label: Répondre à un sujet lié
+ label: Répondre via un sujet lié
desc: Créer un nouveau sujet lié à ce sujet
+ confirm: Vous avez un brouillon sauvegardé pour ce nouveau sujet mais il sera perdu si vous créez un sujet lié.
reply_as_private_message:
label: Nouveau message direct
desc: Créer un nouveau message direct
@@ -1760,7 +1761,7 @@ fr:
title: "Ajouter une image"
title_with_attachments: "Ajouter une image ou un fichier"
from_my_computer: "Depuis mon appareil"
- from_the_web: "Depuis le Web"
+ from_the_web: "Depuis le web"
remote_tip: "lien vers l'image"
remote_tip_with_attachments: "lien vers l'image ou le fichier {{authorized_extensions}}"
local_tip: "sélectionnez des images depuis votre appareil"
@@ -1827,7 +1828,6 @@ fr:
bookmarks: auxquels j'ai mis un signet
first: qui sont les premiers messages
pinned: qui sont épinglés
- unpinned: qui ne sont pas épinglés
seen: que j'ai lus
unseen: que je n'ai pas lus
wiki: qui sont des wikis
@@ -1859,7 +1859,7 @@ fr:
bulk:
select_all: "Tout sélectionner"
clear_all: "Tout désélectionner"
- unlist_topics: "Ne plus lister les sujets"
+ unlist_topics: "Rendre les sujets invisibles"
relist_topics: "Lister les sujets"
reset_read: "Réinitialiser la lecture"
delete: "Supprimer les sujets"
@@ -1929,7 +1929,7 @@ fr:
help: "Ajouter un lien vers ce sujet sur votre carte d'utilisateur et votre profil"
title: "Mettre en avant sur le profil"
remove_from_profile:
- warning: "Votre profil a déjà un sujet en vedette. Si vous continuez, ce sujet le remplacera."
+ warning: "Votre profil contient déjà un sujet vedette. Si vous continuez, ce sujet le remplacera."
help: "Retirer le lien vers ce sujet de votre profil d'usager"
title: "Retirer du profil"
list: "Sujets"
@@ -1953,11 +1953,11 @@ fr:
title: "Sujet non trouvé"
description: "Désolé, nous n'avons pas trouvé ce sujet. Peut-être a t-il été retiré par un modérateur ?"
total_unread_posts:
- one: "vous avez %{count} message non lu dans ce sujet"
- other: "vous avez {{count}} messages non lus dans ce sujet"
+ one: "il y a %{count} message non lu dans ce sujet"
+ other: " il y a {{count}} messages non lus dans ce sujet"
unread_posts:
- one: "vous avez %{count} message non lu sur ce sujet"
- other: "vous avez {{count}} messages non lus sur ce sujet"
+ one: "il reste %{count} message que vous n'avez pas lu lors de votre visite précédente sur ce sujet"
+ other: "il reste {{count}} messages que vous n'avez pas lus lors de votre visite précédente sur ce sujet"
new_posts:
one: "il y a %{count} nouveau message sur ce sujet depuis votre derniere lecture"
other: "il y a {{count}} nouveaux messages sur ce sujet depuis votre derniere lecture"
@@ -1986,7 +1986,7 @@ fr:
title: "Action planifiée du sujet"
save: "Planifier"
num_of_hours: "Nombre d'heures :"
- num_of_days: "Nombre de jours :"
+ num_of_days: "Nombre de jours :"
remove: "Supprimer la planification"
publish_to: "Publier dans :"
when: "Quand :"
@@ -2088,10 +2088,10 @@ fr:
title: "Surveiller"
description: "Vous serez notifié de chaque nouvelle réponse dans ce sujet, et le nombre de nouvelles réponses apparaîtra."
tracking_pm:
- title: "Suivi"
+ title: "Suivre"
description: "Le nombre de nouvelles réponses apparaîtra pour ce message. Vous serez notifié si quelqu'un vous mentionne ou vous répond."
tracking:
- title: "Suivi"
+ title: "Suivre"
description: "Le nombre de nouvelles réponses apparaîtra pour ce sujet. Vous serez notifié si quelqu'un vous mentionne ou vous répond."
regular:
title: "Normal"
@@ -2131,7 +2131,7 @@ fr:
remove_banner: "Retirer le sujet à la une"
reply:
title: "Répondre"
- help: "commencez à répondre à ce sujet"
+ help: "commencer à répondre à ce sujet"
clear_pin:
title: "Désépingler"
help: "Retirer l'épingle de ce sujet afin qu'il n'apparaisse plus en tête de votre liste de sujet"
@@ -2152,7 +2152,7 @@ fr:
feature_topic:
title: "Mettre ce sujet en évidence"
pin: "Faire apparaître ce sujet en haut de la catégorie {{categoryLink}} jusqu'à"
- confirm_pin: "Vous avez déjà {{count}} sujets épinglés. Trop de sujets épinglés peut être lourd pour les nouveaux utilisateurs et les visiteurs. Êtes-vous sûr de vouloir épingler un autre sujet dans cette catégorie ?"
+ confirm_pin: "Vous avez déjà {{count}} sujets épinglés. Avoir trop de sujets épinglés peut rendre la navigation difficile pour les nouveaux utilisateurs et les visiteurs. Êtes-vous sûr de vouloir épingler un autre sujet dans cette catégorie ?"
unpin: "Enlever ce sujet du haut de la catégorie {{categoryLink}}."
unpin_until: "Enlever ce sujet du haut de la catégorie {{categoryLink}} ou attendre jusqu'à %{until} ."
pin_note: "Les utilisateurs peuvent désépingler le sujet pour eux."
@@ -2221,7 +2221,7 @@ fr:
error: "Il y a eu une erreur en déplaçant les messages vers un nouveau sujet."
instructions:
one: "Vous êtes sur le point de créer un nouveau sujet avec le message que vous avez sélectionné."
- other: "Vous êtes sur le point de créer un nouveau sujet avec les {{count}} messages que vous avez sélectionné."
+ other: "Vous êtes sur le point de créer un nouveau sujet avec les {{count}} messages que vous avez sélectionnés."
merge_topic:
title: "Déplacer vers un sujet existant"
action: "déplacer vers un sujet existant"
@@ -2310,8 +2310,8 @@ fr:
edit_reason: "Raison :"
post_number: "message {{number}}"
ignored: "Contenu ignoré"
- wiki_last_edited_on: "Wiki édité pour la dernière fois"
- last_edited_on: "message édité pour la dernière fois"
+ wiki_last_edited_on: "wiki modifié pour la dernière fois le"
+ last_edited_on: "message modifié pour la dernière fois le"
reply_as_new_topic: "Répondre par un nouveau sujet"
reply_as_new_private_message: "Répondre par un nouveau message direct adressé aux mêmes destinataires"
continue_discussion: "Suite du sujet {{postLink}} :"
@@ -2355,12 +2355,12 @@ fr:
abandon_edit:
confirm: "Êtes-vous sûr de vouloir annuler vos changements ?"
no_value: "Non, garder"
- no_save_draft: "Non, enregistrer le brouillon"
+ no_save_draft: "Non, sauvegarder le brouillon"
yes_value: "Oui, annuler la modification"
abandon:
confirm: "Êtes-vous sûr de vouloir abandonner votre message ?"
no_value: "Non, le conserver"
- no_save_draft: "Non, enregistrer le brouillon"
+ no_save_draft: "Non, sauvegarder le brouillon"
yes_value: "Oui, abandonner"
via_email: "ce message est arrivé par courriel"
via_auto_generated_email: "ce message est arrivé via un courriel généré automatiquement"
@@ -2371,7 +2371,7 @@ fr:
save: "Sauvegarder les options"
few_likes_left: "Merci de partager votre amour ! Vous n'avez plus que quelques J'aime à distribuer pour aujourd'hui."
controls:
- reply: "commencez à répondre à ce message"
+ reply: "commencer à répondre à ce message"
like: "J'aime ce message"
has_liked: "vous avez aimé ce message"
read_indicator: "Membres ayant lu cette publication"
@@ -2394,11 +2394,11 @@ fr:
other: "Oui et les {{count}} réponses"
just_the_post: "Non, uniquement ce message"
admin: "actions d'administration sur le message"
- wiki: "Basculer en mode Wiki"
- unwiki: "Retirer le mode Wiki"
+ wiki: "Basculer en mode wiki"
+ unwiki: "Retirer le mode wiki"
convert_to_moderator: "Ajouter la couleur modérateur"
revert_to_regular: "Retirer la couleur modérateur"
- rebake: "Reconstruire l'HTML"
+ rebake: "Reconstruire le HTML"
publish_page: "Publication de pages"
unhide: "Ré-afficher"
change_owner: "Modifier l'auteur"
@@ -2502,7 +2502,7 @@ fr:
actions:
delete_bookmark:
name: "Supprimer le signet"
- description: "Supprime le signet de votre profile et annule tous les rappels pour ce signet"
+ description: "Supprime le signet de votre profil et annule tous les rappels pour ce signet"
edit_bookmark:
name: "Modifier le signet"
description: "Modifier le nom du signet ou changer la date et l'heure du rappel"
@@ -2556,7 +2556,7 @@ fr:
already_used: "Cette couleur est déjà utilisée par une autre catégorie"
security: "Sécurité"
special_warning: "Avertissement : cette catégorie est une catégorie pré-remplie et les réglages de sécurité ne peuvent pas être modifiés. Si vous ne souhaitez pas utiliser cette catégorie, supprimez là au lieu de détourner sa fonction."
- uncategorized_security_warning: "Cette catégorie est spéciale. Elle sert de zone d'attente pour les sujets qui n'ont pas de catégorie ; vous ne pouvez pas changer ses paramètres de sécurité."
+ uncategorized_security_warning: "Cette catégorie est spéciale. Elle sert à rassembler les sujets qui n'ont pas de catégorie ; vous ne pouvez pas changer ses paramètres de sécurité."
uncategorized_general_warning: 'Cette catégorie est spéciale. Elle sert de catégorie par défaut pour les nouveaux sujets qui ne sont pas liés à une catégorie. Si vous souhaitez changer cela et forcer la sélection de catégorie, veuillez désactiver ce paramètre . Si vous voulez modifier son nom ou sa description, allez dans Personnaliser / Contenu .'
pending_permission_change_alert: "Vous n'avez pas ajouté %{group} à cette catégorie ; cliquez sur ce bouton pour l'ajouter."
images: "Images"
@@ -2566,10 +2566,10 @@ fr:
email_in_disabled_click: 'activez le paramètre « email in ».'
mailinglist_mirror: "La catégorie reflète une liste de diffusion"
show_subcategory_list: "Afficher la liste des sous-catégories au dessus des sujets dans cette catégorie."
- read_only_banner: "Texte du bandeau à afficher lorsqu'un utilisateur ne peut pas créer un sujet dans cette catégorie :"
+ read_only_banner: "Texte du bandeau à afficher lorsqu'un utilisateur ne peut pas créer un sujet dans cette catégorie :"
num_featured_topics: "Nombre de sujets affichés sur la page des catégories :"
subcategory_num_featured_topics: "Nombre de sujets à la une sur la page de la catégorie parente :"
- all_topics_wiki: "Faire des nouveaux sujets des Wikis par défaut"
+ all_topics_wiki: "Faire des nouveaux sujets des wikis par défaut"
subcategory_list_style: "Style des listes de sous-catégories :"
sort_order: "Trier la liste de sujets par :"
default_view: "Liste de sujets par défaut :"
@@ -2597,7 +2597,7 @@ fr:
title: "Surveiller les nouveaux sujets"
description: "Vous serez averti de nouveaux sujets dans cette catégorie mais pas de réponses aux sujets."
tracking:
- title: "Suivi"
+ title: "Suivre"
description: "Vous allez suivre automatiquement tous les sujets dans ces catégories. Vous serez notifié lorsque quelqu'un vous mentionne ou vous répond, et le nombre de nouvelles réponses sera affiché."
regular:
title: "Normal"
@@ -2716,7 +2716,7 @@ fr:
low {avec un ratio élevé de J'aime par message}
- med {avec un ratio très élevée de J'aime par message}
+ med {avec un ratio très élevé de J'aime par message}
high {avec un ratio extrêmement élevé de J'aime par message}
@@ -3012,7 +3012,7 @@ fr:
title: "Surveiller les nouveaux sujets"
description: "Vous serez averti de nouveaux sujets avec cette étiquette mais pas de réponses aux sujets."
tracking:
- title: "Suivi"
+ title: "Suivre"
description: "Vous allez suivre automatiquement tous les sujets avec cette étiquette. Le nombre de messages non lus et nouveaux apparaîtra à côté du sujet."
regular:
title: "Normal"
@@ -3089,14 +3089,14 @@ fr:
critical_available: "Une mise à jour critique est disponible."
updates_available: "Des mises à jour sont disponibles."
please_upgrade: "Veuillez mettre à jour !"
- no_check_performed: "Une vérification des mises à jour n'a pas été effectuée. Vérifiez que sidekiq est en cours d'exécution."
+ no_check_performed: "Une vérification de mises à jour n'a pas été effectuée. Vérifiez que sidekiq est en cours d'exécution."
stale_data: "Une vérification des mises à jour n'a pas été effectuée récemment. Vérifiez que sidekiq est en cours d'exécution."
version_check_pending: "On dirait que vous avez récemment fait une mise à jour. Super !"
installed_version: "Installée"
latest_version: "Dernière"
problems_found: "Quelques conseils d'après vos paramètres actuels"
last_checked: "Dernière vérification"
- refresh_problems: "Rafraîchir"
+ refresh_problems: "Actualiser"
no_problems: "Aucun problème n'a été trouvé."
moderators: "Modérateurs :"
admins: "Administateurs :"
@@ -3114,7 +3114,7 @@ fr:
other: "%{count} sauvegardes sur %{location}"
lastest_backup: "Dernière sauvegarde : %{date}"
traffic_short: "Trafic"
- traffic: "Requêtes d'application Web"
+ traffic: "Requêtes d'application web"
page_views: "Pages vues"
page_views_short: "Pages vues"
show_traffic_report: "Afficher le rapport de trafic détaillé"
@@ -3212,7 +3212,7 @@ fr:
automatic_membership_email_domains: "Les utilisateurs qui s'enregistrent avec un domaine courriel qui correspond exactement à un élément de cette liste seront automatiquement ajoutés à ce groupe :"
automatic_membership_user_count: "%{count} utilisateurs ont les nouveaux domaines de courriel et seront ajoutés au groupe."
primary_group: "Définir comme groupe principal automatiquement"
- name_placeholder: "Nom du groupe (sans espace, mêmes règles que pour les noms d'utilisateurs)"
+ name_placeholder: "Nom du groupe (sans espaces, mêmes règles que pour les noms d'utilisateurs)"
primary: "Groupe principal"
no_primary: "(pas de groupe principal)"
title: "Groupes"
@@ -3409,7 +3409,7 @@ fr:
download:
label: "Télécharger"
title: "Envoyer un courriel avec un lien de téléchargement"
- alert: "Un lien pour télécharger la sauvegarder vous a été envoyé. "
+ alert: "Un lien pour télécharger la sauvegarde vous a été envoyé par courriel. "
destroy:
title: "Supprimer la sauvegarde"
confirm: "Êtes-vous sûr de vouloir détruire cette sauvegarde ?"
@@ -3433,7 +3433,7 @@ fr:
button_title:
user: "Exporter la liste des utilisateurs dans un fichier CSV."
staff_action: "Exporter la liste des actions des responsables dans un fichier CSV."
- screened_email: "Exporter la liste des adresses de courriel sous surveillance dans un fichier CSV."
+ screened_email: "Exporter la liste des adresses courriel sous surveillance dans un fichier CSV."
screened_ip: "Exporter la liste des adresses IP sous surveillance dans un fichier CSV."
screened_url: "Exporter toutes les URL sous surveillance vers un fichier CSV"
export_json:
@@ -3534,7 +3534,7 @@ fr:
upload: "Envoyer"
select_component: "Sélectionner un composant…"
unsaved_changes_alert: "Vous n'avez pas encore sauvegardé vos modifications, voulez-vous les abandonner et continuer ?"
- unsaved_parent_themes: "Vous n'avez assigné le composant à aucun thème, voulez-vous continuer ?"
+ unsaved_parent_themes: "Vous n'avez assigné le composant à aucun thème, voulez-vous continuer ?"
discard: "Abandonner"
stay: "Rester"
css_html: "CSS/HTML personnalisé"
@@ -3597,7 +3597,7 @@ fr:
title: "Entrez du HTML à afficher sur toutes les pages après l'entête"
footer:
text: "Pied de page"
- title: "Entrez du HTML à afficher de le pied de page"
+ title: "Entrez du HTML à afficher dans le pied de page"
embedded_scss:
text: "CSS intégré"
title: "Entrez du CSS personnalisé pour la version intégrée des commentaires"
@@ -3609,7 +3609,7 @@ fr:
title: "HTML qui sera inséré avant la balise