Merge branch 'main' into feature/wizard-look-and-feel-improvements
This commit is contained in:
commit
8533290a9c
60
Gemfile.lock
60
Gemfile.lock
|
@ -119,7 +119,7 @@ GEM
|
||||||
css_parser (1.19.1)
|
css_parser (1.19.1)
|
||||||
addressable
|
addressable
|
||||||
csv (3.3.0)
|
csv (3.3.0)
|
||||||
date (3.4.0)
|
date (3.4.1)
|
||||||
debug_inspector (1.2.0)
|
debug_inspector (1.2.0)
|
||||||
diff-lcs (1.5.1)
|
diff-lcs (1.5.1)
|
||||||
diffy (3.4.3)
|
diffy (3.4.3)
|
||||||
|
@ -161,16 +161,16 @@ GEM
|
||||||
fspath (3.1.2)
|
fspath (3.1.2)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
google-protobuf (4.29.0-aarch64-linux)
|
google-protobuf (4.29.1-aarch64-linux)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
google-protobuf (4.29.0-arm64-darwin)
|
google-protobuf (4.29.1-arm64-darwin)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
google-protobuf (4.29.0-x86_64-darwin)
|
google-protobuf (4.29.1-x86_64-darwin)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
google-protobuf (4.29.0-x86_64-linux)
|
google-protobuf (4.29.1-x86_64-linux)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
guess_html_encoding (0.0.11)
|
guess_html_encoding (0.0.11)
|
||||||
|
@ -197,9 +197,10 @@ GEM
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
iso8601 (0.13.0)
|
iso8601 (0.13.0)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.8.2)
|
json (2.9.0)
|
||||||
json-schema (5.1.0)
|
json-schema (5.1.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
|
bigdecimal (~> 3.1)
|
||||||
json_schemer (2.3.0)
|
json_schemer (2.3.0)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
hana (~> 1.3)
|
hana (~> 1.3)
|
||||||
|
@ -217,7 +218,7 @@ GEM
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
literate_randomizer (0.4.0)
|
literate_randomizer (0.4.0)
|
||||||
logger (1.6.1)
|
logger (1.6.2)
|
||||||
lograge (0.14.0)
|
lograge (0.14.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
|
@ -252,7 +253,7 @@ GEM
|
||||||
mini_suffix (0.3.3)
|
mini_suffix (0.3.3)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
minio_runner (0.1.2)
|
minio_runner (0.1.2)
|
||||||
minitest (5.25.2)
|
minitest (5.25.4)
|
||||||
mocha (2.6.1)
|
mocha (2.6.1)
|
||||||
ruby2_keywords (>= 0.0.5)
|
ruby2_keywords (>= 0.0.5)
|
||||||
msgpack (1.7.5)
|
msgpack (1.7.5)
|
||||||
|
@ -260,7 +261,7 @@ GEM
|
||||||
multi_xml (0.7.1)
|
multi_xml (0.7.1)
|
||||||
bigdecimal (~> 3.1)
|
bigdecimal (~> 3.1)
|
||||||
mustache (1.1.1)
|
mustache (1.1.1)
|
||||||
net-http (0.5.0)
|
net-http (0.6.0)
|
||||||
uri
|
uri
|
||||||
net-imap (0.5.1)
|
net-imap (0.5.1)
|
||||||
date
|
date
|
||||||
|
@ -272,13 +273,13 @@ GEM
|
||||||
net-smtp (0.5.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.4)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.16.7-aarch64-linux)
|
nokogiri (1.16.8-aarch64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-arm64-darwin)
|
nokogiri (1.16.8-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-x86_64-darwin)
|
nokogiri (1.16.8-x86_64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-x86_64-linux)
|
nokogiri (1.16.8-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oauth (1.1.0)
|
oauth (1.1.0)
|
||||||
oauth-tty (~> 1.0, >= 1.0.1)
|
oauth-tty (~> 1.0, >= 1.0.1)
|
||||||
|
@ -343,7 +344,8 @@ GEM
|
||||||
pry-stack_explorer (0.6.1)
|
pry-stack_explorer (0.6.1)
|
||||||
binding_of_caller (~> 1.0)
|
binding_of_caller (~> 1.0)
|
||||||
pry (~> 0.13)
|
pry (~> 0.13)
|
||||||
psych (5.2.0)
|
psych (5.2.1)
|
||||||
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.5.0)
|
puma (6.5.0)
|
||||||
|
@ -366,9 +368,9 @@ GEM
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.0)
|
rails-html-sanitizer (1.6.1)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (~> 1.14)
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||||
rails_failover (2.1.1)
|
rails_failover (2.1.1)
|
||||||
activerecord (>= 6.1, < 8.0)
|
activerecord (>= 6.1, < 8.0)
|
||||||
concurrent-ruby
|
concurrent-ruby
|
||||||
|
@ -441,7 +443,7 @@ GEM
|
||||||
rspec-expectations (~> 3.13)
|
rspec-expectations (~> 3.13)
|
||||||
rspec-mocks (~> 3.13)
|
rspec-mocks (~> 3.13)
|
||||||
rspec-support (~> 3.13)
|
rspec-support (~> 3.13)
|
||||||
rspec-support (3.13.1)
|
rspec-support (3.13.2)
|
||||||
rss (0.3.1)
|
rss (0.3.1)
|
||||||
rexml
|
rexml
|
||||||
rswag-specs (2.16.0)
|
rswag-specs (2.16.0)
|
||||||
|
@ -451,21 +453,21 @@ GEM
|
||||||
rspec-core (>= 2.14)
|
rspec-core (>= 2.14)
|
||||||
rtlcss (0.2.1)
|
rtlcss (0.2.1)
|
||||||
mini_racer (>= 0.6.3)
|
mini_racer (>= 0.6.3)
|
||||||
rubocop (1.69.0)
|
rubocop (1.69.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.4, < 3.0)
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
rubocop-ast (>= 1.36.1, < 2.0)
|
rubocop-ast (>= 1.36.2, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.36.2)
|
rubocop-ast (1.36.2)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.1.0)
|
||||||
rubocop-capybara (2.21.0)
|
rubocop-capybara (2.21.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-discourse (3.8.6)
|
rubocop-discourse (3.9.0)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
rubocop (>= 1.59.0)
|
rubocop (>= 1.59.0)
|
||||||
rubocop-capybara (>= 2.0.0)
|
rubocop-capybara (>= 2.0.0)
|
||||||
|
@ -505,7 +507,7 @@ GEM
|
||||||
google-protobuf (>= 3.25, < 5.0)
|
google-protobuf (>= 3.25, < 5.0)
|
||||||
sassc-embedded (1.77.7)
|
sassc-embedded (1.77.7)
|
||||||
sass-embedded (~> 1.77)
|
sass-embedded (~> 1.77)
|
||||||
securerandom (0.3.2)
|
securerandom (0.4.0)
|
||||||
selenium-devtools (0.131.0)
|
selenium-devtools (0.131.0)
|
||||||
selenium-webdriver (~> 4.2)
|
selenium-webdriver (~> 4.2)
|
||||||
selenium-webdriver (4.27.0)
|
selenium-webdriver (4.27.0)
|
||||||
|
@ -538,10 +540,10 @@ GEM
|
||||||
actionpack (>= 6.1)
|
actionpack (>= 6.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
sprockets (>= 3.0.0)
|
sprockets (>= 3.0.0)
|
||||||
sqlite3 (2.3.1-aarch64-linux-gnu)
|
sqlite3 (2.4.0-aarch64-linux-gnu)
|
||||||
sqlite3 (2.3.1-arm64-darwin)
|
sqlite3 (2.4.0-arm64-darwin)
|
||||||
sqlite3 (2.3.1-x86_64-darwin)
|
sqlite3 (2.4.0-x86_64-darwin)
|
||||||
sqlite3 (2.3.1-x86_64-linux-gnu)
|
sqlite3 (2.4.0-x86_64-linux-gnu)
|
||||||
sshkey (3.0.0)
|
sshkey (3.0.0)
|
||||||
stackprof (0.2.26)
|
stackprof (0.2.26)
|
||||||
stringio (3.1.2)
|
stringio (3.1.2)
|
||||||
|
@ -567,7 +569,7 @@ GEM
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
uniform_notifier (1.16.0)
|
uniform_notifier (1.16.0)
|
||||||
uri (1.0.2)
|
uri (1.0.2)
|
||||||
useragent (0.16.10)
|
useragent (0.16.11)
|
||||||
version_gem (1.1.4)
|
version_gem (1.1.4)
|
||||||
web-push (3.0.1)
|
web-push (3.0.1)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
|
@ -576,7 +578,7 @@ GEM
|
||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.9.0)
|
webrick (1.9.1)
|
||||||
websocket (1.2.11)
|
websocket (1.2.11)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
|
|
|
@ -27,6 +27,7 @@ const FORM_FIELDS = [
|
||||||
"badge_grouping_id",
|
"badge_grouping_id",
|
||||||
"trigger",
|
"trigger",
|
||||||
"badge_type_id",
|
"badge_type_id",
|
||||||
|
"show_in_post_header",
|
||||||
];
|
];
|
||||||
|
|
||||||
export default class AdminBadgesShowController extends Controller {
|
export default class AdminBadgesShowController extends Controller {
|
||||||
|
@ -40,8 +41,6 @@ export default class AdminBadgesShowController extends Controller {
|
||||||
@tracked model;
|
@tracked model;
|
||||||
@tracked previewLoading = false;
|
@tracked previewLoading = false;
|
||||||
@tracked selectedGraphicType = null;
|
@tracked selectedGraphicType = null;
|
||||||
@tracked userBadges;
|
|
||||||
@tracked userBadgesAll;
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get formData() {
|
get formData() {
|
||||||
|
@ -80,6 +79,17 @@ export default class AdminBadgesShowController extends Controller {
|
||||||
return this.model.system;
|
return this.model.system;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
postHeaderDescription(data) {
|
||||||
|
return this.disableBadgeOnPosts(data) && !data.system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
disableBadgeOnPosts(data) {
|
||||||
|
const { listable, show_posts } = data;
|
||||||
|
return !listable || !show_posts;
|
||||||
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
// this is needed because the model doesnt have default values
|
// this is needed because the model doesnt have default values
|
||||||
// Using `set` here isn't ideal, but we don't know that tracking is set up on the model yet.
|
// Using `set` here isn't ideal, but we don't know that tracking is set up on the model yet.
|
||||||
|
|
|
@ -245,7 +245,7 @@
|
||||||
</field.Menu>
|
</field.Menu>
|
||||||
</form.Field>
|
</form.Field>
|
||||||
|
|
||||||
<form.CheckboxGroup as |group|>
|
<form.CheckboxGroup @title={{i18n "admin.badges.usage_heading"}} as |group|>
|
||||||
<group.Field
|
<group.Field
|
||||||
@title={{i18n "admin.badges.allow_title"}}
|
@title={{i18n "admin.badges.allow_title"}}
|
||||||
@showTitle={{false}}
|
@showTitle={{false}}
|
||||||
|
@ -264,7 +264,12 @@
|
||||||
>
|
>
|
||||||
<field.Checkbox />
|
<field.Checkbox />
|
||||||
</group.Field>
|
</group.Field>
|
||||||
|
</form.CheckboxGroup>
|
||||||
|
|
||||||
|
<form.CheckboxGroup
|
||||||
|
@title={{i18n "admin.badges.visibility_heading"}}
|
||||||
|
as |group|
|
||||||
|
>
|
||||||
<group.Field
|
<group.Field
|
||||||
@title={{i18n "admin.badges.listable"}}
|
@title={{i18n "admin.badges.listable"}}
|
||||||
@showTitle={{false}}
|
@showTitle={{false}}
|
||||||
|
@ -284,6 +289,20 @@
|
||||||
>
|
>
|
||||||
<field.Checkbox />
|
<field.Checkbox />
|
||||||
</group.Field>
|
</group.Field>
|
||||||
|
|
||||||
|
<group.Field
|
||||||
|
@title={{i18n "admin.badges.show_in_post_header"}}
|
||||||
|
@showTitle={{false}}
|
||||||
|
@name="show_in_post_header"
|
||||||
|
@disabled={{this.disableBadgeOnPosts data}}
|
||||||
|
as |field|
|
||||||
|
>
|
||||||
|
<field.Checkbox>
|
||||||
|
{{#if (this.postHeaderDescription data)}}
|
||||||
|
{{i18n "admin.badges.show_in_post_header_disabled"}}
|
||||||
|
{{/if}}
|
||||||
|
</field.Checkbox>
|
||||||
|
</group.Field>
|
||||||
</form.CheckboxGroup>
|
</form.CheckboxGroup>
|
||||||
</form.Section>
|
</form.Section>
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,10 @@
|
||||||
|
|
||||||
<PluginOutlet @name="admin-users-list-show-before" />
|
<PluginOutlet @name="admin-users-list-show-before" />
|
||||||
|
|
||||||
<div class="admin-users-list__controls">
|
<div class="d-admin-filter admin-users-list__controls">
|
||||||
<div class="admin-users-list__search">
|
<div class="admin-filter__input-container admin-users-list__search">
|
||||||
<input
|
<input
|
||||||
|
class="admin-filter__input"
|
||||||
type="text"
|
type="text"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
placeholder={{this.searchHint}}
|
placeholder={{this.searchHint}}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"@types/jquery": "^3.5.32",
|
"@types/jquery": "^3.5.32",
|
||||||
"@types/qunit": "^2.19.12",
|
"@types/qunit": "^2.19.12",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -4,10 +4,6 @@ const DEPRECATION_WORKFLOW = [
|
||||||
handler: "silence",
|
handler: "silence",
|
||||||
matchId: "discourse.decorate-widget.hamburger-widget-links",
|
matchId: "discourse.decorate-widget.hamburger-widget-links",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
handler: "silence",
|
|
||||||
matchId: "discourse.fontawesome-6-upgrade",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
handler: "silence",
|
handler: "silence",
|
||||||
matchId: "discourse.post-menu-widget-overrides",
|
matchId: "discourse.post-menu-widget-overrides",
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -187,7 +187,7 @@ function renderImageOrPlayableMedia(tokens, idx, options, env, slf) {
|
||||||
options.discourse.previewing &&
|
options.discourse.previewing &&
|
||||||
!options.discourse.limitedSiteSettings.enableDiffhtmlPreview
|
!options.discourse.limitedSiteSettings.enableDiffhtmlPreview
|
||||||
) {
|
) {
|
||||||
const origSrc = token.attrGet("data-orig-src");
|
const origSrc = token.attrGet("data-orig-src") || token.attrGet("src");
|
||||||
const origSrcId = origSrc
|
const origSrcId = origSrc
|
||||||
.substring(origSrc.lastIndexOf("/") + 1)
|
.substring(origSrc.lastIndexOf("/") + 1)
|
||||||
.split(".")[0];
|
.split(".")[0];
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ember-cli": "~6.0.1",
|
"ember-cli": "~6.0.1",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"@ember/optional-features": "^2.2.0",
|
"@ember/optional-features": "^2.2.0",
|
||||||
"@embroider/test-setup": "^4.0.0",
|
"@embroider/test-setup": "^4.0.0",
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/syntax": "^0.92.3",
|
"@glimmer/syntax": "^0.93.1",
|
||||||
"broccoli-asset-rev": "^3.0.0",
|
"broccoli-asset-rev": "^3.0.0",
|
||||||
"ember-cli": "~6.0.1",
|
"ember-cli": "~6.0.1",
|
||||||
"ember-cli-inject-live-reload": "^2.1.0",
|
"ember-cli-inject-live-reload": "^2.1.0",
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -215,7 +215,7 @@ export default class AboutPage extends Component {
|
||||||
{{#if this.currentUser.admin}}
|
{{#if this.currentUser.admin}}
|
||||||
<p>
|
<p>
|
||||||
<LinkTo class="edit-about-page" @route="adminConfig.about">
|
<LinkTo class="edit-about-page" @route="adminConfig.about">
|
||||||
{{dIcon "pencil-alt"}}
|
{{dIcon "pencil"}}
|
||||||
<span>{{i18n "about.edit"}}</span>
|
<span>{{i18n "about.edit"}}</span>
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -10,6 +10,10 @@ export default class BadgeButton extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showName() {
|
||||||
|
return this.args.showName ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
title={{this.title}}
|
title={{this.title}}
|
||||||
|
@ -20,7 +24,9 @@ export default class BadgeButton extends Component {
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
{{iconOrImage @badge}}
|
{{iconOrImage @badge}}
|
||||||
|
{{#if this.showName}}
|
||||||
<span class="badge-display-name">{{@badge.name}}</span>
|
<span class="badge-display-name">{{@badge.name}}</span>
|
||||||
|
{{/if}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { tagName } from "@ember-decorators/component";
|
import { tagName } from "@ember-decorators/component";
|
||||||
|
import { applyValueTransformer } from "discourse/lib/transformer";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const LIST_TYPE = {
|
const LIST_TYPE = {
|
||||||
|
@ -40,4 +41,8 @@ export default class CategoryListItem extends Component {
|
||||||
slugPath(categoryPath) {
|
slugPath(categoryPath) {
|
||||||
return categoryPath.substring("/c/".length);
|
return categoryPath.substring("/c/".length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyValueTransformer(name, value, context) {
|
||||||
|
return applyValueTransformer(name, value, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
@outletArgs={{hash composer=this.composer.model editorType="composer"}}
|
@outletArgs={{hash composer=this.composer.model editorType="composer"}}
|
||||||
@topicId={{this.composer.model.topic.id}}
|
@topicId={{this.composer.model.topic.id}}
|
||||||
@categoryId={{this.composer.model.category.id}}
|
@categoryId={{this.composer.model.category.id}}
|
||||||
|
@onSetup={{this.setupEditor}}
|
||||||
>
|
>
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</DEditor>
|
</DEditor>
|
||||||
|
|
|
@ -18,7 +18,6 @@ import {
|
||||||
linkSeenMentions,
|
linkSeenMentions,
|
||||||
} from "discourse/lib/link-mentions";
|
} from "discourse/lib/link-mentions";
|
||||||
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
||||||
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
|
|
||||||
import {
|
import {
|
||||||
authorizesOneOrMoreImageExtensions,
|
authorizesOneOrMoreImageExtensions,
|
||||||
IMAGE_MARKDOWN_REGEX,
|
IMAGE_MARKDOWN_REGEX,
|
||||||
|
@ -139,7 +138,7 @@ export default class ComposerEditor extends Component {
|
||||||
@observes("composer.focusTarget")
|
@observes("composer.focusTarget")
|
||||||
setFocus() {
|
setFocus() {
|
||||||
if (this.composer.focusTarget === "editor") {
|
if (this.composer.focusTarget === "editor") {
|
||||||
putCursorAtEnd(this.element.querySelector("textarea"));
|
this.textManipulation.putCursorAtEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,21 +187,9 @@ export default class ComposerEditor extends Component {
|
||||||
|
|
||||||
@on("didInsertElement")
|
@on("didInsertElement")
|
||||||
_composerEditorInit() {
|
_composerEditorInit() {
|
||||||
const input = this.element.querySelector(".d-editor-input");
|
|
||||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||||
|
|
||||||
input?.addEventListener(
|
|
||||||
"scroll",
|
|
||||||
this._throttledSyncEditorAndPreviewScroll
|
|
||||||
);
|
|
||||||
|
|
||||||
this._registerImageAltTextButtonClick(preview);
|
this._registerImageAltTextButtonClick(preview);
|
||||||
|
|
||||||
// Focus on the body unless we have a title
|
|
||||||
if (!this.get("composer.model.canEditTitle")) {
|
|
||||||
putCursorAtEnd(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.composer.allowUpload) {
|
if (this.composer.allowUpload) {
|
||||||
this.uppyComposerUpload.setup(this.element);
|
this.uppyComposerUpload.setup(this.element);
|
||||||
}
|
}
|
||||||
|
@ -210,6 +197,30 @@ export default class ComposerEditor extends Component {
|
||||||
this.appEvents.trigger(`${this.composerEventPrefix}:will-open`);
|
this.appEvents.trigger(`${this.composerEventPrefix}:will-open`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
setupEditor(textManipulation) {
|
||||||
|
this.textManipulation = textManipulation;
|
||||||
|
|
||||||
|
const input = this.element.querySelector(".d-editor-input");
|
||||||
|
|
||||||
|
input?.addEventListener(
|
||||||
|
"scroll",
|
||||||
|
this._throttledSyncEditorAndPreviewScroll
|
||||||
|
);
|
||||||
|
|
||||||
|
// Focus on the body unless we have a title
|
||||||
|
if (!this.get("composer.model.canEditTitle")) {
|
||||||
|
this.textManipulation.putCursorAtEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
input?.removeEventListener(
|
||||||
|
"scroll",
|
||||||
|
this._throttledSyncEditorAndPreviewScroll
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
"composer.model.reply",
|
"composer.model.reply",
|
||||||
"composer.model.replyLength",
|
"composer.model.replyLength",
|
||||||
|
@ -785,7 +796,6 @@ export default class ComposerEditor extends Component {
|
||||||
|
|
||||||
@on("willDestroyElement")
|
@on("willDestroyElement")
|
||||||
_composerClosed() {
|
_composerClosed() {
|
||||||
const input = this.element.querySelector(".d-editor-input");
|
|
||||||
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
const preview = this.element.querySelector(".d-editor-preview-wrapper");
|
||||||
|
|
||||||
if (this.composer.allowUpload) {
|
if (this.composer.allowUpload) {
|
||||||
|
@ -802,11 +812,6 @@ export default class ComposerEditor extends Component {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
input?.removeEventListener(
|
|
||||||
"scroll",
|
|
||||||
this._throttledSyncEditorAndPreviewScroll
|
|
||||||
);
|
|
||||||
|
|
||||||
preview?.removeEventListener("click", this._handleAltTextCancelButtonClick);
|
preview?.removeEventListener("click", this._handleAltTextCancelButtonClick);
|
||||||
preview?.removeEventListener("click", this._handleAltTextEditButtonClick);
|
preview?.removeEventListener("click", this._handleAltTextEditButtonClick);
|
||||||
preview?.removeEventListener("click", this._handleAltTextOkButtonClick);
|
preview?.removeEventListener("click", this._handleAltTextOkButtonClick);
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { PLATFORM_KEY_MODIFIER } from "discourse/lib/keyboard-shortcuts";
|
||||||
import { linkSeenMentions } from "discourse/lib/link-mentions";
|
import { linkSeenMentions } from "discourse/lib/link-mentions";
|
||||||
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
||||||
import { emojiUrlFor, generateCookFunction } from "discourse/lib/text";
|
import { emojiUrlFor, generateCookFunction } from "discourse/lib/text";
|
||||||
import { getHead } from "discourse/lib/textarea-text-manipulation";
|
|
||||||
import userSearch from "discourse/lib/user-search";
|
import userSearch from "discourse/lib/user-search";
|
||||||
import {
|
import {
|
||||||
destroyUserStatuses,
|
destroyUserStatuses,
|
||||||
|
@ -36,8 +35,6 @@ import { findRawTemplate } from "discourse-common/lib/raw-templates";
|
||||||
import discourseComputed, { bind } from "discourse-common/utils/decorators";
|
import discourseComputed, { bind } from "discourse-common/utils/decorators";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
const FOUR_SPACES_INDENT = "4-spaces-indent";
|
|
||||||
|
|
||||||
let _createCallbacks = [];
|
let _createCallbacks = [];
|
||||||
|
|
||||||
export function addToolbarCallback(func) {
|
export function addToolbarCallback(func) {
|
||||||
|
@ -145,8 +142,6 @@ export default class DEditor extends Component {
|
||||||
|
|
||||||
keymap["tab"] = () => this.textManipulation.indentSelection("right");
|
keymap["tab"] = () => this.textManipulation.indentSelection("right");
|
||||||
keymap["shift+tab"] = () => this.textManipulation.indentSelection("left");
|
keymap["shift+tab"] = () => this.textManipulation.indentSelection("left");
|
||||||
keymap[`${PLATFORM_KEY_MODIFIER}+shift+.`] = () =>
|
|
||||||
this.send("insertCurrentTime");
|
|
||||||
|
|
||||||
return keymap;
|
return keymap;
|
||||||
}
|
}
|
||||||
|
@ -485,34 +480,6 @@ export default class DEditor extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_applyList(sel, head, exampleKey, opts) {
|
|
||||||
if (sel.value.includes("\n")) {
|
|
||||||
this.textManipulation.applySurround(sel, head, "", exampleKey, opts);
|
|
||||||
} else {
|
|
||||||
const [hval, hlen] = getHead(head);
|
|
||||||
if (sel.start === sel.end) {
|
|
||||||
sel.value = i18n(`composer.${exampleKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmedPre = sel.pre.trim();
|
|
||||||
const number = sel.value.startsWith(hval)
|
|
||||||
? sel.value.slice(hlen)
|
|
||||||
: `${hval}${sel.value}`;
|
|
||||||
const preLines = trimmedPre.length ? `${trimmedPre}\n\n` : "";
|
|
||||||
|
|
||||||
const trimmedPost = sel.post.trim();
|
|
||||||
const post = trimmedPost.length ? `\n\n${trimmedPost}` : trimmedPost;
|
|
||||||
|
|
||||||
this.set("value", `${preLines}${number}${post}`);
|
|
||||||
this.textManipulation.selectText(preLines.length, number.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_applySurround(head, tail, exampleKey, opts) {
|
|
||||||
const selected = this.textManipulation.getSelected();
|
|
||||||
this.textManipulation.applySurround(selected, head, tail, exampleKey, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
rovingButtonBar(event) {
|
rovingButtonBar(event) {
|
||||||
let target = event.target;
|
let target = event.target;
|
||||||
|
@ -559,6 +526,27 @@ export default class DEditor extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a toolbar event object passed to toolbar buttons.
|
||||||
|
*
|
||||||
|
* @typedef {Object} ToolbarEvent
|
||||||
|
* @property {function} applySurround - Applies surrounding text
|
||||||
|
* @property {function} formatCode - Formats as code
|
||||||
|
* @property {function} replaceText - Replaces text
|
||||||
|
* @property {function} selectText - Selects a range of text
|
||||||
|
* @property {function} toggleDirection - Toggles text direction
|
||||||
|
* @property {function} getText - Gets the text
|
||||||
|
* @property {function} addText - Adds text
|
||||||
|
* @property {function} applyList - Applies a list format
|
||||||
|
* @property {*} selected - The current selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new toolbar event object
|
||||||
|
*
|
||||||
|
* @param {boolean} trimLeading - Whether to trim leading whitespace
|
||||||
|
* @returns {ToolbarEvent} An object with toolbar event actions
|
||||||
|
*/
|
||||||
newToolbarEvent(trimLeading) {
|
newToolbarEvent(trimLeading) {
|
||||||
const selected = this.textManipulation.getSelected(trimLeading);
|
const selected = this.textManipulation.getSelected(trimLeading);
|
||||||
return {
|
return {
|
||||||
|
@ -574,8 +562,8 @@ export default class DEditor extends Component {
|
||||||
opts
|
opts
|
||||||
),
|
),
|
||||||
applyList: (head, exampleKey, opts) =>
|
applyList: (head, exampleKey, opts) =>
|
||||||
this._applyList(selected, head, exampleKey, opts),
|
this.textManipulation.applyList(selected, head, exampleKey, opts),
|
||||||
formatCode: (...args) => this.send("formatCode", args),
|
formatCode: () => this.textManipulation.formatCode(),
|
||||||
addText: (text) => this.textManipulation.addText(selected, text),
|
addText: (text) => this.textManipulation.addText(selected, text),
|
||||||
getText: () => this.value,
|
getText: () => this.value,
|
||||||
toggleDirection: () => this.textManipulation.toggleDirection(),
|
toggleDirection: () => this.textManipulation.toggleDirection(),
|
||||||
|
@ -628,71 +616,6 @@ export default class DEditor extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
formatCode() {
|
|
||||||
if (this.disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sel = this.textManipulation.getSelected("", { lineVal: true });
|
|
||||||
const selValue = sel.value;
|
|
||||||
const hasNewLine = selValue.includes("\n");
|
|
||||||
const isBlankLine = sel.lineVal.trim().length === 0;
|
|
||||||
const isFourSpacesIndent =
|
|
||||||
this.siteSettings.code_formatting_style === FOUR_SPACES_INDENT;
|
|
||||||
|
|
||||||
if (!hasNewLine) {
|
|
||||||
if (selValue.length === 0 && isBlankLine) {
|
|
||||||
if (isFourSpacesIndent) {
|
|
||||||
const example = i18n(`composer.code_text`);
|
|
||||||
this.set("value", `${sel.pre} ${example}${sel.post}`);
|
|
||||||
return this.textManipulation.selectText(
|
|
||||||
sel.pre.length + 4,
|
|
||||||
example.length
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return this.textManipulation.applySurround(
|
|
||||||
sel,
|
|
||||||
"```\n",
|
|
||||||
"\n```",
|
|
||||||
"paste_code_text"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.textManipulation.applySurround(sel, "`", "`", "code_title");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isFourSpacesIndent) {
|
|
||||||
return this.textManipulation.applySurround(
|
|
||||||
sel,
|
|
||||||
" ",
|
|
||||||
"",
|
|
||||||
"code_text"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const preNewline = sel.pre[-1] !== "\n" && sel.pre !== "" ? "\n" : "";
|
|
||||||
const postNewline = sel.post[0] !== "\n" ? "\n" : "";
|
|
||||||
return this.textManipulation.addText(
|
|
||||||
sel,
|
|
||||||
`${preNewline}\`\`\`\n${sel.value}\n\`\`\`${postNewline}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
insertCurrentTime() {
|
|
||||||
const sel = this.textManipulation.getSelected("", { lineVal: true });
|
|
||||||
const timezone = this.currentUser.user_option.timezone;
|
|
||||||
const time = moment().format("HH:mm:ss");
|
|
||||||
const date = moment().format("YYYY-MM-DD");
|
|
||||||
|
|
||||||
this.textManipulation.addText(
|
|
||||||
sel,
|
|
||||||
`[date=${date} time=${time} timezone="${timezone}"]`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
handleFocusIn() {
|
handleFocusIn() {
|
||||||
this.set("isEditorFocused", true);
|
this.set("isEditorFocused", true);
|
||||||
|
@ -715,6 +638,8 @@ export default class DEditor extends Component {
|
||||||
this._applyHashtagAutocomplete();
|
this._applyHashtagAutocomplete();
|
||||||
this._applyMentionAutocomplete();
|
this._applyMentionAutocomplete();
|
||||||
|
|
||||||
|
const destroyEditor = this.onSetup?.(textManipulation);
|
||||||
|
|
||||||
scheduleOnce("afterRender", this, this._readyNow);
|
scheduleOnce("afterRender", this, this._readyNow);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -723,6 +648,8 @@ export default class DEditor extends Component {
|
||||||
this.element?.removeEventListener("paste", textManipulation.paste);
|
this.element?.removeEventListener("paste", textManipulation.paste);
|
||||||
|
|
||||||
textManipulation.autocomplete("destroy");
|
textManipulation.autocomplete("destroy");
|
||||||
|
|
||||||
|
destroyEditor?.();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -741,7 +668,11 @@ export default class DEditor extends Component {
|
||||||
textManipulation,
|
textManipulation,
|
||||||
"replaceText"
|
"replaceText"
|
||||||
);
|
);
|
||||||
this.appEvents.on("composer:apply-surround", this, "_applySurround");
|
this.appEvents.on(
|
||||||
|
"composer:apply-surround",
|
||||||
|
textManipulation,
|
||||||
|
"applySurroundSelection"
|
||||||
|
);
|
||||||
this.appEvents.on(
|
this.appEvents.on(
|
||||||
"composer:indent-selected-text",
|
"composer:indent-selected-text",
|
||||||
textManipulation,
|
textManipulation,
|
||||||
|
@ -764,7 +695,11 @@ export default class DEditor extends Component {
|
||||||
textManipulation,
|
textManipulation,
|
||||||
"replaceText"
|
"replaceText"
|
||||||
);
|
);
|
||||||
this.appEvents.off("composer:apply-surround", this, "_applySurround");
|
this.appEvents.off(
|
||||||
|
"composer:apply-surround",
|
||||||
|
textManipulation,
|
||||||
|
"applySurroundSelection"
|
||||||
|
);
|
||||||
this.appEvents.off(
|
this.appEvents.off(
|
||||||
"composer:indent-selected-text",
|
"composer:indent-selected-text",
|
||||||
textManipulation,
|
textManipulation,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import DButton from "discourse/components/d-button";
|
||||||
import FlashMessage from "discourse/components/flash-message";
|
import FlashMessage from "discourse/components/flash-message";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
import element from "discourse/helpers/element";
|
import element from "discourse/helpers/element";
|
||||||
|
import htmlClass from "discourse/helpers/html-class";
|
||||||
import {
|
import {
|
||||||
disableBodyScroll,
|
disableBodyScroll,
|
||||||
enableBodyScroll,
|
enableBodyScroll,
|
||||||
|
@ -261,6 +262,7 @@ export default class DModal extends Component {
|
||||||
@inline={{@inline}}
|
@inline={{@inline}}
|
||||||
@append={{true}}
|
@append={{true}}
|
||||||
>
|
>
|
||||||
|
{{htmlClass "modal-open"}}
|
||||||
<this.dynamicElement
|
<this.dynamicElement
|
||||||
class={{concatClass
|
class={{concatClass
|
||||||
"modal"
|
"modal"
|
||||||
|
|
|
@ -5,6 +5,14 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{{#if this.site.mobileView}}
|
{{#if this.site.mobileView}}
|
||||||
|
<PluginOutlet
|
||||||
|
@name="category-list-before-category-mobile"
|
||||||
|
@outletArgs={{hash
|
||||||
|
category=this.category
|
||||||
|
listType=this.listType
|
||||||
|
isMuted=this.isMuted
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
data-category-id={{this.category.id}}
|
data-category-id={{this.category.id}}
|
||||||
data-notification-level={{this.category.notificationLevelString}}
|
data-notification-level={{this.category.notificationLevelString}}
|
||||||
|
@ -80,8 +88,19 @@
|
||||||
'has-description'
|
'has-description'
|
||||||
'no-description'
|
'no-description'
|
||||||
}}
|
}}
|
||||||
|
{{this.applyValueTransformer
|
||||||
|
'parent-category-row-class'
|
||||||
|
(array)
|
||||||
|
(hash category=this.category)
|
||||||
|
}}
|
||||||
{{if this.category.uploaded_logo.url 'has-logo' 'no-logo'}}"
|
{{if this.category.uploaded_logo.url 'has-logo' 'no-logo'}}"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="category-list-before-category-section"
|
||||||
|
@outletArgs={{hash category=this.category listType=this.listType}}
|
||||||
|
/>
|
||||||
|
|
||||||
<td
|
<td
|
||||||
class="category {{if this.isMuted 'muted'}}"
|
class="category {{if this.isMuted 'muted'}}"
|
||||||
style={{category-color-variable this.category.color}}
|
style={{category-color-variable this.category.color}}
|
||||||
|
@ -141,6 +160,11 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="category-list-before-topics-section"
|
||||||
|
@outletArgs={{hash category=this.category}}
|
||||||
|
/>
|
||||||
|
|
||||||
<td class="topics">
|
<td class="topics">
|
||||||
<div title={{this.category.statTitle}}>{{html-safe
|
<div title={{this.category.statTitle}}>{{html-safe
|
||||||
this.category.stat
|
this.category.stat
|
||||||
|
@ -154,6 +178,11 @@
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="category-list-after-topics-section"
|
||||||
|
@outletArgs={{hash category=this.category}}
|
||||||
|
/>
|
||||||
|
|
||||||
{{#unless this.isMuted}}
|
{{#unless this.isMuted}}
|
||||||
{{#if this.showTopics}}
|
{{#if this.showTopics}}
|
||||||
<td class="latest">
|
<td class="latest">
|
||||||
|
@ -165,6 +194,10 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</td>
|
</td>
|
||||||
|
<PluginOutlet
|
||||||
|
@name="category-list-after-latest-section"
|
||||||
|
@outletArgs={{hash category=this.category}}
|
||||||
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default class PostMenuEditButton extends Component {
|
||||||
}}
|
}}
|
||||||
...attributes
|
...attributes
|
||||||
@action={{@buttonActions.editPost}}
|
@action={{@buttonActions.editPost}}
|
||||||
@icon={{if @post.wiki "far-edit" "pencil-alt"}}
|
@icon={{if @post.wiki "far-edit" "pencil"}}
|
||||||
@label={{if this.showLabel "post.controls.edit_action"}}
|
@label={{if this.showLabel "post.controls.edit_action"}}
|
||||||
@title="post.controls.edit"
|
@title="post.controls.edit"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
<div class="author">
|
<PluginOutlet
|
||||||
|
@name="search-results-topic-avatar-wrapper"
|
||||||
|
@outletArgs={{hash post=this.post}}
|
||||||
|
>
|
||||||
|
<div class="author">
|
||||||
<a href={{this.post.userPath}} data-user-card={{this.post.username}}>
|
<a href={{this.post.userPath}} data-user-card={{this.post.username}}>
|
||||||
{{avatar this.post imageSize="large"}}
|
{{avatar this.post imageSize="large"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</PluginOutlet>
|
||||||
|
|
||||||
<div class="fps-topic" data-topic-id={{this.post.topic.id}}>
|
<div class="fps-topic" data-topic-id={{this.post.topic.id}}>
|
||||||
<div class="topic">
|
<div class="topic">
|
||||||
|
|
||||||
{{#if this.bulkSelectEnabled}}
|
{{#if this.bulkSelectEnabled}}
|
||||||
<TrackSelected
|
<TrackSelected
|
||||||
@selectedList={{this.selected}}
|
@selectedList={{this.selected}}
|
||||||
|
@ -55,6 +62,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="search-result-entry-blurb-wrapper"
|
||||||
|
@outletArgs={{hash post=this.post logClick=this.logClick}}
|
||||||
|
>
|
||||||
<div class="blurb container">
|
<div class="blurb container">
|
||||||
<span class="date">
|
<span class="date">
|
||||||
{{format-date this.post.created_at format="tiny"}}
|
{{format-date this.post.created_at format="tiny"}}
|
||||||
|
@ -73,7 +84,12 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
</PluginOutlet>
|
||||||
|
|
||||||
|
<PluginOutlet
|
||||||
|
@name="search-result-entry-stats-wrapper"
|
||||||
|
@outletArgs={{hash post=this.post}}
|
||||||
|
>
|
||||||
{{#if this.showLikeCount}}
|
{{#if this.showLikeCount}}
|
||||||
{{#if this.post.like_count}}
|
{{#if this.post.like_count}}
|
||||||
<span class="like-count">
|
<span class="like-count">
|
||||||
|
@ -82,6 +98,7 @@
|
||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
</PluginOutlet>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PluginOutlet @name="after-search-result-entry" />
|
<PluginOutlet @name="after-search-result-entry" />
|
|
@ -1,5 +1,5 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { concat, hash } from "@ember/helper";
|
import { array, concat, hash } from "@ember/helper";
|
||||||
import { on } from "@ember/modifier";
|
import { on } from "@ember/modifier";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
|
@ -20,6 +20,7 @@ import discourseTags from "discourse/helpers/discourse-tags";
|
||||||
import formatDate from "discourse/helpers/format-date";
|
import formatDate from "discourse/helpers/format-date";
|
||||||
import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
import topicFeaturedLink from "discourse/helpers/topic-featured-link";
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||||
|
import { applyValueTransformer } from "discourse/lib/transformer";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
|
@ -208,6 +209,9 @@ export default class Item extends Component {
|
||||||
(if @topic.pinned "pinned")
|
(if @topic.pinned "pinned")
|
||||||
(if @topic.closed "closed")
|
(if @topic.closed "closed")
|
||||||
this.tagClassNames
|
this.tagClassNames
|
||||||
|
(applyValueTransformer
|
||||||
|
"topic-list-item-class" (array) (hash topic=@topic)
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default class UserBadge extends Component {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a class="user-card-badge-link" href={{this.badgeUrl}}>
|
<a class="user-card-badge-link" href={{this.badgeUrl}}>
|
||||||
<BadgeButton @badge={{@badge}}>
|
<BadgeButton @badge={{@badge}} @showName={{@showName}}>
|
||||||
{{#if this.showGrantCount}}
|
{{#if this.showGrantCount}}
|
||||||
<span class="count"> (×{{@count}})</span>
|
<span class="count"> (×{{@count}})</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -848,12 +848,7 @@ export default class TopicController extends Controller.extend(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const composer = this.composer;
|
const topic = this.model;
|
||||||
let topic = this.model;
|
|
||||||
const composerModel = composer.get("model");
|
|
||||||
let editingFirst =
|
|
||||||
composerModel &&
|
|
||||||
(post.get("firstPost") || composerModel.get("editingFirstPost"));
|
|
||||||
|
|
||||||
let editingSharedDraft = false;
|
let editingSharedDraft = false;
|
||||||
let draftsCategoryId = this.get("site.shared_drafts_category_id");
|
let draftsCategoryId = this.get("site.shared_drafts_category_id");
|
||||||
|
@ -872,22 +867,14 @@ export default class TopicController extends Controller.extend(
|
||||||
opts.destinationCategoryId = topic.get("destination_category_id");
|
opts.destinationCategoryId = topic.get("destination_category_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reopen the composer if we're editing the same post
|
const { composer } = this;
|
||||||
const editingExisting =
|
const composerModel = composer.get("model");
|
||||||
post.id === composerModel?.post?.id &&
|
const editingSamePost =
|
||||||
opts?.action === Composer.EDIT &&
|
opts.post.id === composerModel?.post?.id &&
|
||||||
composerModel?.draftKey === opts.draftKey;
|
opts.action === composerModel?.action &&
|
||||||
if (editingExisting) {
|
opts.draftKey === composerModel?.draftKey;
|
||||||
composer.unshrink();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel and reopen the composer for the first post
|
return editingSamePost ? composer.unshrink() : composer.open(opts);
|
||||||
if (editingFirst) {
|
|
||||||
composer.cancelComposer(opts).then(() => composer.open(opts));
|
|
||||||
} else {
|
|
||||||
composer.open(opts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default class Toolbar {
|
||||||
icon: "code",
|
icon: "code",
|
||||||
preventFocus: true,
|
preventFocus: true,
|
||||||
trimLeading: true,
|
trimLeading: true,
|
||||||
action: (...args) => this.context.send("formatCode", args),
|
perform: (e) => e.formatCode(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addButton({
|
this.addButton({
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { next, schedule } from "@ember/runloop";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
import putCursorAtEnd from "discourse/lib/put-cursor-at-end";
|
||||||
import { generateLinkifyFunction } from "discourse/lib/text";
|
import { generateLinkifyFunction } from "discourse/lib/text";
|
||||||
import { siteDir } from "discourse/lib/text-direction";
|
import { siteDir } from "discourse/lib/text-direction";
|
||||||
import toMarkdown from "discourse/lib/to-markdown";
|
import toMarkdown from "discourse/lib/to-markdown";
|
||||||
|
@ -28,6 +29,8 @@ const OP = {
|
||||||
ADDED: 2,
|
ADDED: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FOUR_SPACES_INDENT = "4-spaces-indent";
|
||||||
|
|
||||||
// Our head can be a static string or a function that returns a string
|
// Our head can be a static string or a function that returns a string
|
||||||
// based on input (like for numbered lists).
|
// based on input (like for numbered lists).
|
||||||
export function getHead(head, prev) {
|
export function getHead(head, prev) {
|
||||||
|
@ -42,6 +45,7 @@ export default class TextareaTextManipulation {
|
||||||
@service appEvents;
|
@service appEvents;
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
@service capabilities;
|
@service capabilities;
|
||||||
|
@service currentUser;
|
||||||
|
|
||||||
eventPrefix;
|
eventPrefix;
|
||||||
textarea;
|
textarea;
|
||||||
|
@ -180,6 +184,10 @@ export default class TextareaTextManipulation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applySurroundSelection(head, tail, exampleKey, opts) {
|
||||||
|
this.applySurround(this.getSelected(), head, tail, exampleKey, opts);
|
||||||
|
}
|
||||||
|
|
||||||
applySurround(sel, head, tail, exampleKey, opts) {
|
applySurround(sel, head, tail, exampleKey, opts) {
|
||||||
const pre = sel.pre;
|
const pre = sel.pre;
|
||||||
const post = sel.post;
|
const post = sel.post;
|
||||||
|
@ -730,6 +738,7 @@ export default class TextareaTextManipulation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
async inCodeBlock() {
|
async inCodeBlock() {
|
||||||
return inCodeBlock(
|
return inCodeBlock(
|
||||||
this.$textarea.value ?? this.$textarea.val(),
|
this.$textarea.value ?? this.$textarea.val(),
|
||||||
|
@ -737,6 +746,7 @@ export default class TextareaTextManipulation {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
toggleDirection() {
|
toggleDirection() {
|
||||||
let currentDir = this.$textarea.attr("dir")
|
let currentDir = this.$textarea.attr("dir")
|
||||||
? this.$textarea.attr("dir")
|
? this.$textarea.attr("dir")
|
||||||
|
@ -746,6 +756,75 @@ export default class TextareaTextManipulation {
|
||||||
this.$textarea.attr("dir", newDir).focus();
|
this.$textarea.attr("dir", newDir).focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
applyList(sel, head, exampleKey, opts) {
|
||||||
|
if (sel.value.includes("\n")) {
|
||||||
|
this.applySurround(sel, head, "", exampleKey, opts);
|
||||||
|
} else {
|
||||||
|
const [hval, hlen] = getHead(head);
|
||||||
|
if (sel.start === sel.end) {
|
||||||
|
sel.value = i18n(`composer.${exampleKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const number = sel.value.startsWith(hval)
|
||||||
|
? sel.value.slice(hlen)
|
||||||
|
: `${hval}${sel.value}`;
|
||||||
|
|
||||||
|
const preNewlines = sel.pre.trim() && "\n\n";
|
||||||
|
const postNewlines = sel.post.trim() && "\n\n";
|
||||||
|
|
||||||
|
const textToInsert = `${preNewlines}${number}${postNewlines}`;
|
||||||
|
|
||||||
|
const preChars = sel.pre.length - sel.pre.trimEnd().length;
|
||||||
|
const postChars = sel.post.length - sel.post.trimStart().length;
|
||||||
|
|
||||||
|
this._insertAt(sel.start - preChars, sel.end + postChars, textToInsert);
|
||||||
|
this.selectText(
|
||||||
|
sel.start + (preNewlines.length - preChars),
|
||||||
|
number.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
formatCode() {
|
||||||
|
const sel = this.getSelected("", { lineVal: true });
|
||||||
|
const selValue = sel.value;
|
||||||
|
const hasNewLine = selValue.includes("\n");
|
||||||
|
const isBlankLine = sel.lineVal.trim().length === 0;
|
||||||
|
const isFourSpacesIndent =
|
||||||
|
this.siteSettings.code_formatting_style === FOUR_SPACES_INDENT;
|
||||||
|
|
||||||
|
if (!hasNewLine) {
|
||||||
|
if (selValue.length === 0 && isBlankLine) {
|
||||||
|
if (isFourSpacesIndent) {
|
||||||
|
const example = i18n(`composer.code_text`);
|
||||||
|
this._insertAt(sel.start, sel.end, ` ${example}`);
|
||||||
|
return this.selectText(sel.pre.length + 4, example.length);
|
||||||
|
} else {
|
||||||
|
return this.applySurround(sel, "```\n", "\n```", "paste_code_text");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.applySurround(sel, "`", "`", "code_title");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isFourSpacesIndent) {
|
||||||
|
return this.applySurround(sel, " ", "", "code_text");
|
||||||
|
} else {
|
||||||
|
const preNewline = sel.pre[-1] !== "\n" && sel.pre !== "" ? "\n" : "";
|
||||||
|
const postNewline = sel.post[0] !== "\n" ? "\n" : "";
|
||||||
|
return this.addText(
|
||||||
|
sel,
|
||||||
|
`${preNewline}\`\`\`\n${sel.value}\n\`\`\`${postNewline}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
putCursorAtEnd() {
|
||||||
|
putCursorAtEnd(this.textarea);
|
||||||
|
}
|
||||||
|
|
||||||
autocomplete() {
|
autocomplete() {
|
||||||
return this.$textarea.autocomplete(...arguments);
|
return this.$textarea.autocomplete(...arguments);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
import { userPath } from "discourse/lib/url";
|
import { userPath } from "discourse/lib/url";
|
||||||
|
import Badge from "discourse/models/badge";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
|
@ -37,6 +38,9 @@ export function transformBasicPost(post) {
|
||||||
user_id: post.user_id,
|
user_id: post.user_id,
|
||||||
usernameUrl: userPath(post.username),
|
usernameUrl: userPath(post.username),
|
||||||
username: post.username,
|
username: post.username,
|
||||||
|
badgesGranted: post.badges_granted?.map(
|
||||||
|
(badge) => Badge.createFromJson(badge)[0]
|
||||||
|
),
|
||||||
avatar_template: post.avatar_template,
|
avatar_template: post.avatar_template,
|
||||||
bookmarked: post.bookmarked,
|
bookmarked: post.bookmarked,
|
||||||
bookmarkReminderAt: post.bookmark_reminder_at,
|
bookmarkReminderAt: post.bookmark_reminder_at,
|
||||||
|
|
|
@ -13,8 +13,11 @@ export const VALUE_TRANSFORMERS = Object.freeze([
|
||||||
"invite-simple-mode-topic",
|
"invite-simple-mode-topic",
|
||||||
"mentions-class",
|
"mentions-class",
|
||||||
"more-topics-tabs",
|
"more-topics-tabs",
|
||||||
|
"parent-category-row-class-mobile",
|
||||||
|
"parent-category-row-class",
|
||||||
"post-menu-buttons",
|
"post-menu-buttons",
|
||||||
"small-user-attrs",
|
"small-user-attrs",
|
||||||
"topic-list-columns",
|
"topic-list-columns",
|
||||||
"topic-list-header-sortable-column",
|
"topic-list-header-sortable-column",
|
||||||
|
"topic-list-item-class",
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -156,16 +156,20 @@ export function selectedText() {
|
||||||
} else if (oneboxTest) {
|
} else if (oneboxTest) {
|
||||||
// This is a partial quote from a onebox.
|
// This is a partial quote from a onebox.
|
||||||
// Treat it as though the entire onebox was quoted.
|
// Treat it as though the entire onebox was quoted.
|
||||||
const oneboxUrl = oneboxTest.dataset.oneboxSrc;
|
div.append(oneboxTest.dataset.oneboxSrc);
|
||||||
div.append(oneboxUrl);
|
|
||||||
} else {
|
} else {
|
||||||
div.append(range.cloneContents());
|
div.append(range.cloneContents());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.querySelectorAll("aside.onebox[data-onebox-src]").forEach((element) => {
|
div.querySelectorAll("aside.onebox[data-onebox-src]").forEach((element) => {
|
||||||
const oneboxUrl = element.dataset.oneboxSrc;
|
element.replaceWith(element.dataset.oneboxSrc);
|
||||||
element.replaceWith(oneboxUrl);
|
});
|
||||||
|
|
||||||
|
div
|
||||||
|
.querySelectorAll("div.video-placeholder-container[data-video-src]")
|
||||||
|
.forEach((element) => {
|
||||||
|
element.replaceWith(`![|video](${element.dataset.videoSrc})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return toMarkdown(div.outerHTML);
|
return toMarkdown(div.outerHTML);
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { isEmpty } from "@ember/utils";
|
||||||
import { observes, on } from "@ember-decorators/object";
|
import { observes, on } from "@ember-decorators/object";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import { extractError, throwAjaxError } from "discourse/lib/ajax-error";
|
import { extractError, throwAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { propertyNotEqual } from "discourse/lib/computed";
|
|
||||||
import { QUOTE_REGEXP } from "discourse/lib/quote";
|
import { QUOTE_REGEXP } from "discourse/lib/quote";
|
||||||
import { prioritizeNameFallback } from "discourse/lib/settings";
|
import { prioritizeNameFallback } from "discourse/lib/settings";
|
||||||
import { emailValid, escapeExpression } from "discourse/lib/utilities";
|
import { emailValid, escapeExpression } from "discourse/lib/utilities";
|
||||||
|
@ -73,13 +72,15 @@ const CLOSED = "closed",
|
||||||
_update_serializer = {
|
_update_serializer = {
|
||||||
raw: "reply",
|
raw: "reply",
|
||||||
topic_id: "topic.id",
|
topic_id: "topic.id",
|
||||||
raw_old: "rawOld",
|
original_text: "originalText",
|
||||||
},
|
},
|
||||||
_edit_topic_serializer = {
|
_edit_topic_serializer = {
|
||||||
title: "topic.title",
|
title: "topic.title",
|
||||||
categoryId: "topic.category.id",
|
categoryId: "topic.category.id",
|
||||||
tags: "topic.tags",
|
tags: "topic.tags",
|
||||||
featuredLink: "topic.featured_link",
|
featuredLink: "topic.featured_link",
|
||||||
|
original_title: "originalTitle",
|
||||||
|
original_tags: "originalTags",
|
||||||
},
|
},
|
||||||
_draft_serializer = {
|
_draft_serializer = {
|
||||||
reply: "reply",
|
reply: "reply",
|
||||||
|
@ -94,6 +95,9 @@ const CLOSED = "closed",
|
||||||
typingTime: "typingTime",
|
typingTime: "typingTime",
|
||||||
postId: "post.id",
|
postId: "post.id",
|
||||||
recipients: "targetRecipients",
|
recipients: "targetRecipients",
|
||||||
|
original_text: "originalText",
|
||||||
|
original_title: "originalTitle",
|
||||||
|
original_tags: "originalTags",
|
||||||
},
|
},
|
||||||
_add_draft_fields = {},
|
_add_draft_fields = {},
|
||||||
FAST_REPLY_LENGTH_THRESHOLD = 10000;
|
FAST_REPLY_LENGTH_THRESHOLD = 10000;
|
||||||
|
@ -210,8 +214,6 @@ export default class Composer extends RestModel {
|
||||||
@equal("composeState", FULLSCREEN) viewFullscreen;
|
@equal("composeState", FULLSCREEN) viewFullscreen;
|
||||||
@or("viewOpen", "viewFullscreen") viewOpenOrFullscreen;
|
@or("viewOpen", "viewFullscreen") viewOpenOrFullscreen;
|
||||||
@and("editingPost", "post.firstPost") editingFirstPost;
|
@and("editingPost", "post.firstPost") editingFirstPost;
|
||||||
@propertyNotEqual("reply", "originalText") replyDirty;
|
|
||||||
@propertyNotEqual("title", "originalTitle") titleDirty;
|
|
||||||
|
|
||||||
@or(
|
@or(
|
||||||
"creatingTopic",
|
"creatingTopic",
|
||||||
|
@ -226,6 +228,16 @@ export default class Composer extends RestModel {
|
||||||
|
|
||||||
@tracked _categoryId = null;
|
@tracked _categoryId = null;
|
||||||
|
|
||||||
|
@discourseComputed("reply", "originalText")
|
||||||
|
replyDirty(reply, original) {
|
||||||
|
return (reply || "").trim() !== (original || "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("title", "originalTitle")
|
||||||
|
titleDirty(title, original) {
|
||||||
|
return (title || "").trim() !== (original || "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
@dependentKeyCompat
|
@dependentKeyCompat
|
||||||
get categoryId() {
|
get categoryId() {
|
||||||
return this._categoryId;
|
return this._categoryId;
|
||||||
|
@ -787,6 +799,7 @@ export default class Composer extends RestModel {
|
||||||
const category = Category.findById(categoryId);
|
const category = Category.findById(categoryId);
|
||||||
if (category) {
|
if (category) {
|
||||||
this.set("reply", category.topic_template || "");
|
this.set("reply", category.topic_template || "");
|
||||||
|
this.set("originalText", category.topic_template || "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,6 +874,9 @@ export default class Composer extends RestModel {
|
||||||
whisper: opts.whisper,
|
whisper: opts.whisper,
|
||||||
tags: opts.tags || [],
|
tags: opts.tags || [],
|
||||||
noBump: opts.noBump,
|
noBump: opts.noBump,
|
||||||
|
originalText: opts.originalText,
|
||||||
|
originalTitle: opts.originalTitle,
|
||||||
|
originalTags: opts.originalTags,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (opts.post) {
|
if (opts.post) {
|
||||||
|
@ -926,6 +942,13 @@ export default class Composer extends RestModel {
|
||||||
reply: post.raw,
|
reply: post.raw,
|
||||||
originalText: post.raw,
|
originalText: post.raw,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (post.post_number === 1 && this.canEditTitle) {
|
||||||
|
this.setProperties({
|
||||||
|
originalTitle: post.topic.title,
|
||||||
|
originalTags: post.topic.tags,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// edge case ... make a post then edit right away
|
// edge case ... make a post then edit right away
|
||||||
|
@ -945,24 +968,18 @@ export default class Composer extends RestModel {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (opts.action === REPLY && opts.quote) {
|
} else if (opts.action === REPLY && opts.quote) {
|
||||||
this.setProperties({
|
this.set("reply", opts.quote);
|
||||||
reply: opts.quote,
|
this.set("originalText", opts.quote);
|
||||||
originalText: opts.quote,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.title) {
|
if (opts.title) {
|
||||||
this.set("title", opts.title);
|
this.set("title", opts.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDraft = opts.draft || opts.skipDraftCheck;
|
|
||||||
this.set("originalText", isDraft ? "" : this.reply);
|
|
||||||
|
|
||||||
if (this.canEditTitle) {
|
if (this.canEditTitle) {
|
||||||
if (isEmpty(this.title) && this.title !== "") {
|
if (isEmpty(this.title) && this.title !== "") {
|
||||||
this.set("title", "");
|
this.set("title", "");
|
||||||
}
|
}
|
||||||
this.set("originalTitle", this.title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEdit(opts.action) || !opts.post) {
|
if (!isEdit(opts.action) || !opts.post) {
|
||||||
|
@ -1001,6 +1018,8 @@ export default class Composer extends RestModel {
|
||||||
clearState() {
|
clearState() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
originalText: null,
|
originalText: null,
|
||||||
|
originalTitle: null,
|
||||||
|
originalTags: null,
|
||||||
reply: null,
|
reply: null,
|
||||||
post: null,
|
post: null,
|
||||||
title: null,
|
title: null,
|
||||||
|
@ -1016,12 +1035,9 @@ export default class Composer extends RestModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@discourseComputed("editConflict", "originalText")
|
|
||||||
rawOld(editConflict, originalText) {
|
|
||||||
return editConflict ? null : originalText;
|
|
||||||
}
|
|
||||||
|
|
||||||
editPost(opts) {
|
editPost(opts) {
|
||||||
|
this.set("composeState", SAVING);
|
||||||
|
|
||||||
const post = this.post;
|
const post = this.post;
|
||||||
const oldCooked = post.cooked;
|
const oldCooked = post.cooked;
|
||||||
let promise = Promise.resolve();
|
let promise = Promise.resolve();
|
||||||
|
@ -1054,14 +1070,19 @@ export default class Composer extends RestModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = {
|
let props = {
|
||||||
edit_reason: opts.editReason,
|
edit_reason: opts.editReason,
|
||||||
image_sizes: opts.imageSizes,
|
image_sizes: opts.imageSizes,
|
||||||
cooked: this.getCookedHtml(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.serialize(_update_serializer, props);
|
this.serialize(_update_serializer, props);
|
||||||
this.set("composeState", SAVING);
|
|
||||||
|
// user clicked "overwrite edits" button
|
||||||
|
if (this.editConflict) {
|
||||||
|
delete props.original_text;
|
||||||
|
delete props.original_title;
|
||||||
|
delete props.original_tags;
|
||||||
|
}
|
||||||
|
|
||||||
const rollback = throwAjaxError((error) => {
|
const rollback = throwAjaxError((error) => {
|
||||||
post.setProperties("cooked", oldCooked);
|
post.setProperties("cooked", oldCooked);
|
||||||
|
@ -1071,7 +1092,8 @@ export default class Composer extends RestModel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
post.setProperties({ cooked: props.cooked, staged: true });
|
const cooked = this.getCookedHtml();
|
||||||
|
post.setProperties({ cooked, staged: true });
|
||||||
this.appEvents.trigger("post-stream:refresh", { id: post.id });
|
this.appEvents.trigger("post-stream:refresh", { id: post.id });
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
|
@ -1292,16 +1314,9 @@ export default class Composer extends RestModel {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setProperties({
|
this.set("draftSaving", true);
|
||||||
draftSaving: true,
|
|
||||||
draftConflictUser: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = this.serialize(_draft_serializer);
|
const data = this.serialize(_draft_serializer);
|
||||||
|
|
||||||
if (data.postId && !isEmpty(this.originalText)) {
|
|
||||||
data.originalText = this.originalText;
|
|
||||||
}
|
|
||||||
|
|
||||||
const draftSequence = this.draftSequence;
|
const draftSequence = this.draftSequence;
|
||||||
this.set("draftSequence", this.draftSequence + 1);
|
this.set("draftSequence", this.draftSequence + 1);
|
||||||
|
|
|
@ -21,14 +21,10 @@ export default class HistoryStore extends Service {
|
||||||
|
|
||||||
#routeData = new TrackedMap();
|
#routeData = new TrackedMap();
|
||||||
#uuid;
|
#uuid;
|
||||||
#pendingStore;
|
#pendingStore = DEBUG && isTesting() ? new TrackedMap() : null;
|
||||||
|
|
||||||
get #currentStore() {
|
get #currentStore() {
|
||||||
if (this.#pendingStore) {
|
return this.#pendingStore || this.#dataFor(this.#uuid);
|
||||||
return this.#pendingStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.#dataFor(this.#uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,10 +80,7 @@ export default class HistoryStore extends Service {
|
||||||
if (key === undefined) {
|
if (key === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (key === this.#uuid) {
|
return key !== this.#uuid;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +119,6 @@ export default class HistoryStore extends Service {
|
||||||
|
|
||||||
if (DEBUG && isTesting()) {
|
if (DEBUG && isTesting()) {
|
||||||
// Can't use window.history in tests
|
// Can't use window.history in tests
|
||||||
this.#pendingStore = new TrackedMap();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="about-wrapper"
|
@name="about-wrapper"
|
||||||
@outletArgs={{hash model=this.model contactInfo=this.contactInfo}}
|
@outletArgs={{hash
|
||||||
|
model=this.model
|
||||||
|
contactInfo=this.contactInfo
|
||||||
|
faqOverridden=this.faqOverridden
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{{body-class "about-page"}}
|
{{body-class "about-page"}}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
<div class="edit-topic-title">
|
<div class="edit-topic-title">
|
||||||
<PrivateMessageGlyph @shouldShow={{this.model.isPrivateMessage}} />
|
<PrivateMessageGlyph @shouldShow={{this.model.isPrivateMessage}} />
|
||||||
|
|
||||||
|
<div class="edit-title__wrapper">
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="edit-topic-title"
|
@name="edit-topic-title"
|
||||||
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
||||||
|
@ -63,8 +64,10 @@
|
||||||
@autofocus="true"
|
@autofocus="true"
|
||||||
/>
|
/>
|
||||||
</PluginOutlet>
|
</PluginOutlet>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#if this.showCategoryChooser}}
|
{{#if this.showCategoryChooser}}
|
||||||
|
<div class="edit-category__wrapper">
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="edit-topic-category"
|
@name="edit-topic-category"
|
||||||
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
||||||
|
@ -75,9 +78,11 @@
|
||||||
class="small"
|
class="small"
|
||||||
/>
|
/>
|
||||||
</PluginOutlet>
|
</PluginOutlet>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.canEditTags}}
|
{{#if this.canEditTags}}
|
||||||
|
<div class="edit-tags__wrapper">
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="edit-topic-tags"
|
@name="edit-topic-tags"
|
||||||
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
||||||
|
@ -94,16 +99,14 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PluginOutlet>
|
</PluginOutlet>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<span>
|
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="edit-topic"
|
@name="edit-topic"
|
||||||
@connectorTagName="div"
|
@connectorTagName="div"
|
||||||
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
@outletArgs={{hash model=this.model buffered=this.buffered}}
|
||||||
/>
|
/>
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="edit-controls">
|
<div class="edit-controls">
|
||||||
<DButton
|
<DButton
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{body-class "user-activity-page"}}
|
{{body-class "user-activity-page"}}
|
||||||
|
<PluginOutlet @name="user-activity-navigation-wrapper">
|
||||||
<div class="user-navigation user-navigation-secondary">
|
<div class="user-navigation user-navigation-secondary">
|
||||||
<HorizontalOverflowNav @ariaLabel="User secondary - activity">
|
<HorizontalOverflowNav @ariaLabel="User secondary - activity">
|
||||||
<DNavigationItem
|
<DNavigationItem
|
||||||
@route="userActivity.index"
|
@route="userActivity.index"
|
||||||
|
@ -88,8 +88,8 @@
|
||||||
@outletArgs={{hash model=this.model}}
|
@outletArgs={{hash model=this.model}}
|
||||||
/>
|
/>
|
||||||
</HorizontalOverflowNav>
|
</HorizontalOverflowNav>
|
||||||
</div>
|
</div>
|
||||||
|
</PluginOutlet>
|
||||||
<section class="user-content" id="user-content">
|
<section class="user-content" id="user-content">
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
</section>
|
</section>
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
import { prioritizeNameInUx } from "discourse/lib/settings";
|
import { prioritizeNameInUx } from "discourse/lib/settings";
|
||||||
import { formatUsername } from "discourse/lib/utilities";
|
import { formatUsername } from "discourse/lib/utilities";
|
||||||
|
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||||
import { applyDecorators, createWidget } from "discourse/widgets/widget";
|
import { applyDecorators, createWidget } from "discourse/widgets/widget";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
|
@ -148,10 +150,7 @@ export default createWidget("poster-name", {
|
||||||
h("span", { className: classNames.join(" ") }, nameContents),
|
h("span", { className: classNames.join(" ") }, nameContents),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!this.settings.showNameAndGroup) {
|
if (this.settings.showNameAndGroup) {
|
||||||
return contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
name &&
|
name &&
|
||||||
this.siteSettings.display_name_on_posts &&
|
this.siteSettings.display_name_on_posts &&
|
||||||
|
@ -172,6 +171,32 @@ export default createWidget("poster-name", {
|
||||||
if (this.siteSettings.enable_user_status) {
|
if (this.siteSettings.enable_user_status) {
|
||||||
this.addUserStatus(contents, attrs);
|
this.addUserStatus(contents, attrs);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs.badgesGranted?.length) {
|
||||||
|
const badges = [];
|
||||||
|
|
||||||
|
attrs.badgesGranted.forEach((badge) => {
|
||||||
|
// Alter the badge description to show that the badge was granted for this post.
|
||||||
|
badge.description = i18n("post.badge_granted_tooltip", {
|
||||||
|
username: attrs.username,
|
||||||
|
badge_name: badge.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const badgeIcon = new RenderGlimmer(
|
||||||
|
this,
|
||||||
|
`span.user-badge-button-${badge.slug}`,
|
||||||
|
hbs`<UserBadge @badge={{@data.badge}} @user={{@data.user}} @showName={{false}} />`,
|
||||||
|
{
|
||||||
|
badge,
|
||||||
|
user: attrs.user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
badges.push(badgeIcon);
|
||||||
|
});
|
||||||
|
|
||||||
|
contents.push(h("span.user-badge-buttons", badges));
|
||||||
|
}
|
||||||
|
|
||||||
return contents;
|
return contents;
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
"test": "ember test"
|
"test": "ember test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "^9.2.0",
|
"@faker-js/faker": "^9.3.0",
|
||||||
"@glimmer/syntax": "^0.92.3",
|
"@glimmer/syntax": "^0.93.1",
|
||||||
"@highlightjs/cdn-assets": "^11.10.0",
|
"@highlightjs/cdn-assets": "^11.10.0",
|
||||||
"@json-editor/json-editor": "2.15.2",
|
"@json-editor/json-editor": "2.15.2",
|
||||||
"@messageformat/core": "^3.4.0",
|
"@messageformat/core": "^3.4.0",
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
"@glimmer/component": "^1.1.2",
|
"@glimmer/component": "^1.1.2",
|
||||||
"@glimmer/tracking": "^1.1.2",
|
"@glimmer/tracking": "^1.1.2",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@swc/core": "^1.9.3",
|
"@swc/core": "^1.10.0",
|
||||||
"@types/jquery": "^3.5.32",
|
"@types/jquery": "^3.5.32",
|
||||||
"@types/qunit": "^2.19.12",
|
"@types/qunit": "^2.19.12",
|
||||||
"@types/rsvp": "^4.0.9",
|
"@types/rsvp": "^4.0.9",
|
||||||
|
@ -107,13 +107,13 @@
|
||||||
"imports-loader": "^5.0.0",
|
"imports-loader": "^5.0.0",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"jsuites": "^5.7.2",
|
"jsuites": "^5.8.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"make-plural": "^7.4.0",
|
"make-plural": "^7.4.0",
|
||||||
"message-bus-client": "^4.3.8",
|
"message-bus-client": "^4.3.8",
|
||||||
"pretender": "^3.4.7",
|
"pretender": "^3.4.7",
|
||||||
"qunit": "^2.22.0",
|
"qunit": "^2.23.0",
|
||||||
"qunit-dom": "^3.3.0",
|
"qunit-dom": "^3.4.0",
|
||||||
"sass": "^1.77.7",
|
"sass": "^1.77.7",
|
||||||
"select-kit": "workspace:1.0.0",
|
"select-kit": "workspace:1.0.0",
|
||||||
"sinon": "^19.0.2",
|
"sinon": "^19.0.2",
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
"truth-helpers": "workspace:1.0.0",
|
"truth-helpers": "workspace:1.0.0",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
"virtual-dom": "^2.1.1",
|
"virtual-dom": "^2.1.1",
|
||||||
"webpack": "^5.96.1",
|
"webpack": "^5.97.0",
|
||||||
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
"webpack-retry-chunk-load-plugin": "^3.1.1",
|
||||||
"webpack-stats-plugin": "^1.1.3",
|
"webpack-stats-plugin": "^1.1.3",
|
||||||
"xss": "^1.0.15"
|
"xss": "^1.0.15"
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { toggleCheckDraftPopup } from "discourse/services/composer";
|
||||||
import userFixtures from "discourse/tests/fixtures/user-fixtures";
|
import userFixtures from "discourse/tests/fixtures/user-fixtures";
|
||||||
import {
|
import {
|
||||||
acceptance,
|
acceptance,
|
||||||
query,
|
|
||||||
selectText,
|
selectText,
|
||||||
updateCurrentUser,
|
updateCurrentUser,
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
@ -127,7 +126,7 @@ acceptance("Composer Actions", function (needs) {
|
||||||
|
|
||||||
assert.strictEqual(categoryChooserReplyArea.header().name(), "faq");
|
assert.strictEqual(categoryChooserReplyArea.header().name(), "faq");
|
||||||
assert.dom(".action-title").hasText(i18n("topic.create_long"));
|
assert.dom(".action-title").hasText(i18n("topic.create_long"));
|
||||||
assert.true(query(".d-editor-input").value.includes(quote));
|
assert.dom(".d-editor-input").includesValue(quote);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("reply_as_new_topic without a new_topic draft", async function (assert) {
|
test("reply_as_new_topic without a new_topic draft", async function (assert) {
|
||||||
|
@ -210,7 +209,7 @@ acceptance("Composer Actions", function (needs) {
|
||||||
await composerActions.expand();
|
await composerActions.expand();
|
||||||
|
|
||||||
assert.dom(".action-title").hasText(i18n("topic.create_long"));
|
assert.dom(".action-title").hasText(i18n("topic.create_long"));
|
||||||
assert.true(query(".d-editor-input").value.includes(quote));
|
assert.dom(".d-editor-input").includesValue(quote);
|
||||||
assert.strictEqual(composerActions.rowByIndex(0).value(), "reply_to_post");
|
assert.strictEqual(composerActions.rowByIndex(0).value(), "reply_to_post");
|
||||||
assert.strictEqual(composerActions.rowByIndex(1).value(), "reply_to_topic");
|
assert.strictEqual(composerActions.rowByIndex(1).value(), "reply_to_topic");
|
||||||
assert.strictEqual(composerActions.rowByIndex(2).value(), "shared_draft");
|
assert.strictEqual(composerActions.rowByIndex(2).value(), "shared_draft");
|
||||||
|
|
|
@ -13,17 +13,17 @@ acceptance("Composer - Edit conflict", function (needs) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Should not send originalText when posting a new reply", async function (assert) {
|
test("Should not send 'original_text' when posting a new reply", async function (assert) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click(".topic-post:nth-of-type(1) button.reply");
|
await click(".topic-post:nth-of-type(1) button.reply");
|
||||||
await fillIn(
|
await fillIn(
|
||||||
".d-editor-input",
|
".d-editor-input",
|
||||||
"hello world hello world hello world hello world hello world"
|
"hello world hello world hello world hello world hello world"
|
||||||
);
|
);
|
||||||
assert.false(lastBody.includes("originalText"));
|
assert.false(lastBody.includes("original_text"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Should send originalText when editing a reply", async function (assert) {
|
test("Should send 'original_text' when editing a reply", async function (assert) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click(".topic-post:nth-of-type(1) button.show-more-actions");
|
await click(".topic-post:nth-of-type(1) button.show-more-actions");
|
||||||
await click(".topic-post:nth-of-type(1) button.edit");
|
await click(".topic-post:nth-of-type(1) button.edit");
|
||||||
|
@ -31,6 +31,6 @@ acceptance("Composer - Edit conflict", function (needs) {
|
||||||
".d-editor-input",
|
".d-editor-input",
|
||||||
"hello world hello world hello world hello world hello world"
|
"hello world hello world hello world hello world hello world"
|
||||||
);
|
);
|
||||||
assert.true(lastBody.includes("originalText"));
|
assert.true(lastBody.includes("original_text"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
|
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
|
||||||
acceptance("Composer - Image Preview", function (needs) {
|
acceptance("Composer - Image Preview", function (needs) {
|
||||||
needs.user({});
|
needs.user({});
|
||||||
|
@ -339,10 +339,9 @@ acceptance("Composer - Image Preview", function (needs) {
|
||||||
.dom(".d-editor-input")
|
.dom(".d-editor-input")
|
||||||
.hasValue(uploads.join("\n"), "Image should be removed from the editor");
|
.hasValue(uploads.join("\n"), "Image should be removed from the editor");
|
||||||
|
|
||||||
assert.false(
|
assert
|
||||||
query(".d-editor-input").value.includes("image_example_0"),
|
.dom(".d-editor-input")
|
||||||
"does not have the first image"
|
.doesNotIncludeValue("image_example_0", "does not have the first image");
|
||||||
);
|
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom(".d-editor-input")
|
.dom(".d-editor-input")
|
||||||
|
|
|
@ -576,7 +576,7 @@ acceptance("Composer", function (needs) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Composer can toggle between edit and reply", async function (assert) {
|
test("Composer can toggle between edit and reply on the OP", async function (assert) {
|
||||||
await visit("/t/this-is-a-test-topic/9");
|
await visit("/t/this-is-a-test-topic/9");
|
||||||
|
|
||||||
await click(".topic-post:nth-of-type(1) button.edit");
|
await click(".topic-post:nth-of-type(1) button.edit");
|
||||||
|
@ -586,8 +586,10 @@ acceptance("Composer", function (needs) {
|
||||||
/^This is the first post\./,
|
/^This is the first post\./,
|
||||||
"populates the input with the post text"
|
"populates the input with the post text"
|
||||||
);
|
);
|
||||||
|
|
||||||
await click(".topic-post:nth-of-type(1) button.reply");
|
await click(".topic-post:nth-of-type(1) button.reply");
|
||||||
assert.dom(".d-editor-input").hasNoValue("clears the input");
|
assert.dom(".d-editor-input").hasNoValue("clears the composer input");
|
||||||
|
|
||||||
await click(".topic-post:nth-of-type(1) button.edit");
|
await click(".topic-post:nth-of-type(1) button.edit");
|
||||||
assert
|
assert
|
||||||
.dom(".d-editor-input")
|
.dom(".d-editor-input")
|
||||||
|
@ -597,6 +599,27 @@ acceptance("Composer", function (needs) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Composer can toggle between edit and reply on a reply", async function (assert) {
|
||||||
|
await visit("/t/this-is-a-test-topic/9");
|
||||||
|
|
||||||
|
await click(".topic-post:nth-of-type(2) button.edit");
|
||||||
|
assert
|
||||||
|
.dom(".d-editor-input")
|
||||||
|
.hasValue(
|
||||||
|
/^This is the second post\./,
|
||||||
|
"populates the input with the post text"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(".topic-post:nth-of-type(2) button.reply");
|
||||||
|
assert.dom(".d-editor-input").hasNoValue("clears the composer input");
|
||||||
|
|
||||||
|
await click(".topic-post:nth-of-type(2) button.edit");
|
||||||
|
assert.true(
|
||||||
|
query(".d-editor-input").value.startsWith("This is the second post."),
|
||||||
|
"populates the input with the post text"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("Composer can toggle whispers when whisperer user", async function (assert) {
|
test("Composer can toggle whispers when whisperer user", async function (assert) {
|
||||||
const menu = selectKit(".toolbar-popup-menu-options");
|
const menu = selectKit(".toolbar-popup-menu-options");
|
||||||
|
|
||||||
|
@ -802,6 +825,7 @@ acceptance("Composer", function (needs) {
|
||||||
i18n("post.cancel_composer.keep_editing"),
|
i18n("post.cancel_composer.keep_editing"),
|
||||||
"has keep editing button"
|
"has keep editing button"
|
||||||
);
|
);
|
||||||
|
|
||||||
await click(".d-modal__footer button.save-draft");
|
await click(".d-modal__footer button.save-draft");
|
||||||
assert.dom(".d-editor-input").hasNoValue("clears the composer input");
|
assert.dom(".d-editor-input").hasNoValue("clears the composer input");
|
||||||
});
|
});
|
||||||
|
@ -847,10 +871,9 @@ acceptance("Composer", function (needs) {
|
||||||
|
|
||||||
assert.dom(".d-modal__body").doesNotExist("abandon popup shouldn't come");
|
assert.dom(".d-modal__body").doesNotExist("abandon popup shouldn't come");
|
||||||
|
|
||||||
assert.true(
|
assert
|
||||||
query(".d-editor-input").value.includes(longText),
|
.dom(".d-editor-input")
|
||||||
"entered text should still be there"
|
.includesValue(longText, "entered text should still be there");
|
||||||
);
|
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom('.action-title a[href="/t/internationalization-localization/280"]')
|
.dom('.action-title a[href="/t/internationalization-localization/280"]')
|
||||||
|
@ -1293,32 +1316,6 @@ acceptance("Composer - default category not set", function (needs) {
|
||||||
});
|
});
|
||||||
// END: Default Composer Category tests
|
// END: Default Composer Category tests
|
||||||
|
|
||||||
acceptance("Composer - current time", function (needs) {
|
|
||||||
needs.user();
|
|
||||||
|
|
||||||
test("composer insert current time shortcut", async function (assert) {
|
|
||||||
await visit("/t/internationalization-localization/280");
|
|
||||||
|
|
||||||
await click("#topic-footer-buttons .btn.create");
|
|
||||||
assert.dom(".d-editor-input").exists("the composer input is visible");
|
|
||||||
await fillIn(".d-editor-input", "and the time now is: ");
|
|
||||||
|
|
||||||
const date = moment().format("YYYY-MM-DD");
|
|
||||||
|
|
||||||
await triggerKeyEvent(".d-editor-input", "keydown", ".", {
|
|
||||||
...metaModifier,
|
|
||||||
shiftKey: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.true(
|
|
||||||
query("#reply-control .d-editor-input")
|
|
||||||
.value.trim()
|
|
||||||
.startsWith(`and the time now is: [date=${date}`),
|
|
||||||
"adds the current date"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
acceptance("composer buttons API", function (needs) {
|
acceptance("composer buttons API", function (needs) {
|
||||||
needs.user();
|
needs.user();
|
||||||
needs.settings({
|
needs.settings({
|
||||||
|
|
|
@ -627,10 +627,10 @@ acceptance("Search - Authenticated", function (needs) {
|
||||||
.dom(".d-editor-input")
|
.dom(".d-editor-input")
|
||||||
.hasValue(/a link/, "still has the original composer content");
|
.hasValue(/a link/, "still has the original composer content");
|
||||||
|
|
||||||
assert.true(
|
assert
|
||||||
query(".d-editor-input").value.includes(
|
.dom(".d-editor-input")
|
||||||
searchFixtures["search/query"].topics[0].slug
|
.includesValue(
|
||||||
),
|
searchFixtures["search/query"].topics[0].slug,
|
||||||
"adds link from search to composer"
|
"adds link from search to composer"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { click, currentURL, visit } from "@ember/test-helpers";
|
import { click, currentURL, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import CategoryFixtures from "discourse/tests/fixtures/category-fixtures";
|
import CategoryFixtures from "discourse/tests/fixtures/category-fixtures";
|
||||||
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
|
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
|
@ -37,11 +37,11 @@ acceptance("Share and Invite modal", function (needs) {
|
||||||
.dom("#modal-alert.alert-warning")
|
.dom("#modal-alert.alert-warning")
|
||||||
.doesNotExist("it does not show the alert with restricted groups");
|
.doesNotExist("it does not show the alert with restricted groups");
|
||||||
|
|
||||||
assert.true(
|
assert
|
||||||
query("input.invite-link").value.includes(
|
.dom("input.invite-link")
|
||||||
"/t/internationalization-localization/280?u=eviltrout"
|
.includesValue(
|
||||||
),
|
"/t/internationalization-localization/280?u=eviltrout",
|
||||||
"it shows the topic sharing url"
|
"shows the topic sharing url"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert
|
assert
|
||||||
|
@ -127,9 +127,11 @@ acceptance("Share url with badges disabled - desktop", function (needs) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click("#topic-footer-button-share-and-invite");
|
await click("#topic-footer-button-share-and-invite");
|
||||||
|
|
||||||
assert.false(
|
assert
|
||||||
query("input.invite-link").value.includes("?u=eviltrout"),
|
.dom("input.invite-link")
|
||||||
"it doesn't add the username param when badges are disabled"
|
.doesNotIncludeValue(
|
||||||
|
"?u=eviltrout",
|
||||||
|
"doesn't add the username param when badges are disabled"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -148,9 +150,11 @@ acceptance("With username in share links disabled - desktop", function (needs) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click("#topic-footer-button-share-and-invite");
|
await click("#topic-footer-button-share-and-invite");
|
||||||
|
|
||||||
assert.false(
|
assert
|
||||||
query("input.invite-link").value.includes("?u=eviltrout"),
|
.dom("input.invite-link")
|
||||||
"it doesn't add the username param when username in share links are disabled"
|
.doesNotIncludeValue(
|
||||||
|
"?u=eviltrout",
|
||||||
|
"doesn't add the username param when username in share links are disabled"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
acceptance,
|
acceptance,
|
||||||
chromeTest,
|
chromeTest,
|
||||||
publishToMessageBus,
|
publishToMessageBus,
|
||||||
query,
|
|
||||||
selectText,
|
selectText,
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||||
|
@ -372,11 +371,9 @@ acceptance("Topic featured links", function (needs) {
|
||||||
await selectText("#post_5 blockquote");
|
await selectText("#post_5 blockquote");
|
||||||
await click(".quote-button .insert-quote");
|
await click(".quote-button .insert-quote");
|
||||||
|
|
||||||
assert.true(
|
assert
|
||||||
query(".d-editor-input").value.includes(
|
.dom(".d-editor-input")
|
||||||
'quote="codinghorror said, post:3, topic:280"'
|
.includesValue('quote="codinghorror said, post:3, topic:280"');
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Quoting a quote of a different topic keeps the original topic title", async function (assert) {
|
test("Quoting a quote of a different topic keeps the original topic title", async function (assert) {
|
||||||
|
@ -384,10 +381,10 @@ acceptance("Topic featured links", function (needs) {
|
||||||
await selectText("#post_9 blockquote");
|
await selectText("#post_9 blockquote");
|
||||||
await click(".quote-button .insert-quote");
|
await click(".quote-button .insert-quote");
|
||||||
|
|
||||||
assert.true(
|
assert
|
||||||
query(".d-editor-input").value.includes(
|
.dom(".d-editor-input")
|
||||||
|
.includesValue(
|
||||||
'quote="A new topic with a link to another topic, post:3, topic:62"'
|
'quote="A new topic with a link to another topic, post:3, topic:62"'
|
||||||
)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -396,11 +393,9 @@ acceptance("Topic featured links", function (needs) {
|
||||||
await selectText("#post_5 blockquote");
|
await selectText("#post_5 blockquote");
|
||||||
await click(".reply");
|
await click(".reply");
|
||||||
|
|
||||||
assert.true(
|
assert
|
||||||
query(".d-editor-input").value.includes(
|
.dom(".d-editor-input")
|
||||||
'quote="codinghorror said, post:3, topic:280"'
|
.includesValue('quote="codinghorror said, post:3, topic:280"');
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Using J/K on Firefox clean the text selection, so this won't work there
|
// Using J/K on Firefox clean the text selection, so this won't work there
|
||||||
|
@ -412,11 +407,9 @@ acceptance("Topic featured links", function (needs) {
|
||||||
await triggerKeyEvent(document, "keypress", "J");
|
await triggerKeyEvent(document, "keypress", "J");
|
||||||
await triggerKeyEvent(document, "keypress", "T");
|
await triggerKeyEvent(document, "keypress", "T");
|
||||||
|
|
||||||
assert.true(
|
assert
|
||||||
query(".d-editor-input").value.includes(
|
.dom(".d-editor-input")
|
||||||
'quote="codinghorror said, post:3, topic:280"'
|
.includesValue('quote="codinghorror said, post:3, topic:280"');
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -424,11 +417,9 @@ acceptance("Topic featured links", function (needs) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await selectText("#post_5 .cooked");
|
await selectText("#post_5 .cooked");
|
||||||
await click(".quote-button .insert-quote");
|
await click(".quote-button .insert-quote");
|
||||||
assert.true(
|
assert
|
||||||
query(".d-editor-input").value.includes(
|
.dom(".d-editor-input")
|
||||||
'quote="pekka, post:5, topic:280, full:true"'
|
.includesValue('quote="pekka, post:5, topic:280, full:true"');
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default {
|
||||||
grant_count: 7,
|
grant_count: 7,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -64,7 +64,7 @@ export default {
|
||||||
grant_count: 30,
|
grant_count: 30,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -334,7 +334,7 @@ export default {
|
||||||
grant_count: 7,
|
grant_count: 7,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -364,7 +364,7 @@ export default {
|
||||||
grant_count: 30,
|
grant_count: 30,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -2462,7 +2462,7 @@ export default {
|
||||||
grant_count: 3,
|
grant_count: 3,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -2758,7 +2758,7 @@ export default {
|
||||||
grant_count: 3,
|
grant_count: 3,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -2837,7 +2837,7 @@ export default {
|
||||||
grant_count: 3,
|
grant_count: 3,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -2907,7 +2907,7 @@ export default {
|
||||||
grant_count: 3,
|
grant_count: 3,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -3195,7 +3195,7 @@ export default {
|
||||||
grant_count: 7,
|
grant_count: 7,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -3225,7 +3225,7 @@ export default {
|
||||||
grant_count: 30,
|
grant_count: 30,
|
||||||
allow_title: true,
|
allow_title: true,
|
||||||
multiple_grant: false,
|
multiple_grant: false,
|
||||||
icon: "fa-user",
|
icon: "user",
|
||||||
image: null,
|
image: null,
|
||||||
listable: true,
|
listable: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
|
@ -77,4 +77,22 @@ module("Integration | Component | badge-button", function (hooks) {
|
||||||
|
|
||||||
assert.dom(".user-badge.foo").exists();
|
assert.dom(".user-badge.foo").exists();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("setting showName to false hides the name", async function (assert) {
|
||||||
|
this.set("badge", { name: "foo" });
|
||||||
|
|
||||||
|
await render(
|
||||||
|
hbs`<BadgeButton @badge={{this.badge}} @showName={{false}} />`
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom(".badge-display-name").doesNotExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("showName defaults to true", async function (assert) {
|
||||||
|
this.set("badge", { name: "foo" });
|
||||||
|
|
||||||
|
await render(hbs`<BadgeButton @badge={{this.badge}} />`);
|
||||||
|
|
||||||
|
assert.dom(".badge-display-name").exists();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -344,6 +344,26 @@ third line`
|
||||||
assert.strictEqual(textarea.selectionEnd, 23);
|
assert.strictEqual(textarea.selectionEnd, 23);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("code button does not reset undo history", async function (assert) {
|
||||||
|
this.set("value", "existing");
|
||||||
|
|
||||||
|
await render(hbs`<DEditor @value={{this.value}} />`);
|
||||||
|
const textarea = query("textarea.d-editor-input");
|
||||||
|
textarea.selectionStart = 0;
|
||||||
|
textarea.selectionEnd = 8;
|
||||||
|
|
||||||
|
await click("button.code");
|
||||||
|
assert.strictEqual(this.value, "`existing`");
|
||||||
|
|
||||||
|
await click("button.code");
|
||||||
|
assert.strictEqual(this.value, "existing");
|
||||||
|
|
||||||
|
document.execCommand("undo");
|
||||||
|
assert.strictEqual(this.value, "`existing`");
|
||||||
|
document.execCommand("undo");
|
||||||
|
assert.strictEqual(this.value, "existing");
|
||||||
|
});
|
||||||
|
|
||||||
test("code fences", async function (assert) {
|
test("code fences", async function (assert) {
|
||||||
this.set("value", "");
|
this.set("value", "");
|
||||||
|
|
||||||
|
@ -615,6 +635,22 @@ third line`
|
||||||
assert.strictEqual(textarea.selectionEnd, 18);
|
assert.strictEqual(textarea.selectionEnd, 18);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testCase(
|
||||||
|
"list button does not reset undo history",
|
||||||
|
async function (assert, textarea) {
|
||||||
|
this.set("value", "existing");
|
||||||
|
textarea.selectionStart = 0;
|
||||||
|
textarea.selectionEnd = 8;
|
||||||
|
|
||||||
|
await click("button.list");
|
||||||
|
assert.strictEqual(this.value, "1. existing");
|
||||||
|
|
||||||
|
document.execCommand("undo");
|
||||||
|
|
||||||
|
assert.strictEqual(this.value, "existing");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
test("clicking the toggle-direction changes dir from ltr to rtl and back", async function (assert) {
|
test("clicking the toggle-direction changes dir from ltr to rtl and back", async function (assert) {
|
||||||
this.siteSettings.support_mixed_text_direction = true;
|
this.siteSettings.support_mixed_text_direction = true;
|
||||||
this.siteSettings.default_locale = "en";
|
this.siteSettings.default_locale = "en";
|
||||||
|
|
|
@ -31,7 +31,7 @@ module("Integration | Component | select-kit/tag-drop", function (hooks) {
|
||||||
await render(<template>
|
await render(<template>
|
||||||
<TagDrop
|
<TagDrop
|
||||||
@currentCategory={{category}}
|
@currentCategory={{category}}
|
||||||
@tagId={{"jeff"}}
|
@tagId="jeff"
|
||||||
@options={{hash tagId="jeff"}}
|
@options={{hash tagId="jeff"}}
|
||||||
/>
|
/>
|
||||||
</template>);
|
</template>);
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { render } from "@ember/test-helpers";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import TopicListItem from "discourse/components/topic-list/item";
|
||||||
|
import HbrTopicListItem from "discourse/components/topic-list-item";
|
||||||
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
|
||||||
|
module("Integration | Component | topic-list-item", function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("checkbox is rendered checked if topic is in selected array", async function (assert) {
|
||||||
|
const store = this.owner.lookup("service:store");
|
||||||
|
const topic = store.createRecord("topic", { id: 24234 });
|
||||||
|
const topic2 = store.createRecord("topic", { id: 24235 });
|
||||||
|
const selected = [topic];
|
||||||
|
|
||||||
|
await render(<template>
|
||||||
|
<HbrTopicListItem
|
||||||
|
@topic={{topic}}
|
||||||
|
@bulkSelectEnabled={{true}}
|
||||||
|
@selected={{selected}}
|
||||||
|
/>
|
||||||
|
<HbrTopicListItem
|
||||||
|
@topic={{topic2}}
|
||||||
|
@bulkSelectEnabled={{true}}
|
||||||
|
@selected={{selected}}
|
||||||
|
/>
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
const checkboxes = [...document.querySelectorAll("input.bulk-select")];
|
||||||
|
assert.dom(checkboxes[0]).isChecked();
|
||||||
|
assert.dom(checkboxes[1]).isNotChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("topic-list-item-class value transformer", async function (assert) {
|
||||||
|
withPluginApi("1.39.0", (api) => {
|
||||||
|
api.registerValueTransformer(
|
||||||
|
"topic-list-item-class",
|
||||||
|
({ value, context }) => {
|
||||||
|
if (context.topic.get("foo")) {
|
||||||
|
value.push("bar");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = this.owner.lookup("service:store");
|
||||||
|
const topic = store.createRecord("topic", { id: 1234, foo: true });
|
||||||
|
const topic2 = store.createRecord("topic", { id: 1235, foo: false });
|
||||||
|
await render(<template>
|
||||||
|
<TopicListItem @topic={{topic}} />
|
||||||
|
<TopicListItem @topic={{topic2}} />
|
||||||
|
</template>);
|
||||||
|
|
||||||
|
assert.dom(".topic-list-item[data-topic-id='1234']").hasClass("bar");
|
||||||
|
assert
|
||||||
|
.dom(".topic-list-item[data-topic-id='1235']")
|
||||||
|
.doesNotHaveClass("bar");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,37 +0,0 @@
|
||||||
import { getOwner } from "@ember/owner";
|
|
||||||
import { render } from "@ember/test-helpers";
|
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
|
||||||
import { module, test } from "qunit";
|
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
|
||||||
|
|
||||||
module("Integration | Component | topic-list-item", function (hooks) {
|
|
||||||
setupRenderingTest(hooks);
|
|
||||||
|
|
||||||
test("checkbox is rendered checked if topic is in selected array", async function (assert) {
|
|
||||||
const store = getOwner(this).lookup("service:store");
|
|
||||||
const topic = store.createRecord("topic", { id: 24234 });
|
|
||||||
const topic2 = store.createRecord("topic", { id: 24235 });
|
|
||||||
this.setProperties({
|
|
||||||
topic,
|
|
||||||
topic2,
|
|
||||||
selected: [topic],
|
|
||||||
});
|
|
||||||
|
|
||||||
await render(hbs`
|
|
||||||
<TopicListItem
|
|
||||||
@topic={{this.topic}}
|
|
||||||
@bulkSelectEnabled={{true}}
|
|
||||||
@selected={{this.selected}}
|
|
||||||
/>
|
|
||||||
<TopicListItem
|
|
||||||
@topic={{this.topic2}}
|
|
||||||
@bulkSelectEnabled={{true}}
|
|
||||||
@selected={{this.selected}}
|
|
||||||
/>
|
|
||||||
`);
|
|
||||||
|
|
||||||
const checkboxes = [...document.querySelectorAll("input.bulk-select")];
|
|
||||||
assert.dom(checkboxes[0]).isChecked();
|
|
||||||
assert.dom(checkboxes[1]).isNotChecked();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { render } from "@ember/test-helpers";
|
import { render } from "@ember/test-helpers";
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
|
import Badge from "discourse/models/badge";
|
||||||
|
import User from "discourse/models/user";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
|
||||||
module("Integration | Component | Widget | poster-name", function (hooks) {
|
module("Integration | Component | Widget | poster-name", function (hooks) {
|
||||||
|
@ -70,4 +72,38 @@ module("Integration | Component | Widget | poster-name", function (hooks) {
|
||||||
|
|
||||||
assert.dom(".second").doesNotExist();
|
assert.dom(".second").doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("renders badges that are passed in", async function (assert) {
|
||||||
|
this.set("args", {
|
||||||
|
username: "eviltrout",
|
||||||
|
usernameUrl: "/u/eviltrout",
|
||||||
|
user: User.create({
|
||||||
|
username: "eviltrout",
|
||||||
|
}),
|
||||||
|
badgesGranted: [
|
||||||
|
{ id: 1, icon: "heart", slug: "badge1", name: "Badge One" },
|
||||||
|
{ id: 2, icon: "target", slug: "badge2", name: "Badge Two" },
|
||||||
|
].map((badge) => Badge.createFromJson({ badges: [badge] })[0]),
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(
|
||||||
|
hbs`<MountWidget @widget="poster-name" @args={{this.args}} />`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the custom CSS classes are set
|
||||||
|
assert.dom("span.user-badge-button-badge1").exists();
|
||||||
|
assert.dom("span.user-badge-button-badge2").exists();
|
||||||
|
|
||||||
|
// Check that the custom titles are set
|
||||||
|
assert.dom("span.user-badge[title*='Badge One']").exists();
|
||||||
|
assert.dom("span.user-badge[title*='Badge Two']").exists();
|
||||||
|
|
||||||
|
// Check that the badges link to the correct badge page
|
||||||
|
assert
|
||||||
|
.dom("a.user-card-badge-link[href='/badges/1/badge1?username=eviltrout']")
|
||||||
|
.exists();
|
||||||
|
assert
|
||||||
|
.dom("a.user-card-badge-link[href='/badges/2/badge2?username=eviltrout']")
|
||||||
|
.exists();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { concat } from "@ember/helper";
|
import { concat } from "@ember/helper";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
import { getOwner } from "@ember/owner";
|
import { getOwner } from "@ember/owner";
|
||||||
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { modifier } from "ember-modifier";
|
import { modifier } from "ember-modifier";
|
||||||
import { and } from "truth-helpers";
|
import { and } from "truth-helpers";
|
||||||
|
@ -31,6 +34,28 @@ export default class DMenu extends Component {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@action
|
||||||
|
registerFloatBody(element) {
|
||||||
|
this.body = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
forwardTabToContent(event) {
|
||||||
|
if (!this.body) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Tab") {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const firstFocusable = this.body.querySelector(
|
||||||
|
'button, a, input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||||
|
);
|
||||||
|
|
||||||
|
firstFocusable?.focus() || this.body.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get menuId() {
|
get menuId() {
|
||||||
return `d-menu-${this.menuInstance.id}`;
|
return `d-menu-${this.menuInstance.id}`;
|
||||||
}
|
}
|
||||||
|
@ -73,6 +98,7 @@ export default class DMenu extends Component {
|
||||||
@translatedTitle={{@title}}
|
@translatedTitle={{@title}}
|
||||||
@disabled={{@disabled}}
|
@disabled={{@disabled}}
|
||||||
aria-expanded={{if this.menuInstance.expanded "true" "false"}}
|
aria-expanded={{if this.menuInstance.expanded "true" "false"}}
|
||||||
|
{{on "keydown" this.forwardTabToContent}}
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
{{#if (has-block "trigger")}}
|
{{#if (has-block "trigger")}}
|
||||||
|
@ -122,6 +148,7 @@ export default class DMenu extends Component {
|
||||||
@innerClass="fk-d-menu__inner-content"
|
@innerClass="fk-d-menu__inner-content"
|
||||||
@role="dialog"
|
@role="dialog"
|
||||||
@inline={{this.options.inline}}
|
@inline={{this.options.inline}}
|
||||||
|
{{didInsert this.registerFloatBody}}
|
||||||
>
|
>
|
||||||
{{#if (has-block)}}
|
{{#if (has-block)}}
|
||||||
{{yield this.componentArgs}}
|
{{yield this.componentArgs}}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
"ember-source": "~5.5.0",
|
"ember-source": "~5.5.0",
|
||||||
"ember-source-channel-url": "^3.0.0",
|
"ember-source-channel-url": "^3.0.0",
|
||||||
"loader.js": "^4.7.0",
|
"loader.js": "^4.7.0",
|
||||||
"webpack": "^5.96.1"
|
"webpack": "^5.97.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18",
|
"node": ">= 18",
|
||||||
|
|
|
@ -1104,6 +1104,7 @@ a.inline-editable-field {
|
||||||
@import "common/admin/site-settings";
|
@import "common/admin/site-settings";
|
||||||
@import "common/admin/admin_config_area";
|
@import "common/admin/admin_config_area";
|
||||||
@import "common/admin/admin_table";
|
@import "common/admin/admin_table";
|
||||||
|
@import "common/admin/admin_filter";
|
||||||
@import "common/admin/admin_reports";
|
@import "common/admin/admin_reports";
|
||||||
@import "common/admin/admin_report";
|
@import "common/admin/admin_report";
|
||||||
@import "common/admin/admin_report_counters";
|
@import "common/admin/admin_report_counters";
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
.d-admin-filter {
|
||||||
|
background-color: var(--primary-very-low);
|
||||||
|
padding: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-users-list__search {
|
||||||
|
min-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-filter__input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
|
@ -8,6 +8,13 @@ em > code {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a > code {
|
||||||
|
padding: 2px 4px;
|
||||||
|
background: var(--inline-code-bg);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
color: var(--tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
color: var(--primary-very-high);
|
color: var(--primary-very-high);
|
||||||
background: var(--hljs-bg);
|
background: var(--hljs-bg);
|
||||||
|
|
|
@ -257,6 +257,24 @@
|
||||||
|
|
||||||
.names {
|
.names {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
||||||
|
.first {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badge-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badge {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-infos {
|
.post-infos {
|
||||||
|
@ -265,8 +283,9 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-status-message {
|
.user-status-message-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
img.emoji {
|
img.emoji {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
|
|
|
@ -249,9 +249,6 @@
|
||||||
.title-wrapper {
|
.title-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
button {
|
|
||||||
margin: 0 0.5em 0 0;
|
|
||||||
}
|
|
||||||
.topic-statuses {
|
.topic-statuses {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
.d-icon {
|
.d-icon {
|
||||||
|
@ -268,7 +265,9 @@
|
||||||
.title-wrapper {
|
.title-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
width: 90%;
|
@media screen and (min-width: 925px) {
|
||||||
|
width: 90%; // topic title isn't full-width on wide screens
|
||||||
|
}
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -282,36 +281,66 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
gap: 0.5em;
|
||||||
|
width: 100%;
|
||||||
max-width: calc(
|
max-width: calc(
|
||||||
var(--topic-body-width) + (var(--topic-body-width-padding) * 2) +
|
var(--topic-body-width) + (var(--topic-body-width-padding) * 2) +
|
||||||
var(--topic-avatar-width)
|
var(--topic-avatar-width)
|
||||||
);
|
);
|
||||||
#edit-title {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
|
||||||
.category-chooser,
|
|
||||||
.mini-tag-chooser {
|
|
||||||
flex: 1 1 35%;
|
|
||||||
margin: 0 0 9px 0;
|
|
||||||
@media all and (max-width: 500px) {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.mini-tag-chooser {
|
|
||||||
flex: 1 1 54%;
|
|
||||||
margin: 0 0 9px 0;
|
|
||||||
margin-left: 1%; // category at 40%, tag chooser at 58%
|
|
||||||
@media all and (max-width: 500px) {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.edit-controls {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
|
||||||
.select-kit .category-row {
|
.select-kit .category-row {
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-title__wrapper {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
#edit-title {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-category__wrapper {
|
||||||
|
flex: 1 1 5%;
|
||||||
|
@include breakpoint(tablet) {
|
||||||
|
min-width: 0; // allows category name to shrink to fit narrow screens
|
||||||
|
}
|
||||||
|
.select-kit.combo-box.category-chooser {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-tags__wrapper {
|
||||||
|
flex: 1 1 33%;
|
||||||
|
@include breakpoint(tablet) {
|
||||||
|
flex: 1 1 100%; // force full row on narrow screens
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-tag-chooser {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.select-kit-header--filter {
|
||||||
|
flex-wrap: nowrap; // forces the whole input to wrap if needed, rather than individual tags
|
||||||
|
min-width: 0;
|
||||||
|
@include breakpoint(tablet) {
|
||||||
|
flex-wrap: wrap; // individual tags will need to wrap on narrow screens
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-filter {
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 2em; // always provide a minimal space for input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-controls {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.private-message-glyph {
|
.private-message-glyph {
|
||||||
|
|
|
@ -43,6 +43,7 @@ body:not(.archetype-private_message) {
|
||||||
var(--topic-avatar-width) + var(--topic-body-width) +
|
var(--topic-avatar-width) + var(--topic-body-width) +
|
||||||
(var(--topic-body-width-padding) * 2)
|
(var(--topic-body-width-padding) * 2)
|
||||||
);
|
);
|
||||||
|
padding-block: 0.5em;
|
||||||
|
|
||||||
@include breakpoint(mobile-large) {
|
@include breakpoint(mobile-large) {
|
||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-1);
|
||||||
|
@ -91,7 +92,6 @@ body:not(.archetype-private_message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
&__contents {
|
&__contents {
|
||||||
padding-block: 0.5em;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
html.modal-open {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.d-modal {
|
.d-modal {
|
||||||
--modal-max-width: 600px;
|
--modal-max-width: 600px;
|
||||||
--modal-width: 30em; // set in ems to scale with user font-size
|
--modal-width: 30em; // set in ems to scale with user font-size
|
||||||
|
|
|
@ -18,11 +18,6 @@
|
||||||
z-index: z("base");
|
z-index: z("base");
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
|
||||||
#edit-title,
|
|
||||||
.category-chooser,
|
|
||||||
.edit-controls {
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: var(--font-up-4);
|
font-size: var(--font-up-4);
|
||||||
line-height: var(--line-height-medium);
|
line-height: var(--line-height-medium);
|
||||||
|
|
|
@ -1,8 +1,24 @@
|
||||||
// Shared styles
|
// Shared styles
|
||||||
|
|
||||||
|
.login-page,
|
||||||
|
.signup-page,
|
||||||
|
.invite-page {
|
||||||
|
#main-outlet,
|
||||||
|
#main-outlet-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.login-fullpage,
|
.login-fullpage,
|
||||||
.signup-fullpage,
|
.signup-fullpage,
|
||||||
.invites-show {
|
.invites-show {
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
html.keyboard-visible:not(.ios-device) & {
|
||||||
|
height: calc((var(--composer-vh, 1vh) * 100) - var(--header-offset));
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
.signup-body,
|
.signup-body,
|
||||||
.login-body {
|
.login-body {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -49,6 +49,10 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||||
on_failed_policy(:setting_is_visible) do
|
on_failed_policy(:setting_is_visible) do
|
||||||
raise Discourse::InvalidParameters, I18n.t("errors.site_settings.site_setting_is_hidden")
|
raise Discourse::InvalidParameters, I18n.t("errors.site_settings.site_setting_is_hidden")
|
||||||
end
|
end
|
||||||
|
on_failed_policy(:setting_is_shadowed_globally) do
|
||||||
|
raise Discourse::InvalidParameters,
|
||||||
|
I18n.t("errors.site_settings.site_setting_is_shadowed_globally")
|
||||||
|
end
|
||||||
on_failed_policy(:setting_is_configurable) do
|
on_failed_policy(:setting_is_configurable) do
|
||||||
raise Discourse::InvalidParameters,
|
raise Discourse::InvalidParameters,
|
||||||
I18n.t("errors.site_settings.site_setting_is_unconfigurable")
|
I18n.t("errors.site_settings.site_setting_is_unconfigurable")
|
||||||
|
|
|
@ -96,16 +96,26 @@ class DraftsController < ApplicationController
|
||||||
|
|
||||||
json = success_json.merge(draft_sequence: sequence)
|
json = success_json.merge(draft_sequence: sequence)
|
||||||
|
|
||||||
if data.present?
|
# check for conflicts when editing a post
|
||||||
# this is a bit of a kludge we need to remove (all the parsing) too many special cases here
|
if data.present? && data["postId"].present? && data["action"].to_s.start_with?("edit")
|
||||||
# we need to catch action edit and action editSharedDraft
|
original_text = data["original_text"] || data["originalText"]
|
||||||
if data["postId"].present? && data["originalText"].present? &&
|
original_title = data["original_title"]
|
||||||
data["action"].to_s.start_with?("edit")
|
original_tags = data["original_tags"]
|
||||||
post = Post.find_by(id: data["postId"])
|
|
||||||
if post && post.raw != data["originalText"]
|
if original_text.present?
|
||||||
|
if post = Post.find_by(id: data["postId"])
|
||||||
|
conflict = original_text != post.raw
|
||||||
|
|
||||||
|
if post.post_number == 1
|
||||||
|
conflict ||= original_title.present? && original_title != post.topic.title
|
||||||
|
conflict ||=
|
||||||
|
original_tags.present? && original_tags.sort != post.topic.tags.pluck(:name).sort
|
||||||
|
end
|
||||||
|
|
||||||
|
if conflict
|
||||||
conflict_user = BasicUserSerializer.new(post.last_editor, root: false)
|
conflict_user = BasicUserSerializer.new(post.last_editor, root: false)
|
||||||
render json: json.merge(conflict_user: conflict_user)
|
json.merge!(conflict_user:)
|
||||||
return
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -240,8 +240,9 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
Post.plugin_permitted_update_params.keys.each { |param| changes[param] = params[:post][param] }
|
Post.plugin_permitted_update_params.keys.each { |param| changes[param] = params[:post][param] }
|
||||||
|
|
||||||
raw_old = params[:post][:raw_old]
|
# keep `raw_old` for backwards compatibility
|
||||||
if raw_old.present? && raw_old != post.raw
|
original_text = params[:post][:original_text] || params[:post][:raw_old]
|
||||||
|
if original_text.present? && original_text != post.raw
|
||||||
return render_json_error(I18n.t("edit_conflict"), status: 409)
|
return render_json_error(I18n.t("edit_conflict"), status: 409)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -359,8 +359,19 @@ class TopicsController < ApplicationController
|
||||||
|
|
||||||
def update
|
def update
|
||||||
topic = Topic.find_by(id: params[:topic_id])
|
topic = Topic.find_by(id: params[:topic_id])
|
||||||
|
|
||||||
guardian.ensure_can_edit!(topic)
|
guardian.ensure_can_edit!(topic)
|
||||||
|
|
||||||
|
original_title = params[:original_title]
|
||||||
|
if original_title.present? && original_title != topic.title
|
||||||
|
return render_json_error(I18n.t("edit_conflict"), status: 409)
|
||||||
|
end
|
||||||
|
|
||||||
|
original_tags = params[:original_tags]
|
||||||
|
if original_tags.present? && original_tags.sort != topic.tags.pluck(:name).sort
|
||||||
|
return render_json_error(I18n.t("edit_conflict"), status: 409)
|
||||||
|
end
|
||||||
|
|
||||||
if params[:category_id] && (params[:category_id].to_i != topic.category_id.to_i)
|
if params[:category_id] && (params[:category_id].to_i != topic.category_id.to_i)
|
||||||
if topic.shared_draft
|
if topic.shared_draft
|
||||||
topic.shared_draft.update(category_id: params[:category_id])
|
topic.shared_draft.update(category_id: params[:category_id])
|
||||||
|
|
|
@ -363,6 +363,7 @@ end
|
||||||
# trigger :integer
|
# trigger :integer
|
||||||
# show_posts :boolean default(FALSE), not null
|
# show_posts :boolean default(FALSE), not null
|
||||||
# system :boolean default(FALSE), not null
|
# system :boolean default(FALSE), not null
|
||||||
|
# show_in_post_header :boolean default(FALSE), not null
|
||||||
# long_description :text
|
# long_description :text
|
||||||
# image_upload_id :integer
|
# image_upload_id :integer
|
||||||
#
|
#
|
||||||
|
|
|
@ -327,6 +327,6 @@ class Emoji
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.sanitize_emoji_name(name)
|
def self.sanitize_emoji_name(name)
|
||||||
name.gsub(/[^a-z0-9]+/i, "_").gsub(/_{2,}/, "_").downcase
|
name.gsub(/[^a-z0-9\+\-]+/i, "_").gsub(/_{2,}/, "_").downcase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -658,6 +658,7 @@ class Post < ActiveRecord::Base
|
||||||
"flag_reasons.#{post_action_type_view.types[post_action_type_id]}",
|
"flag_reasons.#{post_action_type_view.types[post_action_type_id]}",
|
||||||
locale: SiteSetting.default_locale,
|
locale: SiteSetting.default_locale,
|
||||||
base_path: Discourse.base_path,
|
base_path: Discourse.base_path,
|
||||||
|
default: PostActionType.names[post_action_type_id],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,11 @@ class PostMover
|
||||||
close_topic_and_schedule_deletion if moving_all_posts
|
close_topic_and_schedule_deletion if moving_all_posts
|
||||||
|
|
||||||
destination_topic.reload
|
destination_topic.reload
|
||||||
|
DiscourseEvent.trigger(
|
||||||
|
:posts_moved,
|
||||||
|
destination_topic_id: destination_topic.id,
|
||||||
|
original_topic_id: original_topic.id,
|
||||||
|
)
|
||||||
destination_topic
|
destination_topic
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -299,6 +304,7 @@ class PostMover
|
||||||
end
|
end
|
||||||
|
|
||||||
moved_post.attributes = update
|
moved_post.attributes = update
|
||||||
|
moved_post.disable_rate_limits! if @options[:freeze_original]
|
||||||
moved_post.save(validate: false)
|
moved_post.save(validate: false)
|
||||||
|
|
||||||
DiscourseEvent.trigger(:post_moved, moved_post, original_topic.id)
|
DiscourseEvent.trigger(:post_moved, moved_post, original_topic.id)
|
||||||
|
|
|
@ -35,6 +35,19 @@ class UserBadge < ActiveRecord::Base
|
||||||
scope :for_enabled_badges,
|
scope :for_enabled_badges,
|
||||||
-> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") }
|
-> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") }
|
||||||
|
|
||||||
|
scope :by_post_and_user,
|
||||||
|
->(posts) do
|
||||||
|
posts.reduce(UserBadge.none) do |scope, post|
|
||||||
|
scope.or(UserBadge.where(user_id: post.user_id, post_id: post.id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
scope :for_post_header_badges,
|
||||||
|
->(posts) do
|
||||||
|
by_post_and_user(posts).where(
|
||||||
|
"user_badges.badge_id IN (SELECT id FROM badges WHERE show_posts AND enabled AND listable AND show_in_post_header)",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge?
|
validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge?
|
||||||
|
|
||||||
validates :user_id, presence: true
|
validates :user_id, presence: true
|
||||||
|
|
|
@ -16,7 +16,8 @@ class BadgeSerializer < ApplicationSerializer
|
||||||
:long_description,
|
:long_description,
|
||||||
:slug,
|
:slug,
|
||||||
:has_badge,
|
:has_badge,
|
||||||
:manually_grantable?
|
:manually_grantable?,
|
||||||
|
:show_in_post_header
|
||||||
|
|
||||||
has_one :badge_type
|
has_one :badge_type
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ class PostSerializer < BasicPostSerializer
|
||||||
:flair_bg_color,
|
:flair_bg_color,
|
||||||
:flair_color,
|
:flair_color,
|
||||||
:flair_group_id,
|
:flair_group_id,
|
||||||
|
:badges_granted,
|
||||||
:version,
|
:version,
|
||||||
:can_edit,
|
:can_edit,
|
||||||
:can_delete,
|
:can_delete,
|
||||||
|
@ -223,6 +224,18 @@ class PostSerializer < BasicPostSerializer
|
||||||
object.user&.flair_group_id
|
object.user&.flair_group_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def badges_granted
|
||||||
|
return [] unless SiteSetting.enable_badges && SiteSetting.show_badges_in_post_header
|
||||||
|
|
||||||
|
if @topic_view
|
||||||
|
user_badges = @topic_view.post_user_badges[object.id] || []
|
||||||
|
else
|
||||||
|
user_badges = UserBadge.for_post_header_badges([object])
|
||||||
|
end
|
||||||
|
|
||||||
|
user_badges.map { |user_badge| BasicUserBadgeSerializer.new(user_badge, scope: scope).as_json }
|
||||||
|
end
|
||||||
|
|
||||||
def link_counts
|
def link_counts
|
||||||
return @single_post_link_counts if @single_post_link_counts.present?
|
return @single_post_link_counts if @single_post_link_counts.present?
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class WebHookPostSerializer < PostSerializer
|
||||||
flair_color
|
flair_color
|
||||||
notice
|
notice
|
||||||
mentioned_users
|
mentioned_users
|
||||||
|
badges_granted
|
||||||
].each { |attr| define_method("include_#{attr}?") { false } }
|
].each { |attr| define_method("include_#{attr}?") { false } }
|
||||||
|
|
||||||
def topic_posts
|
def topic_posts
|
||||||
|
|
|
@ -4,12 +4,15 @@ class AdminNotices::Dismiss
|
||||||
include Service::Base
|
include Service::Base
|
||||||
|
|
||||||
policy :invalid_access
|
policy :invalid_access
|
||||||
|
|
||||||
params do
|
params do
|
||||||
attribute :id, :integer
|
attribute :id, :integer
|
||||||
|
|
||||||
validates :id, presence: true
|
validates :id, presence: true
|
||||||
end
|
end
|
||||||
|
|
||||||
model :admin_notice, optional: true
|
model :admin_notice, optional: true
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
step :destroy
|
step :destroy
|
||||||
step :reset_problem_check
|
step :reset_problem_check
|
||||||
|
|
|
@ -4,10 +4,12 @@ class Experiments::Toggle
|
||||||
include Service::Base
|
include Service::Base
|
||||||
|
|
||||||
policy :current_user_is_admin
|
policy :current_user_is_admin
|
||||||
|
|
||||||
params do
|
params do
|
||||||
attribute :setting_name, :string
|
attribute :setting_name, :string
|
||||||
validates :setting_name, presence: true
|
validates :setting_name, presence: true
|
||||||
end
|
end
|
||||||
|
|
||||||
policy :setting_is_available
|
policy :setting_is_available
|
||||||
transaction { step :toggle }
|
transaction { step :toggle }
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Flags::CreateFlag
|
||||||
include Service::Base
|
include Service::Base
|
||||||
|
|
||||||
policy :invalid_access
|
policy :invalid_access
|
||||||
|
|
||||||
params do
|
params do
|
||||||
attribute :name, :string
|
attribute :name, :string
|
||||||
attribute :description, :string
|
attribute :description, :string
|
||||||
|
@ -18,8 +19,10 @@ class Flags::CreateFlag
|
||||||
validates :description, length: { maximum: Flag::MAX_DESCRIPTION_LENGTH }
|
validates :description, length: { maximum: Flag::MAX_DESCRIPTION_LENGTH }
|
||||||
validates :applies_to, inclusion: { in: -> { Flag.valid_applies_to_types } }, allow_nil: false
|
validates :applies_to, inclusion: { in: -> { Flag.valid_applies_to_types } }, allow_nil: false
|
||||||
end
|
end
|
||||||
|
|
||||||
policy :unique_name
|
policy :unique_name
|
||||||
model :flag, :instantiate_flag
|
model :flag, :instantiate_flag
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
step :create
|
step :create
|
||||||
step :log
|
step :log
|
||||||
|
|
|
@ -8,10 +8,12 @@ class Flags::DestroyFlag
|
||||||
|
|
||||||
validates :id, presence: true
|
validates :id, presence: true
|
||||||
end
|
end
|
||||||
|
|
||||||
model :flag
|
model :flag
|
||||||
policy :not_system
|
policy :not_system
|
||||||
policy :not_used
|
policy :not_used
|
||||||
policy :invalid_access
|
policy :invalid_access
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
step :destroy
|
step :destroy
|
||||||
step :log
|
step :log
|
||||||
|
|
|
@ -10,10 +10,12 @@ class Flags::ReorderFlag
|
||||||
validates :flag_id, presence: true
|
validates :flag_id, presence: true
|
||||||
validates :direction, inclusion: { in: %w[up down] }
|
validates :direction, inclusion: { in: %w[up down] }
|
||||||
end
|
end
|
||||||
|
|
||||||
model :flag
|
model :flag
|
||||||
policy :invalid_access
|
policy :invalid_access
|
||||||
model :all_flags
|
model :all_flags
|
||||||
policy :invalid_move
|
policy :invalid_move
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
step :move
|
step :move
|
||||||
step :log
|
step :log
|
||||||
|
|
|
@ -4,12 +4,15 @@ class Flags::ToggleFlag
|
||||||
include Service::Base
|
include Service::Base
|
||||||
|
|
||||||
policy :invalid_access
|
policy :invalid_access
|
||||||
|
|
||||||
params do
|
params do
|
||||||
attribute :flag_id, :integer
|
attribute :flag_id, :integer
|
||||||
|
|
||||||
validates :flag_id, presence: true
|
validates :flag_id, presence: true
|
||||||
end
|
end
|
||||||
|
|
||||||
model :flag
|
model :flag
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
step :toggle
|
step :toggle
|
||||||
step :log
|
step :log
|
||||||
|
|
|
@ -19,11 +19,13 @@ class Flags::UpdateFlag
|
||||||
validates :description, length: { maximum: Flag::MAX_DESCRIPTION_LENGTH }
|
validates :description, length: { maximum: Flag::MAX_DESCRIPTION_LENGTH }
|
||||||
validates :applies_to, inclusion: { in: -> { Flag.valid_applies_to_types } }, allow_nil: false
|
validates :applies_to, inclusion: { in: -> { Flag.valid_applies_to_types } }, allow_nil: false
|
||||||
end
|
end
|
||||||
|
|
||||||
model :flag
|
model :flag
|
||||||
policy :not_system
|
policy :not_system
|
||||||
policy :not_used
|
policy :not_used
|
||||||
policy :invalid_access
|
policy :invalid_access
|
||||||
policy :unique_name
|
policy :unique_name
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
step :update
|
step :update
|
||||||
step :log
|
step :log
|
||||||
|
|
|
@ -6,6 +6,7 @@ class SiteSetting::Update
|
||||||
options { attribute :allow_changing_hidden, :boolean, default: false }
|
options { attribute :allow_changing_hidden, :boolean, default: false }
|
||||||
|
|
||||||
policy :current_user_is_admin
|
policy :current_user_is_admin
|
||||||
|
|
||||||
params do
|
params do
|
||||||
attribute :setting_name
|
attribute :setting_name
|
||||||
attribute :new_value
|
attribute :new_value
|
||||||
|
@ -34,6 +35,8 @@ class SiteSetting::Update
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
policy :setting_is_shadowed_globally
|
||||||
policy :setting_is_visible
|
policy :setting_is_visible
|
||||||
policy :setting_is_configurable
|
policy :setting_is_configurable
|
||||||
step :save
|
step :save
|
||||||
|
@ -44,6 +47,10 @@ class SiteSetting::Update
|
||||||
guardian.is_admin?
|
guardian.is_admin?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setting_is_shadowed_globally(params:)
|
||||||
|
!SiteSetting.shadowed_settings.include?(params.setting_name)
|
||||||
|
end
|
||||||
|
|
||||||
def setting_is_visible(params:, options:)
|
def setting_is_visible(params:, options:)
|
||||||
options.allow_changing_hidden || !SiteSetting.hidden_settings.include?(params.setting_name)
|
options.allow_changing_hidden || !SiteSetting.hidden_settings.include?(params.setting_name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,7 @@ class User::Silence
|
||||||
validates :other_user_ids, length: { maximum: User::MAX_SIMILAR_USERS }
|
validates :other_user_ids, length: { maximum: User::MAX_SIMILAR_USERS }
|
||||||
validates :post_action, inclusion: { in: %w[delete delete_replies edit] }, allow_blank: true
|
validates :post_action, inclusion: { in: %w[delete delete_replies edit] }, allow_blank: true
|
||||||
end
|
end
|
||||||
|
|
||||||
model :user
|
model :user
|
||||||
policy :not_silenced_already, class_name: User::Policy::NotAlreadySilenced
|
policy :not_silenced_already, class_name: User::Policy::NotAlreadySilenced
|
||||||
model :users
|
model :users
|
||||||
|
|
|
@ -19,6 +19,7 @@ class User::Suspend
|
||||||
validates :other_user_ids, length: { maximum: User::MAX_SIMILAR_USERS }
|
validates :other_user_ids, length: { maximum: User::MAX_SIMILAR_USERS }
|
||||||
validates :post_action, inclusion: { in: %w[delete delete_replies edit] }, allow_blank: true
|
validates :post_action, inclusion: { in: %w[delete delete_replies edit] }, allow_blank: true
|
||||||
end
|
end
|
||||||
|
|
||||||
model :user
|
model :user
|
||||||
policy :not_suspended_already, class_name: User::Policy::NotAlreadySuspended
|
policy :not_suspended_already, class_name: User::Policy::NotAlreadySuspended
|
||||||
model :users
|
model :users
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<%- if can_sign_up? %>
|
<%- if can_sign_up? %>
|
||||||
<a href="<%= path "/signup"%>" class='btn btn-primary btn-small sign-up-button'><%= I18n.t('sign_up') %></a>
|
<a href="<%= path "/signup"%>" class='btn btn-primary btn-small sign-up-button'><%= I18n.t('sign_up') %></a>
|
||||||
<%- end %>
|
<%- end %>
|
||||||
<a href="<%= path "/login"%>" class='btn btn-primary btn-small login-button btn-icon-text'><%= SvgSprite.raw_svg('fa-user') %><%= I18n.t('log_in') %></a>
|
<a href="<%= path "/login"%>" class='btn btn-primary btn-small login-button btn-icon-text'><%= SvgSprite.raw_svg('user') %><%= I18n.t('log_in') %></a>
|
||||||
</span>
|
</span>
|
||||||
<%- end %>
|
<%- end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<h1 class="title"><%= @title %></h1>
|
<h1 class="title"><%= @title %></h1>
|
||||||
|
|
||||||
<%- if !@current_user %>
|
<%- if !@current_user %>
|
||||||
<a href="<%= path "/login" %>" class='btn btn-primary'><%= SvgSprite.raw_svg('fa-user') %><%= I18n.t('log_in') %></a>
|
<a href="<%= path "/login" %>" class='btn btn-primary'><%= SvgSprite.raw_svg('user') %><%= I18n.t('log_in') %></a>
|
||||||
<%- end %>
|
<%- end %>
|
||||||
|
|
||||||
<%- if @group&.dig(:allow_membership_requests) %>
|
<%- if @group&.dig(:allow_membership_requests) %>
|
||||||
|
|
|
@ -4920,7 +4920,6 @@ ar:
|
||||||
with_topics: "موضوعات %{filter}"
|
with_topics: "موضوعات %{filter}"
|
||||||
with_category: "موضوعات %{filter} في %{category}"
|
with_category: "موضوعات %{filter} في %{category}"
|
||||||
filter:
|
filter:
|
||||||
title: "المرشح"
|
|
||||||
button:
|
button:
|
||||||
label: "المرشح"
|
label: "المرشح"
|
||||||
latest:
|
latest:
|
||||||
|
@ -5770,6 +5769,7 @@ ar:
|
||||||
all: "الكل"
|
all: "الكل"
|
||||||
new_features:
|
new_features:
|
||||||
title: "ما الجديد"
|
title: "ما الجديد"
|
||||||
|
check_for_updates: "تحقق من وجود تحديثات"
|
||||||
dashboard:
|
dashboard:
|
||||||
title: "لوحة المعلومات"
|
title: "لوحة المعلومات"
|
||||||
last_updated: "تم تحديث لوحة المعلومات:"
|
last_updated: "تم تحديث لوحة المعلومات:"
|
||||||
|
@ -6409,6 +6409,7 @@ ar:
|
||||||
new_theme: "سمة جديدة"
|
new_theme: "سمة جديدة"
|
||||||
user_selectable: "قابلة للاختيار من قِبل المستخدم"
|
user_selectable: "قابلة للاختيار من قِبل المستخدم"
|
||||||
user_fields:
|
user_fields:
|
||||||
|
field: "الحقل"
|
||||||
type: "النوع"
|
type: "النوع"
|
||||||
more_options:
|
more_options:
|
||||||
title: "المزيد من الخيارات"
|
title: "المزيد من الخيارات"
|
||||||
|
@ -7633,6 +7634,7 @@ ar:
|
||||||
confirm_delete: "هل تريد بالتأكيد حذف سجل DiscourseConnect هذا؟"
|
confirm_delete: "هل تريد بالتأكيد حذف سجل DiscourseConnect هذا؟"
|
||||||
user_fields:
|
user_fields:
|
||||||
title: "حقول المستخدم"
|
title: "حقول المستخدم"
|
||||||
|
add: "أضف حقل المستخدم"
|
||||||
untitled: "بلا لقب"
|
untitled: "بلا لقب"
|
||||||
name: "اسم الحقل"
|
name: "اسم الحقل"
|
||||||
type: "نوع الحقل"
|
type: "نوع الحقل"
|
||||||
|
@ -7836,6 +7838,7 @@ ar:
|
||||||
none_selected: "تحديد شارة للبدء"
|
none_selected: "تحديد شارة للبدء"
|
||||||
allow_title: السماح للمستخدمين باستخدام الشارة كعنوان
|
allow_title: السماح للمستخدمين باستخدام الشارة كعنوان
|
||||||
multiple_grant: يمكن منحها عدة مرات
|
multiple_grant: يمكن منحها عدة مرات
|
||||||
|
visibility_heading: الرؤية
|
||||||
listable: عرض الشارة في صفحة الشارات العامة
|
listable: عرض الشارة في صفحة الشارات العامة
|
||||||
enabled: مفعَّلة
|
enabled: مفعَّلة
|
||||||
disabled: متوقف
|
disabled: متوقف
|
||||||
|
|
|
@ -1279,7 +1279,6 @@ be:
|
||||||
categories_list: "Спіс катэгорый"
|
categories_list: "Спіс катэгорый"
|
||||||
filters:
|
filters:
|
||||||
filter:
|
filter:
|
||||||
title: "Filter"
|
|
||||||
button:
|
button:
|
||||||
label: "Filter"
|
label: "Filter"
|
||||||
latest:
|
latest:
|
||||||
|
|
|
@ -3104,7 +3104,6 @@ bg:
|
||||||
with_topics: "%{filter} теми"
|
with_topics: "%{filter} теми"
|
||||||
with_category: "%{filter} %{category} теми"
|
with_category: "%{filter} %{category} теми"
|
||||||
filter:
|
filter:
|
||||||
title: "Филтър"
|
|
||||||
button:
|
button:
|
||||||
label: "Филтър"
|
label: "Филтър"
|
||||||
latest:
|
latest:
|
||||||
|
@ -4350,6 +4349,7 @@ bg:
|
||||||
none_selected: "Изберете значка за да започнете."
|
none_selected: "Изберете значка за да започнете."
|
||||||
allow_title: Позволете значките да бъдат ползвани като титли.
|
allow_title: Позволете значките да бъдат ползвани като титли.
|
||||||
multiple_grant: Може да бъде присъдена няколко пъти
|
multiple_grant: Може да бъде присъдена няколко пъти
|
||||||
|
visibility_heading: Видимост
|
||||||
listable: Показвай значката на страницата със значки
|
listable: Показвай значката на страницата със значки
|
||||||
enabled: да е включен
|
enabled: да е включен
|
||||||
disabled: деактивирани
|
disabled: деактивирани
|
||||||
|
|
|
@ -2710,7 +2710,6 @@ bs_BA:
|
||||||
with_topics: "%{filter} teme"
|
with_topics: "%{filter} teme"
|
||||||
with_category: "%{filter} %{category} teme"
|
with_category: "%{filter} %{category} teme"
|
||||||
filter:
|
filter:
|
||||||
title: "Filter"
|
|
||||||
button:
|
button:
|
||||||
label: "Filter"
|
label: "Filter"
|
||||||
latest:
|
latest:
|
||||||
|
@ -4378,6 +4377,7 @@ bs_BA:
|
||||||
none_selected: "Izaberite značku za početak"
|
none_selected: "Izaberite značku za početak"
|
||||||
allow_title: Allow badge to be used as a title
|
allow_title: Allow badge to be used as a title
|
||||||
multiple_grant: Can be granted multiple times
|
multiple_grant: Can be granted multiple times
|
||||||
|
visibility_heading: Vidljivost
|
||||||
listable: Show badge on the public badges page
|
listable: Show badge on the public badges page
|
||||||
enabled: omogućen
|
enabled: omogućen
|
||||||
disabled: deaktiviran
|
disabled: deaktiviran
|
||||||
|
|
|
@ -2619,7 +2619,6 @@ ca:
|
||||||
with_topics: "%{filter} temes"
|
with_topics: "%{filter} temes"
|
||||||
with_category: "%{filter} %{category} temes"
|
with_category: "%{filter} %{category} temes"
|
||||||
filter:
|
filter:
|
||||||
title: "Filtre"
|
|
||||||
button:
|
button:
|
||||||
label: "Filtre"
|
label: "Filtre"
|
||||||
latest:
|
latest:
|
||||||
|
@ -4236,6 +4235,7 @@ ca:
|
||||||
none_selected: "Tria una insígnia per a començar"
|
none_selected: "Tria una insígnia per a començar"
|
||||||
allow_title: Permet que es faci servir la insígnia com a títol
|
allow_title: Permet que es faci servir la insígnia com a títol
|
||||||
multiple_grant: Pot concedir-se moltes vegades
|
multiple_grant: Pot concedir-se moltes vegades
|
||||||
|
visibility_heading: Visibilitat
|
||||||
listable: Mostra la insígnia en la pàgina pública d'insígnies
|
listable: Mostra la insígnia en la pàgina pública d'insígnies
|
||||||
enabled: activat
|
enabled: activat
|
||||||
disabled: desactivat
|
disabled: desactivat
|
||||||
|
|
|
@ -4310,7 +4310,6 @@ cs:
|
||||||
with_topics: "%{filter} témata"
|
with_topics: "%{filter} témata"
|
||||||
with_category: "%{filter} %{category} témata"
|
with_category: "%{filter} %{category} témata"
|
||||||
filter:
|
filter:
|
||||||
title: "Filtr"
|
|
||||||
button:
|
button:
|
||||||
label: "Filtr"
|
label: "Filtr"
|
||||||
latest:
|
latest:
|
||||||
|
@ -6540,6 +6539,7 @@ cs:
|
||||||
none_selected: "Vyberte odznak, abyste mohli začít"
|
none_selected: "Vyberte odznak, abyste mohli začít"
|
||||||
allow_title: Povolit užití odzanku jako titul
|
allow_title: Povolit užití odzanku jako titul
|
||||||
multiple_grant: Může být přiděleno několikrát
|
multiple_grant: Může být přiděleno několikrát
|
||||||
|
visibility_heading: Viditelnost
|
||||||
listable: Zobrazit odznak na veřejné stránce s odzanky
|
listable: Zobrazit odznak na veřejné stránce s odzanky
|
||||||
enabled: zapnuto
|
enabled: zapnuto
|
||||||
disabled: vypnuto
|
disabled: vypnuto
|
||||||
|
|
|
@ -3310,7 +3310,6 @@ da:
|
||||||
with_topics: "%{filter} emner"
|
with_topics: "%{filter} emner"
|
||||||
with_category: "%{filter} %{category} emner"
|
with_category: "%{filter} %{category} emner"
|
||||||
filter:
|
filter:
|
||||||
title: "Filter"
|
|
||||||
button:
|
button:
|
||||||
label: "Filter"
|
label: "Filter"
|
||||||
latest:
|
latest:
|
||||||
|
@ -5355,6 +5354,7 @@ da:
|
||||||
none_selected: "Vælg et emblem for at komme igang"
|
none_selected: "Vælg et emblem for at komme igang"
|
||||||
allow_title: Tillad at bruge dette emblem som titel
|
allow_title: Tillad at bruge dette emblem som titel
|
||||||
multiple_grant: Kan tildeles flere gange
|
multiple_grant: Kan tildeles flere gange
|
||||||
|
visibility_heading: Synlighed
|
||||||
listable: Vis emblemer på den offentlige emblem side
|
listable: Vis emblemer på den offentlige emblem side
|
||||||
enabled: aktiveret
|
enabled: aktiveret
|
||||||
disabled: deaktiveret
|
disabled: deaktiveret
|
||||||
|
|
|
@ -3546,6 +3546,7 @@ de:
|
||||||
other: "Zeige %{count} Antworten an"
|
other: "Zeige %{count} Antworten an"
|
||||||
in_reply_to: "Übergeordneten Beitrag laden"
|
in_reply_to: "Übergeordneten Beitrag laden"
|
||||||
view_all_posts: "Alle Beiträge anzeigen"
|
view_all_posts: "Alle Beiträge anzeigen"
|
||||||
|
badge_granted_tooltip: "%{username} hat für diesen Beitrag das Abzeichen „%{badge_name}“ erhalten!"
|
||||||
errors:
|
errors:
|
||||||
create: "Entschuldige, es gab einen Fehler beim Anlegen des Beitrags. Bitte versuche es noch einmal."
|
create: "Entschuldige, es gab einen Fehler beim Anlegen des Beitrags. Bitte versuche es noch einmal."
|
||||||
edit: "Entschuldige, es gab einen Fehler beim Bearbeiten des Beitrags. Bitte versuche es noch einmal."
|
edit: "Entschuldige, es gab einen Fehler beim Bearbeiten des Beitrags. Bitte versuche es noch einmal."
|
||||||
|
@ -4049,7 +4050,7 @@ de:
|
||||||
with_topics: "%{filter}"
|
with_topics: "%{filter}"
|
||||||
with_category: "%{filter} in %{category}"
|
with_category: "%{filter} in %{category}"
|
||||||
filter:
|
filter:
|
||||||
title: "Filter"
|
title: "Gefilterte Ergebnisse für %{filter}"
|
||||||
button:
|
button:
|
||||||
label: "Filter"
|
label: "Filter"
|
||||||
latest:
|
latest:
|
||||||
|
@ -4771,6 +4772,7 @@ de:
|
||||||
all: "Gesamt"
|
all: "Gesamt"
|
||||||
new_features:
|
new_features:
|
||||||
title: "Was ist neu?"
|
title: "Was ist neu?"
|
||||||
|
check_for_updates: "Nach Aktualisierungen suchen"
|
||||||
dashboard:
|
dashboard:
|
||||||
title: "Dashboard"
|
title: "Dashboard"
|
||||||
last_updated: "Dashboard aktualisiert:"
|
last_updated: "Dashboard aktualisiert:"
|
||||||
|
@ -6265,6 +6267,7 @@ de:
|
||||||
invalid: "Entschuldige, du darfst nicht in die Rolle dieses Benutzers schlüpfen."
|
invalid: "Entschuldige, du darfst nicht in die Rolle dieses Benutzers schlüpfen."
|
||||||
users:
|
users:
|
||||||
title: "Benutzer"
|
title: "Benutzer"
|
||||||
|
description: "Benutzer anzeigen und verwalten."
|
||||||
create: "Administrator hinzufügen"
|
create: "Administrator hinzufügen"
|
||||||
last_emailed: "Letzte E-Mail"
|
last_emailed: "Letzte E-Mail"
|
||||||
not_found: "Entschuldige, dieser Benutzername ist im System nicht vorhanden."
|
not_found: "Entschuldige, dieser Benutzername ist im System nicht vorhanden."
|
||||||
|
@ -6785,9 +6788,13 @@ de:
|
||||||
no_user_badges: "%{name} wurden keine Abzeichen verliehen."
|
no_user_badges: "%{name} wurden keine Abzeichen verliehen."
|
||||||
no_badges: Es gibt keine Abzeichen, die verliehen werden können.
|
no_badges: Es gibt keine Abzeichen, die verliehen werden können.
|
||||||
none_selected: "Wähle ein Abzeichen aus, um loszulegen"
|
none_selected: "Wähle ein Abzeichen aus, um loszulegen"
|
||||||
|
usage_heading: Verwendung
|
||||||
allow_title: Abzeichen darf als Titel verwendet werden
|
allow_title: Abzeichen darf als Titel verwendet werden
|
||||||
multiple_grant: Kann mehrfach verliehen werden
|
multiple_grant: Kann mehrfach verliehen werden
|
||||||
|
visibility_heading: Sichtbarkeit
|
||||||
listable: Zeige Abzeichen auf der öffentlichen Abzeichenseite an
|
listable: Zeige Abzeichen auf der öffentlichen Abzeichenseite an
|
||||||
|
show_in_post_header: Abzeichen auf dem Beitrag anzeigen, für den es gewährt wurde
|
||||||
|
show_in_post_header_disabled: Erfordert, dass sowohl "Abzeichen auf der Seite mit den öffentlichen Abzeichen anzeigen" als auch "Abzeichen für Beiträge auf der Seite mit den Abzeichen anzeigen" aktiviert sind.
|
||||||
enabled: aktiviert
|
enabled: aktiviert
|
||||||
disabled: deaktiviert
|
disabled: deaktiviert
|
||||||
icon: Symbol
|
icon: Symbol
|
||||||
|
|
|
@ -3319,7 +3319,6 @@ el:
|
||||||
with_topics: "%{filter} θέματα"
|
with_topics: "%{filter} θέματα"
|
||||||
with_category: "%{filter} %{category} θέματα"
|
with_category: "%{filter} %{category} θέματα"
|
||||||
filter:
|
filter:
|
||||||
title: "Φίλτρο"
|
|
||||||
button:
|
button:
|
||||||
label: "Φίλτρο"
|
label: "Φίλτρο"
|
||||||
latest:
|
latest:
|
||||||
|
@ -5228,6 +5227,7 @@ el:
|
||||||
none_selected: "Διάλεξε ένα παράσημο για να ξεκινήσεις"
|
none_selected: "Διάλεξε ένα παράσημο για να ξεκινήσεις"
|
||||||
allow_title: Το παράσημο μπορεί να χρησιμοποιηθεί σαν τίτλος
|
allow_title: Το παράσημο μπορεί να χρησιμοποιηθεί σαν τίτλος
|
||||||
multiple_grant: Μπορεί να απονεμηθεί πολλές φορές
|
multiple_grant: Μπορεί να απονεμηθεί πολλές φορές
|
||||||
|
visibility_heading: Ορατότητα
|
||||||
listable: Εμφάνιση του παράσημου στη δημόσια σελίδα παρασήμων
|
listable: Εμφάνιση του παράσημου στη δημόσια σελίδα παρασήμων
|
||||||
enabled: ενεργοποιημένα
|
enabled: ενεργοποιημένα
|
||||||
disabled: απενεργοποιημένα
|
disabled: απενεργοποιημένα
|
||||||
|
|
|
@ -3816,6 +3816,8 @@ en:
|
||||||
in_reply_to: "Load parent post"
|
in_reply_to: "Load parent post"
|
||||||
view_all_posts: "View all posts"
|
view_all_posts: "View all posts"
|
||||||
|
|
||||||
|
badge_granted_tooltip: "%{username} earned the '%{badge_name}' badge for this post!"
|
||||||
|
|
||||||
errors:
|
errors:
|
||||||
create: "Sorry, there was an error creating your post. Please try again."
|
create: "Sorry, there was an error creating your post. Please try again."
|
||||||
edit: "Sorry, there was an error editing your post. Please try again."
|
edit: "Sorry, there was an error editing your post. Please try again."
|
||||||
|
@ -7195,9 +7197,13 @@ en:
|
||||||
no_user_badges: "%{name} has not been granted any badges."
|
no_user_badges: "%{name} has not been granted any badges."
|
||||||
no_badges: There are no badges that can be granted.
|
no_badges: There are no badges that can be granted.
|
||||||
none_selected: "Select a badge to get started"
|
none_selected: "Select a badge to get started"
|
||||||
|
usage_heading: Usage
|
||||||
allow_title: Allow badge to be used as a title
|
allow_title: Allow badge to be used as a title
|
||||||
multiple_grant: Can be granted multiple times
|
multiple_grant: Can be granted multiple times
|
||||||
|
visibility_heading: Visibility
|
||||||
listable: Show badge on the public badges page
|
listable: Show badge on the public badges page
|
||||||
|
show_in_post_header: Show badge on the post it was granted for
|
||||||
|
show_in_post_header_disabled: Requires both 'Show badge on the public badges page' and 'Show post granting badge on badge page' to be enabled.
|
||||||
enabled: enabled
|
enabled: enabled
|
||||||
disabled: disabled
|
disabled: disabled
|
||||||
icon: Icon
|
icon: Icon
|
||||||
|
|
|
@ -1492,12 +1492,12 @@ es:
|
||||||
download_backup_codes: "Descargar códigos de recuperación"
|
download_backup_codes: "Descargar códigos de recuperación"
|
||||||
remaining_codes:
|
remaining_codes:
|
||||||
one: "Te queda <strong>%{count}</strong> código de copia de seguridad sin usar todavía."
|
one: "Te queda <strong>%{count}</strong> código de copia de seguridad sin usar todavía."
|
||||||
other: "Te quedan <strong>%{count}</strong> códigos de respaldo sin usar todavía."
|
other: "Te quedan <strong>%{count}</strong> códigos de copia de seguridad sin usar todavía."
|
||||||
use: "Usar un código de copia de seguridad"
|
use: "Usar un código de recuperación"
|
||||||
enable_prerequisites: "Debes activar un método principal de dos factores antes de generar códigos de respaldo."
|
enable_prerequisites: "Debes activar un método principal de dos factores antes de generar código de copia de seguridad."
|
||||||
codes:
|
codes:
|
||||||
title: "Códigos de respaldo generados"
|
title: "Códigos de copia de seguridad generados"
|
||||||
description: "Cada uno de estos códigos de respaldo se puede usar una única vez. Mantenlos en un lugar seguro pero accesible."
|
description: "Cada uno de estos códigos de copia de seguridad se puede usar una única vez. Mantenlos en un lugar seguro pero accesible."
|
||||||
second_factor:
|
second_factor:
|
||||||
title: "Autenticación de dos factores"
|
title: "Autenticación de dos factores"
|
||||||
enable: "Gestionar la autenticación de dos factores"
|
enable: "Gestionar la autenticación de dos factores"
|
||||||
|
@ -1514,7 +1514,7 @@ es:
|
||||||
extended_description: |
|
extended_description: |
|
||||||
La autenticación de dos factores añade una capa extra de seguridad a tu cuenta al pedirte un código de un solo uso además de tu contraseña. Los códigos se pueden generar en dispositivos <a href="https://www.google.com/search?q=authenticator+apps+for+android" target='_blank'>Android</a> e <a href="https://www.google.com/search?q=authenticator+apps+for+ios">iOS</a>.
|
La autenticación de dos factores añade una capa extra de seguridad a tu cuenta al pedirte un código de un solo uso además de tu contraseña. Los códigos se pueden generar en dispositivos <a href="https://www.google.com/search?q=authenticator+apps+for+android" target='_blank'>Android</a> e <a href="https://www.google.com/search?q=authenticator+apps+for+ios">iOS</a>.
|
||||||
oauth_enabled_warning: "Ten en cuenta que los inicios de sesión con redes sociales y páginas de terceros se desactivarán para tu cuenta una vez que actives la autenticación de dos factores."
|
oauth_enabled_warning: "Ten en cuenta que los inicios de sesión con redes sociales y páginas de terceros se desactivarán para tu cuenta una vez que actives la autenticación de dos factores."
|
||||||
use: "Utilizar la aplicación de autenticación"
|
use: "Usar la aplicación de autenticación"
|
||||||
enforced_with_oauth_notice: "Debes activar la autenticación de dos factores. Solo se te pedirá que la utilices cuando inicies sesión con una contraseña, no con métodos de autenticación externa o de inicio de sesión con red social."
|
enforced_with_oauth_notice: "Debes activar la autenticación de dos factores. Solo se te pedirá que la utilices cuando inicies sesión con una contraseña, no con métodos de autenticación externa o de inicio de sesión con red social."
|
||||||
enforced_notice: "Es obligatorio que actives la autenticación de dos factores antes de acceder al sitio web."
|
enforced_notice: "Es obligatorio que actives la autenticación de dos factores antes de acceder al sitio web."
|
||||||
disable: "Desactivar"
|
disable: "Desactivar"
|
||||||
|
@ -1655,8 +1655,8 @@ es:
|
||||||
confirm_modal_title: "Conectar cuenta de %{provider}"
|
confirm_modal_title: "Conectar cuenta de %{provider}"
|
||||||
confirm_description:
|
confirm_description:
|
||||||
disconnect: "Tu cuenta de %{provider} «%{account_description}» será desenlazada"
|
disconnect: "Tu cuenta de %{provider} «%{account_description}» será desenlazada"
|
||||||
account_specific: "Tu cuenta de %{provider} «%{account_description}» se utilizará para la autenticación."
|
account_specific: "Tu cuenta de %{provider} «%{account_description}» se usará para la autenticación."
|
||||||
generic: "Tu cuenta de %{provider} se utilizará para la autenticación."
|
generic: "Tu cuenta de %{provider} se usará para la autenticación."
|
||||||
activate_account:
|
activate_account:
|
||||||
action: "Haz clic aquí para activar tu cuenta"
|
action: "Haz clic aquí para activar tu cuenta"
|
||||||
already_done: "Lo sentimos, este enlace de confirmación de cuenta ya no es válido. ¿Quizás tu cuenta ya está activa?"
|
already_done: "Lo sentimos, este enlace de confirmación de cuenta ya no es válido. ¿Quizás tu cuenta ya está activa?"
|
||||||
|
@ -1698,7 +1698,7 @@ es:
|
||||||
title: "Código de invitación"
|
title: "Código de invitación"
|
||||||
instructions: "El registro de la cuenta requiere un código de invitación"
|
instructions: "El registro de la cuenta requiere un código de invitación"
|
||||||
auth_tokens:
|
auth_tokens:
|
||||||
title: "Dispositivos utilizados recientemente"
|
title: "Dispositivos usados recientemente"
|
||||||
short_description: "Esta es una lista de los dispositivos que se han conectado recientemente a tu cuenta."
|
short_description: "Esta es una lista de los dispositivos que se han conectado recientemente a tu cuenta."
|
||||||
details: "Detalles"
|
details: "Detalles"
|
||||||
log_out_all: "Cerrar sesión en todos los dispositivos"
|
log_out_all: "Cerrar sesión en todos los dispositivos"
|
||||||
|
@ -2235,7 +2235,7 @@ es:
|
||||||
second_factor_description: "Introduce el código de autenticación desde tu aplicación:"
|
second_factor_description: "Introduce el código de autenticación desde tu aplicación:"
|
||||||
second_factor_backup: "Iniciar sesión utilizando un código de copia de seguridad"
|
second_factor_backup: "Iniciar sesión utilizando un código de copia de seguridad"
|
||||||
second_factor_backup_title: "Código de respaldo de la autenticación de dos factores"
|
second_factor_backup_title: "Código de respaldo de la autenticación de dos factores"
|
||||||
second_factor_backup_description: "Introduce uno de los códigos de respaldo:"
|
second_factor_backup_description: "Introduce uno de los códigos de copia de seguridad:"
|
||||||
second_factor: "Iniciar sesión utilizando la aplicación de autenticación"
|
second_factor: "Iniciar sesión utilizando la aplicación de autenticación"
|
||||||
security_key_description: "Cuando tengas tu clave de seguridad física o tu dispositivo móvil compatible preparado, presiona el botón de autenticar con clave de seguridad que se encuentra debajo."
|
security_key_description: "Cuando tengas tu clave de seguridad física o tu dispositivo móvil compatible preparado, presiona el botón de autenticar con clave de seguridad que se encuentra debajo."
|
||||||
security_key_alternative: "Probar de otra manera"
|
security_key_alternative: "Probar de otra manera"
|
||||||
|
@ -2794,13 +2794,13 @@ es:
|
||||||
aria_label: Filtrar por etiqueta
|
aria_label: Filtrar por etiqueta
|
||||||
filters:
|
filters:
|
||||||
label: Solo temas/publicaciones que…
|
label: Solo temas/publicaciones que…
|
||||||
title: coincide el título únicamente
|
title: Coincide solo en el título
|
||||||
likes: me han gustado
|
likes: me han gustado
|
||||||
posted: he publicado en ellos
|
posted: he publicado en ellos
|
||||||
created: Creado por mi
|
created: Creado por mi
|
||||||
watching: estoy vigilando
|
watching: estoy vigilando
|
||||||
tracking: estoy siguiendo
|
tracking: estoy siguiendo
|
||||||
private: en mis mensajes
|
private: En mis mensajes
|
||||||
bookmarks: he guardado
|
bookmarks: he guardado
|
||||||
first: son la primera publicación
|
first: son la primera publicación
|
||||||
pinned: están anclados
|
pinned: están anclados
|
||||||
|
@ -4047,7 +4047,6 @@ es:
|
||||||
with_topics: "%{filter} temas"
|
with_topics: "%{filter} temas"
|
||||||
with_category: "%{filter} Foro de %{category}"
|
with_category: "%{filter} Foro de %{category}"
|
||||||
filter:
|
filter:
|
||||||
title: "Filtrar"
|
|
||||||
button:
|
button:
|
||||||
label: "Filtrar"
|
label: "Filtrar"
|
||||||
latest:
|
latest:
|
||||||
|
@ -4326,8 +4325,8 @@ es:
|
||||||
one: 'Esta etiqueta pertence al grupo: «%{tag_groups}»'
|
one: 'Esta etiqueta pertence al grupo: «%{tag_groups}»'
|
||||||
other: "Esta etiqueta pertence a estos grupos: %{tag_groups}."
|
other: "Esta etiqueta pertence a estos grupos: %{tag_groups}."
|
||||||
category_restrictions:
|
category_restrictions:
|
||||||
one: "Solo se puede utilizar en esta categoría:"
|
one: "Solo se puede usar en esta categoría:"
|
||||||
other: "Solo se puede utilizar en estas categorías:"
|
other: "Solo se puede usar en estas categorías:"
|
||||||
edit_synonyms: "Editar sinónimos"
|
edit_synonyms: "Editar sinónimos"
|
||||||
add_synonyms_label: "Añadir sinónimos:"
|
add_synonyms_label: "Añadir sinónimos:"
|
||||||
add_synonyms: "Añadir"
|
add_synonyms: "Añadir"
|
||||||
|
@ -5151,7 +5150,7 @@ es:
|
||||||
reviewable_created: "El elemento revisable está listo"
|
reviewable_created: "El elemento revisable está listo"
|
||||||
reviewable_updated: "Se ha actualizado el elemento revisable"
|
reviewable_updated: "Se ha actualizado el elemento revisable"
|
||||||
user_badge_event:
|
user_badge_event:
|
||||||
group_name: "Eventos de Medalla"
|
group_name: "Eventos de medalla"
|
||||||
user_badge_granted: "Se ha concedido la medalla de usuario"
|
user_badge_granted: "Se ha concedido la medalla de usuario"
|
||||||
user_badge_revoked: "Se ha revocado la medalla de usuario"
|
user_badge_revoked: "Se ha revocado la medalla de usuario"
|
||||||
like_event:
|
like_event:
|
||||||
|
@ -6417,7 +6416,7 @@ es:
|
||||||
|
|
||||||
<p><b>¡Esto no se puede deshacer!</b></p>
|
<p><b>¡Esto no se puede deshacer!</b></p>
|
||||||
|
|
||||||
<p>Para continuar escribe: <code>%{text}</code></p>
|
<p>Para continuar, escribe: <code>%{text}</code></p>
|
||||||
text: "eliminar publicaciones de @%{username}"
|
text: "eliminar publicaciones de @%{username}"
|
||||||
delete: "Eliminar publicaciones de @%{username}"
|
delete: "Eliminar publicaciones de @%{username}"
|
||||||
cancel: "Cancelar"
|
cancel: "Cancelar"
|
||||||
|
@ -6742,10 +6741,11 @@ es:
|
||||||
granted_badges: Medallas concedidas
|
granted_badges: Medallas concedidas
|
||||||
grant: Conceder
|
grant: Conceder
|
||||||
no_user_badges: "%{name} no ha recibido ninguna medalla."
|
no_user_badges: "%{name} no ha recibido ninguna medalla."
|
||||||
no_badges: No hay medallas que puedan ser concedidas.
|
no_badges: No hay medallas que se puedan conceder.
|
||||||
none_selected: "Selecciona una medalla para empezar"
|
none_selected: "Selecciona una medalla para empezar"
|
||||||
allow_title: Permitir que se use la medalla como título
|
allow_title: Permitir que se use la medalla como título
|
||||||
multiple_grant: Se puede conceder varias veces
|
multiple_grant: Se puede conceder varias veces
|
||||||
|
visibility_heading: Visibilidad
|
||||||
listable: Mostrar medalla en la página pública de medallas
|
listable: Mostrar medalla en la página pública de medallas
|
||||||
enabled: activado
|
enabled: activado
|
||||||
disabled: desactivado
|
disabled: desactivado
|
||||||
|
@ -6788,8 +6788,8 @@ es:
|
||||||
with_post_time: <span class="username">%{username}</span> por la publicación en %{link} a las <span class="time">%{time}</span>
|
with_post_time: <span class="username">%{username}</span> por la publicación en %{link} a las <span class="time">%{time}</span>
|
||||||
with_time: <span class="username">%{username}</span> a las <span class="time">%{time}</span>
|
with_time: <span class="username">%{username}</span> a las <span class="time">%{time}</span>
|
||||||
badge_intro:
|
badge_intro:
|
||||||
title: "Elige una insignia o crea una nueva"
|
title: "Elige una medalla o crea una nueva"
|
||||||
description: "Empieza seleccionando una insignia existente para personalizarla, o crea una insignia totalmente nueva"
|
description: "Empieza seleccionando una medalla existente para personalizarla, o crea una medalla totalmente nueva"
|
||||||
mass_award:
|
mass_award:
|
||||||
title: Conceder en masa
|
title: Conceder en masa
|
||||||
description: Concede la misma medalla a muchos usuarios a la vez.
|
description: Concede la misma medalla a muchos usuarios a la vez.
|
||||||
|
|
|
@ -2362,7 +2362,6 @@ et:
|
||||||
with_topics: "%{filter} teemat"
|
with_topics: "%{filter} teemat"
|
||||||
with_category: "%{filter} %{category} teemat"
|
with_category: "%{filter} %{category} teemat"
|
||||||
filter:
|
filter:
|
||||||
title: "Filter"
|
|
||||||
button:
|
button:
|
||||||
label: "Filter"
|
label: "Filter"
|
||||||
latest:
|
latest:
|
||||||
|
@ -3685,6 +3684,7 @@ et:
|
||||||
none_selected: "Vali märgis, et alustada"
|
none_selected: "Vali märgis, et alustada"
|
||||||
allow_title: Luba märgise kasutamist tiitlina
|
allow_title: Luba märgise kasutamist tiitlina
|
||||||
multiple_grant: Võib olla määratud mitmeid kordi
|
multiple_grant: Võib olla määratud mitmeid kordi
|
||||||
|
visibility_heading: Nähtavus
|
||||||
listable: Näita märgist avalike märgiste lehel
|
listable: Näita märgist avalike märgiste lehel
|
||||||
enabled: sisse lülitatud
|
enabled: sisse lülitatud
|
||||||
disabled: välja lülitatud
|
disabled: välja lülitatud
|
||||||
|
|
|
@ -753,12 +753,17 @@ fa_IR:
|
||||||
prefill:
|
prefill:
|
||||||
title: "از قبل پر کردن با تنظیمات برای:"
|
title: "از قبل پر کردن با تنظیمات برای:"
|
||||||
gmail: "جیمیل"
|
gmail: "جیمیل"
|
||||||
|
outlook: "Outlook.com"
|
||||||
|
office365: "مایکروسافت ۳۶۵"
|
||||||
ssl_modes:
|
ssl_modes:
|
||||||
none: "هیچ کدام"
|
none: "هیچ کدام"
|
||||||
|
ssl_tls: "SSL/TLS"
|
||||||
|
starttls: "STARTTLS"
|
||||||
credentials:
|
credentials:
|
||||||
title: "اطلاعات ورود"
|
title: "اطلاعات ورود"
|
||||||
smtp_server: "سرور SMTP"
|
smtp_server: "سرور SMTP"
|
||||||
smtp_port: "درگاه SMTP"
|
smtp_port: "درگاه SMTP"
|
||||||
|
smtp_ssl_mode: "حالت SSL"
|
||||||
imap_server: "سرور IMAP"
|
imap_server: "سرور IMAP"
|
||||||
imap_port: "درگاه IMAP"
|
imap_port: "درگاه IMAP"
|
||||||
imap_ssl: "از SSL برای IMAP استفاده کنید"
|
imap_ssl: "از SSL برای IMAP استفاده کنید"
|
||||||
|
@ -3134,7 +3139,6 @@ fa_IR:
|
||||||
with_topics: "%{filter} موضوعات"
|
with_topics: "%{filter} موضوعات"
|
||||||
with_category: "%{filter} %{category} موضوعات"
|
with_category: "%{filter} %{category} موضوعات"
|
||||||
filter:
|
filter:
|
||||||
title: "فیلتر"
|
|
||||||
button:
|
button:
|
||||||
label: "فیلتر"
|
label: "فیلتر"
|
||||||
latest:
|
latest:
|
||||||
|
@ -4748,6 +4752,7 @@ fa_IR:
|
||||||
none_selected: "برای شروع یک نشان رو انتخاب کنید"
|
none_selected: "برای شروع یک نشان رو انتخاب کنید"
|
||||||
allow_title: اجازهی استفاده نشان برای عنوان
|
allow_title: اجازهی استفاده نشان برای عنوان
|
||||||
multiple_grant: نمی توان چندین بار اعطا کرد
|
multiple_grant: نمی توان چندین بار اعطا کرد
|
||||||
|
visibility_heading: قابل مشاهده
|
||||||
listable: نشان دادن نشان در صفحه نشانهای عمومی
|
listable: نشان دادن نشان در صفحه نشانهای عمومی
|
||||||
enabled: فعال شده
|
enabled: فعال شده
|
||||||
disabled: غیرفعال شده
|
disabled: غیرفعال شده
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue