## Cause
When 'selectRecommendedContentForUri' is filtering results against the blocklist, it relies on claim data which could be unresolved yet. It filters to empty results for this scenario, hence the flashing message.
## Change
Just return raw results when claims are not resolved yet, so that the GUI knows it needs to show the placeholders while resolving. After resolving, it'll go through the blocklist filtering again.
This is a follow-up for #1016
Still doesn't affect anything at the moment, but this reduce the amount of work needed when we yank out the homepages from our bundle later.
Currently, homepages are still build as part of the app, so this change doesn't bring much benefit other than to support the wrapper app.
When the service is moved away from the app, we won't have to rebuild the app when homepages change, and also the ui.js bundle would be smaller without the need to code-split.
The function argument order can be a bit ugly, but putting it at the back as optional param makes it easier to test without affecting existing clients.
* Bump react-draggable
Old version was giving out console errors for outdated react functions
* Refactor fileRenderFloating
* Merge fileRenderMobile into fileRenderFloating
* Fixes from review
* Attempt fix failed to view live
* Band-aid: warn about AdGuard blocking our CDN
Getting 50-100 errors per day. This will be a band-aid until the AdGuard fixes it.
* update message
Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
## Ticket
lbry-desktop 6841
## Issue
If you are already at the homepage and you click the "Home" or "Odysee" logo again, the entire page reloads, causing the whole startup sequence to re-run (lots of fetches).
This can be annoying when not intended (e.g. clicked too many times), as startup is slow for some and we also lose non-persistent Redux data (for debugging).
I believe the requirement was just to reload the homepage tiles, as they might be showing stale ones after a while. A full reload was the quick-and-easy fix.
## Approach
Best not to touch the complicated `ClaimTilesDiscover`, so just clear the search cache key in this scenario. `ClaimTilesDiscover` will then pick this up and perform a new `claim_search`.
## Issue
534 prevent new channel creation over x channels
## Notes
Also handled from the API call (`doCreateChannel`) in case there are other UI components that create channels.
- The previous 2MB was a CDN limit (more of a mistake). That has been increased to a far greater number, so we're setting a more reasonable 5MB limit.
- The previous code checks/shows the "size exceeded" message after the file has been uploaded ... in the `catch` block. This will not work since the CDN now allows a 5MB file.
- Fixed by checking the size before actually uploading.
- CommentsList needs to return a title with comment amounts
- mobile player dimensions needed to fill in the cover
- hid livestream header for now until figuring out a better presentation
- ~colum-reverse~ was causing problems with MUI's drawer scrolling, so reversed the chat order and made it ~column~ by default
- Hid bottom expand navigation if component not yet opened
- some other style changes in the middle
- Only pass necessary props
- Created new initialize play redux action
- Removed deprecated ~file app download~ keyboard functionality, moved preventDefault to videojs keyboard events to prevent ~space~ moving down the page
- moved doPrepareEdit incognito and channel calls to separate action
- same with download
- Tooltips
- No major differences between mobile or default returned components so removed condition since mobile style will be changed later on this branch
* Patch to restore position upon returning to video until more fully fleshed out fix can be introduced
* Add code to notify other open tabs of position saving
* Fix typo
* Wrap localStorage access in try/catch in event browser settings make it unavailable
* Remove formatting changes
* Move constant from 'pages' to 'player'
* Move dispatch out of try/catch
* Fixed typo
* fix RESOLVE_URIS_COMPLETED, to have one large action instead of hundreds of small ones
* Addressed comments.
* Addressed comments.
* Addressed comments.
Co-authored-by: Asad Umar <au@visuary.fr>
## Issue
Closes 385
## Approach
As mentioned in the ticket, the current places where that info is needed is in the Invites Page and Social Share Component.
1. Invites Page: it is already doing the fetch on mount, so no issue there.
2. Social Share: show spinner until the data is fetched.
## Issue
A huge list like http://localhost:9090/$/list/d91815c1bc8c80a1f354284a8c8e92d84d5f07a6 (193 items) often produces fewer results in the final rendered list.
## Cause
The same list of IDs was passed into `claim_search`, and we just increment the `page`. However, it seems like `claim_search` does not honor the order between each call, and some results from the previous page took the place of the results in the next page. The total is the same, but there are now duplicates.
## Fix
When batching, slice the ID list ourselves to bypass the problem for now.
Commentron json params are usually underscored instead of camel-cased.
Double-checked commentron code:
```
// MentionedChannel channels mentioned in comment
type MentionedChannel struct {
ChannelName string `json:"channel_name"`
ChannelID string `json:"channel_id"`
}
```
The parameter should probably also be skipped instead of sending empty array, but leaving as-is for now since that is minor.
## Ticket
644 investigate following vs subscription preference data
## Issue
- iOS app uses colon for following/subscriptions.
- Front-end code handled the "colon vs. hash" for CHANNEL_SUBSCRIBE, but not for CHANNEL_UNSUBSCRIBE
* Refactor CommentBadge
* Refactor livestreamComment component
* Refactor and split livestreamComment CSS
* Refactor livestreamComments component
* Refactor and split livestreamComments CSS
* Remove never used spinner
* Refactor livestream Page
* Refactor page component
* Refactor livestreamLayout component
* Break apart livestreamComments into separate sibling components
- This helps separating LivestreamComments to deal with only the comments, and the LivestreamLayout to be used for its own Page as a Popout option, and also for a layered approach for mobile
* Create Popout Chat Page, Add Popout Chat Menu Option
* Add Hide Chat option
* sockety improvements
* Websocket changes
Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
* Refactor doAbandonClaim parameters to only claim
- Gets txid and nout by default now, and passing claim allows using more data to verify ownership in case of txid:nout failing again
- Unused on modalRemoveCard
- Edited the comment on doCollectionDelete to explain better
* Fix doAbandonClaim failing to select my claim
* Add ordering Icons
* Refactor doCollectionEdit
- It required claims as parameter, when only uris are used to populate the collection, so that was changed to pass down the uris instead.
- There were unused and mostly unnecessary functions inside, for example the parameter claimIds was never used so it would never enter the claimSearch function which again would be used to generate uris, so it's better to just use uris as parameter
* Add List Reordering changes
* Add toggle button for list editing
* Add toggle on content page collection sidebar
* Enable drag-n-drop to re-order list items
https://www.youtube.com/watch?v=aYZRRyukuIw
* Allow removing all unavailable claims from a List
* Fix <g> on icons
* Fix section buttons positioning
* Move preventDefault and stopPropagation to buttons div instead of each button, preventing clicking even if disabled opening the claim
* Change dragging cursor
* Fix sizing
* Fix dragging component
* Restrict dragging to vertical axis
* Ignore shuffle state for ordering
* Fix console errors
* Mobile fixes
* Fix sidebar spacing
* Fix grey on mobile after click
- activeChannelClaim is the comment creator, not receiver.
- doSeeNotifications marks a notification as "seen", and not "send a notification".
- Removed comments that are just explaining the syntax.
* Add a setting to hide scheduled livestreams from home/following
* Add a hide button in the scheduled stream header.
* Fix typo + make sure pref is synced
## Issue
- When signing up, the "channel suggestions" page was stuck because `prefsReady` was never set as `preference_get` was never called.
- It was never called due to the optimizations to skip it when there is no difference between the local and server wallet.
## Change
- The first ever `sync/get` will result in a "no wallet" error, and there is already a `catch` to handle it. But the change in 38c13cf5 caused the `catch` to be skipped and went directly to `sync_apply` instead. Although the `catch` is also doing the same thing (`sync_apply`), it also has an additional callback to call `preference_get`.
- Fixed by ensuring this scenario ends up in the `catch` block like it was originally intended.
- We also did some optimization in the callback to skip the final `preference_get` if there is no difference in hash. But for the case of signing up, we do want to run it (so that `prefsReady` and other stuff gets initialized), so pass `hasNewData = true` to the callback.
## Issue
- Closes 592 Force clear stuck upload
- It was possible for an upload to stay "locked" e.g. when browser is killed.
## Change
When refreshing or opening a new tab, always clear the locks. The on-going sessions will re-lock them immediately.
## Issue
In the original Desktop code, new strings encountered during runtime will be automatically added to the local `app-strings.json` file. The feature is unavailable in Web because writing to file would require explicit permissions.
## Change
Partially restore the functionality by saving the strings to memory and retrieving it from the console via `copy(window.new_strings)`. It's a little bit of manual work, but I think it is good as it forces a sanity check before committing (previously, experimental/developmental strings are committed and being translated in Transifex).
* Update publish form when editing livestream + update to radios for liveststream release time
* Fix bug where upload tools may remain visible upon switching upload type, even when no option to upload is available.
* move publish source state up, when editing livestream only show scheduling option when source is none.
* Reset any set release time if switching to live stream mode.
* Update date/time cmpnts to reset any chnages they made on umount. Update schedule date/time cmpnt to clear release time when selecting anytime option.
* Additional filtering of internal tags
* Default to replay view when editing a liveststream
- Query for that tag in the upcoming section
- Improve special tag organization
- Filter out the internal tags in the UI
- Add missing types to publish state
## Issue
- Slow for users with many channels.
- The need to do it in Sync Loop is more applicable to Desktop.
## Approach
- Just remove it from sync loop.
- It will still be called when entering `/$/channels`, so that's an alternative to fix things when it is out of sync.
## Issue
In 38c13cf5, an additional `doGetAndPopulatePreferences` was added in `doSignIn` to ensure we have populated the preferences at least once. With that, `doHandleSyncComplete` can skip `doGetAndPopulatePreferences` is there is no change in the hash.
The addition broke the "initial sync lock", thus incorrectly allowing users to change preferences when the process is not completed.
## Change
I think the additional call is no longer needed since we now store a local hash for comparison, so `doGetAndPopulatePreferences` wouldn't be incorrectly skipped in the first ever `doHandleSyncComplete`.
## Repro
1. Follow a channel.
2. When `preference_set` is sent, unfollow the channel.
3. A few seconds later, the final setting reflects (1) instead of (2).
The current sync loop involves doing a final `sync/get` at the end. While not necessary for the scenario above, the code flow covers various cases, so it's still needed for now.
## Approach
Implement an abort mechanism to the sync-loop.
When syncing from the `buildSharedStateMiddleware` loop, generate an ID for each sync session, and only store the latest one. Pass the ID to the completion-callback (and other places as needed), so we can check if our session is still the latest one before executing the callback.
The check for invalidation can also be placed in more places to cut off the sync process earlier, but it's only done for 2 critical places for now.
Props: either use primitives, stable references or memoized objects to reduce renders. This update will simplify the existing `areEqual`. It is still needed though as some props are hard to memoize from where they are called, or should simply be ignore.
## Ticket
Part of "#385 Defer api.odysee.com calls to their respective pages / install new"
## Change
Pull out `notification/categories` from `doNotificationList` and fetch it in Notifications Page. I don't there there are other places that need it for now.
## Repro
- Do a search like "Test" and filter to "Channels Only".
- Follow a bunch @test channels
- Unfollow any one of them. All of them gets unfollowed.
## Notes
The change in b9fc9b63 to compare the lower-case channel name probably made it even worse, since "@TeSt" would be removed too.
## Change
Not sure why channel name was used in the first place ... perhaps it was to cover canon vs perm uri?
Anyway, comparing uri makes more sense, so doing that instead.
## Issue
Apparently, a user is experiencing 423 locked errors from the server, which should not happen given the locking mechanism plus the user wasn't trying to do concurrent uploads.
## Fix
Anyway, fix the Resume button so that at least they can try to resume.
* Add ability to have claim searches auto-fetch up to 3 pages.
* make total_items and total_pages optional
* use auto pagination strategy when determining live claim
* Bump page size back to 50
* Upload: fix redux key clash
## Issue
`params` is the "final" value that will be passed to the SDK and `channel` is not a valid argument (it should be `channel_name`). Also, it seems like we only pass the channel ID now and skip the channel name entirely.
For the anonymous case, a clash will still happen when since the channel part is hardcoded to `anonymous`.
## Approach
Generate a guid in `params` and use that as the key to handle all the cases above. We couldn't use the `uploadUrl` because v1 doesn't have it.
The old formula is retained to allow users to retry or cancel their existing uploads one last time (otherwise it will persist forever). The next upload will be using the new key.
* Upload: add tab-locking
## Issue
- The previous code does detect uploads from multiple tabs, but it was done by handling the CONFLICT error message from the backend. At certain corner-cases, this does not work well. A better way is to not allow resumption while the same file is being uploading from another tab.
- When an upload from 1 tab finishes, the GUI on the other tab does not remove the completed item. User either have to refresh or click Cancel. Clicking Cancel results in the 404 backend error. This should be avoided.
## Approach
- Added tab synchronization and locking by passing the "locked" and "removed" information through `localStorage`.
## Other considered approaches
- Wallet sync -- but decided not to pollute the wallet.
- 3rd-party redux tab syncing -- but decided it's not worth adding another module for 1 usage.
* Upload: check if locked before confirming delete
## Reproduce
Have 2 tabs + paused upload
Open "cancel" dialog in one of the tabs.
Continue upload in other tab
Confirm cancellation in first tab
Upload disappears from both tabs, but based on network traffic the upload keeps happening.
(If upload finishes the claim seems to get created)
I yanked out the parseURI part in a prior commit ... the comment was misleading me to think it was redundant. But it had another hidden function, which is to handle abandoned claims which `claim` will be `null`.
* Route recommendation search to recsys 5% of the time + add `user_id`
## Ticket
334 send some recommended requests to recsys
## Approach
`doSearch`:
- If the search options include `related_to`, route that to the new `searchRecommendations` which performs the 5% check + appends `user_id` at the end. This way, we don't need to alter the function signature of `doSearch`.
- Else, run proceed as normal.
* Always go to alt provider
f
Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
* Add remove_duplicates to tile/list claim_search except for Channel Page
This removes the any duplicates from reposts.
* Re-activate the "Hide reposts" setting
* Category Rows: default to ['stream', 'repost'] unless specified otherwise.
* apiCall: add option to not send the auth header
## Why
Want an option to make un-authenticated `resolve` calls where appropriate, to improve caching.
## How
All `apiCall`s are authenticated by default, but when clients add NO_AUTH to the params, `apiCall` will exclude the X_LBRY_AUTH_TOKEN. It will also strip NO_AUTH from the param object before sending it out.
* Add hook for 'resolve' and 'claim_search' to check and skip auth...
... if the params does not contain anything that requires the wallet.
* doResolveUri, doClaimSearch: let clients decide when to include_my_output
- No more hardcoding 'include_purchase_receipt' and 'include_is_my_output'
- doResolveUri: include these params when opening a file page. This was the only place that was doing that prior to this PR.
* is_my_output: use the signing_channel as alternative
## Notes
`is_my_output` is more expensive to resolve, so it is not being requested all the time.
## Change
Looking at the signing channel as the additional fallback, on top of `myClaimIds`.
## Aside
I think using `myClaimIds` here is redundant, as it is usually populated from `is_my_ouput`. But leaving as is for now...
## Why
- No memo required (no transformation).
- `makeSelect*` is an incorrect pattern.
## Changes
- Replaced makeSelectClientSetting with selectClientSetting.
- Remove unused selectShowRepostedContent.
## Issue
- Large resolve count (albeit batched) on bootup.
## Changes
- Skip the call on bootup. The same call will happen when you click the notification bell, so it's not too late to resolve at that time.
- Added `true` to `doResolveUris` to return cached results, otherwise it will keep resolving the same channels every time we enter Notifications Page.
## Issue
One of the bottlenecks of livestream page.
The component probably needs a re-design:
- Don't perpetually mount -- only mount when activated by the user through "@". This would avoid the heavy processing entirely.
- Better way of resolving uris (too many arrays, too many loops).
- Tom also mentioned that we should not be resolving every commenter as we see encounter them in a livestream. This is currently the case because the component is always mounted.
## Changes
Until the re-design occurs, attempt to cache the heavy processing. Also, trimmed down the amount of loops.
For the case of livestreams, the comments are added incrementally via websocket. The selector returns everything, which grows as a user watches the livestream.
We could even make it a bit more efficient by passing in `maxCount` to `filterComments`, and do a `for` loop there, but decided to keep things readable by not changing the `filter` usage.
- Memo not required. `resolvingUris` is very dynamic and is a short array anyways.
- Changeg from using `indexOf` to `includes`, which is more concise.
## Changes
- doHandleSyncComplete: only call doGetAndPopulatePreferences when there is new data.
- But for that to work, we'll need to populate preferences at least once. We'll do that in doSignIn.
- We can also remove the "sync/prefs ready" mechanism that was mainly meant for Desktop.
Then came another problem: while trying to spark changes between 2 tabs, `sync/get` was saying "no change" despite the local and server hash being different. I think it is because the both `sync_hash + sync/get` combo is operating on server data, so the hash is the same. I'm guessing this is why we ended up just running doGetAndPopulatePreferences every time before PR, since this flag wasn't correct in this scenario.
- Updated `data.changed` to consider both API results and comparison with local hash.
## Issue
When the muted list was being cleared from another app, the web version ended up restoring the previous muted list.
## Change
- As long as `blocked` is defined, return that since an empty array is a valid result.
- If undefined, something went wrong when calling the reducer, so retain the muted list. I believe this was the original intention of that line.
* Exclude default homepage data at compile time
The youtuber IDs alone is pretty huge, and is unused in the `CUSTOM_HOMEPAGE=true` configuration.
* Remove Desktop items and other cleanup
- Moved constants out of the component.
- Remove SIMPLE_SITE check.
- Remove Desktop-only items
* Sidebar: limit subscription and tag section
## Issue
Too slow for huge lists
## Change
Limit to 10 initially, and load everything on "Show more"
* Fix makeSelectThumbnailForUri
- Fix memo
- Expose function to extract directly from claim if client already have it.
## Issues with `makeSelectIsSubscribed`
- It will not return true if the uri provided is canonical, because the compared subscription uri is in permanent form. This was causing certain elements like the Heart to not appear in claim tiles.
- It is super slow for large subscriptions not just because of the array size + being a hot selector, but also because it is looking up the claim twice (not memo'd) and also calling `parseURI` to determine if it's a channel, which is unnecessary if you already have the claim.
## Changes
- Optimize the selector to only look up the claim once, and make operations using already-obtained info.
## Issue
If you make 2 claims from the same source file, the second upload thinks it's trying to resume from the first one. They should be unique uploads.
## Approach
Stash the upload url for comparison when looking up existing uploads to resume.
Stash that in `params` to minimize code changes. We'll just need to ensure it is cleared before we generate the SDK payload.
## Why
Frequently used; top in perf profile
## Changes
Most of the time, you already have the claim object in the current context. `selectClaimIsMineForUri` will retrieve the claim again, which is wasteful, even if it is memoized (looking up the cache still takes time).
Break apart the logic and added the alternative `selectClaimIsMine` for faster lookup.
* Publish button: use spinner instead of "Publishing..."
Looks better, plus the preview could take a while sometimes.
* Refactor `doPublish`. No functional change
This is to allow `doPublish` to accept a custom payload as an input (for resuming uploads), instead of always resolving it from the redux data.
* Add doPublishResume
* Support resume-able upload via tus
## Issue
38 Handle resumable file upload
## Notes
Since we can't serialize a File object, we'll need to the user to re-select the file to resume.
* Exclude "modified date" for Firefox/Android
## Issue
It appears that the modification date of the Android file changes when selected, so that file was deemed "different" when trying to resume upload.
## Change
Exclude modification date for now. Let's assume a smart user.
* Move 'currentUploads' to 'publish' reducer
`publish` is currently rehydrated, so we can ride on that and don't need to store the `currentUploads` in `localStorage` for persistence. This would allow us to store Markdown Post data too, as `localStorage` has a 5MB limit per app.
We could have also made `webReducer` rehydrate, but in this repo, there is no need to split it to another reducer. It also makes more sense to be part of publish anyway (at least to me).
This change is mostly moving items between files, with the exception of
1. An additional REHYDRATE in the publish reducer to clean up the tusUploader.
2. Not clearing `currentUploads` in CLEAR_PUBLISH.
* Restore v1 code for livestream replay, etc.
v2 (tus) does not handle `remote_url`, so the app still needs v1 for that. Since we'll still have v1 code, use v1 for previews as well.
- memo not required.
- start to not use the confusing and wrongly-named 'selectCommentsByUri' (per comment from Sean); use the existing 'selectClaimIdForUri' instead. This works because currently we do fetch any comments without first visiting a claim/uri, so we'll always have fetched the required claim, and can be queried in 'selectClaimIdForUri'.
It's technically incorrect and was causing the GUI to not update sometimes because the reference did not change, despite the array contents did. The GUI just happens to update most of the time due to other state changes.
## Issue
`normalizeUri` | `parseURI` is expensive and has been causing sluggish operations when called repeatedly or within a loop.
## Change
Since I'm not confident enough to remove the call entirely from makeSelectClaimIsMine (although I've yet to find a scenario that the uri is not already normalized), we'll try caching the calls instead.
## Results
- in a simple test of toggling between 2 category pages, we saved 20ms from `parseURI` calls alone.
- in a test of opening all categories one time, the memory usage remained similar. This makes sense since we removed a `makeSelect*` (which creates a selector for each call + not memoizing), and replaced that with a cached selector that's actually memoizing.
## Issue
- selectMyActiveClaims memo problem -- being recalculated on every click -- high workload for wallet with large uploads.
- Mistake in handling abandoning IDs (it was trying to extract keys from an array)
## Changes
- selectAbandoningIds: never use `state` as an input selector. Breaks memo.
- Don't use selectMyClaimsRaw and then reduce it back to IDs. Use selectMyClaimIdsRaw instead.
- selectAbandoningIds is already in array form, so don't run Object.keys.
- Fix abandoningById never clearing when succeeded.
* Simplify dispatch map
Since none of dispatches are doing any custom transformation, just use a direct map. The number of arguments for the comment function are getting crazy.
* Block: pass comment ID for deletion when being blocked.
* Don't update 'pendingById' if no changes.
'pendingById' isn't frequently updated, but using it as a proof-of-concept to fix how reducers should be written to avoid unnecessary updates.
ImmutableJS apparently does all of this for us, but there are cons to using it as well, so using own wrappers for now.
* Don't update 'byId' if no changes + add 'selectClaimWithId'
## Ticket
116 Claim store optimization ideas (reducing unnecessary renders)
## Changes
- Ignore things like `confirmations` so that already-fetched claims aren't invalidated and causes re-rendering. The `stringify` might look expensive, but the amount of avoided re-renders outweighs it. There might be faster ways to compare, though.
- With `byId[claimId]` references more stable now, memoized selectors can now use 'selectClaimWithId' to pick a specific claim to depend on, instead of 'byId' which changes on every update.
* Fix memo: selectMyChannelClaims, selectActiveChannelClaim
## Issue
These should never recalculate after `channel_list` has been fetched, but they do because of poor selector dependency.
## Change
With the `byId` changes from the previous commit, we are now able to memoize these selectors correctly.
## Ticket
223 Add ability for delegated moderators to delete comments
## Changes
- Refactored doCommentAbandon's signature so we don't end up converting between "uri" and "Claim" several times and needing to lookup redux when the client can already provide us the exact values that we need.
- Pass the new moderator fields to the API.
- Remove the need to call 'makeSelectChannelPermUrlForClaimUri' since it's a simple field query when we already have the claim.
- selectMyChannelClaims depends on `byId`, which currently is always invalidated per update, so it is not memoized.
- Most of the use-cases just needs the ID or the length of the array anyways, so avoid generating a Claim array (in selectMyChannelClaims) unnecessarily -- the client need to reduce it back down to IDs again :/
- The simpler boolean also removes the need to memoize the selector, which saves a bit of memory.
`selectMyActiveClaims` includes `byId`, which gets invalidated on each resolve. Having this as an input selector breaks memoization.
For the case of comment-filtering, we don't really care about pending or abandoned own claims (I think), so just grab the raw IDs.
* Prevent multiple parseURI calls
## Ticket
129
## Issue
Code was shortened to use `isURIValid` during the consolidation. `isURIValid` calls `normalizeURI`, which calls another `parseURI`.
`parseURI` is pretty expensive.
## Approach
- Add optional parameter to `isURIValid` to skip the normalization.
- Set those that were converted during the consolidation to skip the normalization. Also covered a few other instances where it is obvious to me that normalization is not required.
- For the rest, I can't tell for sure if it's safe to remove the normalization, so the default `normalize=true` will leave things as is.
The whole `parseURI` probably needs a refactoring, or a few lighter version for specific needs.
* Simplify isURIEqual
## Issue
`parseURI` is too expensive to be used in a loop, plus `normalizeURI` itself is calling `parseURI`.
## Approach
Not sure if it covers all cases, but just try convert colons to hashes before comparing.
* Refactor filePrice
* Refactor Wallet Tip Components
* Add backend sticker support for comments
* Add stickers
* Refactor commentCreate
* Add Sticker Selector and sticker comment creation
* Add stickers display to comments and hyperchats
* Fix wrong checks for total Super Chats