2021-03-16 23:38:59 +01:00
import * as blueslip from "./blueslip" ;
2021-02-28 00:53:59 +01:00
import * as color_data from "./color_data" ;
import { FoldDict } from "./fold_dict" ;
2021-04-13 06:51:54 +02:00
import { $t } from "./i18n" ;
2021-03-25 22:35:45 +01:00
import { page_params } from "./page_params" ;
2021-02-28 00:53:59 +01:00
import * as peer_data from "./peer_data" ;
import * as people from "./people" ;
import * as settings_config from "./settings_config" ;
2023-05-08 09:01:27 +02:00
import * as settings_data from "./settings_data" ;
2021-04-15 16:35:14 +02:00
import * as sub_store from "./sub_store" ;
2023-07-25 19:22:22 +02:00
import { StreamPostPolicy } from "./sub_store" ;
import type {
ApiStreamSubscription ,
NeverSubscribedStream ,
Stream ,
StreamSpecificNotificationSettings ,
StreamSubscription ,
} from "./sub_store" ;
2022-12-29 17:49:38 +01:00
import * as user_groups from "./user_groups" ;
2021-07-28 16:00:58 +02:00
import { user_settings } from "./user_settings" ;
2021-02-28 00:53:59 +01:00
import * as util from "./util" ;
2019-02-08 11:56:33 +01:00
2023-07-25 19:22:22 +02:00
type StreamInitParams = {
subscriptions : ApiStreamSubscription [ ] ;
unsubscribed : ApiStreamSubscription [ ] ;
never_subscribed : NeverSubscribedStream [ ] ;
realm_default_streams : Stream [ ] ;
} ;
// Type for the parameter of `create_sub_from_server_data` function.
type ApiGenericStreamSubscription = (
| ( NeverSubscribedStream & { previously_subscribed : boolean } )
| ( ApiStreamSubscription & { previously_subscribed : boolean } )
| ( Stream & { stream_weekly_traffic : number | null ; subscribers : number [ ] } )
) & { subscribed : boolean } ;
type InviteStreamData = {
name : string ;
stream_id : number ;
invite_only : boolean ;
is_web_public : boolean ;
default_stream : boolean ;
} ;
2021-03-28 16:51:24 +02:00
const DEFAULT_COLOR = "#c2c2c2" ;
2021-01-12 21:38:01 +01:00
// Expose get_subscriber_count for our automated puppeteer tests.
2021-02-28 00:53:59 +01:00
export const get_subscriber_count = peer_data . get_subscriber_count ;
2021-01-12 21:38:01 +01:00
2023-07-25 19:22:22 +02:00
class BinaryDict < T > {
2019-12-27 15:56:46 +01:00
/ *
A dictionary that keeps track of which objects had the predicate
return true or false for efficient lookups and iteration .
This class is an optimization for managing subscriptions .
Typically you only subscribe to a small minority of streams , and
most common operations want to efficiently iterate through only
streams where the current user is subscribed :
- search bar search
- build left sidebar
- autocomplete # stream_links
- autocomplete stream in compose
* /
2023-07-25 19:22:22 +02:00
trues = new Map < number , T > ( ) ;
falses = new Map < number , T > ( ) ;
pred : ( v : T ) = > boolean ;
2019-12-27 15:56:46 +01:00
2023-07-25 19:22:22 +02:00
constructor ( pred : ( v : T ) = > boolean ) {
2020-07-23 02:42:29 +02:00
this . pred = pred ;
}
2019-12-27 15:56:46 +01:00
2023-07-25 19:22:22 +02:00
true_values ( ) : IterableIterator < T > {
2020-07-23 02:42:29 +02:00
return this . trues . values ( ) ;
}
2019-12-27 15:56:46 +01:00
2023-07-25 19:22:22 +02:00
num_true_items ( ) : number {
2020-07-23 02:42:29 +02:00
return this . trues . size ;
}
2019-12-27 15:56:46 +01:00
2023-07-25 19:22:22 +02:00
false_values ( ) : IterableIterator < T > {
2020-07-23 02:42:29 +02:00
return this . falses . values ( ) ;
}
2023-07-25 19:22:22 +02:00
* values ( ) : IterableIterator < T > {
2023-08-03 02:57:28 +02:00
yield * this . trues . values ( ) ;
yield * this . falses . values ( ) ;
2020-07-23 02:42:29 +02:00
}
2019-12-27 15:56:46 +01:00
2023-07-25 19:22:22 +02:00
get ( k : number ) : T {
2020-07-23 02:42:29 +02:00
const res = this . trues . get ( k ) ;
2019-12-27 15:56:46 +01:00
if ( res !== undefined ) {
return res ;
}
2023-07-25 19:22:22 +02:00
return this . falses . get ( k ) ! ;
2020-07-23 02:42:29 +02:00
}
2019-12-27 15:56:46 +01:00
2023-07-25 19:22:22 +02:00
set ( k : number , v : T ) : void {
2020-07-23 02:42:29 +02:00
if ( this . pred ( v ) ) {
this . set_true ( k , v ) ;
2019-12-27 15:56:46 +01:00
} else {
2020-07-23 02:42:29 +02:00
this . set_false ( k , v ) ;
2019-12-27 15:56:46 +01:00
}
2020-07-23 02:42:29 +02:00
}
2023-07-25 19:22:22 +02:00
set_true ( k : number , v : T ) : void {
2020-07-23 02:42:29 +02:00
this . falses . delete ( k ) ;
this . trues . set ( k , v ) ;
}
2023-07-25 19:22:22 +02:00
set_false ( k : number , v : T ) : void {
2020-07-23 02:42:29 +02:00
this . trues . delete ( k ) ;
this . falses . set ( k , v ) ;
}
2023-07-25 19:22:22 +02:00
delete ( k : number ) : void {
2020-07-23 02:42:29 +02:00
this . trues . delete ( k ) ;
this . falses . delete ( k ) ;
}
}
2013-09-16 23:47:05 +02:00
2023-05-26 02:14:06 +02:00
// The stream_info variable maps stream ids to stream properties objects
2013-09-16 23:47:05 +02:00
// Call clear_subscriptions() to initialize it.
2023-07-25 19:22:22 +02:00
let stream_info : BinaryDict < StreamSubscription > ;
2013-09-16 23:47:05 +02:00
2023-07-25 19:22:22 +02:00
const stream_ids_by_name = new FoldDict < number > ( ) ;
const stream_ids_by_old_names = new FoldDict < number > ( ) ;
const default_stream_ids = new Set < number > ( ) ;
2017-05-11 21:49:38 +02:00
2021-02-28 00:53:59 +01:00
export const stream_privacy_policy_values = {
2021-12-07 07:57:29 +01:00
web_public : {
code : "web-public" ,
2022-01-29 00:54:13 +01:00
name : $t ( { defaultMessage : "Web-public" } ) ,
2021-12-07 07:57:29 +01:00
description : $t ( {
defaultMessage :
"Organization members can join (guests must be invited by a subscriber); anyone on the Internet can view complete message history without creating an account" ,
} ) ,
} ,
2020-07-10 14:25:21 +02:00
public : {
code : "public" ,
2021-04-13 06:51:54 +02:00
name : $t ( { defaultMessage : "Public" } ) ,
description : $t ( {
defaultMessage :
2021-09-14 20:16:15 +02:00
"Organization members can join (guests must be invited by a subscriber); organization members can view complete message history without joining" ,
2021-04-13 06:51:54 +02:00
} ) ,
2020-07-10 14:25:21 +02:00
} ,
private_with_public_history : {
code : "invite-only-public-history" ,
2021-04-13 06:51:54 +02:00
name : $t ( { defaultMessage : "Private, shared history" } ) ,
description : $t ( {
defaultMessage :
2021-09-14 20:16:15 +02:00
"Must be invited by a subscriber; new subscribers can view complete message history; hidden from non-administrator users" ,
2021-04-13 06:51:54 +02:00
} ) ,
2020-07-10 14:25:21 +02:00
} ,
private : {
code : "invite-only" ,
2021-04-13 06:51:54 +02:00
name : $t ( { defaultMessage : "Private, protected history" } ) ,
description : $t ( {
defaultMessage :
2021-09-14 20:16:15 +02:00
"Must be invited by a subscriber; new subscribers can only see messages sent after they join; hidden from non-administrator users" ,
2021-04-13 06:51:54 +02:00
} ) ,
2020-07-10 14:25:21 +02:00
} ,
2021-12-01 23:04:43 +01:00
} ;
2021-05-20 13:02:00 +02:00
2021-02-28 00:53:59 +01:00
export const stream_post_policy_values = {
2021-12-15 01:04:35 +01:00
// These strings should match the strings in the
// Stream.POST_POLICIES object in zerver/models.py.
2020-02-04 21:50:55 +01:00
everyone : {
2023-07-25 19:22:22 +02:00
code : StreamPostPolicy.EVERYONE ,
2022-03-07 07:29:06 +01:00
description : $t ( { defaultMessage : "Everyone" } ) ,
2020-02-04 21:50:55 +01:00
} ,
2022-03-07 07:29:06 +01:00
non_new_members : {
2023-07-25 19:22:22 +02:00
code : StreamPostPolicy.RESTRICT_NEW_MEMBERS ,
2022-03-07 07:29:06 +01:00
description : $t ( { defaultMessage : "Admins, moderators and full members" } ) ,
2020-02-04 21:50:55 +01:00
} ,
2021-05-01 16:19:33 +02:00
moderators : {
2023-07-25 19:22:22 +02:00
code : StreamPostPolicy.MODERATORS ,
2021-05-01 16:19:33 +02:00
description : $t ( {
2022-03-07 07:29:06 +01:00
defaultMessage : "Admins and moderators" ,
2021-05-01 16:19:33 +02:00
} ) ,
} ,
2022-03-07 07:29:06 +01:00
admins : {
2023-07-25 19:22:22 +02:00
code : StreamPostPolicy.ADMINS ,
2022-03-07 07:29:06 +01:00
description : $t ( { defaultMessage : "Admins only" } ) ,
2020-02-04 21:50:55 +01:00
} ,
2023-07-25 19:22:22 +02:00
} as const ;
2020-02-04 21:50:55 +01:00
2023-07-25 19:22:22 +02:00
export function clear_subscriptions ( ) : void {
2021-01-12 16:05:24 +01:00
// This function is only used once at page load, and then
// it should only be used in tests.
2020-07-02 01:45:54 +02:00
stream_info = new BinaryDict ( ( sub ) = > sub . subscribed ) ;
2021-04-15 16:35:14 +02:00
sub_store . clear ( ) ;
2021-02-28 00:53:59 +01:00
}
2016-06-04 22:40:25 +02:00
2021-02-28 00:53:59 +01:00
clear_subscriptions ( ) ;
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function rename_sub ( sub : StreamSubscription , new_name : string ) : void {
2019-11-02 00:06:25 +01:00
const old_name = sub . name ;
2023-05-26 02:14:06 +02:00
stream_ids_by_old_names . set ( old_name , sub . stream_id ) ;
2017-05-11 21:49:38 +02:00
2016-10-30 17:33:23 +01:00
sub . name = new_name ;
2023-05-26 02:14:06 +02:00
stream_info . set ( sub . stream_id , sub ) ;
stream_ids_by_name . delete ( old_name ) ;
stream_ids_by_name . set ( new_name , sub . stream_id ) ;
2021-02-28 00:53:59 +01:00
}
2016-10-30 17:33:23 +01:00
2023-07-25 19:22:22 +02:00
export function subscribe_myself ( sub : StreamSubscription ) : void {
2019-11-02 00:06:25 +01:00
const user_id = people . my_current_user_id ( ) ;
2021-01-12 21:38:01 +01:00
peer_data . add_subscriber ( sub . stream_id , user_id ) ;
2017-01-20 23:04:40 +01:00
sub . subscribed = true ;
2017-04-28 15:59:30 +02:00
sub . newly_subscribed = true ;
2023-05-26 02:14:06 +02:00
stream_info . set_true ( sub . stream_id , sub ) ;
2021-02-28 00:53:59 +01:00
}
2017-01-20 23:04:40 +01:00
2023-07-25 19:22:22 +02:00
export function unsubscribe_myself ( sub : StreamSubscription ) : void {
2016-11-09 16:26:35 +01:00
// Remove user from subscriber's list
2019-11-02 00:06:25 +01:00
const user_id = people . my_current_user_id ( ) ;
2021-01-12 21:38:01 +01:00
peer_data . remove_subscriber ( sub . stream_id , user_id ) ;
2016-11-09 16:26:35 +01:00
sub . subscribed = false ;
2017-04-28 15:59:30 +02:00
sub . newly_subscribed = false ;
2023-05-26 02:14:06 +02:00
stream_info . set_false ( sub . stream_id , sub ) ;
2021-02-28 00:53:59 +01:00
}
2016-11-09 16:26:35 +01:00
2023-07-25 19:22:22 +02:00
export function add_sub ( sub : StreamSubscription ) : void {
2021-01-12 20:58:11 +01:00
// This function is currently used only by tests.
// We use create_sub_from_server_data at page load.
// We use create_streams for new streams in live-update events.
2023-05-26 02:14:06 +02:00
stream_info . set ( sub . stream_id , sub ) ;
stream_ids_by_name . set ( sub . name , sub . stream_id ) ;
2021-04-15 16:35:14 +02:00
sub_store . add_hydrated_sub ( sub . stream_id , sub ) ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function get_sub ( stream_name : string ) : StreamSubscription | undefined {
2023-05-26 02:14:06 +02:00
const stream_id = stream_ids_by_name . get ( stream_name ) ;
if ( stream_id ) {
return stream_info . get ( stream_id ) ;
}
return undefined ;
}
2023-07-25 19:22:22 +02:00
export function get_sub_by_id ( stream_id : number ) : StreamSubscription | undefined {
2023-05-26 02:14:06 +02:00
if ( ! stream_id ) {
return undefined ;
}
return stream_info . get ( stream_id ) ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function get_stream_id ( name : string ) : number | undefined {
2017-05-11 21:49:38 +02:00
// Note: Only use this function for situations where
// you are comfortable with a user dealing with an
// old name of a stream (from prior to a rename).
2023-05-26 02:14:06 +02:00
let stream_id = stream_ids_by_name . get ( name ) ;
if ( ! stream_id ) {
stream_id = stream_ids_by_old_names . get ( name ) ;
2017-05-11 21:49:38 +02:00
}
return stream_id ;
2021-02-28 00:53:59 +01:00
}
2017-05-11 21:49:38 +02:00
2023-07-25 19:22:22 +02:00
export function get_sub_by_name ( name : string ) : StreamSubscription | undefined {
2017-05-11 21:49:38 +02:00
// Note: Only use this function for situations where
// you are comfortable with a user dealing with an
// old name of a stream (from prior to a rename).
2023-05-26 02:14:06 +02:00
let stream_id = stream_ids_by_name . get ( name ) ;
if ( ! stream_id ) {
stream_id = stream_ids_by_old_names . get ( name ) ;
2017-05-11 21:49:38 +02:00
}
if ( ! stream_id ) {
2020-09-24 07:50:36 +02:00
return undefined ;
2017-05-11 21:49:38 +02:00
}
2021-04-15 16:35:14 +02:00
return sub_store . get ( stream_id ) ;
2021-02-28 00:53:59 +01:00
}
2017-05-11 21:49:38 +02:00
2023-07-25 19:22:22 +02:00
export function name_to_slug ( name : string ) : string {
2021-02-28 00:53:59 +01:00
const stream_id = get_stream_id ( name ) ;
2018-02-15 21:02:47 +01:00
if ( ! stream_id ) {
return name ;
}
// The name part of the URL doesn't really matter, so we try to
// make it pretty.
2022-02-28 20:54:06 +01:00
name = name . replaceAll ( " " , "-" ) ;
2018-02-15 21:02:47 +01:00
2023-07-25 19:22:22 +02:00
return ` ${ stream_id } - ${ name } ` ;
2021-02-28 00:53:59 +01:00
}
2018-02-15 21:02:47 +01:00
2023-07-25 19:22:22 +02:00
export function slug_to_name ( slug : string ) : string {
2020-07-03 16:37:16 +02:00
/ *
Modern stream slugs look like this , where 42
is a stream id :
42
42 - stream - name
We have legacy slugs that are just the name
of the stream :
stream - name
And it ' s plausible that old stream slugs will have
be based on stream names that collide with modern
slugs :
4 - horseman
411
2016 - election
If there is any ambiguity about whether a stream slug
is old or modern , we prefer modern , as long as the integer
prefix matches a real stream id . Eventually we will
stop supporting the legacy slugs , which only matter now
because people have linked to Zulip threads in things like
GitHub conversations . We migrated to modern slugs in
early 2018 .
* /
2021-02-10 14:31:52 +01:00
const m = /^(\d+)(-.*)?$/ . exec ( slug ) ;
2018-02-15 21:02:47 +01:00
if ( m ) {
2020-10-07 09:17:30 +02:00
const stream_id = Number . parseInt ( m [ 1 ] , 10 ) ;
2021-04-15 16:35:14 +02:00
const sub = sub_store . get ( stream_id ) ;
2018-02-15 21:02:47 +01:00
if ( sub ) {
return sub . name ;
}
// if nothing was found above, we try to match on the stream
// name in the somewhat unlikely event they had a historical
// link to a stream like 4-horsemen
}
2020-07-03 16:37:16 +02:00
/ *
We are dealing with a pre - 2018 slug that doesn ' t have the
stream id as a prefix .
* /
2018-02-15 21:02:47 +01:00
return slug ;
2021-02-28 00:53:59 +01:00
}
2018-02-15 21:02:47 +01:00
2023-07-25 19:22:22 +02:00
export function delete_sub ( stream_id : number ) : void {
2023-05-26 02:14:06 +02:00
if ( ! stream_info . get ( stream_id ) ) {
2023-07-25 19:22:22 +02:00
blueslip . warn ( "Failed to archive stream " + stream_id . toString ( ) ) ;
2017-02-16 03:47:08 +01:00
return ;
}
2021-04-15 16:35:14 +02:00
sub_store . delete_sub ( stream_id ) ;
2023-05-26 02:14:06 +02:00
stream_info . delete ( stream_id ) ;
2021-02-28 00:53:59 +01:00
}
2013-08-21 23:21:31 +02:00
2023-07-25 19:22:22 +02:00
export function get_non_default_stream_names ( ) : { name : string ; unique_id : string } [ ] {
2023-03-02 01:58:25 +01:00
let subs = [ . . . stream_info . values ( ) ] ;
2021-02-28 00:53:59 +01:00
subs = subs . filter ( ( sub ) = > ! is_default_stream_id ( sub . stream_id ) && ! sub . invite_only ) ;
2022-04-01 19:22:41 +02:00
const names = subs . map ( ( sub ) = > ( {
name : sub.name ,
unique_id : sub.stream_id.toString ( ) ,
} ) ) ;
2017-08-22 20:00:09 +02:00
return names ;
2021-02-28 00:53:59 +01:00
}
2017-08-22 20:00:09 +02:00
2023-07-25 19:22:22 +02:00
export function get_unsorted_subs ( ) : StreamSubscription [ ] {
2023-03-02 01:58:25 +01:00
return [ . . . stream_info . values ( ) ] ;
2021-02-28 00:53:59 +01:00
}
2018-07-30 15:27:18 +02:00
2023-07-25 19:22:22 +02:00
export function num_subscribed_subs ( ) : number {
2019-12-27 15:56:46 +01:00
return stream_info . num_true_items ( ) ;
2021-02-28 00:53:59 +01:00
}
2019-12-27 15:56:46 +01:00
2023-07-25 19:22:22 +02:00
export function subscribed_subs ( ) : StreamSubscription [ ] {
2023-03-02 01:58:25 +01:00
return [ . . . stream_info . true_values ( ) ] ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function unsubscribed_subs ( ) : StreamSubscription [ ] {
2023-03-02 01:58:25 +01:00
return [ . . . stream_info . false_values ( ) ] ;
2021-02-28 00:53:59 +01:00
}
2016-10-25 21:45:19 +02:00
2023-07-25 19:22:22 +02:00
export function subscribed_streams ( ) : string [ ] {
2021-02-28 00:53:59 +01:00
return subscribed_subs ( ) . map ( ( sub ) = > sub . name ) ;
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function subscribed_stream_ids ( ) : number [ ] {
2021-02-28 00:53:59 +01:00
return subscribed_subs ( ) . map ( ( sub ) = > sub . stream_id ) ;
}
2020-07-25 18:20:54 +02:00
2023-07-25 19:22:22 +02:00
export function muted_stream_ids ( ) : number [ ] {
2021-11-20 11:51:35 +01:00
return subscribed_subs ( )
. filter ( ( sub ) = > sub . is_muted )
. map ( ( sub ) = > sub . stream_id ) ;
}
2023-07-25 19:22:22 +02:00
export function get_subscribed_streams_for_user ( user_id : number ) : StreamSubscription [ ] {
2021-06-17 22:26:17 +02:00
// Note that we only have access to subscribers of some streams
// depending on our role.
const all_subs = get_unsorted_subs ( ) ;
const subscribed_subs = [ ] ;
for ( const sub of all_subs ) {
if ( ! can_view_subscribers ( sub ) ) {
// Private streams that we have been removed from appear
// in get_unsorted_subs; we don't attempt to check their
// subscribers (which would trigger a warning).
continue ;
}
if ( is_user_subscribed ( sub . stream_id , user_id ) ) {
subscribed_subs . push ( sub ) ;
}
}
return subscribed_subs ;
}
2023-07-25 19:22:22 +02:00
export function get_invite_stream_data ( ) : InviteStreamData [ ] {
function get_data ( sub : StreamSubscription ) : InviteStreamData {
2019-01-10 17:57:35 +01:00
return {
name : sub.name ,
stream_id : sub.stream_id ,
invite_only : sub.invite_only ,
2022-12-15 06:03:19 +01:00
is_web_public : sub.is_web_public ,
2020-03-22 17:04:47 +01:00
default_stream : default_stream_ids.has ( sub . stream_id ) ,
2019-01-10 17:57:35 +01:00
} ;
2020-03-22 17:31:47 +01:00
}
const streams = [ ] ;
// Invite users to all default streams...
for ( const stream_id of default_stream_ids ) {
2023-07-25 19:22:22 +02:00
const sub = sub_store . get ( stream_id ) ! ;
2020-03-22 17:31:47 +01:00
streams . push ( get_data ( sub ) ) ;
}
// ...plus all your subscribed streams (avoiding repeats).
2021-02-28 00:53:59 +01:00
for ( const sub of subscribed_subs ( ) ) {
2020-03-22 17:31:47 +01:00
if ( ! default_stream_ids . has ( sub . stream_id ) ) {
streams . push ( get_data ( sub ) ) ;
2019-01-10 17:57:35 +01:00
}
2020-03-22 17:31:47 +01:00
}
2019-01-10 17:57:35 +01:00
return streams ;
2021-02-28 00:53:59 +01:00
}
2019-01-10 17:57:35 +01:00
2023-07-25 19:22:22 +02:00
export function get_colors ( ) : string [ ] {
2021-02-28 00:53:59 +01:00
return subscribed_subs ( ) . map ( ( sub ) = > sub . color ) ;
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function update_stream_email_address ( sub : StreamSubscription , email : string ) : void {
2018-04-05 19:58:27 +02:00
sub . email_address = email ;
2021-02-28 00:53:59 +01:00
}
2018-04-05 19:58:27 +02:00
2023-07-25 19:22:22 +02:00
export function update_stream_post_policy (
sub : StreamSubscription ,
stream_post_policy : StreamPostPolicy ,
) : void {
2020-02-04 21:50:55 +01:00
sub . stream_post_policy = stream_post_policy ;
2021-02-28 00:53:59 +01:00
}
2019-05-07 07:12:14 +02:00
2023-07-25 19:22:22 +02:00
export function update_stream_privacy (
sub : StreamSubscription ,
values : {
invite_only : boolean ;
history_public_to_subscribers : boolean ;
is_web_public : boolean ;
} ,
) : void {
2019-05-07 07:12:14 +02:00
sub . invite_only = values . invite_only ;
sub . history_public_to_subscribers = values . history_public_to_subscribers ;
2020-11-10 15:57:14 +01:00
sub . is_web_public = values . is_web_public ;
2021-02-28 00:53:59 +01:00
}
2019-05-07 07:12:14 +02:00
2023-07-25 19:22:22 +02:00
export function update_message_retention_setting (
sub : StreamSubscription ,
message_retention_days : number | null ,
) : void {
2020-06-15 17:00:00 +02:00
sub . message_retention_days = message_retention_days ;
2021-02-28 00:53:59 +01:00
}
2020-06-15 17:00:00 +02:00
2023-07-25 19:22:22 +02:00
export function update_can_remove_subscribers_group_id (
sub : StreamSubscription ,
can_remove_subscribers_group_id : number ,
) : void {
2023-07-12 12:57:57 +02:00
sub . can_remove_subscribers_group = can_remove_subscribers_group_id ;
2022-12-29 18:36:06 +01:00
}
2023-07-25 19:22:22 +02:00
export function receives_notifications (
stream_id : number ,
notification_name : keyof StreamSpecificNotificationSettings ,
) : boolean {
2021-04-15 16:35:14 +02:00
const sub = sub_store . get ( stream_id ) ;
2019-06-14 19:41:26 +02:00
if ( sub === undefined ) {
return false ;
}
if ( sub [ notification_name ] !== null ) {
2023-07-25 19:22:22 +02:00
return sub [ notification_name ] ! ;
2019-06-14 19:41:26 +02:00
}
2022-08-25 21:09:32 +02:00
return user_settings [ settings_config . generalize_stream_notification_setting [ notification_name ] ] ;
2021-02-28 00:53:59 +01:00
}
2019-06-14 19:41:26 +02:00
2023-07-25 19:22:22 +02:00
export function all_subscribed_streams_are_in_home_view ( ) : boolean {
2021-02-28 00:53:59 +01:00
return subscribed_subs ( ) . every ( ( sub ) = > ! sub . is_muted ) ;
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function home_view_stream_names ( ) : string [ ] {
2021-02-28 00:53:59 +01:00
const home_view_subs = subscribed_subs ( ) . filter ( ( sub ) = > ! sub . is_muted ) ;
2020-07-02 01:39:34 +02:00
return home_view_subs . map ( ( sub ) = > sub . name ) ;
2021-02-28 00:53:59 +01:00
}
2014-01-15 20:59:31 +01:00
2023-07-25 19:22:22 +02:00
export function canonicalized_name ( stream_name : string ) : string {
2018-05-06 21:43:17 +02:00
return stream_name . toString ( ) . toLowerCase ( ) ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function get_color ( stream_id : number ) : string {
2023-06-27 01:40:25 +02:00
const sub = get_sub_by_id ( stream_id ) ;
2013-08-15 21:11:07 +02:00
if ( sub === undefined ) {
2021-03-28 16:51:24 +02:00
return DEFAULT_COLOR ;
2013-08-15 21:11:07 +02:00
}
return sub . color ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function is_muted ( stream_id : number ) : boolean {
2021-04-15 16:35:14 +02:00
const sub = sub_store . get ( stream_id ) ;
2019-05-21 09:33:21 +02:00
// Return true for undefined streams
if ( sub === undefined ) {
return true ;
}
return sub . is_muted ;
2021-02-28 00:53:59 +01:00
}
2017-05-13 20:54:53 +02:00
2023-07-25 19:22:22 +02:00
export function is_stream_muted_by_name ( stream_name : string ) : boolean {
2021-02-28 00:53:59 +01:00
const sub = get_sub ( stream_name ) ;
2019-05-21 09:33:21 +02:00
// Return true for undefined streams
if ( sub === undefined ) {
return true ;
}
return sub . is_muted ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function is_notifications_stream_muted ( ) : boolean {
2021-02-28 00:53:59 +01:00
return is_muted ( page_params . realm_notifications_stream_id ) ;
}
2017-05-13 20:54:53 +02:00
2023-07-25 19:22:22 +02:00
export function can_toggle_subscription ( sub : StreamSubscription ) : boolean {
2021-11-25 02:43:43 +01:00
// You can always remove your subscription if you're subscribed.
//
// One can only join a stream if it is public (!invite_only) and
// your role is Member or above (!is_guest).
2021-11-16 07:05:04 +01:00
// Spectators cannot subscribe to any streams.
2021-11-25 02:43:43 +01:00
//
// Note that the correctness of this logic relies on the fact that
// one cannot be subscribed to a deactivated stream, and
// deactivated streams are automatically made private during the
// archive stream process.
2021-11-16 07:05:04 +01:00
return (
( sub . subscribed || ( ! page_params . is_guest && ! sub . invite_only ) ) && ! page_params . is_spectator
) ;
2021-04-04 17:23:40 +02:00
}
2023-07-25 19:22:22 +02:00
export function can_access_topic_history ( sub : StreamSubscription ) : boolean {
2022-10-18 19:01:58 +02:00
// Anyone can access topic history for web-public streams and
// subscriptions; additionally, members can access history for
// public streams.
return sub . is_web_public || can_toggle_subscription ( sub ) ;
}
2023-07-25 19:22:22 +02:00
export function can_preview ( sub : StreamSubscription ) : boolean | undefined {
2021-04-04 17:36:38 +02:00
return sub . subscribed || ! sub . invite_only || sub . previously_subscribed ;
}
2023-07-25 19:22:22 +02:00
export function can_change_permissions ( sub : StreamSubscription ) : boolean {
2021-04-04 17:40:48 +02:00
return page_params . is_admin && ( ! sub . invite_only || sub . subscribed ) ;
}
2023-07-25 19:22:22 +02:00
export function can_view_subscribers ( sub : StreamSubscription ) : boolean {
2021-04-04 17:52:09 +02:00
// Guest users can't access subscribers of any(public or private) non-subscribed streams.
return page_params . is_admin || sub . subscribed || ( ! page_params . is_guest && ! sub . invite_only ) ;
}
2023-07-25 19:22:22 +02:00
export function can_subscribe_others ( sub : StreamSubscription ) : boolean {
2023-05-08 09:01:27 +02:00
// User can add other users to stream if stream is public or user is subscribed to stream
// and realm level setting allows user to add subscribers.
return (
! page_params . is_guest &&
( ! sub . invite_only || sub . subscribed ) &&
settings_data . user_can_subscribe_other_users ( )
) ;
2021-04-04 18:15:17 +02:00
}
2023-07-25 19:22:22 +02:00
export function can_unsubscribe_others ( sub : StreamSubscription ) : boolean {
2022-12-29 17:49:38 +01:00
// Whether the current user has permission to remove other users
// from the stream. Organization administrators can remove users
// from any stream; additionally, users who can access the stream
// and are in the stream's can_remove_subscribers_group can do so
// as well.
//
// TODO: The API allows the current user to remove bots that it
// administers from streams; so we might need to refactor this
// logic to accept a target_user_id parameter in order to support
// that in the UI.
// A user must be able to view subscribers in a stream in order to
// remove them. This check may never fire in practice, since the
// UI for removing subscribers generally is a list of the stream's
// subscribers.
if ( ! can_view_subscribers ( sub ) ) {
return false ;
}
if ( page_params . is_admin ) {
return true ;
}
return user_groups . is_user_in_group (
2023-07-12 12:57:57 +02:00
sub . can_remove_subscribers_group ,
2022-12-29 17:49:38 +01:00
people . my_current_user_id ( ) ,
) ;
}
2023-07-25 19:22:22 +02:00
export function can_post_messages_in_stream ( stream : StreamSubscription ) : boolean {
2022-11-10 01:45:15 +01:00
if ( page_params . is_spectator ) {
return false ;
}
2021-12-27 10:27:03 +01:00
if ( page_params . is_admin ) {
return true ;
}
if ( stream . stream_post_policy === stream_post_policy_values . admins . code ) {
return false ;
}
if ( page_params . is_moderator ) {
return true ;
}
if ( stream . stream_post_policy === stream_post_policy_values . moderators . code ) {
return false ;
}
if (
page_params . is_guest &&
stream . stream_post_policy !== stream_post_policy_values . everyone . code
) {
return false ;
}
const person = people . get_by_user_id ( people . my_current_user_id ( ) ) ;
2023-07-25 19:22:22 +02:00
const current_datetime = Date . now ( ) ;
const person_date_joined = new Date ( person . date_joined ) . getTime ( ) ;
2021-12-27 10:27:03 +01:00
const days = ( current_datetime - person_date_joined ) / 1000 / 86400 ;
if (
stream . stream_post_policy === stream_post_policy_values . non_new_members . code &&
days < page_params . realm_waiting_period_threshold
) {
return false ;
}
return true ;
}
2023-07-25 19:22:22 +02:00
export function is_subscribed_by_name ( stream_name : string ) : boolean | undefined {
2021-02-28 00:53:59 +01:00
const sub = get_sub ( stream_name ) ;
2023-07-25 19:22:22 +02:00
return sub ? . subscribed ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function is_subscribed ( stream_id : number ) : boolean | undefined {
2021-04-15 16:35:14 +02:00
const sub = sub_store . get ( stream_id ) ;
2023-07-25 19:22:22 +02:00
return sub ? . subscribed ;
2021-02-28 00:53:59 +01:00
}
2017-05-14 16:32:18 +02:00
2023-07-25 19:22:22 +02:00
export function get_stream_privacy_policy ( stream_id : number ) : string {
const sub = sub_store . get ( stream_id ) ! ;
2020-07-10 14:25:21 +02:00
2021-05-20 13:02:00 +02:00
if ( sub . is_web_public ) {
return stream_privacy_policy_values . web_public . code ;
}
2020-07-10 14:25:21 +02:00
if ( ! sub . invite_only ) {
2021-02-28 00:53:59 +01:00
return stream_privacy_policy_values . public . code ;
2020-07-10 14:25:21 +02:00
}
if ( sub . invite_only && ! sub . history_public_to_subscribers ) {
2021-02-28 00:53:59 +01:00
return stream_privacy_policy_values . private . code ;
2020-07-10 14:25:21 +02:00
}
2021-02-28 00:53:59 +01:00
return stream_privacy_policy_values . private_with_public_history . code ;
}
2020-07-10 14:25:21 +02:00
2023-07-25 19:22:22 +02:00
export function is_web_public ( stream_id : number ) : boolean | undefined {
2021-05-20 13:02:00 +02:00
const sub = sub_store . get ( stream_id ) ;
2023-07-25 19:22:22 +02:00
return sub ? . is_web_public ;
2021-05-20 13:02:00 +02:00
}
2023-07-25 19:22:22 +02:00
export function is_invite_only_by_stream_name ( stream_name : string ) : boolean {
2021-02-28 00:53:59 +01:00
const sub = get_sub ( stream_name ) ;
2013-08-15 21:11:07 +02:00
if ( sub === undefined ) {
return false ;
}
return sub . invite_only ;
2021-02-28 00:53:59 +01:00
}
2013-08-15 21:11:07 +02:00
2023-07-25 19:22:22 +02:00
export function is_web_public_by_stream_name ( stream_name : string ) : boolean {
2021-11-20 04:20:21 +01:00
const sub = get_sub ( stream_name ) ;
if ( sub === undefined ) {
return false ;
}
return sub . is_web_public ;
}
2023-07-25 19:22:22 +02:00
export function set_realm_default_streams ( realm_default_streams : Stream [ ] ) : void {
2017-08-22 18:20:00 +02:00
default_stream_ids . clear ( ) ;
2021-01-22 22:29:08 +01:00
for ( const stream of realm_default_streams ) {
2019-12-30 13:20:45 +01:00
default_stream_ids . add ( stream . stream_id ) ;
2021-01-22 22:29:08 +01:00
}
2021-02-28 00:53:59 +01:00
}
2017-08-22 18:20:00 +02:00
2023-07-25 19:22:22 +02:00
export function get_default_stream_ids ( ) : number [ ] {
2023-03-02 01:58:25 +01:00
return [ . . . default_stream_ids ] ;
2021-02-28 00:53:59 +01:00
}
2018-07-22 11:30:38 +02:00
2023-07-25 19:22:22 +02:00
export function is_default_stream_id ( stream_id : number ) : boolean {
2017-08-22 18:20:00 +02:00
return default_stream_ids . has ( stream_id ) ;
2021-02-28 00:53:59 +01:00
}
2017-03-21 21:10:20 +01:00
2023-07-25 19:22:22 +02:00
export function get_name ( stream_name : string ) : string {
2013-08-19 19:25:44 +02:00
// This returns the actual name of a stream if we are subscribed to
// it (i.e "Denmark" vs. "denmark"), while falling thru to
// stream_name if we don't have a subscription. (Stream names
// are case-insensitive, but we try to display the actual name
// when we know it.)
2017-05-11 23:25:42 +02:00
//
// This function will also do the right thing if we have
// an old stream name in memory for a recently renamed stream.
2021-02-28 00:53:59 +01:00
const sub = get_sub_by_name ( stream_name ) ;
2013-08-19 19:25:44 +02:00
if ( sub === undefined ) {
return stream_name ;
}
return sub . name ;
2021-02-28 00:53:59 +01:00
}
2013-08-19 19:25:44 +02:00
2023-07-25 19:22:22 +02:00
export function is_user_subscribed ( stream_id : number , user_id : number ) : boolean | undefined {
2021-04-15 16:35:14 +02:00
const sub = sub_store . get ( stream_id ) ;
2021-04-04 17:52:09 +02:00
if ( sub === undefined || ! can_view_subscribers ( sub ) ) {
2018-04-03 00:36:31 +02:00
// If we don't know about the stream, or we ourselves cannot access subscriber list,
2013-09-07 04:22:18 +02:00
// so we return undefined (treated as falsy if not explicitly handled).
2020-07-15 00:34:28 +02:00
blueslip . warn (
"We got a is_user_subscribed call for a non-existent or inaccessible stream." ,
) ;
2020-09-24 07:50:36 +02:00
return undefined ;
2013-09-07 02:48:44 +02:00
}
2021-03-24 20:14:12 +01:00
if ( user_id === undefined ) {
2018-04-06 05:22:07 +02:00
blueslip . warn ( "Undefined user_id passed to function is_user_subscribed" ) ;
2020-09-24 07:50:36 +02:00
return undefined ;
2016-10-30 15:47:20 +01:00
}
2021-01-12 21:38:01 +01:00
return peer_data . is_user_subscribed ( stream_id , user_id ) ;
2021-02-28 00:53:59 +01:00
}
2013-09-07 02:48:44 +02:00
2023-07-25 19:22:22 +02:00
export function create_streams ( streams : Stream [ ] ) : void {
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
for ( const stream of streams ) {
2016-10-30 15:47:20 +01:00
// We handle subscriber stuff in other events.
2020-06-21 15:23:43 +02:00
2020-02-09 04:21:30 +01:00
const attrs = {
2023-03-22 10:10:58 +01:00
stream_weekly_traffic : null ,
2016-10-30 15:47:20 +01:00
subscribers : [ ] ,
2017-01-12 00:17:43 +01:00
subscribed : false ,
2020-02-09 04:21:30 +01:00
. . . stream ,
} ;
2021-02-28 00:53:59 +01:00
create_sub_from_server_data ( attrs ) ;
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
}
2021-02-28 00:53:59 +01:00
}
2016-10-15 21:10:10 +02:00
2023-07-25 19:22:22 +02:00
export function clean_up_description ( sub : StreamSubscription ) : void {
2021-04-04 19:07:25 +02:00
if ( sub . rendered_description !== undefined ) {
sub . rendered_description = sub . rendered_description . replace ( "<p>" , "" ) . replace ( "</p>" , "" ) ;
}
}
2023-07-25 19:22:22 +02:00
export function create_sub_from_server_data (
attrs : ApiGenericStreamSubscription ,
) : StreamSubscription {
2016-10-15 20:17:32 +02:00
if ( ! attrs . stream_id ) {
2020-09-24 07:56:29 +02:00
// fail fast
throw new Error ( "We cannot create a sub without a stream_id" ) ;
2016-10-15 20:17:32 +02:00
}
2021-04-15 16:35:14 +02:00
let sub = sub_store . get ( attrs . stream_id ) ;
2020-06-21 15:23:43 +02:00
if ( sub !== undefined ) {
// We've already created this subscription, no need to continue.
return sub ;
}
2016-10-15 20:17:32 +02:00
// Our internal data structure for subscriptions is mostly plain dictionaries,
// so we just reuse the attrs that are passed in to us, but we encapsulate how
2018-11-29 21:50:20 +01:00
// we handle subscribers. We defensively remove the `subscribers` field from
// the original `attrs` object, which will get thrown away. (We used to make
// a copy of the object with `_.omit(attrs, 'subscribers')`, but `_.omit` is
// slow enough to show up in timings when you have 1000s of streams.
2019-11-02 00:06:25 +01:00
const subscriber_user_ids = attrs . subscribers ;
2016-10-15 20:17:32 +02:00
2018-11-29 21:50:20 +01:00
delete attrs . subscribers ;
2020-02-09 04:21:30 +01:00
sub = {
2023-07-25 19:22:22 +02:00
render_subscribers : ! page_params . realm_is_zephyr_mirror_realm || attrs . invite_only === true ,
2017-04-28 15:59:30 +02:00
newly_subscribed : false ,
2019-05-15 08:54:25 +02:00
is_muted : false ,
2021-10-08 13:51:55 +02:00
desktop_notifications : null ,
audible_notifications : null ,
push_notifications : null ,
email_notifications : null ,
wildcard_mentions_notify : null ,
2023-07-25 19:22:22 +02:00
color : "color" in attrs ? attrs.color : color_data.pick_color ( ) ,
2020-02-09 04:21:30 +01:00
. . . attrs ,
} ;
2016-10-15 20:17:32 +02:00
2023-07-25 19:22:22 +02:00
peer_data . set_subscribers ( sub . stream_id , subscriber_user_ids ? ? [ ] ) ;
2016-10-15 20:17:32 +02:00
2021-04-04 19:07:25 +02:00
clean_up_description ( sub ) ;
2018-03-20 22:28:57 +01:00
2023-05-26 02:14:06 +02:00
stream_info . set ( sub . stream_id , sub ) ;
stream_ids_by_name . set ( sub . name , sub . stream_id ) ;
2021-04-15 16:35:14 +02:00
sub_store . add_hydrated_sub ( sub . stream_id , sub ) ;
2016-10-15 20:17:32 +02:00
return sub ;
2021-02-28 00:53:59 +01:00
}
2016-10-15 20:17:32 +02:00
2023-07-25 19:22:22 +02:00
export function get_streams_for_admin ( ) : StreamSubscription [ ] {
2017-08-22 20:51:41 +02:00
// Sort and combine all our streams.
2023-07-25 19:22:22 +02:00
function by_name ( a : StreamSubscription , b : StreamSubscription ) : number {
2017-08-22 20:51:41 +02:00
return util . strcmp ( a . name , b . name ) ;
}
2023-03-02 01:58:25 +01:00
const subs = [ . . . stream_info . values ( ) ] ;
2017-08-22 20:51:41 +02:00
subs . sort ( by_name ) ;
return subs ;
2021-02-28 00:53:59 +01:00
}
2017-08-22 20:51:41 +02:00
2020-04-15 18:29:26 +02:00
/ *
This module provides a common helper for finding the notification
stream , but we don ' t own the data . The ` page_params ` structure
is the authoritative source of this data , and it will be updated by
server_events_dispatch in case of changes .
* /
2023-07-25 19:22:22 +02:00
export function realm_has_notifications_stream ( ) : boolean {
2021-02-28 00:53:59 +01:00
return page_params . realm_notifications_stream_id !== - 1 ;
}
2020-04-15 18:29:26 +02:00
2023-07-25 19:22:22 +02:00
export function get_notifications_stream ( ) : string {
2020-04-14 12:55:18 +02:00
const stream_id = page_params . realm_notifications_stream_id ;
if ( stream_id !== - 1 ) {
2021-04-15 16:35:14 +02:00
const stream_obj = sub_store . get ( stream_id ) ;
2020-04-14 12:55:18 +02:00
if ( stream_obj ) {
return stream_obj . name ;
}
// We reach here when the notifications stream is a private
// stream the current user is not subscribed to.
}
2020-07-15 01:29:15 +02:00
return "" ;
2021-02-28 00:53:59 +01:00
}
2020-04-14 12:55:18 +02:00
2023-07-25 19:22:22 +02:00
export function initialize ( params : StreamInitParams ) : void {
2020-02-25 12:16:26 +01:00
/ *
We get ` params ` data , which is data that we "own"
and which has already been removed from ` page_params ` .
We only use it in this function to populate other
data structures .
* /
const subscriptions = params . subscriptions ;
const unsubscribed = params . unsubscribed ;
const never_subscribed = params . never_subscribed ;
2020-03-22 17:32:31 +01:00
const realm_default_streams = params . realm_default_streams ;
2020-02-25 12:16:26 +01:00
/ *
We also consume some data directly from ` page_params ` .
This data can be accessed by any other module ,
and we consider the authoritative source to be
` page_params ` . Some of this data should eventually
2020-03-22 17:32:31 +01:00
be fully handled by stream_data .
2020-02-25 12:16:26 +01:00
* /
color_data . claim_colors ( subscriptions ) ;
2018-11-28 23:12:40 +01:00
2023-07-25 19:22:22 +02:00
function populate_subscriptions (
subs : ApiStreamSubscription [ ] | NeverSubscribedStream [ ] ,
subscribed : boolean ,
previously_subscribed : boolean ,
) : void {
2021-01-22 22:29:08 +01:00
for ( const sub of subs ) {
2023-07-24 18:38:33 +02:00
const attrs = {
. . . sub ,
subscribed ,
previously_subscribed ,
} ;
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
2023-07-24 18:38:33 +02:00
create_sub_from_server_data ( attrs ) ;
2021-01-22 22:29:08 +01:00
}
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
}
2021-02-28 00:53:59 +01:00
set_realm_default_streams ( realm_default_streams ) ;
2017-03-21 21:10:20 +01:00
2020-02-25 12:16:26 +01:00
populate_subscriptions ( subscriptions , true , true ) ;
populate_subscriptions ( unsubscribed , false , true ) ;
populate_subscriptions ( never_subscribed , false , false ) ;
2021-02-28 00:53:59 +01:00
}
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
2023-07-25 19:22:22 +02:00
export function remove_default_stream ( stream_id : number ) : void {
2019-12-30 13:20:45 +01:00
default_stream_ids . delete ( stream_id ) ;
2021-02-28 00:53:59 +01:00
}
2023-06-20 11:30:30 +02:00
2023-07-25 19:22:22 +02:00
export function get_options_for_dropdown_widget ( ) : {
name : string ;
unique_id : number ;
stream : StreamSubscription ;
} [ ] {
2023-07-03 08:42:43 +02:00
return subscribed_subs ( )
. map ( ( stream ) = > ( {
name : stream.name ,
unique_id : stream.stream_id ,
stream ,
} ) )
. sort ( ( a , b ) = > util . strcmp ( a . name . toLowerCase ( ) , b . name . toLowerCase ( ) ) ) ;
2023-06-20 11:30:30 +02:00
}