mirror of
				https://github.com/zulip/zulip-desktop.git
				synced 2025-11-04 05:53:21 +00:00 
			
		
		
		
	Add a tagged template function for HTML supporting HTML interpolation.
This allows better Prettier integration: Prettier recognizes and reformats tagged template literals with a tag named ‘html’. Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
		
							
								
								
									
										26
									
								
								app/common/html.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/common/html.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
export class HTML {
 | 
			
		||||
	html: string;
 | 
			
		||||
 | 
			
		||||
	constructor({html}: {html: string}) {
 | 
			
		||||
		this.html = html;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	join(htmls: readonly HTML[]): HTML {
 | 
			
		||||
		return new HTML({html: htmls.map(html => html.html).join(this.html)});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function html(
 | 
			
		||||
	template: TemplateStringsArray,
 | 
			
		||||
	...values: unknown[]
 | 
			
		||||
): HTML {
 | 
			
		||||
	let html = template[0];
 | 
			
		||||
	for (const [index, value] of values.entries()) {
 | 
			
		||||
		html += value instanceof HTML ? value.html : htmlEscape(String(value));
 | 
			
		||||
		html += template[index + 1];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return new HTML({html});
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import type {HTML} from '../../../common/html';
 | 
			
		||||
 | 
			
		||||
export default class BaseComponent {
 | 
			
		||||
	generateNodeFromHTML(html: string): Element | null {
 | 
			
		||||
	generateNodeFromHTML(html: HTML): Element | null {
 | 
			
		||||
		const wrapper = document.createElement('div');
 | 
			
		||||
		wrapper.innerHTML = html;
 | 
			
		||||
		wrapper.innerHTML = html.html;
 | 
			
		||||
		return wrapper.firstElementChild;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
import type {HTML} from '../../../common/html';
 | 
			
		||||
import {html} from '../../../common/html';
 | 
			
		||||
 | 
			
		||||
import type {TabProps} from './tab';
 | 
			
		||||
import Tab from './tab';
 | 
			
		||||
@@ -11,8 +12,8 @@ export default class FunctionalTab extends Tab {
 | 
			
		||||
		this.init();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
 | 
			
		||||
				<div class="server-tab-badge close-button">
 | 
			
		||||
					<i class="material-icons">close</i>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import {ipcRenderer} from 'electron';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import type {HTML} from '../../../common/html';
 | 
			
		||||
import {html} from '../../../common/html';
 | 
			
		||||
import * as SystemUtil from '../utils/system-util';
 | 
			
		||||
 | 
			
		||||
import type {TabProps} from './tab';
 | 
			
		||||
@@ -15,8 +15,8 @@ export default class ServerTab extends Tab {
 | 
			
		||||
		this.init();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="tab" data-tab-id="${this.props.tabIndex}">
 | 
			
		||||
				<div class="server-tooltip" style="display:none">${this.props.name}</div>
 | 
			
		||||
				<div class="server-tab-badge"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,8 @@ import {ipcRenderer, remote} from 'electron';
 | 
			
		||||
import fs from 'fs';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import * as ConfigUtil from '../../../common/config-util';
 | 
			
		||||
import {HTML, html} from '../../../common/html';
 | 
			
		||||
import * as SystemUtil from '../utils/system-util';
 | 
			
		||||
 | 
			
		||||
import BaseComponent from './base';
 | 
			
		||||
@@ -52,14 +51,14 @@ export default class WebView extends BaseComponent {
 | 
			
		||||
		this.$webviewsContainer = document.querySelector('#webviews-container').classList;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<webview
 | 
			
		||||
				class="disabled"
 | 
			
		||||
				data-tab-id="${this.props.tabIndex}"
 | 
			
		||||
				src="${this.props.url}"
 | 
			
		||||
				` + (this.props.nodeIntegration ? 'nodeIntegration' : '') + htmlEscape`
 | 
			
		||||
				` + (this.props.preload ? 'preload="js/preload.js"' : '') + htmlEscape`
 | 
			
		||||
				${new HTML({html: this.props.nodeIntegration ? 'nodeIntegration' : ''})}
 | 
			
		||||
				${new HTML({html: this.props.preload ? 'preload="js/preload.js"' : ''})}
 | 
			
		||||
				partition="persist:webviewsession"
 | 
			
		||||
				name="${this.props.name}"
 | 
			
		||||
				webpreferences="
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import {ipcRenderer} from 'electron';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import BaseComponent from '../../components/base';
 | 
			
		||||
 | 
			
		||||
interface BaseSectionProps {
 | 
			
		||||
@@ -25,24 +25,24 @@ export default class BaseSection extends BaseComponent {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	generateOptionHTML(settingOption: boolean, disabled?: boolean): string {
 | 
			
		||||
		const labelHTML = disabled ? '<label class="disallowed" title="Setting locked by system administrator."></label>' : '<label></label>';
 | 
			
		||||
	generateOptionHTML(settingOption: boolean, disabled?: boolean): HTML {
 | 
			
		||||
		const labelHTML = disabled ? html`<label class="disallowed" title="Setting locked by system administrator."></label>` : html`<label></label>`;
 | 
			
		||||
		if (settingOption) {
 | 
			
		||||
			return htmlEscape`
 | 
			
		||||
			return html`
 | 
			
		||||
				<div class="action">
 | 
			
		||||
					<div class="switch">
 | 
			
		||||
						<input class="toggle toggle-round" type="checkbox" checked disabled>
 | 
			
		||||
						` + labelHTML + htmlEscape`
 | 
			
		||||
						${labelHTML}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			`;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="action">
 | 
			
		||||
				<div class="switch">
 | 
			
		||||
					<input class="toggle toggle-round" type="checkbox">
 | 
			
		||||
					` + labelHTML + htmlEscape`
 | 
			
		||||
					${labelHTML}
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		`;
 | 
			
		||||
@@ -51,15 +51,15 @@ export default class BaseSection extends BaseComponent {
 | 
			
		||||
	/* A method that in future can be used to create dropdown menus using <select> <option> tags.
 | 
			
		||||
		it needs an object which has ``key: value`` pairs and will return a string that can be appended to HTML
 | 
			
		||||
	*/
 | 
			
		||||
	generateSelectHTML(options: Record<string, string>, className?: string, idName?: string): string {
 | 
			
		||||
		let html = htmlEscape`<select class="${className}" id="${idName}">\n`;
 | 
			
		||||
 | 
			
		||||
		for (const key of Object.keys(options)) {
 | 
			
		||||
			html += htmlEscape`<option name="${key}" value="${key}">${options[key]}</option>\n`;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		html += '</select>';
 | 
			
		||||
		return html;
 | 
			
		||||
	generateSelectHTML(options: Record<string, string>, className?: string, idName?: string): HTML {
 | 
			
		||||
		const optionsHTML = html``.join(Object.keys(options).map(key => html`
 | 
			
		||||
			<option name="${key}" value="${key}">${options[key]}</option>
 | 
			
		||||
		`));
 | 
			
		||||
		return html`
 | 
			
		||||
			<select class="${className}" id="${idName}">
 | 
			
		||||
				${optionsHTML}
 | 
			
		||||
			</select>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reloadApp(): void {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import {ipcRenderer} from 'electron';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
import * as DomainUtil from '../../utils/domain-util';
 | 
			
		||||
 | 
			
		||||
@@ -24,8 +24,8 @@ export default class ConnectedOrgSection extends BaseSection {
 | 
			
		||||
		this.props = props;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="settings-pane" id="server-settings-pane">
 | 
			
		||||
				<div class="page-title">${t.__('Connected organizations')}</div>
 | 
			
		||||
				<div class="title" id="existing-servers">${t.__('All the connected orgnizations will appear here.')}</div>
 | 
			
		||||
@@ -45,7 +45,7 @@ export default class ConnectedOrgSection extends BaseSection {
 | 
			
		||||
		this.props.$root.textContent = '';
 | 
			
		||||
 | 
			
		||||
		const servers = DomainUtil.getDomains();
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML();
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML().html;
 | 
			
		||||
 | 
			
		||||
		this.$serverInfoContainer = document.querySelector('#server-info-container');
 | 
			
		||||
		this.$existingServers = document.querySelector('#existing-servers');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
import BaseComponent from '../../components/base';
 | 
			
		||||
import * as LinkUtil from '../../utils/link-util';
 | 
			
		||||
@@ -18,8 +18,8 @@ export default class FindAccounts extends BaseComponent {
 | 
			
		||||
		this.props = props;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="settings-card certificate-card">
 | 
			
		||||
				<div class="certificate-input">
 | 
			
		||||
					<div>${t.__('Organization URL')}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,12 @@ import fs from 'fs';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
import Tagify from '@yaireo/tagify';
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
import ISO6391 from 'iso-639-1';
 | 
			
		||||
 | 
			
		||||
import * as ConfigUtil from '../../../../common/config-util';
 | 
			
		||||
import * as EnterpriseUtil from '../../../../common/enterprise-util';
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
import supportedLocales from '../../../../translations/supported-locales.json';
 | 
			
		||||
 | 
			
		||||
@@ -28,8 +29,8 @@ export default class GeneralSection extends BaseSection {
 | 
			
		||||
		this.props = props;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
            <div class="settings-pane">
 | 
			
		||||
                <div class="title">${t.__('Appearance')}</div>
 | 
			
		||||
                <div id="appearance-option-settings" class="settings-card">
 | 
			
		||||
@@ -159,7 +160,7 @@ export default class GeneralSection extends BaseSection {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	init(): void {
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML();
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML().html;
 | 
			
		||||
		this.updateTrayOption();
 | 
			
		||||
		this.updateBadgeOption();
 | 
			
		||||
		this.updateSilentOption();
 | 
			
		||||
@@ -402,7 +403,7 @@ export default class GeneralSection extends BaseSection {
 | 
			
		||||
	setLocale(): void {
 | 
			
		||||
		const langDiv: HTMLSelectElement = document.querySelector('.lang-div');
 | 
			
		||||
		const langListHTML = this.generateSelectHTML(supportedLocales, 'lang-menu');
 | 
			
		||||
		langDiv.innerHTML += langListHTML;
 | 
			
		||||
		langDiv.innerHTML += langListHTML.html;
 | 
			
		||||
		// `langMenu` is the select-option dropdown menu formed after executing the previous command
 | 
			
		||||
		const langMenu: HTMLSelectElement = document.querySelector('.lang-menu');
 | 
			
		||||
 | 
			
		||||
@@ -520,9 +521,10 @@ export default class GeneralSection extends BaseSection {
 | 
			
		||||
			const note: HTMLElement = document.querySelector('#note');
 | 
			
		||||
			note.append(t.__('You can select a maximum of 3 languages for spellchecking.'));
 | 
			
		||||
			const spellDiv: HTMLElement = document.querySelector('#spellcheck-langs');
 | 
			
		||||
			spellDiv.innerHTML += htmlEscape`
 | 
			
		||||
			spellDiv.innerHTML += html`
 | 
			
		||||
				<div class="setting-description">${t.__('Spellchecker Languages')}</div>
 | 
			
		||||
				<input name='spellcheck' placeholder='Enter Languages'>`;
 | 
			
		||||
				<input name='spellcheck' placeholder='Enter Languages'>
 | 
			
		||||
			`.html;
 | 
			
		||||
 | 
			
		||||
			const availableLanguages = session.fromPartition('persist:webviewsession').availableSpellCheckerLanguages;
 | 
			
		||||
			let languagePairs: Map<string, string> = new Map();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
import BaseComponent from '../../components/base';
 | 
			
		||||
 | 
			
		||||
@@ -19,16 +19,15 @@ export default class PreferenceNav extends BaseComponent {
 | 
			
		||||
		this.init();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		let navItemsHTML = '';
 | 
			
		||||
		for (const navItem of this.navItems) {
 | 
			
		||||
			navItemsHTML += htmlEscape`<div class="nav" id="nav-${navItem}">${t.__(navItem)}</div>`;
 | 
			
		||||
		}
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		const navItemsHTML = html``.join(this.navItems.map(navItem => html`
 | 
			
		||||
			<div class="nav" id="nav-${navItem}">${t.__(navItem)}</div>
 | 
			
		||||
		`));
 | 
			
		||||
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
		return html`
 | 
			
		||||
			<div>
 | 
			
		||||
				<div id="settings-header">${t.__('Settings')}</div>
 | 
			
		||||
				<div id="nav-container">` + navItemsHTML + htmlEscape`</div>
 | 
			
		||||
				<div id="nav-container">${navItemsHTML}</div>
 | 
			
		||||
			</div>
 | 
			
		||||
		`;
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import {ipcRenderer} from 'electron';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import * as ConfigUtil from '../../../../common/config-util';
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
 | 
			
		||||
import BaseSection from './base-section';
 | 
			
		||||
@@ -23,8 +23,8 @@ export default class NetworkSection extends BaseSection {
 | 
			
		||||
		this.props = props;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
            <div class="settings-pane">
 | 
			
		||||
                <div class="title">${t.__('Proxy')}</div>
 | 
			
		||||
                <div id="appearance-option-settings" class="settings-card">
 | 
			
		||||
@@ -61,7 +61,7 @@ export default class NetworkSection extends BaseSection {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	init(): void {
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML();
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML().html;
 | 
			
		||||
		this.$proxyPAC = document.querySelector('#proxy-pac-option .setting-input-value');
 | 
			
		||||
		this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
 | 
			
		||||
		this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import {ipcRenderer, remote} from 'electron';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
import BaseComponent from '../../components/base';
 | 
			
		||||
import * as DomainUtil from '../../utils/domain-util';
 | 
			
		||||
@@ -24,8 +24,8 @@ export default class NewServerForm extends BaseComponent {
 | 
			
		||||
		this.props = props;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="server-input-container">
 | 
			
		||||
				<div class="title">${t.__('Organization URL')}</div>
 | 
			
		||||
				<div class="add-server-info-row">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import {remote, ipcRenderer} from 'electron';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import * as Messages from '../../../../common/messages';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
import type {ServerConf} from '../../../../common/types';
 | 
			
		||||
@@ -29,8 +29,8 @@ export default class ServerInfoForm extends BaseComponent {
 | 
			
		||||
		this.props = props;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="settings-card">
 | 
			
		||||
				<div class="server-info-left">
 | 
			
		||||
					<img class="server-info-icon" src="${this.props.server.icon}"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
 | 
			
		||||
import BaseSection from './base-section';
 | 
			
		||||
@@ -17,8 +17,8 @@ export default class ServersSection extends BaseSection {
 | 
			
		||||
		this.props = props;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		return html`
 | 
			
		||||
			<div class="add-server-modal">
 | 
			
		||||
				<div class="modal-container">
 | 
			
		||||
					<div class="settings-pane" id="server-settings-pane">
 | 
			
		||||
@@ -37,7 +37,7 @@ export default class ServersSection extends BaseSection {
 | 
			
		||||
	initServers(): void {
 | 
			
		||||
		this.props.$root.textContent = '';
 | 
			
		||||
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML();
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML().html;
 | 
			
		||||
		this.$newServerContainer = document.querySelector('#new-server-container');
 | 
			
		||||
 | 
			
		||||
		this.initNewServerForm();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import type {HTML} from '../../../../common/html';
 | 
			
		||||
import {html} from '../../../../common/html';
 | 
			
		||||
import * as t from '../../../../common/translation-util';
 | 
			
		||||
import * as LinkUtil from '../../utils/link-util';
 | 
			
		||||
 | 
			
		||||
@@ -17,10 +17,10 @@ export default class ShortcutsSection extends BaseSection {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// eslint-disable-next-line complexity
 | 
			
		||||
	templateHTML(): string {
 | 
			
		||||
	templateHTML(): HTML {
 | 
			
		||||
		const cmdOrCtrl = process.platform === 'darwin' ? '⌘' : 'Ctrl';
 | 
			
		||||
 | 
			
		||||
		return htmlEscape`
 | 
			
		||||
		return html`
 | 
			
		||||
						<div class="settings-pane">
 | 
			
		||||
						<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>${t.__('Tip')}:  </b>${t.__('These desktop app shortcuts extend the Zulip webapp\'s')} <span id="open-hotkeys-link"> ${t.__('keyboard shortcuts')}</span>.</p></div>
 | 
			
		||||
							<div class="title">${t.__('Application Shortcuts')}</div>
 | 
			
		||||
@@ -234,7 +234,7 @@ export default class ShortcutsSection extends BaseSection {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	init(): void {
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML();
 | 
			
		||||
		this.props.$root.innerHTML = this.templateHTML().html;
 | 
			
		||||
		this.openHotkeysExternalLink();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@ import fs from 'fs';
 | 
			
		||||
import os from 'os';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
import {html} from '../../../common/html';
 | 
			
		||||
 | 
			
		||||
export function isUploadsUrl(server: string, url: URL): boolean {
 | 
			
		||||
	return url.origin === server && url.pathname.startsWith('/user_uploads/');
 | 
			
		||||
@@ -19,7 +19,7 @@ export async function openBrowser(url: URL): Promise<void> {
 | 
			
		||||
			path.join(os.tmpdir(), 'zulip-redirect-')
 | 
			
		||||
		);
 | 
			
		||||
		const file = path.join(dir, 'redirect.html');
 | 
			
		||||
		fs.writeFileSync(file, htmlEscape`\
 | 
			
		||||
		fs.writeFileSync(file, html`\
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
    <head>
 | 
			
		||||
@@ -36,7 +36,7 @@ export async function openBrowser(url: URL): Promise<void> {
 | 
			
		||||
        <p>Opening <a href="${url.href}">${url.href}</a>…</p>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
`);
 | 
			
		||||
`.html);
 | 
			
		||||
		await shell.openPath(file);
 | 
			
		||||
		setTimeout(() => {
 | 
			
		||||
			fs.unlinkSync(file);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import {ipcRenderer} from 'electron';
 | 
			
		||||
 | 
			
		||||
import * as backoff from 'backoff';
 | 
			
		||||
import {htmlEscape} from 'escape-goat';
 | 
			
		||||
 | 
			
		||||
import {html} from '../../../common/html';
 | 
			
		||||
import Logger from '../../../common/logger-util';
 | 
			
		||||
import type WebView from '../components/webview';
 | 
			
		||||
 | 
			
		||||
@@ -60,9 +60,10 @@ export default class ReconnectUtil {
 | 
			
		||||
		logger.log('There is no internet connection, try checking network cables, modem and router.');
 | 
			
		||||
		const errorMessageHolder = document.querySelector('#description');
 | 
			
		||||
		if (errorMessageHolder) {
 | 
			
		||||
			errorMessageHolder.innerHTML = htmlEscape`
 | 
			
		||||
			errorMessageHolder.innerHTML = html`
 | 
			
		||||
						<div>Your internet connection doesn't seem to work properly!</div>
 | 
			
		||||
						<div>Verify that it works and then click try again.</div>`;
 | 
			
		||||
						<div>Verify that it works and then click try again.</div>
 | 
			
		||||
			`.html;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return false;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user