From 8e953d98963b2fb4649bad03b0f252df97b89e75 Mon Sep 17 00:00:00 2001 From: Lauryn Menard Date: Tue, 30 Apr 2024 13:30:24 +0200 Subject: [PATCH] streams-settings-overlay: Update hash for stream to channel rename. Updates the base hash for the streams setting overlay to be "channels" instead of "streams". Because there are Welcome Bot and Notification Bot messages that would have been sent with the "/#streams" hash, we will need to support parsing those overlay hashes as an alias for "/#channels" permanently. Part of the stream to channels rename project. --- docs/subsystems/hashchange-system.md | 6 ++-- web/e2e-tests/lib/common.ts | 2 +- web/e2e-tests/message-basics.test.ts | 2 +- web/e2e-tests/navigation.test.ts | 2 +- web/src/add_stream_options_popover.ts | 2 +- web/src/gear_menu.js | 2 +- web/src/hash_parser.ts | 18 ++++++++---- web/src/hash_util.ts | 28 +++++++++--------- web/src/hashchange.js | 29 +++++++++++++------ web/src/stream_edit.js | 4 +-- web/src/stream_settings_ui.js | 4 +-- .../stream_does_not_exist_error.hbs | 2 +- web/templates/gear_menu_popover.hbs | 2 +- .../left_sidebar_stream_setting_popover.hbs | 4 +-- .../stream_settings_overlay.hbs | 4 +-- web/templates/subscribe_to_more_streams.hbs | 6 ++-- web/tests/hash_util.test.js | 20 ++++++------- web/tests/hashchange.test.js | 2 +- zerver/actions/message_send.py | 2 +- zerver/lib/markdown/help_relative_links.py | 4 +-- zerver/lib/onboarding.py | 4 +-- zerver/tests/test_docs.py | 4 +-- zerver/tests/test_markdown.py | 4 +-- zerver/tests/test_tutorial.py | 2 +- zerver/webhooks/helloworld/tests.py | 2 +- 25 files changed, 89 insertions(+), 72 deletions(-) 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 @@