Add safe search filter for NSFW rooms (#208)
Fix https://github.com/matrix-org/matrix-public-archive/issues/89
This commit is contained in:
parent
858c9dde8b
commit
b70439e95b
|
@ -237,24 +237,22 @@
|
||||||
color: #abb5be;
|
color: #abb5be;
|
||||||
}
|
}
|
||||||
|
|
||||||
.RoomDirectoryView_roomList {
|
.RoomDirectoryView_mainContentSection {
|
||||||
display: grid;
|
|
||||||
gap: 20px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1180px;
|
max-width: 1180px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
margin-top: 20px;
|
margin-top: 15px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RoomDirectoryView_roomList {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.RoomDirectoryView_roomListError {
|
.RoomDirectoryView_roomListError {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
|
||||||
max-width: 1180px;
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
|
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
@ -269,17 +267,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 750px) {
|
@media (min-width: 750px) {
|
||||||
.RoomDirectoryView_roomList {
|
.RoomDirectoryView_mainContentSection {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
margin-top: 30px;
|
||||||
margin-top: 40px;
|
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.RoomDirectoryView_roomListError {
|
.RoomDirectoryView_roomList {
|
||||||
margin-top: 40px;
|
grid-template-columns: repeat(2, 1fr);
|
||||||
padding-left: 40px;
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +284,40 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RoomDirectoryView_safeSearchToggleSection {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomDirectoryView_safeSearchToggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomDirectoryView_safeSearchToggleLabel {
|
||||||
|
padding-left: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomDirectoryView_safeSearchToggleCheckbox {
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
width: 20px;
|
||||||
|
height: 10px;
|
||||||
|
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 2px solid rgba(200, 200, 200, 1);
|
||||||
|
box-shadow: inset -10px 0px 0px 0px rgba(200, 200, 200, 1);
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomDirectoryView_safeSearchToggleCheckbox:checked {
|
||||||
|
border: 2px solid #2774c2;
|
||||||
|
box-shadow: inset 10px 0px 0px 0px #2774c2;
|
||||||
|
}
|
||||||
|
|
||||||
.RoomCardView {
|
.RoomCardView {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -301,6 +330,10 @@
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RoomCardView.blockedBySafeSearch {
|
||||||
|
background-color: #d0d9e1;
|
||||||
|
}
|
||||||
|
|
||||||
.RoomCardView_header {
|
.RoomCardView_header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: top;
|
align-items: top;
|
||||||
|
@ -350,6 +383,14 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RoomCardView_blockedBySafeSearchTopic {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
line-height: 1.2em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
.RoomCardView_footer {
|
.RoomCardView_footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
/**
|
/**
|
||||||
|
@ -398,14 +439,22 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RoomCardView_viewButton[disabled] {
|
||||||
|
border-color: #2774c299;
|
||||||
|
|
||||||
|
color: #2774c299;
|
||||||
|
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 750px) {
|
@media (max-width: 750px) {
|
||||||
.RoomCardView_viewButton {
|
.RoomCardView_viewButton {
|
||||||
padding: 8px 32px;
|
padding: 8px 32px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.RoomCardView_viewButtonWrapperLink:hover > .RoomCardView_viewButton,
|
.RoomCardView_viewButtonWrapperLink:hover > .RoomCardView_viewButton:not([disabled]),
|
||||||
.RoomCardView_viewButtonWrapperLink:focus > .RoomCardView_viewButton {
|
.RoomCardView_viewButtonWrapperLink:focus > .RoomCardView_viewButton:not([disabled]) {
|
||||||
background-color: #2774c2;
|
background-color: #2774c2;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"dompurify": "^2.3.9",
|
"dompurify": "^2.3.9",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"express": "^4.17.2",
|
"express": "^4.17.2",
|
||||||
"hydrogen-view-sdk": "npm:@mlm/hydrogen-view-sdk@^0.26.0-scratch",
|
"hydrogen-view-sdk": "npm:@mlm/hydrogen-view-sdk@^0.27.0-scratch",
|
||||||
"json5": "^2.2.1",
|
"json5": "^2.2.1",
|
||||||
"linkedom": "^0.14.17",
|
"linkedom": "^0.14.17",
|
||||||
"matrix-public-archive-shared": "file:./shared/",
|
"matrix-public-archive-shared": "file:./shared/",
|
||||||
|
@ -3281,9 +3281,9 @@
|
||||||
},
|
},
|
||||||
"node_modules/hydrogen-view-sdk": {
|
"node_modules/hydrogen-view-sdk": {
|
||||||
"name": "@mlm/hydrogen-view-sdk",
|
"name": "@mlm/hydrogen-view-sdk",
|
||||||
"version": "0.26.0-scratch",
|
"version": "0.27.0-scratch",
|
||||||
"resolved": "https://registry.npmjs.org/@mlm/hydrogen-view-sdk/-/hydrogen-view-sdk-0.26.0-scratch.tgz",
|
"resolved": "https://registry.npmjs.org/@mlm/hydrogen-view-sdk/-/hydrogen-view-sdk-0.27.0-scratch.tgz",
|
||||||
"integrity": "sha512-rgFCbaI7P5eeaxsTYzfDZ1vII2Sb6uVgK7kut3rqAMPrqnc3Y7AVuY/TUrsBTaviIHJ/OH9suAHISkx9kE29ZA==",
|
"integrity": "sha512-wv6GLeAFpdQdAVDSTSiWehslGKxn5DDcS7MtxiL8E2J9OJJkd2VoVncmNrrJ86FN5nORTpbD879HDZ1yevfi9g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
|
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
|
||||||
"another-json": "^0.2.0",
|
"another-json": "^0.2.0",
|
||||||
|
@ -7522,9 +7522,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hydrogen-view-sdk": {
|
"hydrogen-view-sdk": {
|
||||||
"version": "npm:@mlm/hydrogen-view-sdk@0.26.0-scratch",
|
"version": "npm:@mlm/hydrogen-view-sdk@0.27.0-scratch",
|
||||||
"resolved": "https://registry.npmjs.org/@mlm/hydrogen-view-sdk/-/hydrogen-view-sdk-0.26.0-scratch.tgz",
|
"resolved": "https://registry.npmjs.org/@mlm/hydrogen-view-sdk/-/hydrogen-view-sdk-0.27.0-scratch.tgz",
|
||||||
"integrity": "sha512-rgFCbaI7P5eeaxsTYzfDZ1vII2Sb6uVgK7kut3rqAMPrqnc3Y7AVuY/TUrsBTaviIHJ/OH9suAHISkx9kE29ZA==",
|
"integrity": "sha512-wv6GLeAFpdQdAVDSTSiWehslGKxn5DDcS7MtxiL8E2J9OJJkd2VoVncmNrrJ86FN5nORTpbD879HDZ1yevfi9g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
|
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
|
||||||
"another-json": "^0.2.0",
|
"another-json": "^0.2.0",
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
"dompurify": "^2.3.9",
|
"dompurify": "^2.3.9",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"express": "^4.17.2",
|
"express": "^4.17.2",
|
||||||
"hydrogen-view-sdk": "npm:@mlm/hydrogen-view-sdk@^0.26.0-scratch",
|
"hydrogen-view-sdk": "npm:@mlm/hydrogen-view-sdk@^0.27.0-scratch",
|
||||||
"json5": "^2.2.1",
|
"json5": "^2.2.1",
|
||||||
"linkedom": "^0.14.17",
|
"linkedom": "^0.14.17",
|
||||||
"matrix-public-archive-shared": "file:./shared/",
|
"matrix-public-archive-shared": "file:./shared/",
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('./assert');
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_KEYS = {
|
||||||
|
addedHomeservers: 'addedHomeservers',
|
||||||
|
safeSearchEnabled: 'safeSearchEnabled',
|
||||||
|
debugActiveDateIntersectionObserver: 'debugActiveDateIntersectionObserver',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Just make sure they match for sanity. All we really care about is that they are
|
||||||
|
// unique amongst each other.
|
||||||
|
Object.keys(LOCAL_STORAGE_KEYS).every((key) => {
|
||||||
|
const value = LOCAL_STORAGE_KEYS[key];
|
||||||
|
const doesKeyMatchValue = key === value;
|
||||||
|
assert(
|
||||||
|
doesKeyMatchValue,
|
||||||
|
`LOCAL_STORAGE_KEYS should have keys that are the same as their values for sanity but saw ${key}=${value}.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure all of the keys/values are unique
|
||||||
|
assert(
|
||||||
|
new Set(Object.values(LOCAL_STORAGE_KEYS)).length !== Object.values(LOCAL_STORAGE_KEYS).length,
|
||||||
|
'Duplicate values in LOCAL_STORAGE_KEYS. They should be unique otherwise ' +
|
||||||
|
'there will be collisions and LocalStorage will be overwritten.'
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = LOCAL_STORAGE_KEYS;
|
|
@ -85,6 +85,7 @@ async function mountHydrogen() {
|
||||||
urlRouter: urlRouter,
|
urlRouter: urlRouter,
|
||||||
history: archiveHistory,
|
history: archiveHistory,
|
||||||
// Our options
|
// Our options
|
||||||
|
basePath: config.basePath,
|
||||||
homeserverUrl: config.matrixServerUrl,
|
homeserverUrl: config.matrixServerUrl,
|
||||||
homeserverName: config.matrixServerName,
|
homeserverName: config.matrixServerName,
|
||||||
matrixPublicArchiveURLCreator,
|
matrixPublicArchiveURLCreator,
|
||||||
|
|
|
@ -11,7 +11,7 @@ class AvatarViewModel extends ViewModel {
|
||||||
|
|
||||||
const { homeserverUrlToPullMediaFrom, avatarUrl, avatarTitle, avatarLetterString, entityId } =
|
const { homeserverUrlToPullMediaFrom, avatarUrl, avatarTitle, avatarLetterString, entityId } =
|
||||||
options;
|
options;
|
||||||
assert(homeserverUrlToPullMediaFrom);
|
assert(!avatarUrl || homeserverUrlToPullMediaFrom);
|
||||||
assert(avatarTitle);
|
assert(avatarTitle);
|
||||||
assert(avatarLetterString);
|
assert(avatarLetterString);
|
||||||
assert(entityId);
|
assert(entityId);
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
const { ViewModel } = require('hydrogen-view-sdk');
|
const { ViewModel } = require('hydrogen-view-sdk');
|
||||||
|
|
||||||
const DEBUG_ACTIVE_DATE_INTERSECTION_OBSERVER_LOCAL_STORAGE_KEY =
|
const LOCAL_STORAGE_KEYS = require('matrix-public-archive-shared/lib/local-storage-keys');
|
||||||
'debugActiveDateIntersectionObserver';
|
|
||||||
|
|
||||||
class DeveloperOptionsContentViewModel extends ViewModel {
|
class DeveloperOptionsContentViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
@ -17,11 +16,13 @@ class DeveloperOptionsContentViewModel extends ViewModel {
|
||||||
loadValuesFromPersistence() {
|
loadValuesFromPersistence() {
|
||||||
if (window.localStorage) {
|
if (window.localStorage) {
|
||||||
this._debugActiveDateIntersectionObserver = JSON.parse(
|
this._debugActiveDateIntersectionObserver = JSON.parse(
|
||||||
window.localStorage.getItem(DEBUG_ACTIVE_DATE_INTERSECTION_OBSERVER_LOCAL_STORAGE_KEY)
|
window.localStorage.getItem(LOCAL_STORAGE_KEYS.debugActiveDateIntersectionObserver)
|
||||||
);
|
);
|
||||||
this.emitChange('debugActiveDateIntersectionObserver');
|
this.emitChange('debugActiveDateIntersectionObserver');
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Skipping read from LocalStorage since LocalStorage not available`);
|
console.warn(
|
||||||
|
`Skipping \`${LOCAL_STORAGE_KEYS.debugActiveDateIntersectionObserver}\` read from LocalStorage since LocalStorage is not available`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ class DeveloperOptionsContentViewModel extends ViewModel {
|
||||||
toggleDebugActiveDateIntersectionObserver(checkedValue) {
|
toggleDebugActiveDateIntersectionObserver(checkedValue) {
|
||||||
this._debugActiveDateIntersectionObserver = checkedValue;
|
this._debugActiveDateIntersectionObserver = checkedValue;
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
DEBUG_ACTIVE_DATE_INTERSECTION_OBSERVER_LOCAL_STORAGE_KEY,
|
LOCAL_STORAGE_KEYS.debugActiveDateIntersectionObserver,
|
||||||
this._debugActiveDateIntersectionObserver
|
this._debugActiveDateIntersectionObserver
|
||||||
);
|
);
|
||||||
this.emitChange('debugActiveDateIntersectionObserver');
|
this.emitChange('debugActiveDateIntersectionObserver');
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { ViewModel } = require('hydrogen-view-sdk');
|
||||||
|
|
||||||
|
const assert = require('matrix-public-archive-shared/lib/assert');
|
||||||
|
const MatrixPublicArchiveURLCreator = require('matrix-public-archive-shared/lib/url-creator');
|
||||||
|
|
||||||
|
class RoomCardViewModel extends ViewModel {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
const { room, basePath, homeserverUrlToPullMediaFrom, viaServers } = options;
|
||||||
|
assert(room);
|
||||||
|
assert(basePath);
|
||||||
|
assert(homeserverUrlToPullMediaFrom);
|
||||||
|
assert(viaServers);
|
||||||
|
|
||||||
|
this._matrixPublicArchiveURLCreator = new MatrixPublicArchiveURLCreator(basePath);
|
||||||
|
|
||||||
|
this._roomId = room.room_id;
|
||||||
|
this._canonicalAlias = room.canonical_alias;
|
||||||
|
this._name = room.name;
|
||||||
|
this._mxcAvatarUrl = room.avatar_url;
|
||||||
|
this._homeserverUrlToPullMediaFrom = homeserverUrlToPullMediaFrom;
|
||||||
|
this._numJoinedMembers = room.num_joined_members;
|
||||||
|
this._topic = room.topic;
|
||||||
|
|
||||||
|
this._viaServers = viaServers;
|
||||||
|
|
||||||
|
this._blockedBySafeSearch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get roomId() {
|
||||||
|
return this._roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canonicalAlias() {
|
||||||
|
return this._canonicalAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get mxcAvatarUrl() {
|
||||||
|
return this._mxcAvatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
get homeserverUrlToPullMediaFrom() {
|
||||||
|
return this._homeserverUrlToPullMediaFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
get numJoinedMembers() {
|
||||||
|
return this._numJoinedMembers;
|
||||||
|
}
|
||||||
|
|
||||||
|
get topic() {
|
||||||
|
return this._topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
get archiveRoomUrl() {
|
||||||
|
return this._matrixPublicArchiveURLCreator.archiveUrlForRoom(
|
||||||
|
this._canonicalAlias || this._roomId,
|
||||||
|
{
|
||||||
|
// Only include via servers when we have to fallback to the room ID
|
||||||
|
viaServers: this._canonicalAlias ? undefined : this._viaServers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get blockedBySafeSearch() {
|
||||||
|
return this._blockedBySafeSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBlockedBySafeSearch(blockedBySafeSearch) {
|
||||||
|
if (blockedBySafeSearch !== this._blockedBySafeSearch) {
|
||||||
|
this._blockedBySafeSearch = blockedBySafeSearch;
|
||||||
|
this.emitChange('blockedBySafeSearch');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = RoomCardViewModel;
|
|
@ -1,20 +1,24 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { ViewModel, ObservableArray } = require('hydrogen-view-sdk');
|
const { ViewModel, ObservableMap, ApplyMap } = require('hydrogen-view-sdk');
|
||||||
|
|
||||||
const assert = require('matrix-public-archive-shared/lib/assert');
|
const assert = require('matrix-public-archive-shared/lib/assert');
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_KEYS = require('matrix-public-archive-shared/lib/local-storage-keys');
|
||||||
const ModalViewModel = require('matrix-public-archive-shared/viewmodels/ModalViewModel');
|
const ModalViewModel = require('matrix-public-archive-shared/viewmodels/ModalViewModel');
|
||||||
const HomeserverSelectionModalContentViewModel = require('matrix-public-archive-shared/viewmodels/HomeserverSelectionModalContentViewModel');
|
const HomeserverSelectionModalContentViewModel = require('matrix-public-archive-shared/viewmodels/HomeserverSelectionModalContentViewModel');
|
||||||
|
const RoomCardViewModel = require('matrix-public-archive-shared/viewmodels/RoomCardViewModel');
|
||||||
|
|
||||||
const DEFAULT_SERVER_LIST = ['matrix.org', 'gitter.im', 'libera.chat'];
|
const DEFAULT_SERVER_LIST = ['matrix.org', 'gitter.im', 'libera.chat'];
|
||||||
|
|
||||||
const ADDED_HOMESERVERS_LIST_LOCAL_STORAGE_KEY = 'addedHomeservers';
|
const NSFW_WORDS = ['nsfw', 'porn', 'nudes', 'sex', '18+'];
|
||||||
|
const NSFW_REGEXES = NSFW_WORDS.map((word) => new RegExp(`\\b${word}\\b`, 'i'));
|
||||||
|
|
||||||
class RoomDirectoryViewModel extends ViewModel {
|
class RoomDirectoryViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
const {
|
const {
|
||||||
|
basePath,
|
||||||
homeserverUrl,
|
homeserverUrl,
|
||||||
homeserverName,
|
homeserverName,
|
||||||
matrixPublicArchiveURLCreator,
|
matrixPublicArchiveURLCreator,
|
||||||
|
@ -24,6 +28,7 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
nextPaginationToken,
|
nextPaginationToken,
|
||||||
prevPaginationToken,
|
prevPaginationToken,
|
||||||
} = options;
|
} = options;
|
||||||
|
assert(basePath);
|
||||||
assert(homeserverUrl);
|
assert(homeserverUrl);
|
||||||
assert(homeserverName);
|
assert(homeserverName);
|
||||||
assert(matrixPublicArchiveURLCreator);
|
assert(matrixPublicArchiveURLCreator);
|
||||||
|
@ -66,26 +71,32 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
this._rooms = new ObservableArray(
|
// This is based off of
|
||||||
rooms.map((room) => {
|
// https://github.com/vector-im/hydrogen-web/blob/e77727ea5992a3ec2649edd53a42e6d75f50a0ca/src/domain/session/leftpanel/LeftPanelViewModel.js#L30-L32
|
||||||
return {
|
this._roomCardViewModelsMap = new ObservableMap();
|
||||||
roomId: room.room_id,
|
rooms.forEach((room) => {
|
||||||
canonicalAlias: room.canonical_alias,
|
this._roomCardViewModelsMap.set(
|
||||||
name: room.name,
|
room.room_id,
|
||||||
mxcAvatarUrl: room.avatar_url,
|
new RoomCardViewModel({
|
||||||
|
room,
|
||||||
|
basePath,
|
||||||
homeserverUrlToPullMediaFrom: homeserverUrl,
|
homeserverUrlToPullMediaFrom: homeserverUrl,
|
||||||
numJoinedMembers: room.num_joined_members,
|
viaServers: [
|
||||||
topic: room.topic,
|
// If the room is being shown in the directory from this server, then surely
|
||||||
archiveRoomUrl: matrixPublicArchiveURLCreator.archiveUrlForRoom(
|
// we can join via this server
|
||||||
room.canonical_alias || room.room_id,
|
this._pageSearchParameters.homeserver,
|
||||||
{
|
],
|
||||||
// Only include via servers when we have to fallback to the room ID
|
|
||||||
viaServers: room.canonical_alias ? undefined : [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();
|
||||||
|
|
||||||
this.#setupNavigation();
|
this.#setupNavigation();
|
||||||
}
|
}
|
||||||
|
@ -171,11 +182,11 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
let addedHomeserversFromPersistence = [];
|
let addedHomeserversFromPersistence = [];
|
||||||
try {
|
try {
|
||||||
addedHomeserversFromPersistence = JSON.parse(
|
addedHomeserversFromPersistence = JSON.parse(
|
||||||
window.localStorage.getItem(ADDED_HOMESERVERS_LIST_LOCAL_STORAGE_KEY)
|
window.localStorage.getItem(LOCAL_STORAGE_KEYS.addedHomeservers)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Resetting \`${ADDED_HOMESERVERS_LIST_LOCAL_STORAGE_KEY}\` stored in LocalStorage since we ran into an error parsing what was stored`,
|
`Resetting \`${LOCAL_STORAGE_KEYS.addedHomeservers}\` stored in LocalStorage since we ran into an error parsing what was stored`,
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
this.setAddedHomeserversList([]);
|
this.setAddedHomeserversList([]);
|
||||||
|
@ -184,7 +195,7 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
|
|
||||||
if (!Array.isArray(addedHomeserversFromPersistence)) {
|
if (!Array.isArray(addedHomeserversFromPersistence)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Resetting \`${ADDED_HOMESERVERS_LIST_LOCAL_STORAGE_KEY}\` stored in LocalStorage since it wasn't an array as expected, addedHomeservers=${addedHomeserversFromPersistence}`
|
`Resetting \`${LOCAL_STORAGE_KEYS.addedHomeservers}\` stored in LocalStorage since it wasn't an array as expected, addedHomeservers=${addedHomeserversFromPersistence}`
|
||||||
);
|
);
|
||||||
this.setAddedHomeserversList([]);
|
this.setAddedHomeserversList([]);
|
||||||
return;
|
return;
|
||||||
|
@ -194,7 +205,7 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Skipping \`${ADDED_HOMESERVERS_LIST_LOCAL_STORAGE_KEY}\` read from LocalStorage since LocalStorage is not available`
|
`Skipping \`${LOCAL_STORAGE_KEYS.addedHomeservers}\` read from LocalStorage since LocalStorage is not available`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,11 +213,11 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
setAddedHomeserversList(addedHomeserversList) {
|
setAddedHomeserversList(addedHomeserversList) {
|
||||||
this._addedHomeserversList = addedHomeserversList;
|
this._addedHomeserversList = addedHomeserversList;
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
ADDED_HOMESERVERS_LIST_LOCAL_STORAGE_KEY,
|
LOCAL_STORAGE_KEYS.addedHomeservers,
|
||||||
JSON.stringify(this._addedHomeserversList)
|
JSON.stringify(this._addedHomeserversList)
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the added homeserver list changes, make sure the default page selected
|
// If the added homeserver list changes, make sure the default page-selected
|
||||||
// homeserver is still somewhere in the list. If it's no longer in the added
|
// 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.
|
// homeserver list, we will put it in the default available list.
|
||||||
this._calculateAvailableHomeserverList();
|
this._calculateAvailableHomeserverList();
|
||||||
|
@ -218,6 +229,63 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
return this._addedHomeserversList;
|
return this._addedHomeserversList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
onNewHomeserverAdded(newHomeserver) {
|
onNewHomeserverAdded(newHomeserver) {
|
||||||
const addedHomeserversList = this.addedHomeserversList;
|
const addedHomeserversList = this.addedHomeserversList;
|
||||||
this.setAddedHomeserversList(addedHomeserversList.concat(newHomeserver));
|
this.setAddedHomeserversList(addedHomeserversList.concat(newHomeserver));
|
||||||
|
@ -284,8 +352,8 @@ class RoomDirectoryViewModel extends ViewModel {
|
||||||
return this._availableHomeserverList;
|
return this._availableHomeserverList;
|
||||||
}
|
}
|
||||||
|
|
||||||
get rooms() {
|
get roomCardViewModels() {
|
||||||
return this._rooms;
|
return this._roomCardViewModels;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { TemplateView, AvatarView } = require('hydrogen-view-sdk');
|
const { TemplateView, AvatarView, text } = require('hydrogen-view-sdk');
|
||||||
const AvatarViewModel = require('../viewmodels/AvatarViewModel');
|
const AvatarViewModel = require('../viewmodels/AvatarViewModel');
|
||||||
|
|
||||||
|
const safeSearchBlockedRoomTitle = 'Blocked by safe search';
|
||||||
|
const safeSearchBlockedRoomDescription =
|
||||||
|
'This room was blocked because safe search is turned on and may contain explicit content. Turn off safe search to view this room.';
|
||||||
|
|
||||||
|
const blockedBySafeSearchAvatarViewModel = new AvatarViewModel({
|
||||||
|
avatarTitle: 'x',
|
||||||
|
avatarLetterString: 'x',
|
||||||
|
entityId: 'x',
|
||||||
|
});
|
||||||
|
|
||||||
class RoomCardView extends TemplateView {
|
class RoomCardView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
const avatarViewModel = new AvatarViewModel({
|
const avatarViewModel = new AvatarViewModel({
|
||||||
|
@ -31,6 +41,7 @@ class RoomCardView extends TemplateView {
|
||||||
{
|
{
|
||||||
className: {
|
className: {
|
||||||
RoomCardView: true,
|
RoomCardView: true,
|
||||||
|
blockedBySafeSearch: (vm) => vm.blockedBySafeSearch,
|
||||||
},
|
},
|
||||||
'data-room-id': vm.roomId,
|
'data-room-id': vm.roomId,
|
||||||
'data-testid': 'room-card',
|
'data-testid': 'room-card',
|
||||||
|
@ -39,13 +50,29 @@ class RoomCardView extends TemplateView {
|
||||||
t.a(
|
t.a(
|
||||||
{
|
{
|
||||||
className: 'RoomCardView_header',
|
className: 'RoomCardView_header',
|
||||||
href: vm.archiveRoomUrl,
|
href: (vm) => {
|
||||||
|
if (vm.blockedBySafeSearch) {
|
||||||
|
// Omit the href so the link is not clickable when it's blocked by
|
||||||
|
// safe search
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm.archiveRoomUrl;
|
||||||
|
},
|
||||||
// Since this is the same button as the "View" link, just tab to
|
// Since this is the same button as the "View" link, just tab to
|
||||||
// that instead
|
// that instead
|
||||||
tabindex: -1,
|
tabindex: -1,
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
t.view(new AvatarView(avatarViewModel, 24)),
|
t.mapView(
|
||||||
|
(vm) => vm.blockedBySafeSearch,
|
||||||
|
(blockedBySafeSearch) => {
|
||||||
|
if (blockedBySafeSearch) {
|
||||||
|
return new AvatarView(blockedBySafeSearchAvatarViewModel, 24);
|
||||||
|
}
|
||||||
|
return new AvatarView(avatarViewModel, 24);
|
||||||
|
}
|
||||||
|
),
|
||||||
t.if(
|
t.if(
|
||||||
(vm) => vm.name,
|
(vm) => vm.name,
|
||||||
(t /*, vm*/) =>
|
(t /*, vm*/) =>
|
||||||
|
@ -53,13 +80,30 @@ class RoomCardView extends TemplateView {
|
||||||
{
|
{
|
||||||
className: 'RoomCardView_headerTitle',
|
className: 'RoomCardView_headerTitle',
|
||||||
// We add a title so a tooltip shows the full name on hover
|
// We add a title so a tooltip shows the full name on hover
|
||||||
title: displayName,
|
title: (vm) => {
|
||||||
|
if (vm.blockedBySafeSearch) {
|
||||||
|
return safeSearchBlockedRoomTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayName;
|
||||||
},
|
},
|
||||||
displayName
|
},
|
||||||
|
[
|
||||||
|
(vm) => {
|
||||||
|
if (vm.blockedBySafeSearch) {
|
||||||
|
return safeSearchBlockedRoomTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return displayName;
|
||||||
|
},
|
||||||
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
t.if(
|
||||||
|
(vm) => !vm.blockedBySafeSearch,
|
||||||
|
(t /*, vm*/) =>
|
||||||
t.a(
|
t.a(
|
||||||
{
|
{
|
||||||
className: 'RoomCardView_alias',
|
className: 'RoomCardView_alias',
|
||||||
|
@ -69,20 +113,52 @@ class RoomCardView extends TemplateView {
|
||||||
tabindex: -1,
|
tabindex: -1,
|
||||||
},
|
},
|
||||||
[aliasOrRoomId]
|
[aliasOrRoomId]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
t.if(
|
||||||
|
(vm) => vm.blockedBySafeSearch,
|
||||||
|
(t /*, vm*/) =>
|
||||||
|
t.p({ className: 'RoomCardView_blockedBySafeSearchTopic' }, [
|
||||||
|
safeSearchBlockedRoomDescription,
|
||||||
|
])
|
||||||
|
),
|
||||||
|
t.if(
|
||||||
|
(vm) => !vm.blockedBySafeSearch,
|
||||||
|
(t /*, vm*/) =>
|
||||||
|
t.p({ className: 'RoomCardView_topic', title: vm.topic || null }, [vm.topic || ''])
|
||||||
),
|
),
|
||||||
t.p({ className: 'RoomCardView_topic', title: vm.topic || null }, [vm.topic || '']),
|
|
||||||
t.div({ className: 'RoomCardView_footer' }, [
|
t.div({ className: 'RoomCardView_footer' }, [
|
||||||
t.div({ className: 'RoomCardView_footerInner' }, [
|
t.div({ className: 'RoomCardView_footerInner' }, [
|
||||||
t.div({}, [memberDisplay]),
|
t.div({}, [
|
||||||
|
t.if(
|
||||||
|
(vm) => !vm.blockedBySafeSearch,
|
||||||
|
(/*t , vm*/) => text(memberDisplay)
|
||||||
|
),
|
||||||
|
]),
|
||||||
t.a(
|
t.a(
|
||||||
{
|
{
|
||||||
className: 'RoomCardView_viewButtonWrapperLink',
|
className: 'RoomCardView_viewButtonWrapperLink',
|
||||||
href: vm.archiveRoomUrl,
|
href: (vm) => {
|
||||||
title: `View the ${displayName} room`,
|
if (vm.blockedBySafeSearch) {
|
||||||
|
// Omit the href so the link is not clickable when it's blocked by
|
||||||
|
// safe search
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm.archiveRoomUrl;
|
||||||
|
},
|
||||||
|
title: (vm) => {
|
||||||
|
if (vm.blockedBySafeSearch) {
|
||||||
|
return `Turn off safe search to view this room`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `View the ${displayName} room`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
t.span(
|
t.span(
|
||||||
{
|
{
|
||||||
className: 'RoomCardView_viewButton',
|
className: 'RoomCardView_viewButton',
|
||||||
|
disabled: (vm) => vm.blockedBySafeSearch,
|
||||||
},
|
},
|
||||||
['View']
|
['View']
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,8 +19,8 @@ class RoomDirectoryView extends TemplateView {
|
||||||
|
|
||||||
const roomList = new ListView(
|
const roomList = new ListView(
|
||||||
{
|
{
|
||||||
className: 'RoomDirectoryView_roomList',
|
className: 'RoomDirectoryView_roomList RoomDirectoryView_mainContentSection',
|
||||||
list: vm.rooms,
|
list: vm.roomCardViewModels,
|
||||||
parentProvidesUpdates: false,
|
parentProvidesUpdates: false,
|
||||||
},
|
},
|
||||||
(room) => {
|
(room) => {
|
||||||
|
@ -210,7 +210,11 @@ class RoomDirectoryView extends TemplateView {
|
||||||
t.if(
|
t.if(
|
||||||
(vm) => vm.roomFetchError,
|
(vm) => vm.roomFetchError,
|
||||||
(t, vm) => {
|
(t, vm) => {
|
||||||
return t.section({ className: 'RoomDirectoryView_roomListError' }, [
|
return t.section(
|
||||||
|
{
|
||||||
|
className: 'RoomDirectoryView_roomListError RoomDirectoryView_mainContentSection',
|
||||||
|
},
|
||||||
|
[
|
||||||
t.h3('❗ Unable to fetch rooms from room directory'),
|
t.h3('❗ Unable to fetch rooms from room directory'),
|
||||||
t.p({}, [
|
t.p({}, [
|
||||||
`This may be a temporary problem with the homeserver where the room directory lives (${vm.pageSearchParameters.homeserver}) or the homeserver that the archive is pulling from (${vm.homeserverName}). You can try adjusting your search or select a different homeserver to look at. If this problem persists, please check the homeserver status and with a homeserver admin first, then open a `,
|
`This may be a temporary problem with the homeserver where the room directory lives (${vm.pageSearchParameters.homeserver}) or the homeserver that the archive is pulling from (${vm.homeserverName}). You can try adjusting your search or select a different homeserver to look at. If this problem persists, please check the homeserver status and with a homeserver admin first, then open a `,
|
||||||
|
@ -275,10 +279,46 @@ class RoomDirectoryView extends TemplateView {
|
||||||
`.`,
|
`.`,
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
// Otherwise, display the rooms that we fetched
|
// Otherwise, display the rooms that we fetched
|
||||||
|
t.section(
|
||||||
|
{
|
||||||
|
className:
|
||||||
|
'RoomDirectoryView_safeSearchToggleSection RoomDirectoryView_mainContentSection',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
t.div({ className: 'RoomDirectoryView_safeSearchToggle' }, [
|
||||||
|
t.input({
|
||||||
|
id: 'safeSearchEnabled',
|
||||||
|
className: 'RoomDirectoryView_safeSearchToggleCheckbox',
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: (vm) => vm.safeSearchEnabled,
|
||||||
|
onInput: (event) => vm.setSafeSearchEnabled(event.target.checked),
|
||||||
|
}),
|
||||||
|
t.label(
|
||||||
|
{
|
||||||
|
className: 'RoomDirectoryView_safeSearchToggleLabel',
|
||||||
|
for: 'safeSearchEnabled',
|
||||||
|
},
|
||||||
|
[
|
||||||
|
t.map(
|
||||||
|
(vm) => vm.safeSearchEnabled,
|
||||||
|
(safeSearchEnabled /*, t, vm*/) => {
|
||||||
|
if (safeSearchEnabled) {
|
||||||
|
return text('Safe search is on');
|
||||||
|
}
|
||||||
|
|
||||||
|
return text('Safe search is off');
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
),
|
||||||
t.view(roomList),
|
t.view(roomList),
|
||||||
t.div({ className: 'RoomDirectoryView_paginationButtonCombo' }, [
|
t.div({ className: 'RoomDirectoryView_paginationButtonCombo' }, [
|
||||||
t.a(
|
t.a(
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
# Currently this is based on Complement Synapse images which are based on the
|
# Currently this is based on Complement Synapse images which are based on the
|
||||||
# published 'synapse:latest' image -- ie, the most recent Synapse release.
|
# published 'synapse:latest' image -- ie, the most recent Synapse release.
|
||||||
|
|
||||||
ARG SYNAPSE_VERSION=latest
|
# FIXME: We're pinning the version to `v1.79.0` until
|
||||||
|
# https://github.com/matrix-org/synapse/issues/15526 is fixed. Feel free to update back
|
||||||
|
# to `latest` once that issue is resolved. More context:
|
||||||
|
# https://github.com/matrix-org/matrix-public-archive/pull/208#discussion_r1183294630
|
||||||
|
ARG SYNAPSE_VERSION=v1.79.0
|
||||||
|
|
||||||
FROM matrixdotorg/synapse:${SYNAPSE_VERSION}
|
FROM matrixdotorg/synapse:${SYNAPSE_VERSION}
|
||||||
|
|
||||||
|
|
|
@ -2452,6 +2452,62 @@ describe('matrix-public-archive', () => {
|
||||||
// Assert that the rooms we searched for on remote hs2 are visible
|
// Assert that the rooms we searched for on remote hs2 are visible
|
||||||
assert.deepStrictEqual(roomsOnPageWithSearch.sort(), [roomXId, roomYId].sort());
|
assert.deepStrictEqual(roomsOnPageWithSearch.sort(), [roomXId, roomYId].sort());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Safe search blocks nsfw rooms by default', async () => {
|
||||||
|
const client = await getTestClientForHs(testMatrixServerUrl1);
|
||||||
|
// This is just an extra room to fill out the room directory and make sure
|
||||||
|
// that it does not appear when searching.
|
||||||
|
await createTestRoom(client);
|
||||||
|
|
||||||
|
// Then create some NSFW rooms we will find with search
|
||||||
|
//
|
||||||
|
// We use a `timeToken` so that we can namespace these rooms away from other
|
||||||
|
// test runs against the same homeserver
|
||||||
|
const timeToken = Date.now();
|
||||||
|
const roomPlanetPrefix = `planet-${timeToken}`;
|
||||||
|
const roomUranusId = await createTestRoom(client, {
|
||||||
|
// NSFW in title
|
||||||
|
name: `${roomPlanetPrefix}-uranus-nsfw`,
|
||||||
|
});
|
||||||
|
const roomMarsId = await createTestRoom(client, {
|
||||||
|
name: `${roomPlanetPrefix}-mars`,
|
||||||
|
// NSFW in room topic/description
|
||||||
|
topic: 'Get your ass to mars (NSFW)',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Browse the room directory searching the room directory for those NSFW rooms
|
||||||
|
// (narrowing down results).
|
||||||
|
archiveUrl = matrixPublicArchiveURLCreator.roomDirectoryUrl({
|
||||||
|
searchTerm: roomPlanetPrefix,
|
||||||
|
});
|
||||||
|
const { data: roomDirectoryWithSearchPageHtml } = await fetchEndpointAsText(archiveUrl);
|
||||||
|
const domWithSearch = parseHTML(roomDirectoryWithSearchPageHtml);
|
||||||
|
|
||||||
|
const roomsCardsOnPageWithSearch = [
|
||||||
|
...domWithSearch.document.querySelectorAll(`[data-testid="room-card"]`),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Assert that the rooms we searched for are on the page
|
||||||
|
const roomsIdsOnPageWithSearch = roomsCardsOnPageWithSearch.map((roomCardEl) => {
|
||||||
|
return roomCardEl.getAttribute('data-room-id');
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(roomsIdsOnPageWithSearch.sort(), [roomUranusId, roomMarsId].sort());
|
||||||
|
|
||||||
|
// Sanity check that safe search does something. Assert that it's *NOT* showing
|
||||||
|
// the "nsfw" content
|
||||||
|
roomsCardsOnPageWithSearch.forEach((roomCardEl) => {
|
||||||
|
assert.match(
|
||||||
|
roomCardEl.innerHTML,
|
||||||
|
/^((?!nsfw).)*$/,
|
||||||
|
`Expected safe search to block any nsfw rooms but saw "nsfw" in the room cards: ${roomCardEl.innerHTML.replaceAll(
|
||||||
|
/nsfw/gi,
|
||||||
|
(match) => {
|
||||||
|
return chalk.yellow(match);
|
||||||
|
}
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('access controls', () => {
|
describe('access controls', () => {
|
||||||
|
|
Loading…
Reference in New Issue