Skip to content

Commit ad771c3

Browse files
committed
Display a banner for unsupported Zulip Server versions.
Signed-off-by: Anders Kaseorg <[email protected]>
1 parent 4c58bc3 commit ad771c3

File tree

7 files changed

+92
-4
lines changed

7 files changed

+92
-4
lines changed

app/common/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export type ServerConf = {
1515
url: string;
1616
alias: string;
1717
icon: string;
18+
zulipVersion: string;
19+
zulipFeatureLevel: number;
1820
};
1921

2022
export type TabRole = "server" | "function";

app/main/request.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,19 @@ export const _getServerSettings = async (
7171

7272
const data: unknown = JSON.parse(await getStream(response));
7373
/* eslint-disable @typescript-eslint/naming-convention */
74-
const {realm_name, realm_uri, realm_icon} = z
74+
const {
75+
realm_name,
76+
realm_uri,
77+
realm_icon,
78+
zulip_version,
79+
zulip_feature_level,
80+
} = z
7581
.object({
7682
realm_name: z.string(),
77-
realm_uri: z.string(),
83+
realm_uri: z.string().url(),
7884
realm_icon: z.string(),
85+
zulip_version: z.string().default("unknown"),
86+
zulip_feature_level: z.number().default(0),
7987
})
8088
.parse(data);
8189
/* eslint-enable @typescript-eslint/naming-convention */
@@ -86,6 +94,8 @@ export const _getServerSettings = async (
8694
icon: realm_icon.startsWith("/") ? realm_uri + realm_icon : realm_icon,
8795
url: realm_uri,
8896
alias: realm_name,
97+
zulipVersion: zulip_version,
98+
zulipFeatureLevel: zulip_feature_level,
8999
};
90100
};
91101

app/renderer/css/main.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,30 @@ webview.focus {
331331
outline: 0 solid transparent;
332332
}
333333

334+
.webview-unsupported {
335+
background: rgb(254 243 199);
336+
border: 1px solid rgb(253 230 138);
337+
color: rgb(69 26 3);
338+
font-family: system-ui;
339+
font-size: 14px;
340+
display: flex;
341+
}
342+
343+
.webview-unsupported[hidden] {
344+
display: none;
345+
}
346+
347+
.webview-unsupported-message {
348+
padding: 0.3em;
349+
flex: 1;
350+
text-align: center;
351+
}
352+
353+
.webview-unsupported-dismiss {
354+
padding: 0.3em;
355+
cursor: pointer;
356+
}
357+
334358
/* Tooltip styling */
335359

336360
#loading-tooltip,

app/renderer/js/components/webview.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,22 @@ type WebViewProps = {
3232
preload?: string;
3333
onTitleChange: () => void;
3434
hasPermission?: (origin: string, permission: string) => boolean;
35+
unsupportedMessage?: string;
3536
};
3637

