diff --git a/app/common/types.ts b/app/common/types.ts index 35df103d..6215df1d 100644 --- a/app/common/types.ts +++ b/app/common/types.ts @@ -15,6 +15,8 @@ export type ServerConf = { url: string; alias: string; icon: string; + zulipVersion: string; + zulipFeatureLevel: number; }; export type TabRole = "server" | "function"; diff --git a/app/main/request.ts b/app/main/request.ts index 618cc9e3..d8bbfb4d 100644 --- a/app/main/request.ts +++ b/app/main/request.ts @@ -71,11 +71,19 @@ export const _getServerSettings = async ( const data: unknown = JSON.parse(await getStream(response)); /* eslint-disable @typescript-eslint/naming-convention */ - const {realm_name, realm_uri, realm_icon} = z + const { + realm_name, + realm_uri, + realm_icon, + zulip_version, + zulip_feature_level, + } = z .object({ realm_name: z.string(), - realm_uri: z.string(), + realm_uri: z.string().url(), realm_icon: z.string(), + zulip_version: z.string().default("unknown"), + zulip_feature_level: z.number().default(0), }) .parse(data); /* eslint-enable @typescript-eslint/naming-convention */ @@ -86,6 +94,8 @@ export const _getServerSettings = async ( icon: realm_icon.startsWith("/") ? realm_uri + realm_icon : realm_icon, url: realm_uri, alias: realm_name, + zulipVersion: zulip_version, + zulipFeatureLevel: zulip_feature_level, }; }; diff --git a/app/renderer/css/main.css b/app/renderer/css/main.css index fdf56d88..1d56abd3 100644 --- a/app/renderer/css/main.css +++ b/app/renderer/css/main.css @@ -331,6 +331,30 @@ webview.focus { outline: 0 solid transparent; } +.webview-unsupported { + background: rgb(254 243 199); + border: 1px solid rgb(253 230 138); + color: rgb(69 26 3); + font-family: system-ui; + font-size: 14px; + display: flex; +} + +.webview-unsupported[hidden] { + display: none; +} + +.webview-unsupported-message { + padding: 0.3em; + flex: 1; + text-align: center; +} + +.webview-unsupported-dismiss { + padding: 0.3em; + cursor: pointer; +} + /* Tooltip styling */ #loading-tooltip, diff --git a/app/renderer/js/components/webview.ts b/app/renderer/js/components/webview.ts index 60829729..eb382c02 100644 --- a/app/renderer/js/components/webview.ts +++ b/app/renderer/js/components/webview.ts @@ -32,12 +32,22 @@ type WebViewProps = { preload?: string; onTitleChange: () => void; hasPermission?: (origin: string, permission: string) => boolean; + unsupportedMessage?: string; }; export default class WebView { static templateHtml(props: WebViewProps): Html { return html`
+
+ ${props.unsupportedMessage ?? ""} + × +
( channel: Channel, ...args: Parameters @@ -266,6 +293,11 @@ export default class WebView { this.$webview.addEventListener("did-stop-loading", () => { this.props.switchLoading(false, this.props.url); }); + + this.$unsupportedDismiss.addEventListener("click", () => { + this.unsupportedDismissed = true; + this.$unsupported.hidden = true; + }); } private getBadgeCount(title: string): number { diff --git a/app/renderer/js/main.ts b/app/renderer/js/main.ts index 0a8953a9..c1bda236 100644 --- a/app/renderer/js/main.ts +++ b/app/renderer/js/main.ts @@ -342,6 +342,9 @@ export class ServerManagerView { const serverConf = await DomainUtil.updateSavedServer(server.url, i); tab.setName(serverConf.alias); tab.setIcon(iconAsUrl(serverConf.icon)); + (await tab.webview).setUnsupportedMessage( + DomainUtil.getUnsupportedMessage(serverConf), + ); })(); } @@ -417,6 +420,7 @@ export class ServerManagerView { }, onTitleChange: this.updateBadge.bind(this), preload: url.pathToFileURL(path.join(bundlePath, "preload.js")).href, + unsupportedMessage: DomainUtil.getUnsupportedMessage(server), }), }); this.tabs.push(tab); diff --git a/app/renderer/js/utils/domain-util.ts b/app/renderer/js/utils/domain-util.ts index 9a4f682d..8375733f 100644 --- a/app/renderer/js/utils/domain-util.ts +++ b/app/renderer/js/utils/domain-util.ts @@ -10,6 +10,7 @@ import {z} from "zod"; import * as EnterpriseUtil from "../../../common/enterprise-util.js"; import Logger from "../../../common/logger-util.js"; import * as Messages from "../../../common/messages.js"; +import * as t from "../../../common/translation-util.js"; import type {ServerConf} from "../../../common/types.js"; import {ipcRenderer} from "../typed-ipc-renderer.js"; @@ -22,9 +23,11 @@ const logger = new Logger({ export const defaultIconSentinel = "../renderer/img/icon.png"; const serverConfSchema = z.object({ - url: z.string(), + url: z.string().url(), alias: z.string(), icon: z.string(), + zulipVersion: z.string().default("unknown"), + zulipFeatureLevel: z.number().default(0), }); let db!: JsonDB; @@ -198,3 +201,15 @@ export function formatUrl(domain: string): string { return `https://${domain}`; } + +export function getUnsupportedMessage(server: ServerConf): string | undefined { + if (server.zulipFeatureLevel < 65 /* Zulip Server 4.0 */) { + const realm = new URL(server.url).hostname; + return t.__( + "{{{server}}} runs an outdated Zulip Server version {{{version}}}. It may not fully work in this app.", + {server: realm, version: server.zulipVersion}, + ); + } + + return undefined; +} diff --git a/public/translations/en.json b/public/translations/en.json index 1e35c18a..42608ec3 100644 --- a/public/translations/en.json +++ b/public/translations/en.json @@ -117,5 +117,6 @@ "Zoom In": "Zoom In", "Zoom Out": "Zoom Out", "keyboard shortcuts": "keyboard shortcuts", - "script": "script" + "script": "script", + "{{{server}}} runs an outdated Zulip Server version {{{version}}}. It may not fully work in this app.": "{{{server}}} runs an outdated Zulip Server version {{{version}}}. It may not fully work in this app." }