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."
}