3738
export default class WebView {
3839
static templateHtml(props: WebViewProps): Html {
3940
return html`
4041
<div class="webview-pane">
42+
<div
43+
class="webview-unsupported"
44+
${props.unsupportedMessage === undefined ? html`hidden` : html``}
45+
>
46+
<span class="webview-unsupported-message"
47+
>${props.unsupportedMessage ?? ""}</span
48+
>
49+
<span class="webview-unsupported-dismiss">×</span>
50+
</div>
4151
<webview
4252
data-tab-id="${props.tabIndex}"
4353
src="${props.url}"
@@ -98,6 +108,10 @@ export default class WebView {
98108
private zoomFactor = 1;
99109
private customCss: string | false | null;
100110
private readonly $webviewsContainer: DOMTokenList;
111+
private readonly $unsupported: HTMLElement;
112+
private readonly $unsupportedMessage: HTMLElement;
113+
private readonly $unsupportedDismiss: HTMLElement;
114+
private unsupportedDismissed = false;
101115

102116
private constructor(
103117
readonly props: WebViewProps,
@@ -109,6 +123,13 @@ export default class WebView {
109123
this.$webviewsContainer = document.querySelector(
110124
"#webviews-container",
111125
)!.classList;
126+
this.$unsupported = $pane.querySelector(".webview-unsupported")!;
127+
this.$unsupportedMessage = $pane.querySelector(
128+
".webview-unsupported-message",
129+
)!;
130+
this.$unsupportedDismiss = $pane.querySelector(
131+
".webview-unsupported-dismiss",
132+
)!;
112133

113134
this.registerListeners();
114135
}
@@ -195,6 +216,12 @@ export default class WebView {
195216
this.getWebContents().reload();
196217
}
197218

219+
setUnsupportedMessage(unsupportedMessage: string | undefined) {
220+
this.$unsupported.hidden =
221+
unsupportedMessage === undefined || this.unsupportedDismissed;
222+
this.$unsupportedMessage.textContent = unsupportedMessage ?? "";
223+
}
224+
198225
send<Channel extends keyof RendererMessage>(
199226
channel: Channel,
200227
...args: Parameters<RendererMessage[Channel]>
@@ -266,6 +293,11 @@ export default class WebView {
266293
this.$webview.addEventListener("did-stop-loading", () => {
267294
this.props.switchLoading(false, this.props.url);
268295
});
296+
297+
this.$unsupportedDismiss.addEventListener("click", () => {
298+
this.unsupportedDismissed = true;
299+
this.$unsupported.hidden = true;
300+
});
269301
}
270302

271303
private getBadgeCount(title: string): number {

app/renderer/js/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,9 @@ export class ServerManagerView {
342342
const serverConf = await DomainUtil.updateSavedServer(server.url, i);
343343
tab.setName(serverConf.alias);
344344
tab.setIcon(iconAsUrl(serverConf.icon));
345+
(await tab.webview).setUnsupportedMessage(
346+
DomainUtil.getUnsupportedMessage(serverConf),
347+
);
345348
})();
346349
}
347350

@@ -417,6 +420,7 @@ export class ServerManagerView {
417420
},
418421
onTitleChange: this.updateBadge.bind(this),
419422
preload: url.pathToFileURL(path.join(bundlePath, "preload.js")).href,
423+
unsupportedMessage: DomainUtil.getUnsupportedMessage(server),
420424
}),
421425
});
422426
this.tabs.push(tab);

app/renderer/js/utils/domain-util.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {z} from "zod";
1010
import * as EnterpriseUtil from "../../../common/enterprise-util.js";
1111
import Logger from "../../../common/logger-util.js";
1212
import * as Messages from "../../../common/messages.js";
13+
import * as t from "../../../common/translation-util.js";
1314
import type {ServerConf} from "../../../common/types.js";
1415
import {ipcRenderer} from "../typed-ipc-renderer.js";
1516

@@ -22,9 +23,11 @@ const logger = new Logger({
2223
export const defaultIconSentinel = "../renderer/img/icon.png";
2324

2425
const serverConfSchema = z.object({
25-
url: z.string(),
26+
url: z.string().url(),
2627
alias: z.string(),
2728
icon: z.string(),
29+
zulipVersion: z.string().default("unknown"),
30+
zulipFeatureLevel: z.number().default(0),
2831
});
2932

3033
let db!: JsonDB;
@@ -198,3 +201,15 @@ export function formatUrl(domain: string): string {
198201

199202
return `https://${domain}`;
200203
}
204+
205+
export function getUnsupportedMessage(server: ServerConf): string | undefined {
206+
if (server.zulipFeatureLevel < 65 /* Zulip Server 4.0 */) {
207+
const realm = new URL(server.url).hostname;
208+
return t.__(
209+
"{{{server}}} runs an outdated Zulip Server version {{{version}}}. It may not fully work in this app.",
210+
{server: realm, version: server.zulipVersion},
211+
);
212+
}
213+
214+
return undefined;
215+
}

public/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,6 @@
117117
"Zoom In": "Zoom In",
118118
"Zoom Out": "Zoom Out",
119119
"keyboard shortcuts": "keyboard shortcuts",
120-
"script": "script"
120+
"script": "script",
121+
"{{{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."
121122
}

0 commit comments

Comments
 (0)