diff --git a/docs/subsystems/hashchange-system.md b/docs/subsystems/hashchange-system.md index cd8137a2d8..376054b749 100644 --- a/docs/subsystems/hashchange-system.md +++ b/docs/subsystems/hashchange-system.md @@ -8,9 +8,9 @@ be used to deep-link into the application and allow the browser's Some examples are: - `/#settings/your-bots`: Bots section of the settings overlay. -- `/#streams`: Streams overlay, where the user manages streams +- `/#channels`: Streams overlay, where the user manages streams (subscription etc.) -- `/#streams/11/announce`: Streams overlay with stream ID 11 (called +- `/#channels/11/announce`: Streams overlay with stream ID 11 (called "announce") selected. - `/#narrow/stream/42-android/topic/fun`: Message feed showing stream "android" and topic "fun". (The `42` represents the id of the @@ -25,7 +25,7 @@ different flows: - The user clicking on an in-app link, which in turn opens an overlay. For example the streams overlay opens when the user clicks the small cog symbol on the left sidebar, which is in fact a link to - `/#streams`. This makes it easy to have simple links around the app + `/#channels`. This makes it easy to have simple links around the app without custom click handlers for each one. - The user uses the "back" button in their browser (basically equivalent to the previous one, as a _link_ out of the browser history diff --git a/web/e2e-tests/lib/common.ts b/web/e2e-tests/lib/common.ts index 42a9c93ffa..bf0fc59178 100644 --- a/web/e2e-tests/lib/common.ts +++ b/web/e2e-tests/lib/common.ts @@ -544,7 +544,7 @@ export async function open_streams_modal(page: Page): Promise { await page.waitForSelector("#subscription_overlay.new-style", {visible: true}); const url = await page_url_with_fragment(page); - assert.ok(url.includes("#streams/all")); + assert.ok(url.includes("#channels/all")); } export async function open_personal_menu(page: Page): Promise { diff --git a/web/e2e-tests/message-basics.test.ts b/web/e2e-tests/message-basics.test.ts index 2e28aca932..674a92c01f 100644 --- a/web/e2e-tests/message-basics.test.ts +++ b/web/e2e-tests/message-basics.test.ts @@ -488,7 +488,7 @@ async function test_users_search(page: Page): Promise { async function test_narrow_public_streams(page: Page): Promise { const stream_id = await common.get_stream_id(page, "Denmark"); - await page.goto(`http://zulip.zulipdev.com:9981/#streams/${stream_id}/Denmark`); + await page.goto(`http://zulip.zulipdev.com:9981/#channels/${stream_id}/Denmark`); await page.waitForSelector("button.sub_unsub_button", {visible: true}); await page.click("button.sub_unsub_button"); await page.waitForSelector( diff --git a/web/e2e-tests/navigation.test.ts b/web/e2e-tests/navigation.test.ts index 3625d8b5df..ae5208d36e 100644 --- a/web/e2e-tests/navigation.test.ts +++ b/web/e2e-tests/navigation.test.ts @@ -42,7 +42,7 @@ async function navigate_to_subscriptions(page: Page): Promise { await open_menu(page); - const manage_streams_selector = '.link-item a[href^="#streams"]'; + const manage_streams_selector = '.link-item a[href^="#channels"]'; await page.waitForSelector(manage_streams_selector, {visible: true}); await page.click(manage_streams_selector); diff --git a/web/src/add_stream_options_popover.ts b/web/src/add_stream_options_popover.ts index 11f7c4496d..77a223b8ad 100644 --- a/web/src/add_stream_options_popover.ts +++ b/web/src/add_stream_options_popover.ts @@ -19,7 +19,7 @@ export function initialize(): void { if (!can_create_streams) { // If the user can't create streams, we directly // navigate them to the Stream settings subscribe UI. - window.location.assign("#streams/all"); + window.location.assign("#channels/all"); // Returning false from an onShow handler cancels the show. return false; } diff --git a/web/src/gear_menu.js b/web/src/gear_menu.js index 0f1aecd0af..7ca7b1d281 100644 --- a/web/src/gear_menu.js +++ b/web/src/gear_menu.js @@ -59,7 +59,7 @@ The menu itself has the selector The items with the prefix of "hash:" are in-page links: - #streams + #channels #settings #organization #about-zulip diff --git a/web/src/hash_parser.ts b/web/src/hash_parser.ts index 5bc832190e..16e8d7032b 100644 --- a/web/src/hash_parser.ts +++ b/web/src/hash_parser.ts @@ -1,11 +1,11 @@ export function get_hash_category(hash?: string): string { - // given "#streams/subscribed", returns "streams" + // given "#channels/subscribed", returns "channels" return hash ? hash.replace(/^#/, "").split(/\//)[0] : ""; } export function get_hash_section(hash?: string): string { // given "#settings/profile", returns "profile" - // given '#streams/5/social", returns "5" + // given '#channels/5/social", returns "5" if (!hash) { return ""; } @@ -17,7 +17,7 @@ export function get_hash_section(hash?: string): string { function get_nth_hash_section(hash: string, n: number): string { // given "#settings/profile" and n=1, returns "profile" - // given '#streams/5/social" and n=2, returns "social" + // given '#channels/5/social" and n=2, returns "social" const parts = hash.replace(/\/$/, "").split(/\//); return parts.at(n) ?? ""; } @@ -49,7 +49,13 @@ export function is_same_server_message_link(url: string): boolean { export function is_overlay_hash(hash: string): boolean { // Hash changes within this list are overlays and should not unnarrow (etc.) const overlay_list = [ + // In 2024, stream was renamed to channel in the Zulip API and UI. + // Because pre-change Welcome Bot and Notification Bot messages + // included links to "/#streams/all" and "/#streams/new", we'll + // need to support "streams" as an overlay hash as an alias for + // "channels" permanently. "streams", + "channels", "drafts", "groups", "settings", @@ -72,7 +78,7 @@ export function is_overlay_hash(hash: string): boolean { export function is_editing_stream(desired_stream_id: number): boolean { const hash_components = window.location.hash.slice(1).split(/\//); - if (hash_components[0] !== "streams") { + if (hash_components[0] !== "channels") { return false; } @@ -88,7 +94,7 @@ export function is_editing_stream(desired_stream_id: number): boolean { } export function is_create_new_stream_narrow(): boolean { - return window.location.hash === "#streams/new"; + return window.location.hash === "#channels/new"; } // This checks whether the user is in the stream settings menu @@ -96,7 +102,7 @@ export function is_create_new_stream_narrow(): boolean { export function is_subscribers_section_opened_for_stream(): boolean { const hash_components = window.location.hash.slice(1).split(/\//); - if (hash_components[0] !== "streams") { + if (hash_components[0] !== "channels") { return false; } if (!hash_components[3]) { diff --git a/web/src/hash_util.ts b/web/src/hash_util.ts index a5897cf731..af259373a3 100644 --- a/web/src/hash_util.ts +++ b/web/src/hash_util.ts @@ -145,7 +145,7 @@ export function by_conversation_and_time_url(message: Message): string { } export function stream_edit_url(sub: StreamSubscription, right_side_tab: string): string { - return `#streams/${sub.stream_id}/${internal_url.encodeHashComponent( + return `#channels/${sub.stream_id}/${internal_url.encodeHashComponent( sub.name, )}/${right_side_tab}`; } @@ -192,7 +192,16 @@ export function parse_narrow(hash: string): NarrowTerm[] | undefined { return terms; } -export function validate_stream_settings_hash(hash: string): string { +export function channels_settings_section_url(section = "subscribed"): string { + const valid_section_values = new Set(["new", "subscribed", "all"]); + if (!valid_section_values.has(section)) { + blueslip.warn("invalid section for channels settings: " + section); + return "#channels/subscribed"; + } + return `#channels/${section}`; +} + +export function validate_channels_settings_hash(hash: string): string { const hash_components = hash.slice(1).split(/\//); const section = hash_components[1]; @@ -201,7 +210,7 @@ export function validate_stream_settings_hash(hash: string): string { settings_data.user_can_create_web_public_streams() || settings_data.user_can_create_private_streams(); if (section === "new" && !can_create_streams) { - return "#streams/subscribed"; + return channels_settings_section_url(); } if (/\d+/.test(section)) { @@ -216,27 +225,18 @@ export function validate_stream_settings_hash(hash: string): string { // // In all these cases we redirect the user to 'subscribed' tab. if (sub === undefined || (page_params.is_guest && !stream_data.is_subscribed(stream_id))) { - return "#streams/subscribed"; + return channels_settings_section_url(); } - const stream_name = hash_components[2]; let right_side_tab = hash_components[3]; const valid_right_side_tab_values = new Set(["general", "personal", "subscribers"]); - if (sub.name === stream_name && valid_right_side_tab_values.has(right_side_tab)) { - return hash; - } if (!valid_right_side_tab_values.has(right_side_tab)) { right_side_tab = "general"; } return stream_edit_url(sub, right_side_tab); } - const valid_section_values = ["new", "subscribed", "all"]; - if (!valid_section_values.includes(section)) { - blueslip.warn("invalid section for streams: " + section); - return "#streams/subscribed"; - } - return hash; + return channels_settings_section_url(section); } export function validate_group_settings_hash(hash: string): string { diff --git a/web/src/hashchange.js b/web/src/hashchange.js index 4895cb0684..0dd4f48aa7 100644 --- a/web/src/hashchange.js +++ b/web/src/hashchange.js @@ -216,6 +216,7 @@ function do_hashchange_normal(from_reload) { case "#search-operators": case "#drafts": case "#invite": + case "#channels": case "#streams": case "#organization": case "#settings": @@ -235,7 +236,7 @@ function do_hashchange_overlay(old_hash) { // show the user's home view behind it. show_home_view(); } - const base = hash_parser.get_current_hash_category(); + let base = hash_parser.get_current_hash_category(); const old_base = hash_parser.get_hash_category(old_hash); let section = hash_parser.get_current_hash_section(); @@ -263,11 +264,21 @@ function do_hashchange_overlay(old_hash) { ); } - if (base === "streams") { - const valid_hash = hash_util.validate_stream_settings_hash(window.location.hash); + // In 2024, stream was renamed to channel in the Zulip API and UI. + // Because pre-change Welcome Bot and Notification Bot messages + // included links to "/#streams/all" and "/#streams/new", we'll + // need to support "streams" as an overlay hash as an alias for + // "channels" permanently. + if (base === "streams" || base === "channels") { + const valid_hash = hash_util.validate_channels_settings_hash(window.location.hash); + // Here valid_hash will always return "channels" as the base. + // So, if we update the history because the valid hash does + // not match the window.location.hash, then we also reset the + // base string we're tracking for the hash. if (valid_hash !== window.location.hash) { history.replaceState(null, "", browser_history.get_full_url(valid_hash)); section = hash_parser.get_current_hash_section(); + base = hash_parser.get_current_hash_category(); } } @@ -279,14 +290,14 @@ function do_hashchange_overlay(old_hash) { } } - // Start by handling the specific case of going - // from something like streams/all to streams_subscribed. + // Start by handling the specific case of going from + // something like "#channels/all" to "#channels/subscribed". // // In most situations we skip by this logic and load // the new overlay. if (coming_from_overlay && base === old_base) { - if (base === "streams") { - // e.g. #streams/29/social/subscribers + if (base === "channels") { + // e.g. #channels/29/social/subscribers const right_side_tab = hash_parser.get_current_nth_hash_section(3); stream_settings_ui.change_state(section, undefined, right_side_tab); return; @@ -353,8 +364,8 @@ function do_hashchange_overlay(old_hash) { browser_history.set_hash_before_overlay(old_hash); } - if (base === "streams") { - // e.g. #streams/29/social/subscribers + if (base === "channels") { + // e.g. #channels/29/social/subscribers const right_side_tab = hash_parser.get_current_nth_hash_section(3); if (is_somebody_else_profile_open()) { diff --git a/web/src/stream_edit.js b/web/src/stream_edit.js index 520e6a6b60..b63fc756fc 100644 --- a/web/src/stream_edit.js +++ b/web/src/stream_edit.js @@ -44,9 +44,9 @@ export function setup_subscriptions_tab_hash(tab_key_value) { return; } if (tab_key_value === "all-streams") { - browser_history.update("#streams/all"); + browser_history.update("#channels/all"); } else if (tab_key_value === "subscribed") { - browser_history.update("#streams/subscribed"); + browser_history.update("#channels/subscribed"); } else { blueslip.debug("Unknown tab_key_value: " + tab_key_value); } diff --git a/web/src/stream_settings_ui.js b/web/src/stream_settings_ui.js index 405e6dc11b..20a28bea62 100644 --- a/web/src/stream_settings_ui.js +++ b/web/src/stream_settings_ui.js @@ -731,7 +731,7 @@ function show_right_section() { } export function change_state(section, left_side_tab, right_side_tab) { - // if in #streams/new form. + // if in #channels/new form. if (section === "new") { do_open_create_stream(); show_right_section(); @@ -880,7 +880,7 @@ export function do_open_create_stream() { export function open_create_stream() { do_open_create_stream(); - browser_history.update("#streams/new"); + browser_history.update("#channels/new"); } export function update_stream_privacy_choices(policy) { diff --git a/web/templates/compose_banner/stream_does_not_exist_error.hbs b/web/templates/compose_banner/stream_does_not_exist_error.hbs index 07aa2c4825..0a5052dd49 100644 --- a/web/templates/compose_banner/stream_does_not_exist_error.hbs +++ b/web/templates/compose_banner/stream_does_not_exist_error.hbs @@ -3,7 +3,7 @@ {{#tr}} The channel #{channel_name} does not exist. Manage your subscriptions on your Channels page. - {{#*inline "z-link"}}{{> @partial-block}}{{/inline}} + {{#*inline "z-link"}}{{> @partial-block}}{{/inline}} {{/tr}}

{{/compose_banner}} diff --git a/web/templates/gear_menu_popover.hbs b/web/templates/gear_menu_popover.hbs index 2cda23b9f1..971733a376 100644 --- a/web/templates/gear_menu_popover.hbs +++ b/web/templates/gear_menu_popover.hbs @@ -55,7 +55,7 @@