2022-09-08 00:30:04 -06:00
'use strict' ;
2023-05-03 03:45:33 -06:00
const { ViewModel , ObservableMap , ApplyMap } = require ( 'hydrogen-view-sdk' ) ;
2022-09-08 00:30:04 -06:00
2023-07-14 14:34:54 -06:00
const assert = require ( 'matrix-viewer-shared/lib/assert' ) ;
2022-09-08 00:30:04 -06:00
2023-07-14 14:34:54 -06:00
const LOCAL _STORAGE _KEYS = require ( 'matrix-viewer-shared/lib/local-storage-keys' ) ;
const ModalViewModel = require ( 'matrix-viewer-shared/viewmodels/ModalViewModel' ) ;
const HomeserverSelectionModalContentViewModel = require ( 'matrix-viewer-shared/viewmodels/HomeserverSelectionModalContentViewModel' ) ;
const RoomCardViewModel = require ( 'matrix-viewer-shared/viewmodels/RoomCardViewModel' ) ;
const checkTextForNsfw = require ( 'matrix-viewer-shared/lib/check-text-for-nsfw' ) ;
2023-06-30 02:08:32 -06:00
const { DIRECTION } = require ( '../lib/reference-values' ) ;
2022-10-20 01:06:43 -06:00
2023-06-06 10:33:56 -06:00
const DEFAULT _SERVER _LIST = [ 'matrix.org' , 'gitter.im' ] ;
2022-09-08 00:30:04 -06:00
class RoomDirectoryViewModel extends ViewModel {
constructor ( options ) {
super ( options ) ;
const {
2023-05-03 03:45:33 -06:00
basePath ,
2022-09-08 00:30:04 -06:00
homeserverUrl ,
homeserverName ,
2023-07-14 14:34:54 -06:00
matrixViewerURLCreator ,
2022-09-08 00:30:04 -06:00
rooms ,
2022-10-20 21:48:00 -06:00
roomFetchError ,
2022-10-21 01:09:26 -06:00
pageSearchParameters ,
2022-09-08 00:30:04 -06:00
nextPaginationToken ,
prevPaginationToken ,
} = options ;
2023-05-03 03:45:33 -06:00
assert ( basePath ) ;
2022-09-08 00:30:04 -06:00
assert ( homeserverUrl ) ;
assert ( homeserverName ) ;
2023-07-14 14:34:54 -06:00
assert ( matrixViewerURLCreator ) ;
2022-09-08 00:30:04 -06:00
assert ( rooms ) ;
2022-10-20 21:48:00 -06:00
this . _roomFetchError = roomFetchError ;
2022-09-08 00:30:04 -06:00
this . _homeserverUrl = homeserverUrl ;
this . _homeserverName = homeserverName ;
2023-07-14 14:34:54 -06:00
this . _matrixViewerURLCreator = matrixViewerURLCreator ;
2022-10-20 21:48:00 -06:00
2022-10-27 23:32:24 -06:00
this . _isPageRedirectingFromUrlHash = false ;
2022-10-21 01:09:26 -06:00
this . _pageSearchParameters = pageSearchParameters ;
// Default to what the page started with
this . _searchTerm = pageSearchParameters . searchTerm ;
2022-09-08 00:30:04 -06:00
this . _nextPaginationToken = nextPaginationToken ;
this . _prevPaginationToken = prevPaginationToken ;
2022-10-20 01:06:43 -06:00
2022-10-21 01:09:26 -06:00
// The default selected homeserver should be the one according to the page or the first one in the list
this . _homeserverSelection = pageSearchParameters . homeserver || this . _availableHomeserverList [ 0 ] ;
// The homeservers that the user added themselves (pulled from LocalStorage)
this . _addedHomeserversList = [ ] ;
2023-04-26 23:00:56 -06:00
this . loadAddedHomeserversListFromPersistence ( ) ;
2022-10-21 01:09:26 -06:00
// The default list of homeservers to select from
this . _calculateAvailableHomeserverList ( ) ;
2022-10-20 01:06:43 -06:00
this . _homeserverSelectionModalContentViewModel = new HomeserverSelectionModalContentViewModel ( {
onNewHomeserverAdded : this . onNewHomeserverAdded . bind ( this ) ,
} ) ;
this . homeserverSelectionModalViewModel = new ModalViewModel (
this . childOptions ( {
title : 'Add a new server' ,
contentViewModel : this . _homeserverSelectionModalContentViewModel ,
closeCallback : ( ) => {
const path = this . navigation . pathFrom ( [ ] ) ;
this . navigation . applyPath ( path ) ;
} ,
} )
) ;
2023-05-03 03:45:33 -06:00
// This is based off of
// https://github.com/vector-im/hydrogen-web/blob/e77727ea5992a3ec2649edd53a42e6d75f50a0ca/src/domain/session/leftpanel/LeftPanelViewModel.js#L30-L32
this . _roomCardViewModelsMap = new ObservableMap ( ) ;
rooms . forEach ( ( room ) => {
this . _roomCardViewModelsMap . set (
room . room _id ,
new RoomCardViewModel ( {
room ,
basePath ,
2022-10-21 15:06:11 -06:00
homeserverUrlToPullMediaFrom : homeserverUrl ,
2023-05-03 03:45:33 -06:00
viaServers : [
// If the room is being shown in the directory from this server, then surely
// we can join via this server
this . _pageSearchParameters . homeserver ,
] ,
} )
) ;
} ) ;
this . _roomCardViewModelsFilterMap = new ApplyMap ( this . _roomCardViewModelsMap ) ;
Fix Firefox sorting room cards in the wrong direction (#261)
Room cards will now sort by room members descending (highest to lowest) as expected.
Fix https://github.com/matrix-org/matrix-public-archive/issues/218
The `/publicRooms` (room directory) endpoint already returns rooms in the correct order which is why we didn't care about the order before but the different `[].sort(...)` implementations in browsers necessitates we be explicit about it. Ideally, we wouldn't have to use the `ObservableMap.sortValues()` method at all but it seems like one of the only ways to get the values out. In any case, maybe it's more clear what order things are in now.
This bug stems from the fact that `[1, 2, 3, 4, 5].sort((a, b) => 1)` returns different results in Chrome vs Firefox (found from https://stackoverflow.com/questions/55039157/array-sort-behaves-differently-in-firefox-and-chrome-edge)
- Chrome: `[1, 2, 3, 4, 5].sort((a, b) => 1)` -> `[1, 2, 3, 4, 5]` :white_check_mark:
- Firefox: `[1, 2, 3, 4, 5].sort((a, b) => 1)` -> `[5, 4, 3, 2, 1]` :x:
2023-06-02 16:19:58 -06:00
this . _roomCardViewModels = this . _roomCardViewModelsFilterMap . sortValues ( ( a , b ) => {
// Sort by the number of joined members descending (highest to lowest)
if ( b . numJoinedMembers > a . numJoinedMembers ) {
return 1 ;
} else if ( b . numJoinedMembers < a . numJoinedMembers ) {
return - 1 ;
}
return 0 ;
2023-05-03 03:45:33 -06:00
} ) ;
this . _safeSearchEnabled = true ;
this . loadSafeSearchEnabledFromPersistence ( ) ;
2022-10-21 15:06:11 -06:00
2022-10-20 01:06:43 -06:00
this . # setupNavigation ( ) ;
}
# setupNavigation ( ) {
// Make sure the add-server modal open when the URL changes
const handleAddServerNavigationChange = ( ) => {
const shouldShowAddServerModal = ! ! this . navigation . path . get ( 'add-server' ) ? . value ;
this . setShouldShowAddServerModal ( shouldShowAddServerModal ) ;
} ;
const addServer = this . navigation . observe ( 'add-server' ) ;
this . track ( addServer . subscribe ( handleAddServerNavigationChange ) ) ;
// Also handle the case where the URL already includes `#/add-server`
// stuff from page-load
const initialAddServer = addServer . get ( ) ;
handleAddServerNavigationChange ( initialAddServer ) ;
}
setShouldShowAddServerModal ( shouldShowAddServerModal ) {
this . homeserverSelectionModalViewModel . setOpen ( shouldShowAddServerModal ) ;
2022-09-08 00:30:04 -06:00
}
2022-10-27 23:32:24 -06:00
setPageRedirectingFromUrlHash ( newValue ) {
this . _isPageRedirectingFromUrlHash = newValue ;
this . emitChange ( 'isPageRedirectingFromUrlHash' ) ;
}
get isPageRedirectingFromUrlHash ( ) {
return this . _isPageRedirectingFromUrlHash ;
}
2022-09-08 00:30:04 -06:00
get homeserverUrl ( ) {
return this . _homeserverUrl ;
}
2022-10-20 21:48:00 -06:00
get homeserverName ( ) {
return this . _homeserverName ;
}
2022-09-08 00:30:04 -06:00
get roomDirectoryUrl ( ) {
2023-07-14 14:34:54 -06:00
return this . _matrixViewerURLCreator . roomDirectoryUrl ( ) ;
2022-09-08 00:30:04 -06:00
}
2022-10-21 01:09:26 -06:00
get pageSearchParameters ( ) {
return this . _pageSearchParameters ;
2022-10-20 21:48:00 -06:00
}
2022-09-15 19:41:55 -06:00
get searchTerm ( ) {
return this . _searchTerm || '' ;
}
setSearchTerm ( newSearchTerm ) {
this . _searchTerm = newSearchTerm ;
this . emitChange ( 'searchTerm' ) ;
}
2022-10-20 01:06:43 -06:00
setHomeserverSelection ( newHomeserver ) {
this . _homeserverSelection = newHomeserver ;
this . emitChange ( 'homeserverSelection' ) ;
}
onHomeserverSelectionAction ( action ) {
if ( action === 'action:add-new-server' ) {
const path = this . navigation . pathFrom ( [ this . navigation . segment ( 'add-server' ) ] ) ;
this . navigation . applyPath ( path ) ;
} else if ( action === 'action:clear-servers' ) {
this . setAddedHomeserversList ( [ ] ) ;
// After clearing the added servers, just fallback to the first one in the available list.
// We don't want people to be stuck on the "Clear servers" option.
this . _homeserverSelection = this . availableHomeserverList [ 0 ] ;
this . emitChange ( 'homeserverSelection' ) ;
} else {
console . warn ( ` Unknown action= ${ action } passed to \` onHomeserverSelectionAction \` ` ) ;
}
}
get homeserverSelection ( ) {
return this . _homeserverSelection ;
}
2023-04-26 23:00:56 -06:00
loadAddedHomeserversListFromPersistence ( ) {
2022-10-20 01:06:43 -06:00
if ( window . localStorage ) {
let addedHomeserversFromPersistence = [ ] ;
try {
addedHomeserversFromPersistence = JSON . parse (
2023-05-03 03:45:33 -06:00
window . localStorage . getItem ( LOCAL _STORAGE _KEYS . addedHomeservers )
2022-10-20 01:06:43 -06:00
) ;
} catch ( err ) {
console . warn (
2023-05-03 03:45:33 -06:00
` Resetting \` ${ LOCAL _STORAGE _KEYS . addedHomeservers } \` stored in LocalStorage since we ran into an error parsing what was stored ` ,
2022-10-20 01:06:43 -06:00
err
) ;
this . setAddedHomeserversList ( [ ] ) ;
return ;
}
if ( ! Array . isArray ( addedHomeserversFromPersistence ) ) {
console . warn (
2023-05-03 03:45:33 -06:00
` Resetting \` ${ LOCAL _STORAGE _KEYS . addedHomeservers } \` stored in LocalStorage since it wasn't an array as expected, addedHomeservers= ${ addedHomeserversFromPersistence } `
2022-10-20 01:06:43 -06:00
) ;
this . setAddedHomeserversList ( [ ] ) ;
return ;
}
this . setAddedHomeserversList ( addedHomeserversFromPersistence ) ;
return ;
} else {
console . warn (
2023-05-03 03:45:33 -06:00
` Skipping \` ${ LOCAL _STORAGE _KEYS . addedHomeservers } \` read from LocalStorage since LocalStorage is not available `
2022-10-20 01:06:43 -06:00
) ;
}
}
setAddedHomeserversList ( addedHomeserversList ) {
this . _addedHomeserversList = addedHomeserversList ;
window . localStorage . setItem (
2023-05-03 03:45:33 -06:00
LOCAL _STORAGE _KEYS . addedHomeservers ,
2022-10-20 01:06:43 -06:00
JSON . stringify ( this . _addedHomeserversList )
) ;
2022-10-21 01:09:26 -06:00
2023-05-03 03:45:33 -06:00
// If the added homeserver list changes, make sure the default page-selected
2022-10-21 01:09:26 -06:00
// homeserver is still somewhere in the list. If it's no longer in the added
// homeserver list, we will put it in the default available list.
this . _calculateAvailableHomeserverList ( ) ;
2022-10-20 01:06:43 -06:00
this . emitChange ( 'addedHomeserversList' ) ;
}
get addedHomeserversList ( ) {
return this . _addedHomeserversList ;
}
2023-05-03 03:45:33 -06:00
loadSafeSearchEnabledFromPersistence ( ) {
// Safe search is enabled by default and only disabled with the correct 'false' value
let safeSearchEnabled = true ;
if ( window . localStorage ) {
const safeSearchEnabledFromPersistence = window . localStorage . getItem (
LOCAL _STORAGE _KEYS . safeSearchEnabled
) ;
if ( safeSearchEnabledFromPersistence === 'false' ) {
safeSearchEnabled = false ;
}
} else {
console . warn (
` Skipping \` ${ LOCAL _STORAGE _KEYS . safeSearchEnabled } \` read from LocalStorage since LocalStorage is not available `
) ;
}
this . setSafeSearchEnabled ( safeSearchEnabled ) ;
}
setSafeSearchEnabled ( safeSearchEnabled ) {
this . _safeSearchEnabled = safeSearchEnabled ;
if ( window . localStorage ) {
window . localStorage . setItem (
LOCAL _STORAGE _KEYS . safeSearchEnabled ,
safeSearchEnabled ? 'true' : 'false'
) ;
} else {
console . warn (
` Skipping \` ${ LOCAL _STORAGE _KEYS . safeSearchEnabled } \` write to LocalStorage since LocalStorage is not available `
) ;
}
if ( safeSearchEnabled ) {
this . _roomCardViewModelsFilterMap . setApply ( ( roomId , vm ) => {
// We concat the name, topic, etc together to simply do a single check against
// all of the text.
2023-05-05 14:36:26 -06:00
const isNsfw = checkTextForNsfw ( vm . name + ' --- ' + vm . canonicalAlias + ' --- ' + vm . topic ) ;
2023-05-03 03:45:33 -06:00
vm . setBlockedBySafeSearch ( isNsfw ) ;
} ) ;
} else {
this . _roomCardViewModelsFilterMap . setApply ( null ) ;
this . _roomCardViewModelsFilterMap . applyOnce ( ( roomId , vm ) => {
vm . setBlockedBySafeSearch ( false ) ;
} ) ;
}
this . emitChange ( 'safeSearchEnabled' ) ;
}
get safeSearchEnabled ( ) {
return this . _safeSearchEnabled ;
}
2022-10-20 01:06:43 -06:00
onNewHomeserverAdded ( newHomeserver ) {
const addedHomeserversList = this . addedHomeserversList ;
this . setAddedHomeserversList ( addedHomeserversList . concat ( newHomeserver ) ) ;
this . setHomeserverSelection ( newHomeserver ) ;
}
2022-10-20 21:48:00 -06:00
get roomFetchError ( ) {
return this . _roomFetchError ;
}
2022-09-08 00:30:04 -06:00
get nextPageUrl ( ) {
if ( this . _nextPaginationToken ) {
2023-07-14 14:34:54 -06:00
return this . _matrixViewerURLCreator . roomDirectoryUrl ( {
2022-10-21 14:52:07 -06:00
homeserver : this . homeserverSelection ,
2022-09-15 19:41:55 -06:00
searchTerm : this . searchTerm ,
2022-09-08 00:30:04 -06:00
paginationToken : this . _nextPaginationToken ,
2023-06-30 02:08:32 -06:00
direction : DIRECTION . forward ,
2022-09-08 00:30:04 -06:00
} ) ;
}
return null ;
}
get prevPageUrl ( ) {
if ( this . _prevPaginationToken ) {
2023-07-14 14:34:54 -06:00
return this . _matrixViewerURLCreator . roomDirectoryUrl ( {
2022-10-21 14:52:07 -06:00
homeserver : this . homeserverSelection ,
2022-09-15 19:41:55 -06:00
searchTerm : this . searchTerm ,
2022-09-08 00:30:04 -06:00
paginationToken : this . _prevPaginationToken ,
2023-06-30 02:08:32 -06:00
direction : DIRECTION . backward ,
2022-09-08 00:30:04 -06:00
} ) ;
}
return null ;
}
2022-10-21 01:09:26 -06:00
// The default list of available homeservers to select from. Deduplicates the the
// homeserver we're pulling from and the `DEFAULT_SERVER_LIST`. Also makes sure that
// the page selected homeserver is either in our default available list or in the
// added servers list.
_calculateAvailableHomeserverList ( ) {
2022-09-08 00:30:04 -06:00
// Append the default homeserver to the front
const rawList = [ this . _homeserverName , ... DEFAULT _SERVER _LIST ] ;
2022-10-21 01:09:26 -06:00
// Make sure the page selected homeserver is in the list somewhere
if (
this . homeserverSelection &&
! rawList . includes ( this . homeserverSelection ) &&
! this . _addedHomeserversList . includes ( this . homeserverSelection )
) {
rawList . unshift ( this . homeserverSelection ) ;
}
2022-09-08 00:30:04 -06:00
// Then deduplicate the list
const deduplicatedHomeserverMap = { } ;
rawList . forEach ( ( homeserverName ) => {
deduplicatedHomeserverMap [ homeserverName ] = true ;
} ) ;
const deduplicatedHomeserverList = Object . keys ( deduplicatedHomeserverMap ) ;
2022-10-21 01:09:26 -06:00
this . _availableHomeserverList = deduplicatedHomeserverList ;
2022-10-27 23:32:24 -06:00
this . emitChange ( 'availableHomeserverList' ) ;
2022-10-21 01:09:26 -06:00
}
get availableHomeserverList ( ) {
return this . _availableHomeserverList ;
2022-09-08 00:30:04 -06:00
}
2023-05-03 03:45:33 -06:00
get roomCardViewModels ( ) {
return this . _roomCardViewModels ;
2022-09-08 00:30:04 -06:00
}
}
module . exports = RoomDirectoryViewModel ;