This api allows to add a dropdown at the bottom of a topic, note that this API is mobile only for now.
Also included in the commit:
- various doc fixes
- adding tests for both buttons and dropdowns APIs
- uses thrown instead of @ember/error to ensure execution is halted when incorrect parameters are given
When deleting a for_topic bookmark, we were calling
bookmark.attachedTo() for the bookmarks:changed event,
but the bookmark was not always a Bookmark model instance,
so sometimes that call would error. Now we make sure that
the bookmarks in the topic.bookmarks JS array are all
bookmark model instances, and added a test to cover this
deleting for_topic bookmark case as well.
A follow up PR should investigate why `proposal-logical-assignment-operators` is not getting used here (test file?) but this should be enough to get things running.
The flow goes from:
- getting current user object
- creating a POJO using some of the current user keys
- passing this POJO around, which end up being used in message bus
- the processing fn associated ens up doing User.create on this object will both create a User object, but also inject store in it, store is holding a reference to currentUser Object and...
BOOM, we have an object holding a reference to the same object, which JSON.stringify used in prepareBody of pretender doesn't like.
This commit adds uploadHandler support to composer uploads using
uppy. The only things we have that are using this are discourse-brightcove and
discourse-video, which both pop modal windows to handle the file upload and
completely leave out all the composer-type flows. This implementation simply
follows the existing one, where if a single file is uploaded and there
is a matching upload handler we take control away from uppy and hand
it off to the upload handler.
Trying to get this kind of thing working within uppy would require a few
changes because they have no way to restrict uploaders to certain file types
and with the way their uploaders are run it doesn't look like it would be easy
to add this either, so I don't think this is worth the work unless at some
point in the future we plan to have more upload handler integrations.
I also fixed an issue with `cleanUpComposerUploadHandler` which is used
in tests to reset the state of `uploadHandlers` in the composer. This
was doing `uploadHandlers = []` to clear that array, but that creates
a brand new array so anything else referencing the original array will
lose that reference. Better to set `uploadHandlers.length = 0` to
clear it. This was breaking the tests I added to see if upload handlers
were working.
We were previously showing the "n new or updated topics" alert on
category routes like `/c/category-slug/ID/none` on every new/unread
topic update. This PR looks up the category by ID, which should be more
precise.
Previously when clicking the Delete button for small action posts
there was no way to recover this post if the action was accidental.
Now if canRecover is true on the post, which it is just after it
is deleted and the post is fetched from the server again, we show
an undo button which calls the recover endpoint for the post.
We also now disallow the editing of the post if it is deleted, and
show the proper deleted red CSS on the small action post when deleted.
The method was only used for mega topics but it was redundant as the
first post can be determined from using the condition where
`Post#post_number` equal to one.
* FIX: do not display add to calendar for past dates
There is no value in saving past dates into calendar
* FIX: remove postId and move ICS to frontend
PostId is not necessary and will make the solution more generic for dates which doesn't belong to a specific post.
Also, ICS file can be generated in JavaScript to avoid calling backend.
Sometimes administrators want to permanently delete posts and topics
from the database. To make sure that this is done for a good reasons,
administrators can do this only after one minute has passed since the
post was deleted or immediately if another administrator does it.
Both `aria-label` and `title` have the same value and NVDA reading both the texts while navigating between buttons. NVDA already has an open issue https://github.com/nvaccess/nvda/issues/7841. We're removing `aria-label` until they fix it.
- Allow the `/presence/get` endpoint to return multiple channels in a single request (limited to 50)
- When multiple presence channels are initialized in a single Ember runloop, batch them into a single GET request
- Introduce the `presence-pretender` to allow easy testing of PresenceChannel-related features
- Introduce a `use_cache` boolean (default true) on the the server-side PresenceChannel initializer. Useful during testing.
When hide_email_address_taken was disabled, the forgot password modal
showed a flash message and continued to display the form causing
confusion. This change shows the flash message only when an error occurs
and in all other cases it shows a success message and hides the form.
It allows saving local date to calendar.
Modal is giving option to pick between ics and google. User choice can be remembered as a default for the next actions.
* FIX: Stop tracking incoming message after navigating away take 2.
Previous fix in d82e5cd37c resulted in
counts being flappy as we cleared the active inbox between routes.
Co-authored-by: Osama Sayegh <asooomaasoooma90@gmail.com>
Previously we would store every FakeRequest object for all tests, resulting in many hundreds/thousands of objects in the `handledRequests` array.
This commit ensures all pretender state is reset between tests.
- There's no need to pass `filter` to `user-notifications-large`. The component doesn't use it.
- Rename css class to avoid confusion (this div has nothing to-do with the Select Kit)
- Remove duplicated declarations in test fixtures
This is `console.log`'d to the browser console. run-qunit will print this to stdout. testem will not, so a custom reporter is implemented to print this message.
The `--enable-precise-memory-info` is added so that chrome provides high-resolution memory information. This API is not supported by firefox. The logic will degrade gracefully.
Note this commit is also adding support for teardown in pre-initializers just like we have for initializers.
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
Co-authored-by: David Taylor <david@taylorhq.com>
We were using multiple methods to check which environment we're running in. This commit switches us to use the isLegacyEmber helper consistently. This should be a no-op, but makes the code much easier to read
Under Ember CLI, we create a new application instance for each test. We were not correctly destroying it after the test, causing many references to be maintaned (e.g. at the end of a test run, `Ember.Namespace.NAMESPACES` would have an entry for each application instance).
Calling `destroy` on the application instance tidies up these references, and is one step towards fixing our test memory leak problem. Unfortunately there still seem to be other references being held to the application, so this commit is not a total fix.
The all inboxes was introduced in
016efeadf6 but we decided to roll it back
for performance reasons. The main performance challenge here is that PG
has to basically loop through all the PMs that a user is allowed to view
before being able to order by `Topic#bumped_at`. The all inboxes was not
planned as part of the new/unread filter so we've decided not to tackle
the performance issue for the upcoming release.
Follow-up to 016efeadf6
This commit also hides a number of options which are not used during Discourse development.
Change have been tested on both the legacy `/qunit` route, and the Ember CLI `/tests` route.
This adds support for `qunit_skip_core`, `qunit_skip_plugins` and `qunit_single_plugin` parameters on the Ember CLI `/tests` route using the `addModuleExcludeMatcher` API. Legacy support is maintained for the `/qunit` route.
Allows creating a bookmark with the `for_topic` flag introduced in d1d2298a4c set to true. This happens when clicking on the Bookmark button in the topic footer when no other posts are bookmarked. In a later PR, when clicking on these topic-level bookmarks the user will be taken to the last unread post in the topic, not the OP. Only the OP can have a topic level bookmark, and users can also make a post-level bookmark on the OP of the topic.
I had to do some pretty heavy refactors because most of the bookmark code in the JS topics controller was centred around instances of Post JS models, but the topic level bookmark is not centred around a post. Some refactors were just for readability as well.
Also removes some missed reminderType code from the purge in 41e19adb0d
We want to be able to skip plugins from doing any work under
certain conditions, and to be able raise their own errors if
a file being uploaded is completely incompatible with the concept
of the plugin if it is enabled. For example, the UppyChecksum plugin
is happy to skip hashing large files, but the UppyUploadEncrypt
plugin from discourse-encrypt relies on the file being encrypted
to do anything with the upload, so it is considered a blocking
error if the user uploads a file that is too large.
This improves the base functions available in uppy-plugin-base and
extendable-uploader to handle this, as well as introducing a
HUGE_FILE_THRESHOLD_BYTES variable which represents 100MB in bytes,
matching the ExternalUploadManager::DOWNLOAD_LIMIT on the
server side.
discourse-encrypt to take advantage of this new functionality will
follow in discourse/discourse-encrypt#141
We want to be able to skip plugins from doing any work under
certain conditions, and to be able raise their own errors if
a file being uploaded is completely incompatible with the concept
of the plugin if it is enabled. For example, the UppyChecksum plugin
is happy to skip hashing large files, but the UppyUploadEncrypt
plugin from discourse-encrypt relies on the file being encrypted
to do anything with the upload, so it is considered a blocking
error if the user uploads a file that is too large.
This improves the base functions available in uppy-plugin-base and
extendable-uploader to handle this, as well as introducing a
HUGE_FILE_THRESHOLD_BYTES variable which represents 100MB in bytes,
matching the ExternalUploadManager::DOWNLOAD_LIMIT on the
server side.
discourse-encrypt to take advantage of this new functionality will
follow in https://github.com/discourse/discourse-encrypt/pull/141
There was a check for closed code blocks (which had both opening and
closing markups), but it did not work for the case when the text ends
in an open code block.
Administrators can use second factor to confirm granting admin access
without using email. The old method of confirmation via email is still
used as a fallback when second factor is unavailable.
The previous excerpt was a simple truncated raw message. Starting with
this commit, the raw content of the draft is cooked and an excerpt is
extracted from it. The logic for extracting the excerpt mimics the the
`ExcerptParser` class, but does not implement all functionality, being
a much simpler implementation.
The two draft controllers have been merged into one and the /draft.json
route has been changed to /drafts.json to be consistent with the other
route names.
When a user archives a personal message, they are redirected back to the
inbox and will refresh the list of the topics for the given filter.
Publishing an event to the user results in an incorrect incoming message
because the list of topics has already been refreshed.
This does mean that if a user has two tabs opened, the non-active tab
will not receive the incoming message but at this point we do not think
the technical trade-offs are worth it to support this feature. We
basically have to somehow exclude a client from an incoming message
which is not easy to do.
Follow-up to fc1fd1b416
This abstracts interaction with uppy for uppy plugin classes
into base classes for Preprocessor plugins, so anyone
making these uppy plugins doesn't have to think as much about uppy
underneath the hood. This also makes the logging and validation
nicer, and provides a more consistent way to emit progress and
completion events.
In a future commit, we will introduce another base class for
`UploadUploaderPlugin` which will be used to be able to hijack
the upload process to go to a different provider (e.g. for discourse-video)
At this point in time, we do not think supporting unread and new when an
admin is looking at another user's messages is worth supporting.
Follow-up to fc1fd1b416
In order to include the new/unread count in the browse more message
under suggested topics, a couple of technical changes have to be made.
1. `PrivateMessageTopicTrackingState` is now auto-injected which is
similar to how it is done for `TopicTrackingState`. This is done so
we don't have to attempt to pass the `PrivateMessageTopicTrackingState`
object multiple levels down into the suggested-topics component. While
the object is auto-injected, we only fetch the initial state and start
tracking when the relevant private messages routes has been hit and only
when a private message's suggested topics is loaded. This is
done as we do not want to add the extra overhead of fetching the inital
state to all page loads but instead wait till the private messages
routes are hit.
2. Previously, we would stop tracking once the `user-private-messages`
route has been deactivated. However, that is not ideal since
navigating out of the route and back means we send an API call to the
server each time. Since `PrivateMessageTopicTrackingState` is kept in
sync cheaply via messageBus, we can just continue to track the state
even if the user has navigated away from the relevant stages.
The smoke test has been failing with the error:
```
TypeError: Cannot read properties of undefined (reading 'Core')
```
Since de20c46077
and 9873a942e3 this error has been occurring,
possibly now because Uppy is required by a plugin. Adding uppy.js into
the require list for theme_qunit_vendor.js fixes the issue.
This new interface will be used explicitly to add upload
preprocessors in the form of uppy plugins. These will be
run for each upload in the composer (dependent on the logic
of the plugin itself), before the UppyChecksum plugin is
finally run.
Since discourse-encrypt uses the existing addComposerUploadHandler
API for essentially preprocessing an upload and not uploading it
to a different place, it will be the first plugin to use this interface,
along with the register-media-optimization-upload-processor initializer
in core.
Related https://github.com/discourse/discourse-encrypt/pull/131.
Improves the create account modal for screen readers by doing the following:
* Making the `modal-alert` section into an `aria-role="alert"` region and making it show and hide using height instead of display:none so screen readers pick it up. Made a change so the field-related error messages are always shown beneath the field.
* Add `aria-invalid` and `aria-describedby` attributes to each field in the modal, so the screen reader will read out the error hint on error. This necessitated an Ember component extension to allow both the `aria-*` attributes to be bound and to render on `{{input}}`.
* Moved the social login buttons to the right in the HTML structure so they are not read out first.
* Added `aria-label` attributes to the login buttons so they can have different content for screen readers.
* In some cases for modals, the title that should be used for the `aria-labelledby` attribute is within the modal content and not the discourse-modal-title title. This introduces a new titleAriaElementId property to the d-modal component that is then used by the create-account modal to read out the title
------
This is the same as e0d2de73d8 but
fixes the Ember-input-component-extension to use the public
Ember components TextField and TextArea instead of the private
TextSupport so the extension works in both normal Ember and
Ember CLI.
Improves the create account modal for screen readers by doing the following:
* Making the `modal-alert` section into an `aria-role="alert"` region and making it show and hide using height instead of display:none so screen readers pick it up. Made a change so the field-related error messages are always shown beneath the field.
* Add `aria-invalid` and `aria-describedby` attributes to each field in the modal, so the screen reader will read out the error hint on error. This necessitated an Ember component extension to allow both the `aria-*` attributes to be bound and to render on `{{input}}`.
* Moved the social login buttons to the right in the HTML structure so they are not read out first.
* Added `aria-label` attributes to the login buttons so they can have different content for screen readers.
* In some cases for modals, the title that should be used for the `aria-labelledby` attribute is within the modal content and not the discourse-modal-title title. This introduces a new titleAriaElementId property to the d-modal component that is then used by the create-account modal to read out the
PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc.
A few key pieces of information about the system:
- PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use.
- Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once
- Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes.
- Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users.
- The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel.
- Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in.
- On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server.
- When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel.
- The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties.
- It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use
An example implementation may look something like this. On the server:
```ruby
register_presence_channel_prefix("site") do |channel|
next nil unless channel == "/site/online"
PresenceChannel::Config.new(public: true)
end
```
And on the client, a component could be implemented like this:
```javascript
import Component from "@ember/component";
import { inject as service } from "@ember/service";
export default Component.extend({
presence: service(),
init() {
this._super(...arguments);
this.set("presenceChannel", this.presence.getChannel("/site/online"));
},
didInsertElement() {
this.presenceChannel.enter();
this.presenceChannel.subscribe();
},
willDestroyElement() {
this.presenceChannel.leave();
this.presenceChannel.unsubscribe();
},
});
```
With this template:
```handlebars
Online: {{presenceChannel.count}}
<ul>
{{#each presenceChannel.users as |user|}}
<li>{{avatar user imageSize="tiny"}} {{user.username}}</li>
{{/each}}
</ul>
```
This mixin needs to be shared between the composer and composer-like
user interfaces. This commit makes it so the events and the underlying
data model is configurable by the component extending the ComposerUploadUppy
mixin.
Also removes two MessageBus unsubscribe calls which were unnecessary.
The user-topic-list template is also in use in other places when we want to improve blank page syndrome, so this PR is a preparation for that changes as well.
- uses tagName=""
- removes user property which is not being used
- extract utility functions
- better wording for boolean properties
- initializes all properties
- uses @action
- uses optional chaining
- other minor changes
There are certain design decisions that were made in this commit.
Private messages implements its own version of topic tracking state because there are significant differences between regular and private_message topics. Regular topics have to track categories and tags while private messages do not. It is much easier to design the new topic tracking state if we maintain two different classes, instead of trying to mash this two worlds together.
One MessageBus channel per user and one MessageBus channel per group. This allows each user and each group to have their own channel backlog instead of having one global channel which requires the client to filter away unrelated messages.
Major changes included:
- better support for screen readers
- trapping focus in modals
- better tabbing order in composer
- alerts on no content found/number of items found
- better autofocus in modals
- mini-tag-chooser is now a multi-select component
- each multi-select-component will now display selection on one row
When a theme's default color scheme is not marked as user selectable, we were outputting the numeric ID in the UI. This outputs "Theme default" instead.
Adds uppy upload functionality behind a
enable_experimental_composer_uploader site setting (default false,
and hidden).
When enabled this site setting will make the composer-editor-uppy
component be used within composer.hbs, which in turn points to
a ComposerUploadUppy mixin which overrides the relevant
functions from ComposerUpload. This uppy uploader has parity
with all the features of jQuery file uploader in the original
composer-editor, including:
progress tracking
error handling
number of files validation
pasting files
dragging and dropping files
updating upload placeholders
upload markdown resolvers
processing actions (the only one we have so far is the media optimization
worker by falco, this works)
cancelling uploads
For now all uploads still go via the /uploads.json endpoint, direct
S3 support will be added later.
Also included in this PR are some changes to the media optimization
service, to support uppy's different file data structures, and also
to make the promise tracking and resolving more robust. Currently
it uses the file name to track promises, we can switch to something
more unique later if needed.
Does not include custom upload handlers, that will come
in a later PR, it is a tricky problem to handle.
Also, this new functionality will not be used in encrypted PMs because
encrypted PM uploads rely on custom upload handlers.
The invite acceptance page is an alternative signup flow, so it makes sense to include the new 'link' functionality there as well.
Followup to 7dc8f8b794
When a user signs up via an external auth method, a new link is added to the signup modal which allows them to connect an existing Discourse account. This will only happen if:
- There is at least 1 other auth method available
and
- The current auth method permits users to disconnect/reconnect their accounts themselves
In the group interaction UI, if the default_notification_level for
a group was set to 0 (muted) it incorrectly showed as Watching in
the UI because of the ember or() helper, using JS comparison, considered
0 to be a falsey value and always showed 3 (watching) instead.
When declaring your widget you can now add an option like: `services: ['cool']`
And your widget instances will automatically get a `this.cool` property
which will resolve to the service. This saves having to look it up
yourself.
Currently when a user clicks on an edit notification, we use `appEvents` to
notify the topics controller that it should open up the history modal for the
edited post and the appEvents callback opens up the history modal in the next
Ember runloop (by scheduling an `afterRender` callback).
There are 2 problems with this implementation:
1) the callbacks are fired/executed too early and if the post has never been
loaded from the server (i.e. not in cache), we will not get a modal history
because the method that shows the modal `return`s if it can't find the post:
016efeadf6/app/assets/javascripts/discourse/app/controllers/topic.js (L145-L152)
2) when clicking an edit notification from a non-topic page, you're redirected
to the topic page that contains the edited post and you'll see the history
modal briefly and it'll be closed immediately. The reason for this is because
we attempt to show the history modal before the route transition finishes
completely, and we have cleanup code in `initializers/page-tracking.js` that's
called after every transition and it does several things one of which is
closing any open modals.
The fix in this commit defers showing the history modal until posts are loaded
(whether fresh or cached). It works by storing some bits of information (topic
id, post number, revision number) whenever the user clicks on an edit
notification, and when the user is redirected to the topic (or scrolled to the
edited post if they're already in the topic), the post stream model checks if
we have stored information of an edit notification and requests the history
modal to be shown by the topics controller.
An invalid draft is the draft of a topic with a short title or body.
The client does not save these, but it will ask the client if they want
to save it. Even if the answer is 'yes', the draft is discarded. This
commit skips Save button for small drafts.
The current behaviour was producing random tests failures which where consistently reproducible using `seed=32037592518471299633729129648744282271`
The cause of this error, is a previous test not giving any topicId or categoryId resulting in a cache key "undefined-undefined", just like a possibly previous test. Reseting cache between tests, seems the most straightforward and future proof solution
This adds a few different things to allow for direct S3 uploads using uppy. **These changes are still not the default.** There are hidden `enable_experimental_image_uploader` and `enable_direct_s3_uploads` settings that must be turned on for any of this code to be used, and even if they are turned on only the User Card Background for the user profile actually uses uppy-image-uploader.
A new `ExternalUploadStub` model and database table is introduced in this pull request. This is used to keep track of uploads that are uploaded to a temporary location in S3 with the direct to S3 code, and they are eventually deleted a) when the direct upload is completed and b) after a certain time period of not being used.
### Starting a direct S3 upload
When an S3 direct upload is initiated with uppy, we first request a presigned PUT URL from the new `generate-presigned-put` endpoint in `UploadsController`. This generates an S3 key in the `temp` folder inside the correct bucket path, along with any metadata from the clientside (e.g. the SHA1 checksum described below). This will also create an `ExternalUploadStub` and store the details of the temp object key and the file being uploaded.
Once the clientside has this URL, uppy will upload the file direct to S3 using the presigned URL. Once the upload is complete we go to the next stage.
### Completing a direct S3 upload
Once the upload to S3 is done we call the new `complete-external-upload` route with the unique identifier of the `ExternalUploadStub` created earlier. Only the user who made the stub can complete the external upload. One of two paths is followed via the `ExternalUploadManager`.
1. If the object in S3 is too large (currently 100mb defined by `ExternalUploadManager::DOWNLOAD_LIMIT`) we do not download and generate the SHA1 for that file. Instead we create the `Upload` record via `UploadCreator` and simply copy it to its final destination on S3 then delete the initial temp file. Several modifications to `UploadCreator` have been made to accommodate this.
2. If the object in S3 is small enough, we download it. When the temporary S3 file is downloaded, we compare the SHA1 checksum generated by the browser with the actual SHA1 checksum of the file generated by ruby. The browser SHA1 checksum is stored on the object in S3 with metadata, and is generated via the `UppyChecksum` plugin. Keep in mind that some browsers will not generate this due to compatibility or other issues.
We then follow the normal `UploadCreator` path with one exception. To cut down on having to re-upload the file again, if there are no changes (such as resizing etc) to the file in `UploadCreator` we follow the same copy + delete temp path that we do for files that are too large.
3. Finally we return the serialized upload record back to the client
There are several errors that could happen that are handled by `UploadsController` as well.
Also in this PR is some refactoring of `displayErrorForUpload` to handle both uppy and jquery file uploader errors.
This PR contains only tests. These tests are from my old PR with refactoring of future-date-input-selector. That PR was closed because we had some changes in our planes about our time-pickers and additionally these tests were flaky.
Tests in this PR aren't flaky, since they use fake time moments in the future. Tests just document current behaviour of future-date-input-selector.
Will show the last 6 seen users as filtering suggestions when typing @ in quick search. (Previously the user suggestion required a character after the @.)
This also adds a default limit of 6 to the user search query, previously the backend was returning 20 results but a maximum of 6 results was being shown anyway.
Configuring staged users to watch categories and tags is a way to sign
them up to get many emails. These emails may be unwanted and get marked
as spam, hurting the site's email deliverability.
Users can opt-in to email notifications by logging on to their
account and configuring their own preferences.
If staff need to be able to configure these preferences on behalf of
staged users, the "allow changing staged user tracking" site setting
can be enabled. Default is to not allow it.
Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
* FIX: Clear stale status of reloaded reviewables
Navigating away from and back to the reviewables reloaded Reviewable
records, but did not clear the "stale" attribute.
* FEATURE: Show user who last acted on reviewable
When a user acts on a reviewable, all other clients are notified and a
generic "reviewable was resolved by someone" notice was shown instead of
the buttons. There is no need to keep secret the username of the acting
user.
Replaces the autocomplete overlay for categories and usernames on the search input and adds suggestions as items in the search results instead. Also adds the same behaviour for @mentions as well as special `in: status: order:` keywords. See PR for more details.
Currently when bulk-awarding a badge that can be granted multiple times, users in the CSV file are granted the badge once no matter how many times they're listed in the file and only if they don't have the badge already.
This PR adds a new option to the Badge Bulk Award feature so that it's possible to grant users a badge even if they already have the badge and as many times as they appear in the CSV file.
This PR adds the first use of Uppy in our codebase, hidden behind a enable_experimental_image_uploader site setting. When the setting is enabled only the user card background uploader will use the new uppy-image-uploader component added in this PR.
I've introduced an UppyUpload mixin that has feature parity with the existing Upload mixin, and improves it slightly to deal with multiple/single file distinctions and validations better. For now, this just supports the XHRUpload plugin for uppy, which keeps our existing POST to /uploads.json.
User flair was given by user's primary group. This PR separates the
two, adds a new field to the user model for flair group ID and users
can select their flair from user preferences now.
A more complex algorithm was used to achieve consensus between server
and client lists of notifications. This commit uses a different and
more simple approach that ignores order, but updates read status of
existing notifications and removes stale notifications.
And also move all the "top topics by period" routes to query string param.
/top/monthly => /top?period=monthly
/c/:slug/:id/l/top/monthly => /c/:slug/:id/l/top?period=monthly
/tag/:slug/l/top/daily => /tag/:slug/l/top?period=daily (new)
This PR changes the order of the topic timer options
into a more logical order when the topic is open/closed.
Also, we are now hiding the "Schedule Publishing" option
if the topic is not a private message or in a private category.
It does not make sense to schedule publishing to a different
category for a public topic.
Multiselect data can be saved but when all are removed then data are not cleared
Ajax function is removing an empty array from request data. In that case, we should change `[]` to `null`.
We need that empty values to properly empty data.
The error was:
```
↪ Unit | Model | topic::recover [✔]
↪ Unit | Utility | emoji::emojiUnescape [✔]
↪ Unit | Utility | pretty-text::quoting a quote [✔]
↪ Unit | Utility | click-track::routes to internal urlsUnhandled request in test environment: /forum/t/1234/recover (PUT)
Error: Unhandled request in test environment: /forum/t/1234/recover (PUT)
at Pretender.server.unhandledRequest (discourse/tests/setup-tests:173:15)
at Pretender.handleRequest (pretender:400:14)
at FakeRequest.send (pretender:169:21)
at Object.send (jquery:10100:10)
at Function.ajax (jquery:9683:15)
at performAjax (discourse/app/lib/ajax:174:19)
at eval (discourse/app/lib/ajax:183:11)
at invokeCallback (ember:63104:17)
at publish (ember:63087:9)
at eval (ember:57463:16)
[✘]
```
* DEV: Don't duplicate a function
We changed (https://github.com/discourse/discourse/pull/13407) behaviour of the topic level bookmark button recently. That PR made the button be opening the edit bookmark modal when there is only one bookmark on the topic instead of just removing that bookmark as it was before.
This PR fixes the next problems that weren't taken into account in the previous PR:
1. Everything should work fine even on very big topics when a bookmarked post is unloaded from the post stream. I've added code that loads the post we need and makes everything work as expected
2. When at least one bookmark on the topic has a reminder, we should always be showing the icon with a clock on the topic level bookmark button
3. We should show correct tooltips for the topic level bookmark button
We want to submit the flag modal on pressing CTRL + ENTER and CMD + ENTER.
Here's how our modals work:
Every modal can be dismissed by pressing ESC. This behaviour can be disabled for a specific modal if we need to.
Every modal can be submitted by pressing ENTER if the cursor wasn't on a text area or a form at the moment of pressing.
Now, the flag modal is actually a one big form and pressing ENTER doesn't submit it. I've added submitting by CTRL+ENTER but at first it was interfering with the basic modal submitting by ENTER. It's a pretty tricky thing to fix because we use the keyup event for submitting by ENTER and we need to use the keydown event for submitting with modifiers (because submitting by CMD+ENTER on Macs doesn't work with keyup).
Eventually, I fixed the problem just by adding a possibility to disable default submitting on ENTER (in the same way as we already have the possibility of disabling dismissing on ESC). Then I disabled default submitting for the flag form and implemented submitting by CTRL+ENTER and CMD+ENTER. This way everything is simple and robust. I did it only for the flag modal but it'll be easy and safe to add the same behaviour to another modal.
Add Members could also invite new users via emails, but that was a less
known fact. Splitting the previous modal into two more accessible
modals should make this feature more discoverable.
Effectively reverts 3ddc33b07c
Makes the failure states testable; see the uncommented test.
I don't think we're re-catching these errors anyway?
_update:_
We did in a single instance in discourse-code-review but it wasn't really intentional and I fixed it in https://github.com/discourse/discourse-code-review/pull/73
* pretender wasn't catching the request because it ran after this test finished
* restore wasn't needed, we do `sinon.restore()` after each test
The error was:
```
↪ Unit | Model | user::resolvedTimezone [✔]
↪ Unit | Utility | url::routeTo with prefixUnhandled request in test environment: /forum/u/chuck.json (PUT)
Error: Unhandled request in test environment: /forum/u/chuck.json (PUT)
at Pretender.server.unhandledRequest (discourse/tests/setup-tests:173:15)
at Pretender.handleRequest (pretender:400:14)
at FakeRequest.send (pretender:169:21)
at Object.send (jquery:10100:10)
at Function.ajax (jquery:9683:15)
at performAjax (discourse/app/lib/ajax:174:19)
at eval (discourse/app/lib/ajax:183:11)
at invokeCallback (ember:63104:17)
at publish (ember:63087:9)
at eval (ember:57463:16)
[✘]
```
A minimal reproduction:
`http://localhost:3001/qunit?seed=3&testId=da76996b&testId=e52a53e7`
Sometimes oneboxes contain the same link multiple times and the link
count was shown for each of them. This commit adds link count only to
the most important link, that being either a heading or the header of
the onebox.
The generated regular expressions did not contain \b which matched
every text that contained the word, even if it was only a substring of
a word.
For example, if "art" was a watched word a post containing word
"artist" matched.
The dismiss new keyboard shortcut (x,r) has been broken since
7a79bd7da3. A fix was done and JS
tests were added in 006d52f32b
and b01e4738ab but the test was not
quite correct and so the bottom dismiss new button was not clicked.
This also fixes an issue with our keyboard shortcut click handling.
If multiple elements matched the selector they were all clicked. Now
we just click the first match.
If you click on a bookmark in the post stream you get an Edit Bookmark modal. This does not happen if you click the topic bookmark button.
We want to open the Edit modal too if there is only one bookmark on a topic (it doesn't matter on the first post or not). The other behaviour if there are > 1 bookmarks in the topic is to prompt the user to confirm delete of all the bookmarks in the topic. This behaviour will stay as-is.
I have done some refactoring in this PR, and still, there is a place for improvement. For example, we don't call post.deleteBookmark() method when deleting several bookmarks. I just don't want to refactor too much in one PR.
The first thing we needed here was an enum rather than a boolean to determine how a directory_column was created. Now we have `automatic`, `user_field` and `plugin` directory columns.
This plugin API is assuming that the plugin has added a migration to a column to the `directory_items` table.
This was created to be initially used by discourse-solved. PR with API usage - https://github.com/discourse/discourse-solved/pull/137/
Adds the last updated at and by SMTP/IMAP fields to the UI, we were already storing them in the DB. Also makes sure that `imap_mailbox_name` being changed makes the last_updated_at/by field update for IMAP.
Steps to reproduce the bug:
- Create bookmarks for several posts on a topic
- Click the topic level bookmark button, it’ll open the modal that asks to confirm clearing all bookmarks from the topic
- Choose No
- Try to push the topic level bookmark button again - it won’t work
And it's fixed with this commit
This can happen when an avatar-flair component is rendered to an anonymous user on a login_required site (e.g. when they are redeeming an invite). The lack of group information was causing an error to be raised. With this commit, it now simple skips rendering the flair.
* Revert "DEV: skips three tests following cc1e73 (#13386)"
This reverts commit 2be201660a.
* FIX: Do not refresh post stream twice
This also improves the test suite and simulates a long running request
* FIX: Update local copy of raw
Next Week should mean next Monday, Next Month - the first day of the next month, and so on.
Also, we'll be using the name "Next Monday" instead of "Next Week" because it's easier to understand. No one can get confused by next Monday.
* DEV: skips two tests following cc1e73
Following the fix in cc1e73b8e4 we now refresh the whole stream which causes expected states of these tests to not exist anymore.
I'm skipping theses tests while we decide for a better fix.