Merge branch 'main' into a-pnpm
# Conflicts: # .github/workflows/licenses.yml # .github/workflows/linting.yml # .github/workflows/tests.yml # app/assets/javascripts/discourse-common/package.json # app/assets/javascripts/discourse/lib/dialog-holder/yarn.lock # app/assets/javascripts/discourse/package.json # app/assets/javascripts/yarn.lock # lib/tasks/assets.rake # yarn.lock
This commit is contained in:
commit
5b5a4e1fe2
@ -1,10 +1,8 @@
|
||||
app/assets/javascripts/browser-update.js
|
||||
app/assets/javascripts/locales/i18n.js
|
||||
app/assets/javascripts/ember-addons/
|
||||
app/assets/javascripts/discourse/lib/autosize.js
|
||||
lib/javascripts/locale/
|
||||
lib/javascripts/messageformat.js
|
||||
lib/highlight_js/
|
||||
lib/javascripts/messageformat-lookup.js
|
||||
lib/pretty_text/
|
||||
plugins/**/lib/javascripts/locale
|
||||
public/
|
||||
|
||||
2
.github/workflows/licenses.yml
vendored
2
.github/workflows/licenses.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Get pnpm cache directory
|
||||
id: node-cache-dir
|
||||
run: echo "::set-output name=dir::$(pnpm store path --silent)"
|
||||
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Node cache
|
||||
uses: actions/cache@v3
|
||||
|
||||
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Get pnpm cache directory
|
||||
id: node-cache-dir
|
||||
run: echo "::set-output name=dir::$(pnpm store path --silent)"
|
||||
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Node cache
|
||||
uses: actions/cache@v3
|
||||
|
||||
10
.github/workflows/tests.yml
vendored
10
.github/workflows/tests.yml
vendored
@ -41,8 +41,6 @@ jobs:
|
||||
target: plugins
|
||||
- build_type: frontend
|
||||
target: core # Handled by core_frontend_tests job (below)
|
||||
- build_type: system
|
||||
target: plugins # Enable once at least 1 plugin has system tests
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -83,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Get pnpm cache directory
|
||||
id: node-cache-dir
|
||||
run: echo "::set-output name=dir::$(pnpm store path --silent)"
|
||||
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Node cache
|
||||
uses: actions/cache@v3
|
||||
@ -178,7 +176,7 @@ jobs:
|
||||
|
||||
- name: Plugin System Tests
|
||||
if: matrix.build_type == 'system' && matrix.target == 'plugins'
|
||||
run: bin/system_rspec plugins/*/spec/system
|
||||
run: LOAD_PLUGINS=1 bin/system_rspec plugins/*/spec/system
|
||||
|
||||
- name: Upload failed system test screenshots
|
||||
uses: actions/upload-artifact@v3
|
||||
@ -223,7 +221,7 @@ jobs:
|
||||
TESTEM_FIREFOX_PATH: ${{ (matrix.browser == 'Firefox Evergreen') && '/opt/firefox-evergreen/firefox' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@ -234,7 +232,7 @@ jobs:
|
||||
|
||||
- name: Get pnpm cache directory
|
||||
id: node-cache-dir
|
||||
run: echo "::set-output name=dir::$(pnpm store path --silent)"
|
||||
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Node cache
|
||||
uses: actions/cache@v3
|
||||
|
||||
24
Gemfile.lock
24
Gemfile.lock
@ -89,7 +89,7 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
byebug (11.1.3)
|
||||
capybara (3.37.1)
|
||||
capybara (3.38.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
@ -145,17 +145,17 @@ GEM
|
||||
sprockets (>= 3.3, < 4.1)
|
||||
ember-source (2.18.2)
|
||||
erubi (1.11.0)
|
||||
excon (0.93.1)
|
||||
excon (0.94.0)
|
||||
execjs (2.8.1)
|
||||
exifr (1.3.10)
|
||||
fabrication (2.30.0)
|
||||
faker (2.23.0)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
fakeweb (1.3.0)
|
||||
faraday (2.6.0)
|
||||
faraday (2.7.0)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-net_http (3.0.1)
|
||||
faraday-net_http (3.0.2)
|
||||
faraday-retry (2.0.0)
|
||||
faraday (~> 2.0)
|
||||
fast_blank (1.0.1)
|
||||
@ -185,7 +185,7 @@ GEM
|
||||
image_size (3.2.0)
|
||||
in_threads (1.6.0)
|
||||
jmespath (1.6.1)
|
||||
jquery-rails (4.5.0)
|
||||
jquery-rails (4.5.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
@ -239,7 +239,7 @@ GEM
|
||||
mini_suffix (0.3.3)
|
||||
ffi (~> 1.9)
|
||||
minitest (5.16.3)
|
||||
mocha (2.0.1)
|
||||
mocha (2.0.2)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
msgpack (1.6.0)
|
||||
multi_json (1.15.0)
|
||||
@ -307,7 +307,7 @@ GEM
|
||||
openssl (> 2.0, < 3.1)
|
||||
optimist (3.0.1)
|
||||
parallel (1.22.1)
|
||||
parallel_tests (3.13.0)
|
||||
parallel_tests (4.0.0)
|
||||
parallel
|
||||
parser (3.1.2.1)
|
||||
ast (~> 2.4.1)
|
||||
@ -329,7 +329,7 @@ GEM
|
||||
rack (2.2.4)
|
||||
rack-mini-profiler (3.0.0)
|
||||
rack (>= 1.2.0)
|
||||
rack-protection (3.0.2)
|
||||
rack-protection (3.0.3)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
rack (>= 1.3)
|
||||
@ -371,7 +371,7 @@ GEM
|
||||
rack (>= 1.4)
|
||||
rexml (3.2.5)
|
||||
rinku (2.0.6)
|
||||
rotp (6.2.0)
|
||||
rotp (6.2.1)
|
||||
rqrcode (2.1.2)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
@ -407,7 +407,7 @@ GEM
|
||||
json-schema (>= 2.2, < 4.0)
|
||||
railties (>= 3.1, < 7.1)
|
||||
rspec-core (>= 2.14)
|
||||
rubocop (1.38.0)
|
||||
rubocop (1.39.0)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.1.2.1)
|
||||
@ -443,7 +443,7 @@ GEM
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
selenium-webdriver (4.5.0)
|
||||
selenium-webdriver (4.6.1)
|
||||
childprocess (>= 0.5, < 5.0)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
@ -507,7 +507,7 @@ GEM
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yaml-lint (0.0.10)
|
||||
zeitwerk (2.6.4)
|
||||
zeitwerk (2.6.6)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
|
||||
@ -2,7 +2,7 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import WatchedWord from "admin/models/watched-word";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import { equal, not } from "@ember/object/computed";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
@ -16,7 +16,7 @@ export default Component.extend({
|
||||
showMessage: false,
|
||||
selectedTags: null,
|
||||
isCaseSensitive: false,
|
||||
|
||||
submitDisabled: not("word"),
|
||||
canReplace: equal("actionKey", "replace"),
|
||||
canTag: equal("actionKey", "tag"),
|
||||
canLink: equal("actionKey", "link"),
|
||||
|
||||
@ -6,11 +6,13 @@ import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import { clipboardCopy } from "discourse/lib/utilities";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { or } from "@ember/object/computed";
|
||||
|
||||
export default Controller.extend({
|
||||
dialog: service(),
|
||||
loading: false,
|
||||
filter: null,
|
||||
showSearch: or("model.length", "filter"),
|
||||
|
||||
_debouncedShow() {
|
||||
Permalink.findAll(this.filter).then((result) => {
|
||||
|
||||
@ -1,17 +1,27 @@
|
||||
import { action } from "@ember/object";
|
||||
import Controller from "@ember/controller";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default Controller.extend({
|
||||
@discourseComputed
|
||||
adminRoutes() {
|
||||
router: service(),
|
||||
|
||||
get adminRoutes() {
|
||||
return this.allAdminRoutes.filter((r) => this.routeExists(r.full_location));
|
||||
},
|
||||
|
||||
get brokenAdminRoutes() {
|
||||
return this.allAdminRoutes.filter(
|
||||
(r) => !this.routeExists(r.full_location)
|
||||
);
|
||||
},
|
||||
|
||||
get allAdminRoutes() {
|
||||
return this.model
|
||||
.filter((p) => p?.enabled)
|
||||
.map((p) => {
|
||||
if (p.get("enabled")) {
|
||||
return p.admin_route;
|
||||
}
|
||||
return p.admin_route;
|
||||
})
|
||||
.compact();
|
||||
.filter(Boolean);
|
||||
},
|
||||
|
||||
@action
|
||||
@ -21,4 +31,13 @@ export default Controller.extend({
|
||||
adminDetail.classList.toggle(state);
|
||||
});
|
||||
},
|
||||
|
||||
routeExists(routeName) {
|
||||
try {
|
||||
this.router.urlFor(routeName);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,9 +1,22 @@
|
||||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
adminBackupsLogs: controller(),
|
||||
|
||||
@discourseComputed
|
||||
warningMessage() {
|
||||
// this is never shown here, but we may want to show different
|
||||
// messages in plugins
|
||||
return "";
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
yesLabel() {
|
||||
return "yes_value";
|
||||
},
|
||||
|
||||
actions: {
|
||||
startBackupWithUploads() {
|
||||
this.send("closeModal");
|
||||
|
||||
@ -43,6 +43,7 @@ export default Mixin.create({
|
||||
validationMessage: null,
|
||||
isSecret: oneWay("setting.secret"),
|
||||
setting: null,
|
||||
attributeBindings: ["setting.setting:data-setting"],
|
||||
|
||||
@discourseComputed("buffered.value", "setting.value")
|
||||
dirty(bufferVal, settingVal) {
|
||||
@ -136,6 +137,7 @@ export default Mixin.create({
|
||||
"default_email_mailing_list_mode_frequency",
|
||||
"default_email_previous_replies",
|
||||
"default_email_in_reply_to",
|
||||
"default_hide_profile_and_presence",
|
||||
"default_other_new_topic_duration_minutes",
|
||||
"default_other_auto_track_topics_after_msecs",
|
||||
"default_other_notification_level_when_replying",
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
|
||||
<ComboBox @content={{this.permalinkTypes}} @value={{this.permalinkType}} @onChange={{action (mut this.permalinkType)}} @class="permalink-type" />
|
||||
|
||||
<TextField @value={{this.permalinkTypeValue}} @disabled={{this.formSubmitted}} @placeholderKey={{this.permalinkTypePlaceholder}} @autocorrect="off" @autocapitalize="off" @keyDown={{action "submitFormOnEnter"}} />
|
||||
<TextField @value={{this.permalinkTypeValue}} @disabled={{this.formSubmitted}} @class="permalink-destination" @placeholderKey={{this.permalinkTypePlaceholder}} @autocorrect="off" @autocapitalize="off" @keyDown={{action "submitFormOnEnter"}} />
|
||||
|
||||
<DButton @action={{action "onSubmit"}} @disabled={{this.formSubmitted}} @label="admin.permalink.form.add" />
|
||||
<DButton @action={{action "onSubmit"}} @disabled={{this.formSubmitted}} @class="permalink-add" @label="admin.permalink.form.add" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<DButton @type="submit" @class="btn btn-primary" @action={{action "submit"}} @disabled={{this.formSubmitted}} @label="admin.watched_words.form.add" />
|
||||
<DButton @type="submit" @class="btn btn-primary" @action={{action "submit"}} @disabled={{this.submitDisabled}} @label="admin.watched_words.form.add" />
|
||||
|
||||
{{#if this.showMessage}}
|
||||
<span class="success-message">{{this.message}}</span>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<DModalBody @title="admin.backups.operations.backup.confirm">
|
||||
<DButton @class="btn-primary backup-with-uploads" @action={{action "startBackupWithUploads"}} @label="yes_value" />
|
||||
{{#if this.warningMessage}}
|
||||
<div class="alert alert-warning">{{html-safe this.warningMessage}}</div>
|
||||
{{/if}}
|
||||
<DButton @class="btn-primary backup-with-uploads" @action={{action "startBackupWithUploads"}} @label={{this.yesLabel}} />
|
||||
<DButton @class="backup-no-uploads" @action={{action "startBackupWithoutUploads"}} @label="admin.backups.operations.backup.without_uploads" />
|
||||
<DButton @class="btn-default" @action={{action "cancel"}} @label="no_value" />
|
||||
</DModalBody>
|
||||
|
||||
@ -6,51 +6,58 @@
|
||||
<PermalinkForm @action={{action "recordAdded"}} />
|
||||
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}}>
|
||||
{{#if this.model.length}}
|
||||
<div class="permalink-search">
|
||||
<TextField @value={{this.filter}} @class="url-input" @placeholderKey="admin.permalink.form.filter" @autocorrect="off" @autocapitalize="off" />
|
||||
</div>
|
||||
<table class="admin-logs-table permalinks grid">
|
||||
<thead class="heading-container">
|
||||
<th class="col heading first url">{{i18n "admin.permalink.url"}}</th>
|
||||
<th class="col heading destination">{{i18n "admin.permalink.destination"}}</th>
|
||||
<th class="col heading actions"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.model as |pl|}}
|
||||
<tr class="admin-list-item">
|
||||
<td class="col first url">
|
||||
<FlatButton @title="admin.permalink.copy_to_clipboard" @icon="far-clipboard" @action={{action "copyUrl" pl}} />
|
||||
<span id="admin-permalink-{{pl.id}}" title={{pl.url}}>{{pl.url}}</span>
|
||||
</td>
|
||||
<td class="col destination">
|
||||
{{#if pl.topic_id}}
|
||||
<a href={{pl.topic_url}}>{{pl.topic_title}}</a>
|
||||
{{/if}}
|
||||
{{#if pl.post_id}}
|
||||
<a href={{pl.post_url}}>{{pl.post_topic_title}} #{{pl.post_number}}</a>
|
||||
{{/if}}
|
||||
{{#if pl.category_id}}
|
||||
{{category-link pl.category}}
|
||||
{{/if}}
|
||||
{{#if pl.tag_id}}
|
||||
<a href={{pl.tag_url}}>{{pl.tag_name}}</a>
|
||||
{{/if}}
|
||||
{{#if pl.external_url}}
|
||||
{{#if pl.linkIsExternal}}
|
||||
{{d-icon "external-link-alt"}}
|
||||
<div class="permalink-search">
|
||||
<TextField @value={{this.filter}} @class="url-input" @placeholderKey="admin.permalink.form.filter" @autocorrect="off" @autocapitalize="off" />
|
||||
</div>
|
||||
|
||||
<div class="permalink-results">
|
||||
{{#if this.model.length}}
|
||||
<table class="admin-logs-table permalinks grid">
|
||||
<thead class="heading-container">
|
||||
<th class="col heading first url">{{i18n "admin.permalink.url"}}</th>
|
||||
<th class="col heading destination">{{i18n "admin.permalink.destination"}}</th>
|
||||
<th class="col heading actions"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.model as |pl|}}
|
||||
<tr class="admin-list-item">
|
||||
<td class="col first url">
|
||||
<FlatButton @title="admin.permalink.copy_to_clipboard" @icon="far-clipboard" @action={{action "copyUrl" pl}} />
|
||||
<span id="admin-permalink-{{pl.id}}" title={{pl.url}}>{{pl.url}}</span>
|
||||
</td>
|
||||
<td class="col destination">
|
||||
{{#if pl.topic_id}}
|
||||
<a href={{pl.topic_url}}>{{pl.topic_title}}</a>
|
||||
{{/if}}
|
||||
<a href={{pl.external_url}}>{{pl.external_url}}</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="col action" style="text-align: right;">
|
||||
<DButton @action={{action "destroy"}} @actionParam={{pl}} @icon="far-trash-alt" @class="btn-danger" />
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
{{i18n "search.no_results"}}
|
||||
{{/if}}
|
||||
{{#if pl.post_id}}
|
||||
<a href={{pl.post_url}}>{{pl.post_topic_title}} #{{pl.post_number}}</a>
|
||||
{{/if}}
|
||||
{{#if pl.category_id}}
|
||||
{{category-link pl.category}}
|
||||
{{/if}}
|
||||
{{#if pl.tag_id}}
|
||||
<a href={{pl.tag_url}}>{{pl.tag_name}}</a>
|
||||
{{/if}}
|
||||
{{#if pl.external_url}}
|
||||
{{#if pl.linkIsExternal}}
|
||||
{{d-icon "external-link-alt"}}
|
||||
{{/if}}
|
||||
<a href={{pl.external_url}}>{{pl.external_url}}</a>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="col action" style="text-align: right;">
|
||||
<DButton @action={{action "destroy"}} @actionParam={{pl}} @icon="far-trash-alt" @class="btn-danger" />
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
{{#if this.filter}}
|
||||
<p class="permalink-results__no-result">{{i18n "search.no_results"}}</p>
|
||||
{{else}}
|
||||
<p class="permalink-results__no-permalinks">{{i18n "admin.permalink.no_permalinks"}}</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</ConditionalLoadingSpinner>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.model as |plugin|}}
|
||||
<tr>
|
||||
<tr data-plugin-name={{plugin.name}}>
|
||||
<td>
|
||||
{{#if plugin.is_official}}
|
||||
{{d-icon "check-circle"
|
||||
|
||||
@ -19,5 +19,11 @@
|
||||
</div>
|
||||
|
||||
<div class="admin-detail pull-left mobile-closed">
|
||||
{{#each this.brokenAdminRoutes as |route|}}
|
||||
<div class="alert alert-error">
|
||||
{{i18n "admin.plugins.broken_route" name=(i18n route.label)}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
{{outlet}}
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"paths": {
|
||||
"admin/*": ["./addon/*"],
|
||||
"discourse/*": ["../discourse/app/*"],
|
||||
"discourse/tests/*": ["../discourse/tests/*"],
|
||||
"discourse-common/*": ["../discourse-common/addon/*"],
|
||||
"pretty-text/*": ["../pretty-text/addon/*"],
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
"ember-auto-import": "^2.4.3",
|
||||
"ember-cli-babel": "^7.26.10",
|
||||
"ember-cli-htmlbars": "^6.1.1",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack": "^5.75.0",
|
||||
"xss": "^1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -9,10 +9,18 @@ import { isTesting } from "discourse-common/config/environment";
|
||||
|
||||
export default function () {
|
||||
if (isTesting()) {
|
||||
const lastArgument = arguments[arguments.length - 1];
|
||||
const hasImmediateArgument = typeof lastArgument === "boolean";
|
||||
|
||||
let args = [].slice.call(arguments, 0, hasImmediateArgument ? -2 : -1);
|
||||
|
||||
// Replace the time argument with 10ms
|
||||
let args = [].slice.call(arguments, 0, -1);
|
||||
args.push(10);
|
||||
|
||||
if (hasImmediateArgument) {
|
||||
args.push(lastArgument);
|
||||
}
|
||||
|
||||
return debounce.apply(undefined, args);
|
||||
} else {
|
||||
return debounce(...arguments);
|
||||
|
||||
@ -88,7 +88,7 @@ export function readOnly(target, name, desc) {
|
||||
};
|
||||
}
|
||||
|
||||
export function debounce(delay) {
|
||||
export function debounce(delay, immediate = false) {
|
||||
return function (target, name, descriptor) {
|
||||
return {
|
||||
enumerable: descriptor.enumerable,
|
||||
@ -97,7 +97,13 @@ export function debounce(delay) {
|
||||
initializer() {
|
||||
const originalFunction = descriptor.value;
|
||||
const debounced = function (...args) {
|
||||
return discourseDebounce(this, originalFunction, ...args, delay);
|
||||
return discourseDebounce(
|
||||
this,
|
||||
originalFunction,
|
||||
...args,
|
||||
delay,
|
||||
immediate
|
||||
);
|
||||
};
|
||||
|
||||
return debounced;
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
"ember-resolver": "^8.0.3",
|
||||
"handlebars": "^4.7.0",
|
||||
"truth-helpers": "workspace:*",
|
||||
"webpack": "^5.74.0"
|
||||
"webpack": "^5.75.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
"ember-cli-babel": "^7.26.10",
|
||||
"ember-cli-htmlbars": "^6.1.1",
|
||||
"handlebars": "^4.7.6",
|
||||
"webpack": "^5.74.0"
|
||||
"webpack": "^5.75.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
|
||||
@ -114,18 +114,22 @@ module.exports = {
|
||||
directoryName,
|
||||
"test/javascripts"
|
||||
);
|
||||
const configDirectory = path.resolve(root, directoryName, "config");
|
||||
const hasJs = fs.existsSync(jsDirectory);
|
||||
const hasAdminJs = fs.existsSync(adminJsDirectory);
|
||||
const hasTests = fs.existsSync(testDirectory);
|
||||
const hasConfig = fs.existsSync(configDirectory);
|
||||
return {
|
||||
pluginName,
|
||||
directoryName,
|
||||
jsDirectory,
|
||||
adminJsDirectory,
|
||||
testDirectory,
|
||||
configDirectory,
|
||||
hasJs,
|
||||
hasAdminJs,
|
||||
hasTests,
|
||||
hasConfig,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"paths": {
|
||||
"discourse-widget-hbs/*": ["./addon/*"],
|
||||
"discourse/*": ["../discourse/app/*"],
|
||||
"discourse/tests/*": ["../discourse/tests/*"],
|
||||
"discourse-common/*": ["../discourse-common/addon/*"]
|
||||
}
|
||||
},
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
"ember-cli-babel": "^7.26.10",
|
||||
"ember-cli-htmlbars": "^6.1.1",
|
||||
"handlebars": "^4.7.6",
|
||||
"webpack": "^5.74.0"
|
||||
"webpack": "^5.75.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
|
||||
@ -51,7 +51,7 @@ export default Component.extend({
|
||||
this.set("actions", connectorClass.actions);
|
||||
|
||||
for (const [name, action] of Object.entries(this.actions)) {
|
||||
this.set(name, action);
|
||||
this.set(name, action.bind(this));
|
||||
}
|
||||
|
||||
const merged = buildArgsWithDeprecations(args, deprecatedArgs);
|
||||
|
||||
@ -42,10 +42,12 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed("backupEnabled", "secondFactorMethod")
|
||||
showToggleMethodLink(backupEnabled, secondFactorMethod) {
|
||||
@discourseComputed("backupEnabled", "totpEnabled", "secondFactorMethod")
|
||||
showToggleMethodLink(backupEnabled, totpEnabled, secondFactorMethod) {
|
||||
return (
|
||||
backupEnabled && secondFactorMethod !== SECOND_FACTOR_METHODS.SECURITY_KEY
|
||||
backupEnabled &&
|
||||
totpEnabled &&
|
||||
secondFactorMethod !== SECOND_FACTOR_METHODS.SECURITY_KEY
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{{#if this.shouldDisplay}}
|
||||
<div class="sidebar-section-link-wrapper">
|
||||
<div class="sidebar-section-link-wrapper" {{did-insert this.didInsert this.args}}>
|
||||
{{#if @href}}
|
||||
<a href={{@href}} rel="noopener noreferrer" target="_blank" class={{this.classNames}} title={{@title}}>
|
||||
<Sidebar::SectionLinkPrefix
|
||||
|
||||
@ -7,6 +7,12 @@ export default class SectionLink extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
didInsert(_element, [args]) {
|
||||
if (args.didInsert) {
|
||||
args.didInsert();
|
||||
}
|
||||
}
|
||||
|
||||
get shouldDisplay() {
|
||||
if (this.args.shouldDisplay === undefined) {
|
||||
return true;
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
@hoverAction={{link.hoverAction}}
|
||||
@hoverTitle={{link.hoverTitle}}
|
||||
@currentWhen={{link.currentWhen}}
|
||||
@didInsert={{link.didInsert}}
|
||||
@willDestroy={{link.willDestroy}}
|
||||
@content={{link.text}} />
|
||||
{{/each}}
|
||||
|
||||
@ -238,7 +238,7 @@ const SiteHeaderComponent = MountWidget.extend(
|
||||
this.currentUser.on("status-changed", this, "queueRerender");
|
||||
}
|
||||
|
||||
if (!this.siteSettings.enable_onboarding_popups) {
|
||||
if (!this.siteSettings.enable_user_tips) {
|
||||
if (
|
||||
this.currentUser &&
|
||||
!this.get("currentUser.read_first_notification")
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { alias, and, or } from "@ember/object/computed";
|
||||
import { alias, or } from "@ember/object/computed";
|
||||
import { computed } from "@ember/object";
|
||||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||
import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
|
||||
import { getTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown";
|
||||
|
||||
@ -46,6 +47,11 @@ export default Component.extend({
|
||||
return !isPM || this.canSendPms;
|
||||
},
|
||||
|
||||
@discourseComputed("topic.details.notification_level")
|
||||
showNotificationUserTip(notificationLevel) {
|
||||
return notificationLevel >= NotificationLevels.TRACKING;
|
||||
},
|
||||
|
||||
canSendPms: alias("currentUser.can_send_private_messages"),
|
||||
|
||||
canInviteTo: alias("topic.details.can_invite_to"),
|
||||
@ -54,8 +60,6 @@ export default Component.extend({
|
||||
|
||||
inviteDisabled: or("topic.archived", "topic.closed", "topic.deleted"),
|
||||
|
||||
showEditOnFooter: and("topic.isPrivateMessage", "site.can_tag_pms"),
|
||||
|
||||
@discourseComputed("topic.message_archived")
|
||||
archiveIcon: (archived) => (archived ? "envelope" : "folder"),
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
</LinkTo>
|
||||
</li>
|
||||
|
||||
{{#if this.siteSettings.enable_mentions}}
|
||||
{{#if @siteSettings.enable_mentions}}
|
||||
<li>
|
||||
<LinkTo @route="userNotifications.mentions">
|
||||
{{d-icon "at"}}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
<span {{did-insert this.showUserTip}}></span>
|
||||
35
app/assets/javascripts/discourse/app/components/user-tip.js
Normal file
35
app/assets/javascripts/discourse/app/components/user-tip.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import Component from "@glimmer/component";
|
||||
import { hideUserTip } from "discourse/lib/user-tips";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default class UserTip extends Component {
|
||||
@service currentUser;
|
||||
|
||||
@action
|
||||
showUserTip(element) {
|
||||
if (!this.currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { id, selector, content, placement } = this.args;
|
||||
this.currentUser.showUserTip({
|
||||
id,
|
||||
|
||||
titleText: I18n.t(`user_tips.${id}.title`),
|
||||
contentText: content || I18n.t(`user_tips.${id}.content`),
|
||||
|
||||
reference: selector
|
||||
? element.parentElement.querySelector(selector) || element.parentElement
|
||||
: element,
|
||||
appendTo: element.parentElement,
|
||||
|
||||
placement: placement || "top",
|
||||
});
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
hideUserTip(this.args.id);
|
||||
}
|
||||
}
|
||||
@ -223,21 +223,30 @@ export default Controller.extend(ModalFunctionality, {
|
||||
this.clearFlash();
|
||||
|
||||
if (
|
||||
(result.security_key_enabled || result.totp_enabled) &&
|
||||
(result.security_key_enabled ||
|
||||
result.totp_enabled ||
|
||||
result.backup_enabled) &&
|
||||
!this.secondFactorRequired
|
||||
) {
|
||||
let secondFactorMethod;
|
||||
if (result.security_key_enabled) {
|
||||
secondFactorMethod = SECOND_FACTOR_METHODS.SECURITY_KEY;
|
||||
} else if (result.totp_enabled) {
|
||||
secondFactorMethod = SECOND_FACTOR_METHODS.TOTP;
|
||||
} else {
|
||||
secondFactorMethod = SECOND_FACTOR_METHODS.BACKUP_CODE;
|
||||
}
|
||||
this.setProperties({
|
||||
otherMethodAllowed: result.multiple_second_factor_methods,
|
||||
secondFactorRequired: true,
|
||||
showLoginButtons: false,
|
||||
backupEnabled: result.backup_enabled,
|
||||
showSecondFactor: result.totp_enabled,
|
||||
totpEnabled: result.totp_enabled,
|
||||
showSecondFactor: result.totp_enabled || result.backup_enabled,
|
||||
showSecurityKey: result.security_key_enabled,
|
||||
secondFactorMethod: result.security_key_enabled
|
||||
? SECOND_FACTOR_METHODS.SECURITY_KEY
|
||||
: SECOND_FACTOR_METHODS.TOTP,
|
||||
securityKeyChallenge: result.challenge,
|
||||
securityKeyAllowedCredentialIds: result.allowed_credential_ids,
|
||||
secondFactorMethod,
|
||||
});
|
||||
|
||||
// only need to focus the 2FA input for TOTP
|
||||
|
||||
@ -420,7 +420,7 @@ export default Controller.extend({
|
||||
}
|
||||
},
|
||||
|
||||
resetSeenPopups() {
|
||||
resetSeenUserTips() {
|
||||
this.model.set("skip_new_user_tips", false);
|
||||
this.model.set("seen_popups", null);
|
||||
this.model.set("user_option.skip_new_user_tips", false);
|
||||
|
||||
@ -564,8 +564,8 @@ export default Controller.extend(bufferedProperty("model"), {
|
||||
return this.get("model.details").removeAllowedGroup(group);
|
||||
},
|
||||
|
||||
deleteTopic() {
|
||||
this.deleteTopic();
|
||||
deleteTopic(opts = {}) {
|
||||
this.deleteTopic(opts);
|
||||
},
|
||||
|
||||
// Archive a PM (as opposed to archiving a topic)
|
||||
@ -611,6 +611,10 @@ export default Controller.extend(bufferedProperty("model"), {
|
||||
|
||||
// Post related methods
|
||||
replyToPost(post) {
|
||||
if (this.currentUser) {
|
||||
this.currentUser.hideUserTipForever("post_menu");
|
||||
}
|
||||
|
||||
const composerController = this.composer;
|
||||
const topic = post ? post.get("topic") : this.model;
|
||||
const quoteState = this.quoteState;
|
||||
@ -1522,7 +1526,11 @@ export default Controller.extend(bufferedProperty("model"), {
|
||||
this.model.recover();
|
||||
},
|
||||
|
||||
deleteTopic(opts) {
|
||||
deleteTopic(opts = {}) {
|
||||
if (opts.force_destroy) {
|
||||
return this.model.destroy(this.currentUser, opts);
|
||||
}
|
||||
|
||||
if (
|
||||
this.model.views > this.siteSettings.min_topic_views_for_delete_confirm
|
||||
) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { addComposerUploadPreProcessor } from "discourse/components/composer-editor";
|
||||
import UppyMediaOptimization from "discourse/lib/uppy-media-optimization-plugin";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
export default {
|
||||
name: "register-media-optimization-upload-processor",
|
||||
@ -11,10 +12,15 @@ export default {
|
||||
UppyMediaOptimization,
|
||||
({ isMobileDevice }) => {
|
||||
return {
|
||||
optimizeFn: (data, opts) =>
|
||||
container
|
||||
optimizeFn: (data, opts) => {
|
||||
if (container.isDestroyed || container.isDestroying) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return container
|
||||
.lookup("service:media-optimization-worker")
|
||||
.optimizeImage(data, opts),
|
||||
.optimizeImage(data, opts);
|
||||
},
|
||||
runParallel: !isMobileDevice,
|
||||
};
|
||||
}
|
||||
|
||||
@ -157,7 +157,6 @@ export function excerpt(cooked, length) {
|
||||
resultLength += element.textContent.length;
|
||||
}
|
||||
} else if (element.tagName === "A") {
|
||||
element.innerHTML = element.innerText;
|
||||
result += element.outerHTML;
|
||||
resultLength += element.innerText.length;
|
||||
} else if (element.tagName === "IMG") {
|
||||
|
||||
@ -117,15 +117,6 @@ const DiscourseURL = EmberObject.extend({
|
||||
|
||||
if (!holder) {
|
||||
selector = holderId;
|
||||
|
||||
if (
|
||||
document.getElementsByClassName(
|
||||
`topic-post-visited-line post-${postNumber - 1}`
|
||||
)?.length === 1
|
||||
) {
|
||||
selector = ".small-action.topic-post-visited";
|
||||
}
|
||||
|
||||
holder = document.querySelector(selector);
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@ import tippy from "tippy.js";
|
||||
const instances = {};
|
||||
const queue = [];
|
||||
|
||||
export function showPopup(options) {
|
||||
hidePopup(options.id);
|
||||
export function showUserTip(options) {
|
||||
hideUserTip(options.id);
|
||||
|
||||
if (!options.reference) {
|
||||
return;
|
||||
@ -23,13 +23,14 @@ export function showPopup(options) {
|
||||
showOnCreate: true,
|
||||
hideOnClick: false,
|
||||
trigger: "manual",
|
||||
theme: "d-onboarding",
|
||||
theme: "user-tips",
|
||||
|
||||
// It must be interactive to make buttons work.
|
||||
interactive: true,
|
||||
|
||||
arrow: iconHTML("tippy-rounded-arrow"),
|
||||
placement: options.placement,
|
||||
appendTo: options.appendTo,
|
||||
|
||||
// It often happens for the reference element to be rerendered. In this
|
||||
// case, tippy must be rerendered too. Having an animation means that the
|
||||
@ -40,17 +41,15 @@ export function showPopup(options) {
|
||||
allowHTML: true,
|
||||
|
||||
content: `
|
||||
<div class='onboarding-popup-container'>
|
||||
<div class='onboarding-popup-title'>${escape(options.titleText)}</div>
|
||||
<div class='onboarding-popup-content'>${escape(
|
||||
options.contentText
|
||||
)}</div>
|
||||
<div class='onboarding-popup-buttons'>
|
||||
<div class='user-tip-container'>
|
||||
<div class='user-tip-title'>${escape(options.titleText)}</div>
|
||||
<div class='user-tip-content'>${escape(options.contentText)}</div>
|
||||
<div class='user-tip-buttons'>
|
||||
<button class="btn btn-primary btn-dismiss">${escape(
|
||||
options.primaryBtnText || I18n.t("popup.primary")
|
||||
options.primaryBtnText || I18n.t("user_tips.primary")
|
||||
)}</button>
|
||||
<button class="btn btn-flat btn-text btn-dismiss-all">${escape(
|
||||
options.secondaryBtnText || I18n.t("popup.secondary")
|
||||
options.secondaryBtnText || I18n.t("user_tips.secondary")
|
||||
)}</button>
|
||||
</div>
|
||||
</div>`,
|
||||
@ -73,12 +72,21 @@ export function showPopup(options) {
|
||||
});
|
||||
}
|
||||
|
||||
export function hidePopup(popupId) {
|
||||
const instance = instances[popupId];
|
||||
export function hideUserTip(userTipId) {
|
||||
const instance = instances[userTipId];
|
||||
if (instance && !instance.state.isDestroyed) {
|
||||
instance.destroy();
|
||||
}
|
||||
delete instances[popupId];
|
||||
delete instances[userTipId];
|
||||
|
||||
const index = queue.findIndex((userTip) => userTip.id === userTipId);
|
||||
if (index > -1) {
|
||||
queue.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function hideAllUserTips() {
|
||||
Object.keys(instances).forEach(hideUserTip);
|
||||
}
|
||||
|
||||
function addToQueue(options) {
|
||||
@ -92,9 +100,9 @@ function addToQueue(options) {
|
||||
queue.push(options);
|
||||
}
|
||||
|
||||
export function showNextPopup() {
|
||||
export function showNextUserTip() {
|
||||
const options = queue.shift();
|
||||
if (options) {
|
||||
showPopup(options);
|
||||
showUserTip(options);
|
||||
}
|
||||
}
|
||||
@ -280,10 +280,12 @@ export default Mixin.create({
|
||||
// note: we DO NOT use afterRender here cause _positionCard may
|
||||
// run afterwards, if we allowed this to happen the usercard
|
||||
// may be offscreen and we may scroll all the way to it on focus
|
||||
discourseLater(() => {
|
||||
const firstLink = this.element.querySelector("a");
|
||||
firstLink && firstLink.focus();
|
||||
}, 350);
|
||||
if (event.pointerId === -1) {
|
||||
discourseLater(() => {
|
||||
const firstLink = this.element.querySelector("a");
|
||||
firstLink && firstLink.focus();
|
||||
}, 350);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -143,10 +143,15 @@ const Composer = RestModel.extend({
|
||||
const oldCategoryId = this._categoryId;
|
||||
|
||||
if (isEmpty(categoryId)) {
|
||||
// Set General as the default category
|
||||
const generalCategoryId = this.siteSettings.general_category_id;
|
||||
// Check if there is a default composer category to set
|
||||
const defaultComposerCategoryId = parseInt(
|
||||
this.siteSettings.default_composer_category,
|
||||
10
|
||||
);
|
||||
categoryId =
|
||||
generalCategoryId && generalCategoryId > 0 ? generalCategoryId : null;
|
||||
defaultComposerCategoryId && defaultComposerCategoryId > 0
|
||||
? defaultComposerCategoryId
|
||||
: null;
|
||||
}
|
||||
this._categoryId = categoryId;
|
||||
|
||||
|
||||
@ -207,7 +207,7 @@ const TopicTrackingState = EmberObject.extend({
|
||||
}
|
||||
}
|
||||
|
||||
if (filterTag && !data.payload.tags.includes(filterTag)) {
|
||||
if (filterTag && !data.payload.tags?.includes(filterTag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -43,7 +43,12 @@ import Evented from "@ember/object/evented";
|
||||
import { cancel } from "@ember/runloop";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
import { hidePopup, showNextPopup, showPopup } from "discourse/lib/popup";
|
||||
import {
|
||||
hideAllUserTips,
|
||||
hideUserTip,
|
||||
showNextUserTip,
|
||||
showUserTip,
|
||||
} from "discourse/lib/user-tips";
|
||||
|
||||
export const SECOND_FACTOR_METHODS = {
|
||||
TOTP: 1,
|
||||
@ -1090,57 +1095,70 @@ const User = RestModel.extend({
|
||||
return [...trackedTags, ...watchedTags, ...watchingFirstPostTags];
|
||||
},
|
||||
|
||||
showPopup(options) {
|
||||
const popupTypes = Site.currentProp("onboarding_popup_types");
|
||||
if (!popupTypes[options.id]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Cannot display popup with type =", options.id);
|
||||
showUserTip(options) {
|
||||
const userTips = Site.currentProp("user_tips");
|
||||
if (!userTips || this.skip_new_user_tips) {
|
||||
return;
|
||||
}
|
||||
|
||||
const seenPopups = this.seen_popups || [];
|
||||
if (seenPopups.includes(popupTypes[options.id])) {
|
||||
if (!userTips[options.id]) {
|
||||
if (!isTesting()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Cannot show user tip with type =", options.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
showPopup({
|
||||
const seenUserTips = this.seen_popups || [];
|
||||
if (
|
||||
seenUserTips.includes(-1) ||
|
||||
seenUserTips.includes(userTips[options.id])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
showUserTip({
|
||||
...options,
|
||||
onDismiss: () => this.hidePopupForever(options.id),
|
||||
onDismissAll: () => this.hidePopupForever(),
|
||||
onDismiss: () => this.hideUserTipForever(options.id),
|
||||
onDismissAll: () => this.hideUserTipForever(),
|
||||
});
|
||||
},
|
||||
|
||||
hidePopupForever(popupId) {
|
||||
// Empty popupId means all popups.
|
||||
const popupTypes = Site.currentProp("onboarding_popup_types");
|
||||
if (popupId && !popupTypes[popupId]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Cannot hide popup with type =", popupId);
|
||||
hideUserTipForever(userTipId) {
|
||||
const userTips = Site.currentProp("user_tips");
|
||||
if (!userTips || this.skip_new_user_tips) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide any shown popups.
|
||||
let seenPopups = this.seen_popups || [];
|
||||
if (popupId) {
|
||||
hidePopup(popupId);
|
||||
if (!seenPopups.includes(popupTypes[popupId])) {
|
||||
seenPopups.push(popupTypes[popupId]);
|
||||
}
|
||||
} else {
|
||||
Object.keys(popupTypes).forEach(hidePopup);
|
||||
seenPopups = Object.values(popupTypes);
|
||||
// Empty userTipId means all user tips.
|
||||
if (userTipId && !userTips[userTipId]) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Cannot hide user tip with type =", userTipId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show next popup in queue.
|
||||
showNextPopup();
|
||||
// Hide any shown user tips.
|
||||
let seenUserTips = this.seen_popups || [];
|
||||
if (userTipId) {
|
||||
hideUserTip(userTipId);
|
||||
if (!seenUserTips.includes(userTips[userTipId])) {
|
||||
seenUserTips.push(userTips[userTipId]);
|
||||
}
|
||||
} else {
|
||||
hideAllUserTips();
|
||||
seenUserTips = [-1];
|
||||
}
|
||||
|
||||
// Save seen popups on the server.
|
||||
// Show next user tip in queue.
|
||||
showNextUserTip();
|
||||
|
||||
// Save seen user tips on the server.
|
||||
if (!this.user_option) {
|
||||
this.set("user_option", {});
|
||||
}
|
||||
this.set("seen_popups", seenPopups);
|
||||
this.set("user_option.seen_popups", seenPopups);
|
||||
if (popupId) {
|
||||
this.set("seen_popups", seenUserTips);
|
||||
this.set("user_option.seen_popups", seenUserTips);
|
||||
if (userTipId) {
|
||||
return this.save(["seen_popups"]);
|
||||
} else {
|
||||
this.set("skip_new_user_tips", true);
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
|
||||
<p><TagChooser @tags={{this.tags}} @categoryId={{this.categoryId}} /></p>
|
||||
|
||||
<DButton @action={{this.action}} @disabled={{this.emptyTags}} @label={{concat "topics.bulk." this.label}} />
|
||||
<DButton @action={{action this.action}} @disabled={{this.emptyTags}} @label={{concat "topics.bulk." this.label}} />
|
||||
|
||||
@ -10,8 +10,7 @@
|
||||
|
||||
{{#unless this.showPositionInput}}
|
||||
<section class="field position-disabled">
|
||||
{{i18n "category.position_disabled"}}
|
||||
<a href={{get-url "/admin/site_settings/category/basic"}}>{{i18n "category.position_disabled_click"}}</a>
|
||||
{{html-safe (i18n "category.position_disabled" url=(get-url "/admin/site_settings/category/all_results?filter=fixed_category_positions"))}}
|
||||
</section>
|
||||
{{/unless}}
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
<div id="suggested-topics" class="suggested-topics" role="complementary" aria-labelledby="suggested-topics-title">
|
||||
<UserTip @id="suggested_topics" />
|
||||
|
||||
<h3 id="suggested-topics-title" class="suggested-topics-title">
|
||||
{{i18n this.suggestedTitleLabel}}
|
||||
</h3>
|
||||
|
||||
@ -29,6 +29,10 @@
|
||||
<PinnedButton @pinned={{this.topic.pinned}} @topic={{this.topic}} />
|
||||
|
||||
{{#if this.showNotificationsButton}}
|
||||
{{#if this.showNotificationUserTip}}
|
||||
<UserTip @id="topic_notification_levels" @selector=".notifications-button" />
|
||||
{{/if}}
|
||||
|
||||
<TopicNotificationsButton @notificationLevel={{this.topic.details.notification_level}} @topic={{this.topic}} />
|
||||
{{/if}}
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
<div class="caps-lock-warning {{unless this.capsLockOn "hidden"}}">{{d-icon "exclamation-triangle"}} {{i18n "login.caps_lock_warning"}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<SecondFactorForm @secondFactorMethod={{this.secondFactorMethod}} @secondFactorToken={{this.secondFactorToken}} @class={{this.secondFactorClass}} @backupEnabled={{this.backupEnabled}} @isLogin={{true}}>
|
||||
<SecondFactorForm @secondFactorMethod={{this.secondFactorMethod}} @secondFactorToken={{this.secondFactorToken}} @class={{this.secondFactorClass}} @backupEnabled={{this.backupEnabled}} @totpEnabled={{this.totpEnabled}} @isLogin={{true}}>
|
||||
{{#if this.showSecurityKey}}
|
||||
<SecurityKeyForm @allowedCredentialIds={{this.securityKeyAllowedCredentialIds}} @challenge={{this.securityKeyChallenge}} @showSecurityKey={{this.showSecurityKey}} @showSecondFactor={{this.showSecondFactor}} @secondFactorMethod={{this.secondFactorMethod}} @otherMethodAllowed={{this.otherMethodAllowed}} @action={{action "authenticateSecurityKey"}}>
|
||||
</SecurityKeyForm>
|
||||
|
||||
@ -117,8 +117,8 @@
|
||||
<ComboBox @valueProperty="value" @content={{this.titleCountModes}} @value={{this.model.user_option.title_count_mode}} @id="user-title-count-mode" @onChange={{action (mut this.model.user_option.title_count_mode)}} />
|
||||
</div>
|
||||
<PreferenceCheckbox @labelKey="user.skip_new_user_tips.description" @checked={{this.model.user_option.skip_new_user_tips}} @class="pref-new-user-tips" />
|
||||
{{#if this.site.onboarding_popup_types}}
|
||||
<DButton @class="pref-reset-seen-popups" @action={{action "resetSeenPopups"}}>{{i18n "user.reset_seen_popups"}}</DButton>
|
||||
{{#if this.site.user_tips}}
|
||||
<DButton @class="pref-reset-seen-user-tips" @action={{action "resetSeenUserTips"}}>{{i18n "user.reset_seen_user_tips"}}</DButton>
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
import { logSearchLinkClick } from "discourse/lib/search";
|
||||
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { hidePopup } from "discourse/lib/popup";
|
||||
import { hideUserTip } from "discourse/lib/user-tips";
|
||||
|
||||
let _extraHeaderIcons = [];
|
||||
|
||||
@ -88,7 +88,7 @@ createWidget("header-notifications", {
|
||||
const count = unread + reviewables;
|
||||
if (count > 0) {
|
||||
if (this._shouldHighlightAvatar()) {
|
||||
if (this.siteSettings.enable_onboarding_popups) {
|
||||
if (this.siteSettings.enable_user_tips) {
|
||||
contents.push(h("span.ring"));
|
||||
} else {
|
||||
this._addAvatarHighlight(contents);
|
||||
@ -124,7 +124,7 @@ createWidget("header-notifications", {
|
||||
const unreadHighPriority = user.unread_high_priority_notifications;
|
||||
if (!!unreadHighPriority) {
|
||||
if (this._shouldHighlightAvatar()) {
|
||||
if (this.siteSettings.enable_onboarding_popups) {
|
||||
if (this.siteSettings.enable_user_tips) {
|
||||
contents.push(h("span.ring"));
|
||||
} else {
|
||||
this._addAvatarHighlight(contents);
|
||||
@ -198,32 +198,33 @@ createWidget("header-notifications", {
|
||||
didRenderWidget() {
|
||||
if (
|
||||
!this.currentUser ||
|
||||
!this.siteSettings.enable_onboarding_popups ||
|
||||
!this.siteSettings.enable_user_tips ||
|
||||
!this._shouldHighlightAvatar()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentUser.showPopup({
|
||||
this.currentUser.showUserTip({
|
||||
id: "first_notification",
|
||||
|
||||
titleText: I18n.t("popup.first_notification.title"),
|
||||
contentText: I18n.t("popup.first_notification.content"),
|
||||
titleText: I18n.t("user_tips.first_notification.title"),
|
||||
contentText: I18n.t("user_tips.first_notification.content"),
|
||||
|
||||
reference: document
|
||||
.querySelector(".badge-notification")
|
||||
.querySelector(".d-header .badge-notification")
|
||||
?.parentElement?.querySelector(".avatar"),
|
||||
appendTo: document.querySelector(".d-header .panel"),
|
||||
|
||||
placement: "bottom-end",
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
hidePopup("first_notification");
|
||||
hideUserTip("first_notification");
|
||||
},
|
||||
|
||||
willRerenderWidget() {
|
||||
hidePopup("first_notification");
|
||||
hideUserTip("first_notification");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -711,6 +711,10 @@ export default createWidget("post-menu", {
|
||||
},
|
||||
|
||||
showMoreActions() {
|
||||
if (this.currentUser) {
|
||||
this.currentUser.hideUserTipForever("post_menu");
|
||||
}
|
||||
|
||||
this.state.collapsed = false;
|
||||
const likesPromise = !this.state.likedUsers.length
|
||||
? this.getWhoLiked()
|
||||
@ -730,6 +734,8 @@ export default createWidget("post-menu", {
|
||||
keyValueStore &&
|
||||
keyValueStore.set({ key: "likedPostId", value: attrs.id });
|
||||
return this.sendWidgetAction("showLogin");
|
||||
} else {
|
||||
this.currentUser.hideUserTipForever("post_menu");
|
||||
}
|
||||
|
||||
if (this.capabilities.canVibrate && !isTesting()) {
|
||||
|
||||
@ -25,6 +25,7 @@ import { transformBasicPost } from "discourse/lib/transform-post";
|
||||
import autoGroupFlairForUser from "discourse/lib/avatar-flair";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { nativeShare } from "discourse/lib/pwa-utils";
|
||||
import { hideUserTip } from "discourse/lib/user-tips";
|
||||
|
||||
function transformWithCallbacks(post) {
|
||||
let transformed = transformBasicPost(post);
|
||||
@ -593,6 +594,10 @@ createWidget("post-contents", {
|
||||
},
|
||||
|
||||
share() {
|
||||
if (this.currentUser) {
|
||||
this.currentUser.hideUserTipForever("post_menu");
|
||||
}
|
||||
|
||||
const post = this.findAncestorModel();
|
||||
nativeShare(this.capabilities, { url: post.shareUrl }).catch(() => {
|
||||
const topic = post.topic;
|
||||
@ -928,4 +933,34 @@ export default createWidget("post", {
|
||||
kvs.set({ key: "lastWarnedLikes", value: Date.now() });
|
||||
}
|
||||
},
|
||||
|
||||
didRenderWidget() {
|
||||
if (!this.currentUser || !this.siteSettings.enable_user_tips) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reference = document.querySelector(
|
||||
".post-controls .actions .show-more-actions"
|
||||
);
|
||||
|
||||
this.currentUser.showUserTip({
|
||||
id: "post_menu",
|
||||
|
||||
titleText: I18n.t("user_tips.post_menu.title"),
|
||||
contentText: I18n.t("user_tips.post_menu.content"),
|
||||
|
||||
reference,
|
||||
appendTo: reference?.closest(".post-controls"),
|
||||
|
||||
placement: "top",
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
hideUserTip("post_menu");
|
||||
},
|
||||
|
||||
willRerenderWidget() {
|
||||
hideUserTip("post_menu");
|
||||
},
|
||||
});
|
||||
|
||||
@ -367,7 +367,7 @@ export default createWidget("search-menu", {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.which === 65 /* a */) {
|
||||
if (e.key === "A") {
|
||||
if (document.activeElement?.classList.contains("search-link")) {
|
||||
if (document.querySelector("#reply-control.open")) {
|
||||
// add a link and focus composer
|
||||
@ -388,8 +388,8 @@ export default createWidget("search-menu", {
|
||||
}
|
||||
}
|
||||
|
||||
const up = e.which === 38;
|
||||
const down = e.which === 40;
|
||||
const up = e.key === "ArrowUp";
|
||||
const down = e.key === "ArrowDown";
|
||||
if (up || down) {
|
||||
let focused = document.activeElement.closest(".search-menu")
|
||||
? document.activeElement
|
||||
@ -443,7 +443,7 @@ export default createWidget("search-menu", {
|
||||
}
|
||||
|
||||
const searchInput = document.querySelector("#search-term");
|
||||
if (e.which === 13 && e.target === searchInput) {
|
||||
if (e.key === "Enter" && e.target === searchInput) {
|
||||
const recentEnterHit =
|
||||
this.state._lastEnterTimestamp &&
|
||||
Date.now() - this.state._lastEnterTimestamp < SECOND_ENTER_MAX_DELAY;
|
||||
@ -463,7 +463,7 @@ export default createWidget("search-menu", {
|
||||
this.state._lastEnterTimestamp = Date.now();
|
||||
}
|
||||
|
||||
if (e.target === searchInput && e.which === 8 /* backspace */) {
|
||||
if (e.target === searchInput && e.key === "Backspace") {
|
||||
if (!searchInput.value) {
|
||||
this.clearTopicContext();
|
||||
this.clearPMInboxContext();
|
||||
|
||||
@ -9,7 +9,7 @@ import discourseLater from "discourse-common/lib/later";
|
||||
import { relativeAge } from "discourse/lib/formatter";
|
||||
import renderTags from "discourse/lib/render-tags";
|
||||
import renderTopicFeaturedLink from "discourse/lib/render-topic-featured-link";
|
||||
import { hidePopup } from "discourse/lib/popup";
|
||||
import { hideUserTip } from "discourse/lib/user-tips";
|
||||
|
||||
const SCROLLER_HEIGHT = 50;
|
||||
const LAST_READ_HEIGHT = 20;
|
||||
@ -601,27 +601,28 @@ export default createWidget("topic-timeline", {
|
||||
},
|
||||
|
||||
didRenderWidget() {
|
||||
if (!this.currentUser || !this.siteSettings.enable_onboarding_popups) {
|
||||
if (!this.currentUser || !this.siteSettings.enable_user_tips) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentUser.showPopup({
|
||||
this.currentUser.showUserTip({
|
||||
id: "topic_timeline",
|
||||
|
||||
titleText: I18n.t("popup.topic_timeline.title"),
|
||||
contentText: I18n.t("popup.topic_timeline.content"),
|
||||
titleText: I18n.t("user_tips.topic_timeline.title"),
|
||||
contentText: I18n.t("user_tips.topic_timeline.content"),
|
||||
|
||||
reference: document.querySelector("div.timeline-scrollarea-wrapper"),
|
||||
appendTo: document.querySelector("div.topic-timeline"),
|
||||
|
||||
placement: "left",
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
hidePopup("topic_timeline");
|
||||
hideUserTip("topic_timeline");
|
||||
},
|
||||
|
||||
willRerenderWidget() {
|
||||
hidePopup("topic_timeline");
|
||||
hideUserTip("topic_timeline");
|
||||
},
|
||||
});
|
||||
|
||||
@ -6,6 +6,7 @@ const mergeTrees = require("broccoli-merge-trees");
|
||||
const concat = require("broccoli-concat");
|
||||
const prettyTextEngine = require("./lib/pretty-text-engine");
|
||||
const { createI18nTree } = require("./lib/translation-plugin");
|
||||
const { parsePluginClientSettings } = require("./lib/site-settings-plugin");
|
||||
const discourseScss = require("./lib/discourse-scss");
|
||||
const generateScriptsTree = require("./lib/scripts");
|
||||
const funnel = require("broccoli-funnel");
|
||||
@ -57,6 +58,13 @@ module.exports = function (defaults) {
|
||||
autoImport: {
|
||||
forbidEval: true,
|
||||
insertScriptsAt: "ember-auto-import-scripts",
|
||||
webpack: {
|
||||
// Workarounds for https://github.com/ef4/ember-auto-import/issues/519 and https://github.com/ef4/ember-auto-import/issues/478
|
||||
devtool: isProduction ? false : "source-map", // Sourcemaps contain reference to the ephemeral broccoli cache dir, which changes on every deploy
|
||||
optimization: {
|
||||
moduleIds: "size", // Consistent module references https://github.com/ef4/ember-auto-import/issues/478#issuecomment-1000526638
|
||||
},
|
||||
},
|
||||
},
|
||||
fingerprint: {
|
||||
// Handled by Rails asset pipeline
|
||||
@ -161,6 +169,7 @@ module.exports = function (defaults) {
|
||||
|
||||
return mergeTrees([
|
||||
createI18nTree(discourseRoot, vendorJs),
|
||||
parsePluginClientSettings(discourseRoot, vendorJs, app),
|
||||
app.toTree(),
|
||||
funnel(`${discourseRoot}/public/javascripts`, { destDir: "javascripts" }),
|
||||
funnel(`${vendorJs}/highlightjs`, {
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"discourse/*": ["./app/*"],
|
||||
"discourse/tests/*": ["./tests/*"],
|
||||
"discourse-common/*": ["../discourse-common/addon/*"],
|
||||
"pretty-text/*": ["../pretty-text/addon/*"],
|
||||
"select-kit/*": ["../select-kit/addon/*"],
|
||||
|
||||
96
app/assets/javascripts/discourse/lib/site-settings-plugin.js
Normal file
96
app/assets/javascripts/discourse/lib/site-settings-plugin.js
Normal file
@ -0,0 +1,96 @@
|
||||
const Plugin = require("broccoli-plugin");
|
||||
const Yaml = require("js-yaml");
|
||||
const fs = require("fs");
|
||||
const concat = require("broccoli-concat");
|
||||
const mergeTrees = require("broccoli-merge-trees");
|
||||
const deepmerge = require("deepmerge");
|
||||
const glob = require("glob");
|
||||
const { shouldLoadPluginTestJs } = require("discourse/lib/plugin-js");
|
||||
|
||||
let built = false;
|
||||
|
||||
class SiteSettingsPlugin extends Plugin {
|
||||
constructor(inputNodes, inputFile, options) {
|
||||
super(inputNodes, {
|
||||
...options,
|
||||
persistentOutput: true,
|
||||
});
|
||||
}
|
||||
|
||||
build() {
|
||||
if (built) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed = {};
|
||||
|
||||
this.inputPaths.forEach((path) => {
|
||||
let inputFile;
|
||||
if (path.includes("plugins")) {
|
||||
inputFile = "settings.yml";
|
||||
} else {
|
||||
inputFile = "site_settings.yml";
|
||||
}
|
||||
const file = path + "/" + inputFile;
|
||||
let yaml;
|
||||
try {
|
||||
yaml = fs.readFileSync(file, { encoding: "UTF-8" });
|
||||
} catch (err) {
|
||||
// the plugin does not have a config file, go to the next file
|
||||
return;
|
||||
}
|
||||
const loaded = Yaml.load(yaml, { json: true });
|
||||
parsed = deepmerge(parsed, loaded);
|
||||
});
|
||||
|
||||
let clientSettings = {};
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (const [category, settings] of Object.entries(parsed)) {
|
||||
for (const [setting, details] of Object.entries(settings)) {
|
||||
if (details.client) {
|
||||
clientSettings[setting] = details.default;
|
||||
}
|
||||
}
|
||||
}
|
||||
const contents = `var CLIENT_SITE_SETTINGS_WITH_DEFAULTS = ${JSON.stringify(
|
||||
clientSettings
|
||||
)}`;
|
||||
|
||||
fs.writeFileSync(`${this.outputPath}/` + "settings_out.js", contents);
|
||||
built = true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function siteSettingsPlugin(...params) {
|
||||
return new SiteSettingsPlugin(...params);
|
||||
};
|
||||
|
||||
module.exports.parsePluginClientSettings = function (
|
||||
discourseRoot,
|
||||
vendorJs,
|
||||
app
|
||||
) {
|
||||
let settings = [discourseRoot + "/config"];
|
||||
|
||||
if (shouldLoadPluginTestJs()) {
|
||||
const pluginInfos = app.project
|
||||
.findAddonByName("discourse-plugins")
|
||||
.pluginInfos();
|
||||
pluginInfos.forEach(({ hasConfig, configDirectory }) => {
|
||||
if (hasConfig) {
|
||||
settings = settings.concat(glob.sync(configDirectory));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const loadedSettings = new SiteSettingsPlugin(settings, "site_settings.yml");
|
||||
|
||||
return concat(mergeTrees([loadedSettings]), {
|
||||
inputFiles: [],
|
||||
headerFiles: [],
|
||||
footerFiles: [],
|
||||
outputFile: `assets/test-site-settings.js`,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.SiteSettingsPlugin = SiteSettingsPlugin;
|
||||
@ -16,8 +16,8 @@
|
||||
"test": "ember test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/standalone": "^7.20.1",
|
||||
"@babel/core": "^7.20.2",
|
||||
"@babel/standalone": "^7.20.4",
|
||||
"@discourse/backburner.js": "^2.7.1-0",
|
||||
"@discourse/itsatrap": "^2.0.10",
|
||||
"@ember/jquery": "^2.0.0",
|
||||
@ -80,19 +80,18 @@
|
||||
"markdown-it": "^13.0.1",
|
||||
"message-bus-client": "^4.2.0",
|
||||
"messageformat": "0.1.5",
|
||||
"node-fetch": "^2.6.6",
|
||||
"node-fetch": "^2.6.7",
|
||||
"pretender": "^3.4.7",
|
||||
"pretty-text": "workspace:*",
|
||||
"qunit": "^2.19.3",
|
||||
"qunit-dom": "^2.0.0",
|
||||
"sass": "^1.55.0",
|
||||
"sass": "^1.56.1",
|
||||
"select-kit": "workspace:*",
|
||||
"sinon": "^14.0.1",
|
||||
"sinon": "^14.0.2",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "5.10.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"virtual-dom": "^2.1.1",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack": "^5.75.0",
|
||||
"wizard": "workspace:*",
|
||||
"xss": "^1.0.14"
|
||||
},
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { fillIn, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
|
||||
acceptance("Admin - Permalinks", function (needs) {
|
||||
const startingData = [
|
||||
{
|
||||
id: 38,
|
||||
url: "c/feature/announcements",
|
||||
topic_id: null,
|
||||
topic_title: null,
|
||||
topic_url: null,
|
||||
post_id: null,
|
||||
post_url: null,
|
||||
post_number: null,
|
||||
post_topic_title: null,
|
||||
category_id: 67,
|
||||
category_name: "announcements",
|
||||
category_url: "/c/announcements/67",
|
||||
external_url: null,
|
||||
tag_id: null,
|
||||
tag_name: null,
|
||||
tag_url: null,
|
||||
},
|
||||
];
|
||||
|
||||
needs.user();
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/admin/permalinks.json", (response) => {
|
||||
const result =
|
||||
response.queryParams.filter !== "feature" ? [] : startingData;
|
||||
return helper.response(200, result);
|
||||
});
|
||||
});
|
||||
|
||||
test("search permalinks with result", async function (assert) {
|
||||
await visit("/admin/customize/permalinks");
|
||||
await fillIn(".permalink-search input", "feature");
|
||||
assert.ok(
|
||||
exists(".permalink-results span[title='c/feature/announcements']"),
|
||||
"permalink is found after search"
|
||||
);
|
||||
});
|
||||
|
||||
test("search permalinks without results", async function (assert) {
|
||||
await visit("/admin/customize/permalinks");
|
||||
await fillIn(".permalink-search input", "garboogle");
|
||||
|
||||
assert.ok(
|
||||
exists(".permalink-results__no-result"),
|
||||
"no results message shown"
|
||||
);
|
||||
|
||||
assert.ok(exists(".permalink-search"), "search input still visible");
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,51 @@
|
||||
import {
|
||||
acceptance,
|
||||
exists,
|
||||
query,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
|
||||
acceptance("Admin - Plugins", function (needs) {
|
||||
needs.user();
|
||||
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/admin/plugins", () =>
|
||||
helper.response({
|
||||
plugins: [
|
||||
{
|
||||
id: "some-test-plugin",
|
||||
name: "some-test-plugin",
|
||||
about: "Plugin description",
|
||||
version: "0.1",
|
||||
url: "https://example.com",
|
||||
admin_route: {
|
||||
location: "testlocation",
|
||||
label: "test.plugin.label",
|
||||
full_location: "adminPlugins.testlocation",
|
||||
},
|
||||
enabled: true,
|
||||
enabled_setting: "testplugin_enabled",
|
||||
has_settings: true,
|
||||
is_official: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("shows plugin list", async function (assert) {
|
||||
await visit("/admin/plugins");
|
||||
const table = query("table.admin-plugins");
|
||||
assert.strictEqual(
|
||||
table.querySelector("tr .plugin-name .name").innerText,
|
||||
"some-test-plugin",
|
||||
"displays the plugin in the table"
|
||||
);
|
||||
|
||||
assert.true(
|
||||
exists(".admin-plugins .admin-detail .alert-error"),
|
||||
"displays an error for unknown routes"
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -75,11 +75,21 @@ acceptance("Admin - Watched Words", function (needs) {
|
||||
|
||||
test("add case-sensitive words", async function (assert) {
|
||||
await visit("/admin/customize/watched_words/action/block");
|
||||
|
||||
const submitButton = query(".watched-word-form button");
|
||||
assert.strictEqual(
|
||||
submitButton.disabled,
|
||||
true,
|
||||
"Add button is disabled by default"
|
||||
);
|
||||
await click(".show-words-checkbox");
|
||||
await fillIn(".watched-word-form input", "Discourse");
|
||||
await click(".case-sensitivity-checkbox");
|
||||
await click(".watched-word-form button");
|
||||
assert.strictEqual(
|
||||
submitButton.disabled,
|
||||
false,
|
||||
"Add button should no longer be disabled after input is filled"
|
||||
);
|
||||
await click(submitButton);
|
||||
|
||||
assert
|
||||
.dom(".watched-words-list .watched-word")
|
||||
@ -87,7 +97,7 @@ acceptance("Admin - Watched Words", function (needs) {
|
||||
|
||||
await fillIn(".watched-word-form input", "discourse");
|
||||
await click(".case-sensitivity-checkbox");
|
||||
await click(".watched-word-form button");
|
||||
await click(submitButton);
|
||||
|
||||
assert
|
||||
.dom(".watched-words-list .watched-word")
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { click, currentURL, settled, visit } from "@ember/test-helpers";
|
||||
import { set } from "@ember/object";
|
||||
|
||||
acceptance("Bootstrap Mode Notice", function (needs) {
|
||||
needs.user();
|
||||
needs.user({ admin: true });
|
||||
needs.site({ wizard_required: true });
|
||||
needs.settings({
|
||||
bootstrap_mode_enabled: true,
|
||||
@ -36,8 +35,8 @@ acceptance("Bootstrap Mode Notice", function (needs) {
|
||||
"it transitions to the wizard page"
|
||||
);
|
||||
|
||||
this.siteSettings.bootstrap_mode_enabled = false;
|
||||
await visit("/");
|
||||
set(this.siteSettings, "bootstrap_mode_enabled", false);
|
||||
await settled();
|
||||
assert.ok(
|
||||
!exists(".bootstrap-mode-notice"),
|
||||
|
||||
@ -24,7 +24,7 @@ acceptance("Composer Actions", function (needs) {
|
||||
});
|
||||
needs.settings({
|
||||
prioritize_username_in_ux: true,
|
||||
display_name_on_post: false,
|
||||
display_name_on_posts: false,
|
||||
enable_whispers: true,
|
||||
});
|
||||
needs.site({ can_tag_topics: true });
|
||||
@ -489,7 +489,7 @@ acceptance("Prioritize Username", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
prioritize_username_in_ux: true,
|
||||
display_name_on_post: false,
|
||||
display_name_on_posts: false,
|
||||
});
|
||||
|
||||
test("Reply to post use username", async function (assert) {
|
||||
@ -517,7 +517,7 @@ acceptance("Prioritize Full Name", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
prioritize_username_in_ux: false,
|
||||
display_name_on_post: true,
|
||||
display_name_on_posts: true,
|
||||
});
|
||||
|
||||
test("Reply to post use full name", async function (assert) {
|
||||
@ -555,7 +555,7 @@ acceptance("Prioritizing Name fall back", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
prioritize_username_in_ux: false,
|
||||
display_name_on_post: true,
|
||||
display_name_on_posts: true,
|
||||
});
|
||||
|
||||
test("Quotes fall back to username if name is not present", async function (assert) {
|
||||
|
||||
@ -18,7 +18,7 @@ acceptance("Composer - editor mentions", function (needs) {
|
||||
};
|
||||
|
||||
needs.user();
|
||||
needs.settings({ enable_mentions: true });
|
||||
needs.settings({ enable_mentions: true, allow_uncategorized_topics: true });
|
||||
|
||||
needs.hooks.afterEach(() => {
|
||||
if (clock) {
|
||||
|
||||
@ -11,7 +11,7 @@ import { test } from "qunit";
|
||||
|
||||
acceptance("Composer - Image Preview", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({ enable_whispers: true });
|
||||
needs.settings({ enable_whispers: true, allow_uncategorized_topics: true });
|
||||
needs.site({ can_tag_topics: true });
|
||||
needs.pretender((server, helper) => {
|
||||
server.post("/uploads/lookup-urls", () => {
|
||||
|
||||
@ -18,6 +18,7 @@ acceptance("Composer - Tags", function (needs) {
|
||||
});
|
||||
});
|
||||
needs.site({ can_tag_topics: true });
|
||||
needs.settings({ allow_uncategorized_topics: true });
|
||||
|
||||
test("staff bypass tag validation rule", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
@ -41,6 +41,7 @@ acceptance("Composer", function (needs) {
|
||||
needs.settings({
|
||||
enable_whispers: true,
|
||||
general_category_id: 1,
|
||||
default_composer_category: 1,
|
||||
});
|
||||
needs.site({
|
||||
can_tag_topics: true,
|
||||
@ -90,7 +91,7 @@ acceptance("Composer", function (needs) {
|
||||
test("Composer is opened", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
// Check that General category is selected
|
||||
// Check that the default category is selected
|
||||
assert.strictEqual(selectKit(".category-chooser").header().value(), "1");
|
||||
|
||||
assert.strictEqual(
|
||||
@ -302,7 +303,7 @@ acceptance("Composer", function (needs) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
await fillIn("#reply-title", "This title doesn't matter");
|
||||
await fillIn(".d-editor-input", "custom message");
|
||||
await fillIn(".d-editor-input", "custom message that is a good length");
|
||||
await click("#reply-control button.create");
|
||||
|
||||
assert.strictEqual(
|
||||
@ -1107,6 +1108,7 @@ acceptance("Composer - Customizations", function (needs) {
|
||||
|
||||
acceptance("Composer - Focus Open and Closed", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({ allow_uncategorized_topics: true });
|
||||
|
||||
test("Focusing a composer which is not open with create topic", async function (assert) {
|
||||
await visit("/t/internationalization-localization/280");
|
||||
@ -1200,3 +1202,109 @@ acceptance("Composer - Focus Open and Closed", function (needs) {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Default Composer Category tests
|
||||
acceptance("Composer - Default category", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
general_category_id: 1,
|
||||
default_composer_category: 2,
|
||||
});
|
||||
needs.site({
|
||||
categories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "General",
|
||||
slug: "general",
|
||||
permission: 1,
|
||||
ltopic_template: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "test too",
|
||||
slug: "test-too",
|
||||
permission: 1,
|
||||
topic_template: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test("Default category is selected over general category", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
assert.strictEqual(selectKit(".category-chooser").header().value(), "2");
|
||||
assert.strictEqual(
|
||||
selectKit(".category-chooser").header().name(),
|
||||
"test too"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Composer - Uncategorized category", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
general_category_id: -1, // For sites that never had this seeded
|
||||
default_composer_category: -1, // For sites that never had this seeded
|
||||
allow_uncategorized_topics: true,
|
||||
});
|
||||
needs.site({
|
||||
categories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "General",
|
||||
slug: "general",
|
||||
permission: 1,
|
||||
ltopic_template: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "test too",
|
||||
slug: "test-too",
|
||||
permission: 1,
|
||||
topic_template: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test("Uncategorized category is selected", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
assert.strictEqual(selectKit(".category-chooser").header().value(), null);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Composer - default category not set", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({
|
||||
default_composer_category: "",
|
||||
});
|
||||
needs.site({
|
||||
categories: [
|
||||
{
|
||||
id: 1,
|
||||
name: "General",
|
||||
slug: "general",
|
||||
permission: 1,
|
||||
ltopic_template: null,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "test too",
|
||||
slug: "test-too",
|
||||
permission: 1,
|
||||
topic_template: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test("Nothing is selected", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
assert.strictEqual(selectKit(".category-chooser").header().value(), null);
|
||||
assert.strictEqual(
|
||||
selectKit(".category-chooser").header().name(),
|
||||
"category…"
|
||||
);
|
||||
});
|
||||
});
|
||||
// END: Default Composer Category tests
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
acceptance,
|
||||
chromeTest,
|
||||
createFile,
|
||||
loggedInUser,
|
||||
paste,
|
||||
@ -81,6 +82,7 @@ acceptance("Uppy Composer Attachment - Upload Placeholder", function (needs) {
|
||||
needs.settings({
|
||||
simultaneous_uploads: 2,
|
||||
enable_rich_text_paste: true,
|
||||
allow_uncategorized_topics: true,
|
||||
});
|
||||
needs.hooks.afterEach(() => {
|
||||
uploadNumber = 1;
|
||||
@ -113,34 +115,39 @@ acceptance("Uppy Composer Attachment - Upload Placeholder", function (needs) {
|
||||
appEvents.trigger("composer:add-files", image);
|
||||
});
|
||||
|
||||
test("should handle adding one file for upload then adding another when the first is still in progress", async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
await fillIn(".d-editor-input", "The image:\n");
|
||||
const appEvents = loggedInUser().appEvents;
|
||||
const done = assert.async();
|
||||
// TODO: On Firefox Evergreen this often fails, because the order of uploads
|
||||
// in markdown is reversed
|
||||
chromeTest(
|
||||
"handles adding one file for upload then adding another when the first is still in progress",
|
||||
async function (assert) {
|
||||
await visit("/");
|
||||
await click("#create-topic");
|
||||
await fillIn(".d-editor-input", "The image:\n");
|
||||
const appEvents = loggedInUser().appEvents;
|
||||
const done = assert.async();
|
||||
|
||||
appEvents.on("composer:all-uploads-complete", async () => {
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
query(".d-editor-input").value,
|
||||
"The image:\n\n\n"
|
||||
);
|
||||
done();
|
||||
});
|
||||
appEvents.on("composer:all-uploads-complete", async () => {
|
||||
await settled();
|
||||
assert.strictEqual(
|
||||
query(".d-editor-input").value,
|
||||
"The image:\n\n\n"
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
let image2Added = false;
|
||||
appEvents.on("composer:upload-started", () => {
|
||||
if (!image2Added) {
|
||||
appEvents.trigger("composer:add-files", image2);
|
||||
image2Added = true;
|
||||
}
|
||||
});
|
||||
let image2Added = false;
|
||||
appEvents.on("composer:upload-started", () => {
|
||||
if (!image2Added) {
|
||||
appEvents.trigger("composer:add-files", image2);
|
||||
image2Added = true;
|
||||
}
|
||||
});
|
||||
|
||||
const image1 = createFile("avatar.png");
|
||||
const image2 = createFile("avatar2.png");
|
||||
appEvents.trigger("composer:add-files", image1);
|
||||
});
|
||||
const image1 = createFile("avatar.png");
|
||||
const image2 = createFile("avatar2.png");
|
||||
appEvents.trigger("composer:add-files", image1);
|
||||
}
|
||||
);
|
||||
|
||||
test("should handle placeholders correctly even if the OS rewrites ellipses", async function (assert) {
|
||||
const execCommand = document.execCommand;
|
||||
@ -487,6 +494,7 @@ acceptance("Uppy Composer Attachment - Upload Error", function (needs) {
|
||||
});
|
||||
needs.settings({
|
||||
simultaneous_uploads: 2,
|
||||
allow_uncategorized_topics: true,
|
||||
});
|
||||
|
||||
test("should show an error message for the failed upload", async function (assert) {
|
||||
@ -527,6 +535,7 @@ acceptance("Uppy Composer Attachment - Upload Handler", function (needs) {
|
||||
needs.pretender(pretender);
|
||||
needs.settings({
|
||||
simultaneous_uploads: 2,
|
||||
allow_uncategorized_topics: true,
|
||||
});
|
||||
needs.hooks.beforeEach(() => {
|
||||
withPluginApi("0.8.14", (api) => {
|
||||
|
||||
@ -21,7 +21,7 @@ acceptance("Emoji", function (needs) {
|
||||
assert.strictEqual(
|
||||
normalizeHtml(query(".d-editor-preview").innerHTML.trim()),
|
||||
normalizeHtml(
|
||||
`<p>this is an emoji <img src="/images/emoji/google_classic/blonde_woman.png?v=${v}" title=":blonde_woman:" class="emoji" alt=":blonde_woman:" loading="lazy" width="20" height="20" style="aspect-ratio: 20 / 20;"></p>`
|
||||
`<p>this is an emoji <img src="/images/emoji/twitter/blonde_woman.png?v=${v}" title=":blonde_woman:" class="emoji" alt=":blonde_woman:" loading="lazy" width="20" height="20" style="aspect-ratio: 20 / 20;"></p>`
|
||||
)
|
||||
);
|
||||
});
|
||||
@ -36,7 +36,7 @@ acceptance("Emoji", function (needs) {
|
||||
assert.strictEqual(
|
||||
normalizeHtml(query(".d-editor-preview").innerHTML.trim()),
|
||||
normalizeHtml(
|
||||
`<p>this is an emoji <img src="/images/emoji/google_classic/blonde_woman/5.png?v=${v}" title=":blonde_woman:t5:" class="emoji" alt=":blonde_woman:t5:" loading="lazy" width="20" height="20" style="aspect-ratio: 20 / 20;"></p>`
|
||||
`<p>this is an emoji <img src="/images/emoji/twitter/blonde_woman/5.png?v=${v}" title=":blonde_woman:t5:" class="emoji" alt=":blonde_woman:t5:" loading="lazy" width="20" height="20" style="aspect-ratio: 20 / 20;"></p>`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
@ -94,7 +94,7 @@ acceptance("Group Requests", function (needs) {
|
||||
query(".group-members tr:first-child td:nth-child(1)")
|
||||
.innerText.trim()
|
||||
.replace(/\s+/g, " "),
|
||||
"Robin Ward eviltrout"
|
||||
"eviltrout Robin Ward"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query(".group-members tr:first-child td:nth-child(3)").innerText.trim(),
|
||||
|
||||
@ -123,7 +123,7 @@ acceptance("Invite accept", function (needs) {
|
||||
"submit is disabled because password is not filled"
|
||||
);
|
||||
|
||||
await fillIn("#new-account-password", "top$ecret");
|
||||
await fillIn("#new-account-password", "top$ecretzz");
|
||||
assert.notOk(
|
||||
exists(".invites-show .btn-primary:disabled"),
|
||||
"submit is enabled"
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import {
|
||||
acceptance,
|
||||
exists,
|
||||
query,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
import I18n from "I18n";
|
||||
import { test } from "qunit";
|
||||
import { visit } from "@ember/test-helpers";
|
||||
@ -10,15 +6,6 @@ import { visit } from "@ember/test-helpers";
|
||||
acceptance("Personal Message", function (needs) {
|
||||
needs.user();
|
||||
|
||||
test("footer edit button", async function (assert) {
|
||||
await visit("/t/pm-for-testing/12");
|
||||
|
||||
assert.ok(
|
||||
!exists(".edit-message"),
|
||||
"does not show edit first post button on footer by default"
|
||||
);
|
||||
});
|
||||
|
||||
test("suggested messages", async function (assert) {
|
||||
await visit("/t/pm-for-testing/12");
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ acceptance("Plugin Outlet - Connector Class", function (needs) {
|
||||
extraConnectorClass("user-profile-primary/hello", {
|
||||
actions: {
|
||||
sayHello() {
|
||||
this.set("hello", "hello!");
|
||||
this.set("hello", `${this.hello || ""}hello!`);
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -53,6 +53,7 @@ acceptance("Plugin Outlet - Connector Class", function (needs) {
|
||||
`${PREFIX}/user-profile-primary/hello`
|
||||
] = hbs`<span class='hello-username'>{{model.username}}</span>
|
||||
<button class='say-hello' {{on "click" (action "sayHello")}}></button>
|
||||
<button class='say-hello-using-this' {{on "click" this.sayHello}}></button>
|
||||
<span class='hello-result'>{{hello}}</span>`;
|
||||
Ember.TEMPLATES[
|
||||
`${PREFIX}/user-profile-primary/hi`
|
||||
@ -87,6 +88,12 @@ acceptance("Plugin Outlet - Connector Class", function (needs) {
|
||||
"hello!",
|
||||
"actions delegate properly"
|
||||
);
|
||||
await click(".say-hello-using-this");
|
||||
assert.strictEqual(
|
||||
query(".hello-result").innerText,
|
||||
"hello!hello!",
|
||||
"actions are made available on `this` and are bound correctly"
|
||||
);
|
||||
|
||||
await click(".say-hi");
|
||||
assert.strictEqual(
|
||||
|
||||
@ -128,6 +128,9 @@ acceptance("User Preferences", function (needs) {
|
||||
await categorySelector.fillInFilter("faq");
|
||||
await savePreferences();
|
||||
|
||||
this.siteSettings.tagging_enabled = false;
|
||||
await visit("/");
|
||||
await visit("/u/eviltrout/preferences");
|
||||
assert.ok(
|
||||
!exists(".preferences-nav .nav-tags a"),
|
||||
"tags tab isn't there when tags are disabled"
|
||||
|
||||
@ -4,7 +4,13 @@ import {
|
||||
exists,
|
||||
query,
|
||||
} from "discourse/tests/helpers/qunit-helpers";
|
||||
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
||||
import {
|
||||
click,
|
||||
fillIn,
|
||||
settled,
|
||||
triggerKeyEvent,
|
||||
visit,
|
||||
} from "@ember/test-helpers";
|
||||
import I18n from "I18n";
|
||||
import searchFixtures from "discourse/tests/fixtures/search-fixtures";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
@ -333,7 +339,10 @@ acceptance("Search - Anonymous", function (needs) {
|
||||
|
||||
acceptance("Search - Authenticated", function (needs) {
|
||||
needs.user();
|
||||
needs.settings({ log_search_queries: true });
|
||||
needs.settings({
|
||||
log_search_queries: true,
|
||||
allow_uncategorized_topics: true,
|
||||
});
|
||||
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/search/query", (request) => {
|
||||
@ -476,6 +485,7 @@ acceptance("Search - Authenticated", function (needs) {
|
||||
"href"
|
||||
);
|
||||
await triggerKeyEvent(".search-menu", "keydown", "A");
|
||||
await settled();
|
||||
|
||||
assert.strictEqual(
|
||||
query("#reply-control textarea").value,
|
||||
|
||||
@ -44,6 +44,12 @@ const RESPONSES = {
|
||||
security_keys_enabled: true,
|
||||
allowed_methods: [BACKUP_CODE],
|
||||
},
|
||||
ok010010: {
|
||||
totp_enabled: false,
|
||||
backup_enabled: true,
|
||||
security_keys_enabled: false,
|
||||
allowed_methods: [BACKUP_CODE],
|
||||
},
|
||||
};
|
||||
|
||||
Object.keys(RESPONSES).forEach((k) => {
|
||||
@ -178,6 +184,14 @@ acceptance("Second Factor Auth Page", function (needs) {
|
||||
!exists(".toggle-second-factor-method"),
|
||||
"no alternative methods are shown if only 1 method is allowed"
|
||||
);
|
||||
|
||||
// only backup codes
|
||||
await visit("/session/2fa?nonce=ok010010");
|
||||
assert.ok(exists("form.backup-code-token"), "backup code form is shown");
|
||||
assert.ok(
|
||||
!exists(".toggle-second-factor-method"),
|
||||
"no alternative methods are shown if only 1 method is allowed"
|
||||
);
|
||||
});
|
||||
|
||||
test("switching 2FA methods", async function (assert) {
|
||||
|
||||
@ -19,11 +19,12 @@ acceptance("Sidebar - Plugin API", function (needs) {
|
||||
});
|
||||
|
||||
needs.hooks.afterEach(() => {
|
||||
linkDidInsert = undefined;
|
||||
linkDestroy = undefined;
|
||||
sectionDestroy = undefined;
|
||||
});
|
||||
|
||||
let linkDestroy, sectionDestroy;
|
||||
let linkDidInsert, linkDestroy, sectionDestroy;
|
||||
|
||||
test("Multiple header actions and links", async function (assert) {
|
||||
withPluginApi("1.3.0", (api) => {
|
||||
@ -117,6 +118,11 @@ acceptance("Sidebar - Plugin API", function (needs) {
|
||||
return "unread";
|
||||
}
|
||||
|
||||
@bind
|
||||
didInsert() {
|
||||
linkDidInsert = "link test";
|
||||
}
|
||||
|
||||
@bind
|
||||
willDestroy() {
|
||||
linkDestroy = "link test";
|
||||
@ -201,6 +207,12 @@ acceptance("Sidebar - Plugin API", function (needs) {
|
||||
|
||||
await visit("/");
|
||||
|
||||
assert.strictEqual(
|
||||
linkDidInsert,
|
||||
"link test",
|
||||
"calls link didInsert function"
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
query(
|
||||
".sidebar-section-test-chat-channels .sidebar-section-header-text"
|
||||
|
||||
@ -69,6 +69,7 @@ acceptance("Sidebar - Logged on user - Categories Section", function (needs) {
|
||||
enable_experimental_sidebar_hamburger: true,
|
||||
enable_sidebar: true,
|
||||
suppress_uncategorized_badge: false,
|
||||
allow_uncategorized_topics: true,
|
||||
});
|
||||
|
||||
needs.pretender((server, helper) => {
|
||||
|
||||
@ -30,7 +30,7 @@ acceptance("Topic Discovery", function (needs) {
|
||||
|
||||
assert.strictEqual(
|
||||
query("a[data-user-card=eviltrout] img.avatar").getAttribute("title"),
|
||||
"Evil Trout - Most Posts",
|
||||
"eviltrout - Most Posts",
|
||||
"it shows user's full name in avatar title"
|
||||
);
|
||||
|
||||
|
||||
@ -134,7 +134,7 @@ acceptance("Topic - Edit timer", function (needs) {
|
||||
await timerType.expand();
|
||||
await timerType.selectRowByValue("publish_to_category");
|
||||
|
||||
assert.strictEqual(categoryChooser.header().label(), "uncategorized");
|
||||
assert.strictEqual(categoryChooser.header().label(), "category…");
|
||||
assert.strictEqual(categoryChooser.header().value(), null);
|
||||
|
||||
await categoryChooser.expand();
|
||||
@ -174,7 +174,7 @@ acceptance("Topic - Edit timer", function (needs) {
|
||||
await timerType.expand();
|
||||
await timerType.selectRowByValue("publish_to_category");
|
||||
|
||||
assert.strictEqual(categoryChooser.header().label(), "uncategorized");
|
||||
assert.strictEqual(categoryChooser.header().label(), "category…");
|
||||
assert.strictEqual(categoryChooser.header().value(), null);
|
||||
|
||||
await categoryChooser.expand();
|
||||
@ -218,7 +218,7 @@ acceptance("Topic - Edit timer", function (needs) {
|
||||
await timerType.expand();
|
||||
await timerType.selectRowByValue("publish_to_category");
|
||||
|
||||
assert.strictEqual(categoryChooser.header().label(), "uncategorized");
|
||||
assert.strictEqual(categoryChooser.header().label(), "category…");
|
||||
assert.strictEqual(categoryChooser.header().value(), null);
|
||||
|
||||
await categoryChooser.expand();
|
||||
|
||||
@ -20,6 +20,19 @@ acceptance("Topic - Quote button - logged in", function (needs) {
|
||||
share_quote_buttons: "twitter|email",
|
||||
});
|
||||
|
||||
needs.pretender((server, helper) => {
|
||||
server.get("/inline-onebox", () =>
|
||||
helper.response({
|
||||
"inline-oneboxes": [
|
||||
{
|
||||
url: "http://www.example.com/57350945",
|
||||
title: "This is a great title",
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
chromeTest(
|
||||
"Does not show the quote share buttons by default",
|
||||
async function (assert) {
|
||||
|
||||
@ -211,6 +211,7 @@ acceptance("Topic", function (needs) {
|
||||
});
|
||||
|
||||
test("Deleting a topic", async function (assert) {
|
||||
this.siteSettings.min_topic_views_for_delete_confirm = 10000;
|
||||
await visit("/t/internationalization-localization/280");
|
||||
await click(".topic-post:nth-of-type(1) button.show-more-actions");
|
||||
await click(".widget-button.delete");
|
||||
|
||||
@ -58,7 +58,7 @@ acceptance("User Drafts", function (needs) {
|
||||
query(".user-stream-item:nth-child(3) .excerpt").innerHTML.trim()
|
||||
),
|
||||
normalizeHtml(
|
||||
`here goes a reply to a PM <img src="/images/emoji/google_classic/slight_smile.png?v=${IMAGE_VERSION}" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20" style="aspect-ratio: 20 / 20;">`
|
||||
`here goes a reply to a PM <img src="/images/emoji/twitter/slight_smile.png?v=${IMAGE_VERSION}" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20" style="aspect-ratio: 20 / 20;">`
|
||||
),
|
||||
"shows the excerpt"
|
||||
);
|
||||
|
||||
@ -144,18 +144,18 @@ acceptance("User Preferences - Interface", function (needs) {
|
||||
document.querySelector("meta[name='discourse_theme_id']").remove();
|
||||
});
|
||||
|
||||
test("shows reset seen onboarding popups button", async function (assert) {
|
||||
test("shows reset seen user tips popups button", async function (assert) {
|
||||
let site = Site.current();
|
||||
site.set("onboarding_popup_types", { first_notification: 1 });
|
||||
site.set("user_tips", { first_notification: 1 });
|
||||
|
||||
await visit("/u/eviltrout/preferences/interface");
|
||||
|
||||
assert.ok(
|
||||
exists(".pref-reset-seen-popups"),
|
||||
"has reset seen popups button"
|
||||
exists(".pref-reset-seen-user-tips"),
|
||||
"has reset seen user tips button"
|
||||
);
|
||||
|
||||
await click(".pref-reset-seen-popups");
|
||||
await click(".pref-reset-seen-user-tips");
|
||||
|
||||
assert.deepEqual(lastUserData, {
|
||||
seen_popups: "",
|
||||
|
||||
@ -120,14 +120,16 @@ acceptance(
|
||||
await visit("/u/eviltrout");
|
||||
assert.strictEqual(
|
||||
query(".user-profile-names .username").textContent.trim(),
|
||||
"eviltrout",
|
||||
`eviltrout
|
||||
Robin Ward is an admin`,
|
||||
"eviltrout profile is shown"
|
||||
);
|
||||
|
||||
await visit("/u/e.il.rout");
|
||||
assert.strictEqual(
|
||||
query(".user-profile-names .username").textContent.trim(),
|
||||
"e.il.rout",
|
||||
`e.il.rout
|
||||
Robin Ward is an admin`,
|
||||
"e.il.rout profile is shown"
|
||||
);
|
||||
});
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { hideAllUserTips } from "discourse/lib/user-tips";
|
||||
import { acceptance, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
import I18n from "I18n";
|
||||
import { test } from "qunit";
|
||||
|
||||
acceptance("User Tips - first_notification", function (needs) {
|
||||
needs.user({ unread_high_priority_notifications: 1 });
|
||||
needs.site({ user_tips: { first_notification: 1 } });
|
||||
|
||||
needs.hooks.beforeEach(() => hideAllUserTips());
|
||||
needs.hooks.afterEach(() => hideAllUserTips());
|
||||
|
||||
test("Shows first notification user tip", async function (assert) {
|
||||
this.siteSettings.enable_user_tips = true;
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
assert.equal(
|
||||
query(".user-tip-title").textContent.trim(),
|
||||
I18n.t("user_tips.first_notification.title")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("User Tips - topic_timeline", function (needs) {
|
||||
needs.user();
|
||||
needs.site({ user_tips: { topic_timeline: 2 } });
|
||||
|
||||
needs.hooks.beforeEach(() => hideAllUserTips());
|
||||
needs.hooks.afterEach(() => hideAllUserTips());
|
||||
|
||||
test("Shows topic timeline user tip", async function (assert) {
|
||||
this.siteSettings.enable_user_tips = true;
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
assert.equal(
|
||||
query(".user-tip-title").textContent.trim(),
|
||||
I18n.t("user_tips.topic_timeline.title")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("User Tips - post_menu", function (needs) {
|
||||
needs.user();
|
||||
needs.site({ user_tips: { post_menu: 3 } });
|
||||
|
||||
needs.hooks.beforeEach(() => hideAllUserTips());
|
||||
needs.hooks.afterEach(() => hideAllUserTips());
|
||||
|
||||
test("Shows post menu user tip", async function (assert) {
|
||||
this.siteSettings.enable_user_tips = true;
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
assert.equal(
|
||||
query(".user-tip-title").textContent.trim(),
|
||||
I18n.t("user_tips.post_menu.title")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("User Tips - topic_notification_levels", function (needs) {
|
||||
needs.user();
|
||||
needs.site({ user_tips: { topic_notification_levels: 4 } });
|
||||
|
||||
needs.hooks.beforeEach(() => hideAllUserTips());
|
||||
needs.hooks.afterEach(() => hideAllUserTips());
|
||||
|
||||
test("Shows post menu user tip", async function (assert) {
|
||||
this.siteSettings.enable_user_tips = true;
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
|
||||
assert.equal(
|
||||
query(".user-tip-title").textContent.trim(),
|
||||
I18n.t("user_tips.topic_notification_levels.title")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("User Tips - suggested_topics", function (needs) {
|
||||
needs.user();
|
||||
needs.site({ user_tips: { suggested_topics: 5 } });
|
||||
|
||||
needs.hooks.beforeEach(() => hideAllUserTips());
|
||||
needs.hooks.afterEach(() => hideAllUserTips());
|
||||
|
||||
test("Shows post menu user tip", async function (assert) {
|
||||
this.siteSettings.enable_user_tips = true;
|
||||
|
||||
await visit("/t/internationalization-localization/280");
|
||||
assert.equal(
|
||||
query(".user-tip-title").textContent.trim(),
|
||||
I18n.t("user_tips.suggested_topics.title")
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -612,7 +612,22 @@ export function applyDefaultHandlers(pretender) {
|
||||
pretender.post("/posts", function (request) {
|
||||
const data = parsePostData(request.requestBody);
|
||||
|
||||
if (data.raw === "custom message") {
|
||||
if (data.title === "this title triggers an error") {
|
||||
return response(422, { errors: ["That title has already been taken"] });
|
||||
}
|
||||
|
||||
if (data.raw === "enqueue this content please") {
|
||||
return response(200, {
|
||||
success: true,
|
||||
action: "enqueued",
|
||||
pending_post: {
|
||||
id: 1234,
|
||||
raw: data.raw,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (data.raw === "custom message that is a good length") {
|
||||
return response(200, {
|
||||
success: true,
|
||||
action: "custom",
|
||||
|
||||
@ -1,111 +1,27 @@
|
||||
const ORIGINAL_SETTINGS = {
|
||||
const CLIENT_SETTING_TEST_OVERRIDES = {
|
||||
title: "QUnit Discourse Tests",
|
||||
site_logo_url: "/assets/logo.png",
|
||||
site_logo_url: "/assets/logo.png",
|
||||
site_logo_small_url: "/assets/logo-single.png",
|
||||
site_mobile_logo_url: "",
|
||||
site_favicon_url: "/images/discourse-logo-sketch-small.png",
|
||||
allow_user_locale: false,
|
||||
suggested_topics: 7,
|
||||
ga_universal_tracking_code: "",
|
||||
ga_universal_domain_name: "auto",
|
||||
top_menu: "latest|new|unread|categories|top",
|
||||
post_menu: "like|share|flag|edit|bookmark|delete|admin|reply",
|
||||
post_menu_hidden_items: "flag|bookmark|edit|delete|admin",
|
||||
share_links: "twitter|facebook|email",
|
||||
allow_username_in_share_links: true,
|
||||
category_colors:
|
||||
"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|27AA5B|B3B5B4|E45735",
|
||||
enable_mobile_theme: true,
|
||||
relative_date_duration: 14,
|
||||
fixed_category_positions: false,
|
||||
enable_badges: true,
|
||||
invite_only: false,
|
||||
login_required: false,
|
||||
must_approve_users: false,
|
||||
enable_local_logins: true,
|
||||
allow_new_registrations: true,
|
||||
enable_google_logins: true,
|
||||
enable_google_oauth2_logins: false,
|
||||
enable_twitter_logins: true,
|
||||
enable_facebook_logins: true,
|
||||
enable_github_logins: true,
|
||||
enable_discourse_connect: false,
|
||||
min_username_length: 3,
|
||||
max_username_length: 20,
|
||||
min_password_length: 8,
|
||||
enable_names: true,
|
||||
invites_shown: 30,
|
||||
delete_user_max_post_age: 60,
|
||||
delete_all_posts_max: 15,
|
||||
min_post_length: 20,
|
||||
min_personal_message_post_length: 10,
|
||||
max_post_length: 32000,
|
||||
min_topic_title_length: 15,
|
||||
max_topic_title_length: 255,
|
||||
min_personal_message_title_length: 2,
|
||||
allow_uncategorized_topics: true,
|
||||
min_title_similar_length: 10,
|
||||
edit_history_visible_to_public: true,
|
||||
delete_removed_posts_after: 24,
|
||||
traditional_markdown_linebreaks: false,
|
||||
suppress_reply_directly_below: true,
|
||||
suppress_reply_directly_above: true,
|
||||
newuser_max_embedded_media: 0,
|
||||
newuser_max_attachments: 0,
|
||||
display_name_on_posts: true,
|
||||
short_progress_text_threshold: 10000,
|
||||
default_code_lang: "auto",
|
||||
autohighlight_all_code: false,
|
||||
email_in: false,
|
||||
authorized_extensions: ".jpg|.jpeg|.png|.gif|.svg|.txt|.ico|.yml",
|
||||
authorized_extensions_for_staff: "",
|
||||
max_image_width: 690,
|
||||
max_image_height: 500,
|
||||
allow_profile_backgrounds: true,
|
||||
allow_uploaded_avatars: "0",
|
||||
tl1_requires_read_posts: 30,
|
||||
polling_interval: 3000,
|
||||
authorized_extensions: "jpg|jpeg|png|gif|heic|heif|webp|svg|txt|ico|yml",
|
||||
anon_polling_interval: 30000,
|
||||
flush_timings_secs: 5,
|
||||
enable_user_directory: true,
|
||||
tos_url: "",
|
||||
privacy_policy_url: "",
|
||||
tos_accept_required: false,
|
||||
faq_url: "",
|
||||
allow_restore: false,
|
||||
maximum_backups: 5,
|
||||
version_checks: true,
|
||||
suppress_uncategorized_badge: true,
|
||||
min_search_term_length: 3,
|
||||
topic_views_heat_low: 1000,
|
||||
topic_views_heat_medium: 2000,
|
||||
topic_views_heat_high: 5000,
|
||||
global_notice: "",
|
||||
show_create_topics_notice: true,
|
||||
available_locales:
|
||||
"cs|da|de|en|es|fr|he|id|it|ja|ko|nb_NO|nl|pl_PL|pt|pt_BR|ru|sv|uk|zh_CN|zh_TW",
|
||||
highlighted_languages:
|
||||
"apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars",
|
||||
enable_emoji: true,
|
||||
enable_emoji_shortcuts: true,
|
||||
emoji_set: "google_classic",
|
||||
enable_emoji_shortcuts: true,
|
||||
enable_inline_emoji_translation: false,
|
||||
desktop_category_page_style: "categories_and_latest_topics",
|
||||
enable_mentions: true,
|
||||
enable_personal_messages: true,
|
||||
personal_message_enabled_groups: "11", // TL1 group
|
||||
unicode_usernames: false,
|
||||
secure_uploads: false,
|
||||
external_emoji_url: "",
|
||||
remove_muted_tags_from_latest: "always",
|
||||
enable_group_directory: true,
|
||||
default_sidebar_categories: "",
|
||||
default_sidebar_tags: "",
|
||||
};
|
||||
|
||||
let siteSettings = Object.assign({}, ORIGINAL_SETTINGS);
|
||||
// Note, CLIENT_SITE_SETTINGS_WITH_DEFAULTS is generated by the site-settings-plugin,
|
||||
// writing to test-site-settings.js via the ember-cli-build pipeline.
|
||||
const ORIGINAL_CLIENT_SITE_SETTINGS = Object.assign(
|
||||
{},
|
||||
// eslint-disable-next-line no-undef
|
||||
CLIENT_SITE_SETTINGS_WITH_DEFAULTS,
|
||||
CLIENT_SETTING_TEST_OVERRIDES
|
||||
);
|
||||
let siteSettings = Object.assign({}, ORIGINAL_CLIENT_SITE_SETTINGS);
|
||||
|
||||
export function currentSettings() {
|
||||
return siteSettings;
|
||||
@ -135,7 +51,8 @@ export function mergeSettings(other) {
|
||||
export function resetSettings() {
|
||||
for (let p in siteSettings) {
|
||||
if (siteSettings.hasOwnProperty(p)) {
|
||||
let v = ORIGINAL_SETTINGS[p];
|
||||
// eslint-disable-next-line no-undef
|
||||
let v = ORIGINAL_CLIENT_SITE_SETTINGS[p];
|
||||
typeof v !== "undefined" ? setValue(p, v) : delete siteSettings[p];
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
</style>
|
||||
|
||||
<script src="{{rootURL}}assets/test-i18n.js"></script>
|
||||
<script src="{{rootURL}}assets/test-site-settings.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{{content-for "body"}} {{content-for "test-body"}}
|
||||
|
||||
@ -126,9 +126,9 @@ module(
|
||||
assert.strictEqual(this.subject.header().label(), "category…");
|
||||
});
|
||||
|
||||
test("with allowUncategorized=null and generalCategoryId present", async function (assert) {
|
||||
test("with allowUncategorized=null and defaultComposerCategory present", async function (assert) {
|
||||
this.siteSettings.allow_uncategorized_topics = false;
|
||||
this.siteSettings.general_category_id = 4;
|
||||
this.siteSettings.default_composer_category = 4;
|
||||
|
||||
await render(hbs`
|
||||
<CategoryChooser
|
||||
@ -143,9 +143,9 @@ module(
|
||||
assert.strictEqual(this.subject.header().label(), "");
|
||||
});
|
||||
|
||||
test("with allowUncategorized=null and generalCategoryId present, but not set", async function (assert) {
|
||||
test("with allowUncategorized=null and defaultComposerCategory present, but not set", async function (assert) {
|
||||
this.siteSettings.allow_uncategorized_topics = false;
|
||||
this.siteSettings.general_category_id = -1;
|
||||
this.siteSettings.default_composer_category = -1;
|
||||
|
||||
await render(hbs`
|
||||
<CategoryChooser
|
||||
|
||||
@ -4,6 +4,7 @@ import { render } from "@ember/test-helpers";
|
||||
import I18n from "I18n";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
import { query } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
const DEFAULT_CONTENT = [
|
||||
{ id: 1, name: "foo" },
|
||||
@ -391,4 +392,23 @@ module("Integration | Component | select-kit/single-select", function (hooks) {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("options.verticalOffset", async function (assert) {
|
||||
setDefaultState(this, { verticalOffset: -50 });
|
||||
await render(hbs`
|
||||
<SingleSelect
|
||||
@value={{this.value}}
|
||||
@content={{this.content}}
|
||||
@nameProperty={{this.nameProperty}}
|
||||
@valueProperty={{this.valueProperty}}
|
||||
@onChange={{this.onChange}}
|
||||
@options={{hash verticalOffset=this.verticalOffset}}
|
||||
/>
|
||||
`);
|
||||
await this.subject.expand();
|
||||
const header = query(".select-kit-header").getBoundingClientRect();
|
||||
const body = query(".select-kit-body").getBoundingClientRect();
|
||||
|
||||
assert.ok(header.bottom > body.top, "it correctly offsets the body");
|
||||
});
|
||||
});
|
||||
|
||||
@ -96,6 +96,7 @@ module("Integration | Component | Widget | hamburger-menu", function (hooks) {
|
||||
[...queryAll(".category-link .category-name")].map((el) => el.innerText),
|
||||
this.site
|
||||
.get("categoriesByCount")
|
||||
.reject((c) => c.id === this.site.uncategorized_category_id)
|
||||
.slice(0, 8)
|
||||
.map((c) => c.name)
|
||||
);
|
||||
@ -103,7 +104,7 @@ module("Integration | Component | Widget | hamburger-menu", function (hooks) {
|
||||
|
||||
test("top categories - allow_uncategorized_topics", async function (assert) {
|
||||
this.owner.unregister("service:current-user");
|
||||
this.siteSettings.allow_uncategorized_topics = false;
|
||||
this.siteSettings.allow_uncategorized_topics = true;
|
||||
this.siteSettings.header_dropdown_category_count = 8;
|
||||
|
||||
await render(hbs`<MountWidget @widget="hamburger-menu" />`);
|
||||
@ -113,7 +114,6 @@ module("Integration | Component | Widget | hamburger-menu", function (hooks) {
|
||||
[...queryAll(".category-link .category-name")].map((el) => el.innerText),
|
||||
this.site
|
||||
.get("categoriesByCount")
|
||||
.filter((c) => c.name !== "uncategorized")
|
||||
.slice(0, 8)
|
||||
.map((c) => c.name)
|
||||
);
|
||||
@ -122,7 +122,10 @@ module("Integration | Component | Widget | hamburger-menu", function (hooks) {
|
||||
test("top categories", async function (assert) {
|
||||
this.siteSettings.header_dropdown_category_count = 8;
|
||||
maxCategoriesToDisplay = this.siteSettings.header_dropdown_category_count;
|
||||
categoriesByCount = this.site.get("categoriesByCount").slice();
|
||||
categoriesByCount = this.site
|
||||
.get("categoriesByCount")
|
||||
.reject((c) => c.id === this.site.uncategorized_category_id)
|
||||
.slice();
|
||||
categoriesByCount.every((c) => {
|
||||
if (!topCategoryIds.includes(c.id)) {
|
||||
if (mutedCategoryIds.length === 0) {
|
||||
|
||||
@ -3,15 +3,11 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { count } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import Post from "discourse/models/post";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
|
||||
function postStreamTest(name, attrs) {
|
||||
test(name, async function (assert) {
|
||||
const site = this.container.lookup("service:site");
|
||||
let posts = attrs.posts.call(this);
|
||||
posts.forEach((p) => p.set("site", site));
|
||||
this.set("posts", posts);
|
||||
this.set("posts", attrs.posts.call(this));
|
||||
|
||||
await render(
|
||||
hbs`<MountWidget @widget="post-stream" @args={{hash posts=this.posts}} />`
|
||||
@ -26,13 +22,13 @@ module("Integration | Component | Widget | post-stream", function (hooks) {
|
||||
|
||||
postStreamTest("basics", {
|
||||
posts() {
|
||||
const site = this.container.lookup("service:site");
|
||||
const site = getOwner(this).lookup("service:site");
|
||||
const store = getOwner(this).lookup("service:store");
|
||||
const topic = store.createRecord("topic");
|
||||
topic.set("details.created_by", { id: 123 });
|
||||
|
||||
return [
|
||||
Post.create({
|
||||
store.createRecord("post", {
|
||||
topic,
|
||||
id: 1,
|
||||
post_number: 1,
|
||||
@ -40,27 +36,32 @@ module("Integration | Component | Widget | post-stream", function (hooks) {
|
||||
primary_group_name: "trout",
|
||||
avatar_template: "/images/avatar.png",
|
||||
}),
|
||||
Post.create({
|
||||
store.createRecord("post", {
|
||||
topic,
|
||||
id: 2,
|
||||
post_number: 2,
|
||||
post_type: site.get("post_types.moderator_action"),
|
||||
}),
|
||||
Post.create({ topic, id: 3, post_number: 3, hidden: true }),
|
||||
Post.create({
|
||||
store.createRecord("post", {
|
||||
topic,
|
||||
id: 3,
|
||||
post_number: 3,
|
||||
hidden: true,
|
||||
}),
|
||||
store.createRecord("post", {
|
||||
topic,
|
||||
id: 4,
|
||||
post_number: 4,
|
||||
post_type: site.get("post_types.whisper"),
|
||||
}),
|
||||
Post.create({
|
||||
store.createRecord("post", {
|
||||
topic,
|
||||
id: 5,
|
||||
post_number: 5,
|
||||
wiki: true,
|
||||
via_email: true,
|
||||
}),
|
||||
Post.create({
|
||||
store.createRecord("post", {
|
||||
topic,
|
||||
id: 6,
|
||||
post_number: 6,
|
||||
@ -133,7 +134,7 @@ module("Integration | Component | Widget | post-stream", function (hooks) {
|
||||
topic.set("details.created_by", { id: 123 });
|
||||
|
||||
return [
|
||||
Post.create({
|
||||
store.createRecord("post", {
|
||||
topic,
|
||||
id: 1,
|
||||
post_number: 1,
|
||||
|
||||
@ -23,7 +23,6 @@ module("Integration | Component | Widget | poster-name", function (hooks) {
|
||||
assert.ok(exists("span.username"));
|
||||
assert.ok(exists('a[data-user-card="eviltrout"]'));
|
||||
assert.strictEqual(query(".username a").innerText, "eviltrout");
|
||||
assert.strictEqual(query(".full-name a").innerText, "Robin Ward");
|
||||
assert.strictEqual(query(".user-title").innerText, "Trout Master");
|
||||
});
|
||||
|
||||
|
||||
@ -47,8 +47,8 @@ module("Unit | Controller | create-account", function (hooks) {
|
||||
|
||||
controller.set("authProvider", "");
|
||||
controller.set("accountEmail", "pork@chops.com");
|
||||
controller.set("accountUsername", "porkchops");
|
||||
controller.set("prefilledUsername", "porkchops");
|
||||
controller.set("accountUsername", "porkchops123");
|
||||
controller.set("prefilledUsername", "porkchops123");
|
||||
controller.set("accountPassword", "b4fcdae11f9167");
|
||||
|
||||
assert.strictEqual(
|
||||
@ -79,7 +79,10 @@ module("Unit | Controller | create-account", function (hooks) {
|
||||
|
||||
testInvalidPassword("", null);
|
||||
testInvalidPassword("x", I18n.t("user.password.too_short"));
|
||||
testInvalidPassword("porkchops", I18n.t("user.password.same_as_username"));
|
||||
testInvalidPassword(
|
||||
"porkchops123",
|
||||
I18n.t("user.password.same_as_username")
|
||||
);
|
||||
testInvalidPassword(
|
||||
"pork@chops.com",
|
||||
I18n.t("user.password.same_as_email")
|
||||
|
||||
@ -7,6 +7,7 @@ import { Placeholder } from "discourse/lib/posts-with-placeholders";
|
||||
import User from "discourse/models/user";
|
||||
import { next } from "@ember/runloop";
|
||||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
import sinon from "sinon";
|
||||
|
||||
function topicWithStream(streamDetails) {
|
||||
const topic = this.store.createRecord("topic");
|
||||
@ -78,6 +79,27 @@ module("Unit | Controller | topic", function (hooks) {
|
||||
assert.ok(destroyed, "destroy not popular topic");
|
||||
});
|
||||
|
||||
test("deleteTopic permanentDelete", function (assert) {
|
||||
const opts = { force_destroy: true };
|
||||
const model = this.store.createRecord("topic");
|
||||
const siteSettings = this.owner.lookup("service:site-settings");
|
||||
siteSettings.min_topic_views_for_delete_confirm = 5;
|
||||
|
||||
const controller = this.owner.lookup("controller:topic");
|
||||
controller.setProperties({ model });
|
||||
model.set("views", 100);
|
||||
|
||||
const stub = sinon.stub(model, "destroy");
|
||||
controller.send("deleteTopic", { force_destroy: true });
|
||||
|
||||
assert.deepEqual(
|
||||
stub.getCall(0).args[1],
|
||||
opts,
|
||||
"does not show delete confirm permanently deleting, passes opts to model action"
|
||||
// permanent delete happens after first delete, no need to show modal again
|
||||
);
|
||||
});
|
||||
|
||||
test("toggleMultiSelect", async function (assert) {
|
||||
const model = this.store.createRecord("topic");
|
||||
const controller = getOwner(this).lookup("controller:topic");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user