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
const assert = require ( 'matrix-public-archive-shared/lib/assert' ) ;
2023-05-03 03:45:33 -06:00
const LOCAL _STORAGE _KEYS = require ( 'matrix-public-archive-shared/lib/local-storage-keys' ) ;
2022-10-20 01:06:43 -06:00
const ModalViewModel = require ( 'matrix-public-archive-shared/viewmodels/ModalViewModel' ) ;
const HomeserverSelectionModalContentViewModel = require ( 'matrix-public-archive-shared/viewmodels/HomeserverSelectionModalContentViewModel' ) ;
2023-05-03 03:45:33 -06:00
const RoomCardViewModel = require ( 'matrix-public-archive-shared/viewmodels/RoomCardViewModel' ) ;
2022-10-20 01:06:43 -06:00
2022-09-08 00:30:04 -06:00
const DEFAULT _SERVER _LIST = [ 'matrix.org' , 'gitter.im' , 'libera.chat' ] ;
2023-05-03 03:45:33 -06:00
const NSFW _WORDS = [ 'nsfw' , 'porn' , 'nudes' , 'sex' , '18+' ] ;
const NSFW _REGEXES = NSFW _WORDS . map ( ( word ) => new RegExp ( ` \\ b ${ word } \\ b ` , 'i' ) ) ;
2022-10-20 01:06:43 -06:00
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 ,
matrixPublicArchiveURLCreator ,
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 ) ;
assert ( matrixPublicArchiveURLCreator ) ;
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 ;
this . _matrixPublicArchiveURLCreator = matrixPublicArchiveURLCreator ;
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 ) ;
this . _roomCardViewModels = this . _roomCardViewModelsFilterMap . sortValues ( ( /*a, b*/ ) => {
// Sort doesn't matter
return 1 ;
} ) ;
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 ( ) {
return this . _matrixPublicArchiveURLCreator . roomDirectoryUrl ( ) ;
}
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.
const isNsfw = NSFW _REGEXES . some ( ( regex ) =>
regex . test ( vm . name + ' ---- ' + vm . canonicalAlias + ' --- ' + vm . topic )
) ;
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 ) {
return this . _matrixPublicArchiveURLCreator . 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 ,
} ) ;
}
return null ;
}
get prevPageUrl ( ) {
if ( this . _prevPaginationToken ) {
return this . _matrixPublicArchiveURLCreator . 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 ,
} ) ;
}
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 ;