From cf8c83e3cfbc3fcc538882c53ad561fd82bb63b7 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Thu, 20 Apr 2017 23:15:01 +0800 Subject: [PATCH 01/52] Finish UI template of multi-server manager. --- app/main/index.js | 7 ++- app/renderer/css/servermanager.css | 86 ++++++++++++++++++++++++++++++ app/renderer/main.html | 38 +++++++++++++ package.json | 2 +- 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 app/renderer/css/servermanager.css create mode 100644 app/renderer/main.html diff --git a/app/main/index.js b/app/main/index.js index 7f9755a0..4e94980c 100644 --- a/app/main/index.js +++ b/app/main/index.js @@ -54,10 +54,14 @@ const targetURL = function () { if (data.domain === undefined) { return staticURL; } + // TODO: Use new main window + return 'file://' + path.join(__dirname, '../renderer', 'main.html'); return data.domain; }; function serverError(targetURL) { + // TODO: disabled + return; if (targetURL.indexOf('localhost:') < 0 && data.domain) { const req = https.request(targetURL + '/static/audio/zulip.ogg', res => { console.log('Server StatusCode:', res.statusCode); @@ -184,11 +188,12 @@ function createMainWindow() { icon: iconPath(), minWidth: 600, minHeight: 400, + titleBarStyle: 'hidden-inset', webPreferences: { preload: path.join(__dirname, '../renderer/js/preload.js'), plugins: true, allowDisplayingInsecureContent: true, - nodeIntegration: false + nodeIntegration: true }, show: false }); diff --git a/app/renderer/css/servermanager.css b/app/renderer/css/servermanager.css new file mode 100644 index 00000000..0d02f994 --- /dev/null +++ b/app/renderer/css/servermanager.css @@ -0,0 +1,86 @@ +html, body { + height: 100%; + margin: 0; +} + +#content { + display: flex; + height: 100%; +} + +#sidebar { + background: #222c31; + width: 88px; + padding: 40px 0 20px 0; + justify-content: space-between; + display: flex; + flex-direction: column; + -webkit-app-region: drag; +} + +#webview { + width: 100%; +} + +.action-button { + display: flex; + flex-direction: column; + align-items: center; + padding: 10px; +} + +.action-button i { + color: #6c8592; + font-size: 28px; +} + + +#servers-container { + display: flex; + align-items: center; + flex-direction: column; +} + +.server-button { + position: relative; + margin: 5px 0 5px 6px; +} + +.server-button.active::before{ + content: ""; + background: #43bba6; + border-radius: 3px; + width: 6px; + position: absolute; + height: 6px; + left: -12px; + top: 25px; +} + +.server-button .server-name{ + background: #a4d3c4; + border-radius: 22px; + width: 44px; + height: 44px; + position: relative; + margin: 5px 0; + z-index: 11; + line-height: 44px; + font-size: 24px; + font-family: sans-serif; + color: #194a2b; + text-align: center; + overflow: hidden; + +} + +.server-button.active .server-ring { + background: #43bba6; + border-radius: 24px; + width: 48px; + height: 48px; + position: absolute; + left: -2px; + top: 3px; + z-index: 10; +} \ No newline at end of file diff --git a/app/renderer/main.html b/app/renderer/main.html new file mode 100644 index 00000000..be79f075 --- /dev/null +++ b/app/renderer/main.html @@ -0,0 +1,38 @@ + + + + + + Zulip + + + + +
+ + > + + +
+ + + diff --git a/package.json b/package.json index 54f5360e..fc9be18b 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "win": { "target": "nsis", "icon": "build/icon.ico" - }, + }, "nsis": { "perMachine": true, "oneClick": false From 81c71b1f8360e2d6e82f8c4ef24ae12134881de2 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Fri, 21 Apr 2017 14:40:36 +0800 Subject: [PATCH 02/52] Update prototype design. --- app/renderer/css/servermanager.css | 35 +++++++++++++----------------- app/renderer/js/main.js | 1 + app/renderer/main.html | 9 ++++---- 3 files changed, 21 insertions(+), 24 deletions(-) create mode 100644 app/renderer/js/main.js diff --git a/app/renderer/css/servermanager.css b/app/renderer/css/servermanager.css index 0d02f994..9ef652a4 100644 --- a/app/renderer/css/servermanager.css +++ b/app/renderer/css/servermanager.css @@ -10,7 +10,7 @@ html, body { #sidebar { background: #222c31; - width: 88px; + width: 80px; padding: 40px 0 20px 0; justify-content: space-between; display: flex; @@ -19,7 +19,7 @@ html, body { } #webview { - width: 100%; + flex-grow: 1; } .action-button { @@ -43,23 +43,25 @@ html, body { .server-button { position: relative; - margin: 5px 0 5px 6px; + margin: 5px 0; } .server-button.active::before{ content: ""; - background: #43bba6; - border-radius: 3px; - width: 6px; + background: #fff; + border-radius: 0 3px 3px 0; + width: 4px; position: absolute; - height: 6px; - left: -12px; - top: 25px; + height: 44px; + left: -18px; + top: 5px; } .server-button .server-name{ background: #a4d3c4; - border-radius: 22px; + background-image: url(https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png); + background-size: 100%; + border-radius: 4px; width: 44px; height: 44px; position: relative; @@ -71,16 +73,9 @@ html, body { color: #194a2b; text-align: center; overflow: hidden; - + opacity: 0.6; } -.server-button.active .server-ring { - background: #43bba6; - border-radius: 24px; - width: 48px; - height: 48px; - position: absolute; - left: -2px; - top: 3px; - z-index: 10; +.server-button.active .server-name{ + opacity: 1; } \ No newline at end of file diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js new file mode 100644 index 00000000..ad9a93a7 --- /dev/null +++ b/app/renderer/js/main.js @@ -0,0 +1 @@ +'use strict'; diff --git a/app/renderer/main.html b/app/renderer/main.html index be79f075..517144fd 100644 --- a/app/renderer/main.html +++ b/app/renderer/main.html @@ -12,12 +12,13 @@ - > - - + /> From 1aef53ef94e4031cb23e9c79c1b677299307284b Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sat, 22 Apr 2017 23:33:23 +0800 Subject: [PATCH 05/52] Change domain config schema and update DomainUtil. --- app/renderer/js/utils/domain-util.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 53103063..fa3d3c54 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -15,10 +15,14 @@ class DomainUtil { addDomain() { const servers = { url: 'https://chat.zulip.org', - alias: 'Zulip 2', - avatar: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png' + alias: 'Zulip 2333', + icon: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png' } - db.push("/domains[]", servers, true); + this.db.push("/domains[]", servers, true); + } + + removeDomains() { + this.db.delete("/domains"); } } From d18885ecc9cc5fccb0f27af3733b995025cb06d1 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sat, 22 Apr 2017 23:35:03 +0800 Subject: [PATCH 06/52] Finish interactions of switching servers in ServerManagerView. --- .vscode/settings.json | 3 + app/renderer/css/servermanager.css | 21 ++++++ app/renderer/img/loading.gif | Bin 0 -> 22272 bytes app/renderer/js/main.js | 115 +++++++++++++++++++++++------ app/renderer/main.html | 13 +--- 5 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 app/renderer/img/loading.gif diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..20af2f68 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +// Place your settings in this file to overwrite default and user settings. +{ +} \ No newline at end of file diff --git a/app/renderer/css/servermanager.css b/app/renderer/css/servermanager.css index 1b6834dc..18b809e9 100644 --- a/app/renderer/css/servermanager.css +++ b/app/renderer/css/servermanager.css @@ -6,6 +6,9 @@ html, body { #content { display: flex; height: 100%; + background: #eee url(../img/loading.gif) no-repeat; + background-size: 60px 60px; + background-position: center; } #sidebar { @@ -32,8 +35,12 @@ html, body { .action-button i { color: #6c8592; font-size: 28px; + cursor: default; } +.action-button:hover i { + color: #98a9b3; +} #servers-container { display: flex; @@ -75,6 +82,20 @@ html, body { opacity: 0.6; } +.server-button .server-name:hover{ + opacity: 0.8; +} + .server-button.active .server-name{ opacity: 1; +} + +#webview { + opacity: 1; + transition: opacity 0.3s; +} + +#webview.loading { + opacity: 0; + transition: opacity 0.3s; } \ No newline at end of file diff --git a/app/renderer/img/loading.gif b/app/renderer/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..60d63d367b4a5bae3cbac49665e2516efe84a2d0 GIT binary patch literal 22272 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 literal 0 HcmV?d00001 diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index a538106d..aeea5827 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -3,37 +3,104 @@ const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); class ServerManagerView { - constructor() { - this.serverButtonTemplate = ` -
-
-
`; - this.$serversContainer = document.getElementById('servers-container'); + constructor() { + this.$serversContainer = document.getElementById('servers-container'); - const $actionsContainer = document.getElementById('actions-container'); - this.$addServerButton = $actionsContainer.querySelector('#add-action'); - this.$settingsButton = $actionsContainer.querySelector('#settings-action'); - } - - init() { - this.domainUtil = new DomainUtil(); - console.log(this.domainUtil.getDomains()); + const $actionsContainer = document.getElementById('actions-container'); + this.$addServerButton = $actionsContainer.querySelector('#add-action'); + this.$settingsButton = $actionsContainer.querySelector('#settings-action'); + this.$content = document.getElementById('content'); + + this.isLoading = false; + } + + init() { + this.domainUtil = new DomainUtil(); + this.initServers(); + } + + initServers() { + const servers = this.domainUtil.getDomains(); + for (let server of servers) { + this.initServer(server); + } + + const $firstServerButton = this.$serversContainer.firstChild; + $firstServerButton.classList.add('active'); + this.$activeServerButton = $firstServerButton; + this.initWebView($firstServerButton.getAttribute('domain')); + } + + initServer(server) { + const { + alias, + url + } = server; + const icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; + const serverButtonTemplate = ` +
+
+
`; + const $serverButton = this.__insert_node(serverButtonTemplate); + this.$serversContainer.appendChild($serverButton); + $serverButton.addEventListener('click', () => { + if (this.isLoading || this.$activeServerButton == $serverButton) return; + + this.$activeServerButton.classList.remove('active'); + $serverButton.classList.add('active'); + const url = $serverButton.getAttribute('domain'); + this.$activeServerButton = $serverButton; + this.startLoading(url); + }); + } + + initWebView(url) { + const webViewTemplate = ` + + + `; + this.$webView = this.__insert_node(webViewTemplate); + this.$content.appendChild(this.$webView); + this.$webView.addEventListener('dom-ready', this.endLoading.bind(this)); } - initServers() { + startLoading(url) { + this.$webView.loadURL(url); + this.isLoading = true; + this.$webView.classList.add('loading'); + } + + endLoading() { + this.isLoading = false; + this.$webView.classList.remove('loading'); + } + + initActions() { + + } + + addServer() { + + } + + openSettings() { } - initActions() { - - } - - addServer() { - + __insert_node(html) { + let wrapper= document.createElement('div'); + wrapper.innerHTML= html; + return wrapper.firstElementChild; } } window.onload = () => { - const serverManagerView = new ServerManagerView(); - serverManagerView.init(); -} \ No newline at end of file + const serverManagerView = new ServerManagerView(); + serverManagerView.init(); +} diff --git a/app/renderer/main.html b/app/renderer/main.html index 3e77eaad..2363b5a5 100644 --- a/app/renderer/main.html +++ b/app/renderer/main.html @@ -10,17 +10,7 @@
- />
From 331452edbb21fd3af0b77c530b89f554f4649622 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Mon, 24 Apr 2017 00:01:33 +0800 Subject: [PATCH 07/52] Refactor ServerManagerView and setup layout for PreferenceView. --- app/renderer/css/preference.css | 49 ++++++++++++++++ app/renderer/css/servermanager.css | 25 +++++--- app/renderer/js/main.js | 92 +++++++++++++++++++++--------- app/renderer/js/preference.js | 0 app/renderer/main.html | 2 +- app/renderer/preference.html | 23 ++++++++ 6 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 app/renderer/css/preference.css create mode 100644 app/renderer/js/preference.js create mode 100644 app/renderer/preference.html diff --git a/app/renderer/css/preference.css b/app/renderer/css/preference.css new file mode 100644 index 00000000..70a44115 --- /dev/null +++ b/app/renderer/css/preference.css @@ -0,0 +1,49 @@ +html, body { + height: 100%; + margin: 0; + cursor: default; +} + +#content { + display: flex; + height: 100%; + font-family: sans-serif; + background: #fff; +} + +#sidebar { + width: 80px; + padding: 40px; + display: flex; + flex-direction: column; + font-size: 16px; +} + +#tabs-container { + padding: 20px 0; +} + +.tab { + padding: 5px 0; + color: #999; + cursor: pointer; +} + +.tab.active { + color: #464e5a; + cursor: default; +} + +.tab.active::before { + color: #464e5a; + cursor: default; +} +#settings-header { + font-size: 24px; + color: #5c6166; +} + +.settings-container { + width: 100%; + display: flex; +} \ No newline at end of file diff --git a/app/renderer/css/servermanager.css b/app/renderer/css/servermanager.css index 18b809e9..260f2580 100644 --- a/app/renderer/css/servermanager.css +++ b/app/renderer/css/servermanager.css @@ -1,6 +1,7 @@ html, body { height: 100%; margin: 0; + cursor: default; } #content { @@ -35,25 +36,24 @@ html, body { .action-button i { color: #6c8592; font-size: 28px; - cursor: default; } .action-button:hover i { color: #98a9b3; } -#servers-container { +#tabs-container { display: flex; align-items: center; flex-direction: column; } -.server-button { +.tab { position: relative; margin: 5px 0; } -.server-button.active::before{ +.tab.active::before{ content: ""; background: #fff; border-radius: 0 3px 3px 0; @@ -64,7 +64,7 @@ html, body { top: 5px; } -.server-button .server-name{ +.tab .server-tab{ background: #a4d3c4; background-size: 100%; border-radius: 4px; @@ -74,7 +74,7 @@ html, body { margin: 5px 0; z-index: 11; line-height: 44px; - font-size: 24px; + font-size: 32px; font-family: sans-serif; color: #194a2b; text-align: center; @@ -82,11 +82,20 @@ html, body { opacity: 0.6; } -.server-button .server-name:hover{ +.tab .server-tab:hover{ opacity: 0.8; } -.server-button.active .server-name{ +.tab .settings-tab{ + background: #eee; +} + +.tab .settings-tab i{ + font-size: 28px; + line-height: 44px; +} + +.tab.active .server-tab{ opacity: 1; } diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index aeea5827..751a1f7e 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -4,7 +4,7 @@ const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); class ServerManagerView { constructor() { - this.$serversContainer = document.getElementById('servers-container'); + this.$tabsContainer = document.getElementById('tabs-container'); const $actionsContainer = document.getElementById('actions-container'); this.$addServerButton = $actionsContainer.querySelector('#add-action'); @@ -12,46 +12,38 @@ class ServerManagerView { this.$content = document.getElementById('content'); this.isLoading = false; + this.settingsTabIndex = -1; } init() { this.domainUtil = new DomainUtil(); - this.initServers(); + this.initTabs(); + this.initActions(); } - initServers() { + initTabs() { const servers = this.domainUtil.getDomains(); for (let server of servers) { - this.initServer(server); + this.initTab(server); } - - const $firstServerButton = this.$serversContainer.firstChild; - $firstServerButton.classList.add('active'); - this.$activeServerButton = $firstServerButton; - this.initWebView($firstServerButton.getAttribute('domain')); + + this.activateTab(0); } - initServer(server) { + initTab(tab) { const { alias, url - } = server; - const icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; - const serverButtonTemplate = ` -
-
+ } = tab; + const icon = tab.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; + const tabTemplate = tab.template || ` +
+
`; - const $serverButton = this.__insert_node(serverButtonTemplate); - this.$serversContainer.appendChild($serverButton); - $serverButton.addEventListener('click', () => { - if (this.isLoading || this.$activeServerButton == $serverButton) return; - - this.$activeServerButton.classList.remove('active'); - $serverButton.classList.add('active'); - const url = $serverButton.getAttribute('domain'); - this.$activeServerButton = $serverButton; - this.startLoading(url); - }); + const $tab = this.__insert_node(tabTemplate); + const index = this.$tabsContainer.childNodes.length; + this.$tabsContainer.appendChild($tab); + $tab.addEventListener('click', this.activateTab.bind(this, index)); } initWebView(url) { @@ -79,18 +71,62 @@ class ServerManagerView { endLoading() { this.isLoading = false; this.$webView.classList.remove('loading'); + this.$webView.openDevTools(); } initActions() { - + this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); } addServer() { - + } openSettings() { + if (this.settingsTabIndex != -1) { + this.activateTab(this.settingsTabIndex); + return; + } + const url = 'file:///' + path.resolve(('app/renderer/preference.html')); + console.log(url); + const settingsTabTemplate = ` +
+
+ settings +
+
`; + this.initTab({ + alias: 'Settings', + url: url, + template: settingsTabTemplate + }); + + this.settingsTabIndex = this.$tabsContainer.childNodes.length - 1; + this.activateTab(this.settingsTabIndex); + } + + activateTab(index) { + if (this.isLoading) return; + const $tab = this.$tabsContainer.childNodes[index]; + + if (this.$activeTab) { + if (this.$activeTab == $tab) { + return; + } else { + this.$activeTab.classList.remove('active'); + } + } + + $tab.classList.add('active'); + this.$activeTab = $tab; + + const domain = $tab.getAttribute('domain'); + if (this.$webView){ + this.startLoading(domain); + } else { + this.initWebView(domain); + } } __insert_node(html) { diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js new file mode 100644 index 00000000..e69de29b diff --git a/app/renderer/main.html b/app/renderer/main.html index 2363b5a5..f15e2709 100644 --- a/app/renderer/main.html +++ b/app/renderer/main.html @@ -10,7 +10,7 @@
`; this.$serverInfoContainer.appendChild(this.__insert_node(serverInfoTemplate)); + document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { + this.domainUtil.removeDomain(index); + this.initServers(); + }); } + initNewServerForm() { + const newServerFormTemplate = ` + + `; + this.$serverInfoContainer.appendChild(this.__insert_node(newServerFormTemplate)); + + this.$newServerForm = document.querySelector('.server-info.active'); + this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0]; + this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1]; + this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2]; + } + + initActions() { + this.$newServerButton.addEventListener('click', () => { + this.$newServerForm.classList.remove('hidden'); + this.$saveServerButton.classList.remove('hidden'); + this.$newServerButton.classList.add('hidden'); + }); + this.$saveServerButton.addEventListener('click', () => { + this.domainUtil.checkDomain(this.$newServerUrl.value).then((domain) => { + const server = { + alias: this.$newServerAlias.value, + url: domain, + icon: this.$newServerIcon.value + }; + this.domainUtil.addDomain(server); + this.$saveServerButton.classList.add('hidden'); + this.$newServerButton.classList.remove('hidden'); + this.$newServerForm.classList.add('hidden'); + + this.initServers(); + }, (errorMessage) => { + alert(errorMessage); + }); + }); + } __insert_node(html) { let wrapper= document.createElement('div'); wrapper.innerHTML= html; diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index fa3d3c54..f98d75cc 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -2,6 +2,7 @@ const {app} = require('electron').remote; const JsonDB = require('node-json-db'); +const request = require('request'); class DomainUtil { constructor() { @@ -12,18 +13,37 @@ class DomainUtil { return this.db.getData('/domains'); } - addDomain() { - const servers = { - url: 'https://chat.zulip.org', - alias: 'Zulip 2333', - icon: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png' - } + addDomain(server) { + server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; this.db.push("/domains[]", servers, true); } removeDomains() { this.db.delete("/domains"); } + + removeDomain(index) { + this.db.delete(`/domains[${index}]`); + } + + checkDomain(domain) { + const hasPrefix = (domain.indexOf('http') == 0); + if (!hasPrefix) { + domain = (domain.indexOf('localhost:') >= 0)? `http://${domain}` : `https://${domain}`; + } + + const checkDomain = domain + '/static/audio/zulip.ogg'; + + return new Promise((res, rej) => { + request(checkDomain, (error, response) => { + if (!error && response.statusCode !== 404) { + res(domain); + } else { + rej('Not a valid Zulip server'); + } + }); + }) + } } module.exports = DomainUtil; \ No newline at end of file diff --git a/app/renderer/preference.html b/app/renderer/preference.html index 03226fa7..1a1f61e3 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -25,7 +25,7 @@ add_box New Server
-
+ From a38c933bc880f38f8844ad9afd563fac4ce2a0d8 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Wed, 26 Apr 2017 13:32:51 +0800 Subject: [PATCH 11/52] Redirect to server settings page when domains are empty. --- app/renderer/js/main.js | 21 +++++++++++---------- app/renderer/js/preference.js | 14 +++++++++----- app/renderer/js/utils/domain-util.js | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index 56fd964d..3a540504 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -24,11 +24,15 @@ class ServerManagerView { initTabs() { const servers = this.domainUtil.getDomains(); - for (let server of servers) { - this.initTab(server); - } - - this.activateTab(0); + if (servers.length) { + for (let server of servers) { + this.initTab(server); + } + + this.activateTab(0); + } else { + this.openSettings(); + } } initTab(tab) { @@ -87,10 +91,7 @@ class ServerManagerView { initActions() { this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); - } - - addServer() { - + this.$settingsButton.addEventListener('click', this.openSettings.bind(this)); } openSettings() { @@ -99,7 +100,7 @@ class ServerManagerView { return; } const url = 'file:///' + path.resolve(('app/renderer/preference.html')); - console.log(url); + const settingsTabTemplate = `
diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js index 35e40d61..4e197233 100644 --- a/app/renderer/js/preference.js +++ b/app/renderer/js/preference.js @@ -16,12 +16,14 @@ class PreferenceView { } initServers() { - this.$serverInfoContainer.innerHTML = ''; - this.initNewServerForm(); const servers = this.domainUtil.getDomains(); - for (let i in servers) { - this.initServer(servers[i], i); - } + this.$serverInfoContainer.innerHTML = servers.length? '': 'Add your first server to get started!'; + + this.initNewServerForm(); + + for (let i in servers) { + this.initServer(servers[i], i); + } } initServer(server, index) { @@ -61,6 +63,7 @@ class PreferenceView { document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { this.domainUtil.removeDomain(index); this.initServers(); + alert('Success. Reload to apply changes.') }); } @@ -113,6 +116,7 @@ class PreferenceView { this.$newServerForm.classList.add('hidden'); this.initServers(); + alert('Success. Reload to apply changes.') }, (errorMessage) => { alert(errorMessage); }); diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index f98d75cc..4272adb9 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -15,7 +15,7 @@ class DomainUtil { addDomain(server) { server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; - this.db.push("/domains[]", servers, true); + this.db.push("/domains[]", server, true); } removeDomains() { From 35e6f7dcdd8ed8766507d927cc289573dd6df81a Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Thu, 27 Apr 2017 00:07:22 +0800 Subject: [PATCH 12/52] Move methods from mainWindow.webContents (index.js) to WebView (main.js). --- app/main/index.js | 98 +++------------------------ app/renderer/css/pref.css | 74 -------------------- app/renderer/css/preload.css | 1 - app/renderer/img/topography.png | Bin 41424 -> 0 bytes app/renderer/js/main.js | 40 ++++++++++- app/renderer/js/preload.js | 4 +- app/renderer/js/utils/domain-util.js | 12 +++- app/renderer/pref.html | 19 ------ 8 files changed, 59 insertions(+), 189 deletions(-) delete mode 100644 app/renderer/css/pref.css delete mode 100644 app/renderer/css/preload.css delete mode 100644 app/renderer/img/topography.png delete mode 100644 app/renderer/pref.html diff --git a/app/main/index.js b/app/main/index.js index cee957c8..51d27c2e 100644 --- a/app/main/index.js +++ b/app/main/index.js @@ -48,49 +48,7 @@ let mainWindow; let targetLink; // Load this url in main window -const staticURL = 'file://' + path.join(__dirname, '../renderer', 'index.html'); - -const targetURL = function () { - if (data.domain === undefined) { - return staticURL; - } - // TODO: Use new main window - return 'file://' + path.join(__dirname, '../renderer', 'main.html'); - return data.domain; -}; - -function serverError(targetURL) { - // TODO: disabled - return; - if (targetURL.indexOf('localhost:') < 0 && data.domain) { - const req = https.request(targetURL + '/static/audio/zulip.ogg', res => { - console.log('Server StatusCode:', res.statusCode); - console.log('You are connected to:', res.req._headers.host); - if (res.statusCode >= 500 && res.statusCode <= 599) { - return dialog.showErrorBox('SERVER IS DOWN!', 'We are getting a ' + res.statusCode + ' error status from the server ' + res.req._headers.host + '. Please try again after some time or you may switch server.'); - } - }); - req.on('error', e => { - if (e.toString().indexOf('Error: self signed certificate') >= 0) { - const url = targetURL.replace(/^https?:\/\//, ''); - console.log('Server StatusCode:', 200); - console.log('You are connected to:', url); - } else { - console.error(e); - } - }); - req.end(); - } else if (data.domain) { - const req = http.request(targetURL + '/static/audio/zulip.ogg', res => { - console.log('Server StatusCode:', res.statusCode); - console.log('You are connected to:', res.req._headers.host); - }); - req.on('error', e => { - console.error(e); - }); - req.end(); - } -} +const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html'); function checkConnectivity() { return dialog.showMessageBox({ @@ -117,6 +75,7 @@ const connectivityERR = [ 'ERR_NAME_NOT_RESOLVED' ]; +// TODO function checkConnection() { // eslint-disable-next-line no-unused-vars mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => { @@ -142,13 +101,6 @@ if (isAlreadyRunning) { app.quit(); } -function checkWindowURL() { - if (data.domain !== undefined) { - return data.domain; - } - return targetLink; -} - function isWindowsOrmacOS() { return process.platform === 'darwin' || process.platform === 'win32'; } @@ -201,9 +153,7 @@ function createMainWindow() { win.show(); }); - serverError(targetURL()); - - win.loadURL(targetURL(), { + win.loadURL(mainURL, { userAgent: isUserAgent + ' ' + win.webContents.getUserAgent() }); @@ -314,36 +264,22 @@ app.on('ready', () => { // TODO - use global shortcut instead electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => { - mainWindow.reload(); - mainWindow.webContents.send('destroytray'); + page.send('reload'); + // page.send('destroytray'); }); electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => { - if (page.canGoBack()) { - page.goBack(); - } + page.send('back'); }); electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => { - if (page.canGoForward()) { - page.goForward(); - } + page.send('forward'); }); - + page.on('dom-ready', () => { - page.insertCSS(fs.readFileSync(path.join(__dirname, '../renderer/css/preload.css'), 'utf8')); mainWindow.show(); }); - page.on('new-window', (event, url) => { - if (linkIsInternal(checkWindowURL(), url) && url.match(skipImages) === null) { - event.preventDefault(); - return mainWindow.loadURL(url); - } - event.preventDefault(); - electron.shell.openExternal(url); - }); - page.once('did-frame-finish-load', () => { const checkOS = isWindowsOrmacOS(); if (checkOS && !isDev) { @@ -358,21 +294,3 @@ app.on('will-quit', () => { // Unregister all the shortcuts so that they don't interfare with other apps electronLocalshortcut.unregisterAll(mainWindow); }); - -ipc.on('new-domain', (e, domain) => { - // MainWindow.loadURL(domain); - if (!mainWindow) { - mainWindow = createMainWindow(); - mainWindow.loadURL(domain); - mainWindow.webContents.send('destroytray'); - } else if (mainWindow.isMinimized()) { - mainWindow.webContents.send('destroytray'); - mainWindow.loadURL(domain); - mainWindow.show(); - } else { - mainWindow.webContents.send('destroytray'); - mainWindow.loadURL(domain); - serverError(domain); - } - targetLink = domain; -}); diff --git a/app/renderer/css/pref.css b/app/renderer/css/pref.css deleted file mode 100644 index fcd67827..00000000 --- a/app/renderer/css/pref.css +++ /dev/null @@ -1,74 +0,0 @@ -body{ - background-color: #6BB6C7; -} - -.form { - position: absolute; - top: 35%; - width: 300px; - left: 9%; -} - -.close { - background: transparent url('../img/close.png') no-repeat 4px 4px; - background-size: 24px 24px; - cursor: pointer; - display: inline-block; - height: 32px; - position: absolute; - right: 6px; - text-indent: -10000px; - top: 6px; - width: 32px; - z-index: 1; - -webkit-app-region: no-drag; -} - -input[type="text"] { - display: block; - margin: 0; - width: 100%; - font-family: sans-serif; - font-size: 18px; - appearance: none; - box-shadow: none; - border-radius: none; - color: #646464; -} -input[type="text"]:focus { - outline: none; -} - -.form input[type="text"] { - padding: 10px; - border: solid 1px #dcdcdc; - transition: box-shadow 0.3s, border 0.3s; -} -.form input[type="text"]:focus, -.form input[type="text"].focus { - border: solid 1px #707070; - box-shadow: 0 0 5px 1px #969696; -} -button { - border: none; - color: #fff; - padding: 12px 32px; - text-align: center; - text-decoration: none; - margin-left: 107px; - margin-top: 24px; - display: inline-block; - font-size: 16px; - background: #137b86; -} -button:focus { - outline: 0; -} -#urladded { - font-size: 20px; - position: absolute; - font-family: 'opensans'; - top: -61%; - left: 25%; - text-align: center; -} \ No newline at end of file diff --git a/app/renderer/css/preload.css b/app/renderer/css/preload.css deleted file mode 100644 index 1fa30f28..00000000 --- a/app/renderer/css/preload.css +++ /dev/null @@ -1 +0,0 @@ -/* We'll be overriding default styling so that app look more native * / diff --git a/app/renderer/img/topography.png b/app/renderer/img/topography.png deleted file mode 100644 index a009e12004f6e77e1cf93df878bc0916889bad3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41424 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 mjLGM_m|%IGjIj4#0R{j$>4OiKGe02!0000 { + const {url} = event; + const domainPrefix = this.domainUtil.getDomain(this.activeTabIndex).url; + if (linkIsInternal(domainPrefix, url) && url.match(skipImages) === null) { + event.preventDefault(); + return $webView.loadURL(url); + } + event.preventDefault(); + shell.openExternal(url); + }); + } + + registerIpcs() { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + + ipcRenderer.on('reload', () => { + activeWebview.reload(); + }); + + ipcRenderer.on('back', () => { + if (activeWebview.canGoBack()) { + activeWebview.goBack(); + } + }); + + ipcRenderer.on('forward', () => { + if (activeWebview.canGoForward()) { + activeWebview.goForward(); + } + }); + } } window.onload = () => { diff --git a/app/renderer/js/preload.js b/app/renderer/js/preload.js index 355bc41e..8318c1e6 100644 --- a/app/renderer/js/preload.js +++ b/app/renderer/js/preload.js @@ -11,9 +11,7 @@ process.once('loaded', () => { }); // eslint-disable-next-line import/no-unassigned-import -require('./domain'); -// eslint-disable-next-line import/no-unassigned-import -require('./tray.js'); +// require('./tray.js'); // Calling Tray.js in renderer process everytime app window loads // Handle zooming functionality diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 4272adb9..622f9d5c 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -13,6 +13,10 @@ class DomainUtil { return this.db.getData('/domains'); } + getDomain(index) { + return this.db.getData(`/domains[${index}]`); + } + addDomain(server) { server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; this.db.push("/domains[]", server, true); @@ -38,7 +42,13 @@ class DomainUtil { request(checkDomain, (error, response) => { if (!error && response.statusCode !== 404) { res(domain); - } else { + } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { + if (window.confirm(`Do you trust certificate from ${domain}?`)) { + res(domain); + } else { + rej('Untrusted Certificate.'); + } + } else { rej('Not a valid Zulip server'); } }); diff --git a/app/renderer/pref.html b/app/renderer/pref.html deleted file mode 100644 index 954c66e8..00000000 --- a/app/renderer/pref.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -
Close
-
-
- - -
-

-

- - - From d6a35408b8c37e13ae009c9a46d8a1ae8efe8cbf Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Fri, 28 Apr 2017 00:05:17 +0800 Subject: [PATCH 13/52] Integrate actions from menu and tray with webview. --- app/main/menu.js | 22 +++++++++-------- app/main/windowmanager.js | 5 ---- app/renderer/js/main.js | 48 +++++++++++++++++++++++++++++++++++--- app/renderer/js/preload.js | 45 +++++------------------------------ app/renderer/js/tray.js | 18 ++++++++++---- 5 files changed, 77 insertions(+), 61 deletions(-) diff --git a/app/main/menu.js b/app/main/menu.js index ddc81051..35710916 100644 --- a/app/main/menu.js +++ b/app/main/menu.js @@ -8,9 +8,8 @@ const app = electron.app; const BrowserWindow = electron.BrowserWindow; const shell = electron.shell; const appName = app.getName(); -// Const tray = require('./tray'); -const {addDomain, about} = require('./windowmanager'); +const {about} = require('./windowmanager'); function sendAction(action) { const win = BrowserWindow.getAllWindows()[0]; @@ -35,8 +34,7 @@ const viewSubmenu = [ label: 'Reload', click(item, focusedWindow) { if (focusedWindow) { - focusedWindow.reload(); - focusedWindow.webContents.send('destroytray'); + sendAction('reload'); } } }, @@ -136,10 +134,12 @@ const darwinTpl = [ type: 'separator' }, { - label: 'Change Zulip Server', + label: 'Manage Zulip Servers', accelerator: 'Cmd+,', - click() { - addDomain(); + click(item, focusedWindow) { + if (focusedWindow) { + sendAction('open-settings'); + } } }, { @@ -269,10 +269,12 @@ const otherTpl = [ type: 'separator' }, { - label: 'Change Zulip Server', + label: 'Manage Zulip Servers', accelerator: 'Ctrl+,', - click() { - addDomain(); + click(item, focusedWindow) { + if (focusedWindow) { + sendAction('open-settings'); + } } }, { diff --git a/app/main/windowmanager.js b/app/main/windowmanager.js index a730aa01..005d8a6f 100644 --- a/app/main/windowmanager.js +++ b/app/main/windowmanager.js @@ -89,11 +89,6 @@ ipc.on('trayabout', event => { } }); -ipc.on('traychangeserver', event => { - if (event) { - addDomain(); - } -}); module.exports = { addDomain, about diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index ede7ac58..469f564a 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -3,7 +3,7 @@ const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); const { linkIsInternal, skipImages } = require(path.resolve(('app/main/link-helper'))); -const { shell, ipcRenderer } = require('electron'); +const { shell, ipcRenderer, webFrame } = require('electron'); require(path.resolve(('app/renderer/js/tray.js'))); class ServerManagerView { @@ -18,6 +18,7 @@ class ServerManagerView { this.isLoading = false; this.settingsTabIndex = -1; this.activeTabIndex = -1; + this.zoomFactors = []; } init() { @@ -72,7 +73,8 @@ class ServerManagerView { this.$content.appendChild($webView); this.isLoading = true; $webView.addEventListener('dom-ready', this.endLoading.bind(this, index)); - this.registerListeners($webView); + this.registerListeners($webView); + this.zoomFactors[index] = 1; } startLoading(url, index) { @@ -165,23 +167,63 @@ class ServerManagerView { } registerIpcs() { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); ipcRenderer.on('reload', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); activeWebview.reload(); }); ipcRenderer.on('back', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); if (activeWebview.canGoBack()) { activeWebview.goBack(); } }); ipcRenderer.on('forward', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); if (activeWebview.canGoForward()) { activeWebview.goForward(); } }); + + // Handle zooming functionality + ipcRenderer.on('zoomIn', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] += 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); + + ipcRenderer.on('zoomOut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] -= 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); + + ipcRenderer.on('zoomActualSize', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] = 1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); + + ipcRenderer.on('log-out', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('logout()'); + }); + + ipcRenderer.on('shortcut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('shortcut()'); + }); + + ipcRenderer.on('open-settings', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + if (this.settingsTabIndex == -1) { + this.openSettings(); + } else { + this.activateTab(this.settingsTabIndex); + } + }); } } diff --git a/app/renderer/js/preload.js b/app/renderer/js/preload.js index 8318c1e6..2a9ce077 100644 --- a/app/renderer/js/preload.js +++ b/app/renderer/js/preload.js @@ -1,54 +1,21 @@ 'use strict'; const ipcRenderer = require('electron').ipcRenderer; -const {webFrame} = require('electron'); const {spellChecker} = require('./spellchecker'); -const _setImmediate = setImmediate; -const _clearImmediate = clearImmediate; process.once('loaded', () => { - global.setImmediate = _setImmediate; - global.clearImmediate = _clearImmediate; + global.logout = logout; + global.shortcut = shortcut; }); -// eslint-disable-next-line import/no-unassigned-import -// require('./tray.js'); -// Calling Tray.js in renderer process everytime app window loads - -// Handle zooming functionality -const zoomIn = () => { - webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1); -}; - -const zoomOut = () => { - webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1); -}; - -const zoomActualSize = () => { - webFrame.setZoomFactor(1); -}; - -// Get zooming actions from main process -ipcRenderer.on('zoomIn', () => { - zoomIn(); -}); - -ipcRenderer.on('zoomOut', () => { - zoomOut(); -}); - -ipcRenderer.on('zoomActualSize', () => { - zoomActualSize(); -}); - -ipcRenderer.on('log-out', () => { +const logout = () => { // Create the menu for the below document.querySelector('.dropdown-toggle').click(); const nodes = document.querySelectorAll('.dropdown-menu li:last-child a'); nodes[nodes.length - 1].click(); -}); +}; -ipcRenderer.on('shortcut', () => { +const shortcut = () => { // Create the menu for the below const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]'); // Additional check @@ -58,7 +25,7 @@ ipcRenderer.on('shortcut', () => { // Atleast click the dropdown document.querySelector('.dropdown-toggle').click(); } -}); +}; // To prevent failing this script on linux we need to load it after the document loaded document.addEventListener('DOMContentLoaded', () => { diff --git a/app/renderer/js/tray.js b/app/renderer/js/tray.js index 50311e63..d4391788 100644 --- a/app/renderer/js/tray.js +++ b/app/renderer/js/tray.js @@ -5,7 +5,7 @@ const electron = require('electron'); const {ipcRenderer, remote} = electron; -const {Tray, Menu, nativeImage} = remote; +const {Tray, Menu, nativeImage, BrowserWindow} = remote; const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray'); @@ -102,6 +102,16 @@ const renderNativeImage = function (arg) { }); }; +function sendAction(action) { + const win = BrowserWindow.getAllWindows()[0]; + + if (process.platform === 'darwin') { + win.restore(); + } + + win.webContents.send(action); +} + const createTray = function () { window.tray = new Tray(iconPath()); const contextMenu = Menu.buildFromTemplate([{ @@ -116,7 +126,7 @@ const createTray = function () { { label: 'Change Zulip server', click() { - ipcRenderer.send('traychangeserver'); + sendAction('open-settings'); } }, { @@ -125,8 +135,8 @@ const createTray = function () { { label: 'Reload', click() { - remote.getCurrentWindow().reload(); - window.tray.destroy(); + sendAction('reload'); + // window.tray.destroy(); } }, { From 239cce8a4c332de94e2559e38b0e9c932f9bed00 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Fri, 28 Apr 2017 22:41:36 +0800 Subject: [PATCH 14/52] Initialize domains on first use. --- app/renderer/js/utils/domain-util.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 622f9d5c..8a806f68 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -7,6 +7,9 @@ const request = require('request'); class DomainUtil { constructor() { this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); + if (!this.getDomains()) { + this.db.push("/domains", []); + } } getDomains() { From 9ed09c9e1c2c0342f1341a35a92feb3216e79b21 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Fri, 28 Apr 2017 22:58:14 +0800 Subject: [PATCH 15/52] Add reload button to preference page. --- app/main/index.js | 4 ++++ app/renderer/js/preference.js | 7 +++++++ app/renderer/preference.html | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/app/main/index.js b/app/main/index.js index 51d27c2e..1399e5fc 100644 --- a/app/main/index.js +++ b/app/main/index.js @@ -288,6 +288,10 @@ app.on('ready', () => { } }); checkConnection(); + + ipc.on('reload-main', () =>{ + page.reload(); + }) }); app.on('will-quit', () => { diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js index 4e197233..7e0bca50 100644 --- a/app/renderer/js/preference.js +++ b/app/renderer/js/preference.js @@ -1,11 +1,13 @@ 'use strict'; +const { ipcRenderer } = require('electron'); const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); class PreferenceView { constructor() { this.$newServerButton = document.getElementById('new-server-action'); this.$saveServerButton = document.getElementById('save-server-action'); + this.$reloadServerButton = document.getElementById('reload-server-action'); this.$serverInfoContainer = document.querySelector('.server-info-container'); } @@ -64,6 +66,7 @@ class PreferenceView { this.domainUtil.removeDomain(index); this.initServers(); alert('Success. Reload to apply changes.') + this.$reloadServerButton.classList.remove('hidden'); }); } @@ -117,10 +120,14 @@ class PreferenceView { this.initServers(); alert('Success. Reload to apply changes.') + this.$reloadServerButton.classList.remove('hidden'); }, (errorMessage) => { alert(errorMessage); }); }); + this.$reloadServerButton.addEventListener('click', () => { + ipcRenderer.send('reload-main'); + }); } __insert_node(html) { let wrapper= document.createElement('div'); diff --git a/app/renderer/preference.html b/app/renderer/preference.html index 1a1f61e3..99ae71f1 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -29,6 +29,10 @@ check_box Verify & Save

+
From 47b3dd04fb993b1333bb4de83e12330eff4bc89b Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sat, 29 Apr 2017 01:10:21 +0800 Subject: [PATCH 16/52] Fix linter warnings. --- app/main/index.js | 39 +-- app/renderer/index.html | 30 --- app/renderer/js/domain.js | 80 ------ app/renderer/js/main.js | 351 ++++++++++++++------------- app/renderer/js/pref.js | 69 ------ app/renderer/js/preference.js | 246 +++++++++---------- app/renderer/js/preload.js | 11 +- app/renderer/js/tray.js | 3 +- app/renderer/js/utils/domain-util.js | 88 +++---- app/renderer/preference.html | 2 +- package.json | 5 +- tests/index.js | 2 +- 12 files changed, 359 insertions(+), 567 deletions(-) delete mode 100644 app/renderer/index.html delete mode 100644 app/renderer/js/domain.js delete mode 100644 app/renderer/js/pref.js diff --git a/app/main/index.js b/app/main/index.js index 1399e5fc..803a4332 100644 --- a/app/main/index.js +++ b/app/main/index.js @@ -1,24 +1,16 @@ 'use strict'; const path = require('path'); -const fs = require('fs'); const os = require('os'); const electron = require('electron'); const {app} = require('electron'); const ipc = require('electron').ipcMain; const {dialog} = require('electron'); -const https = require('https'); -const http = require('http'); const electronLocalshortcut = require('electron-localshortcut'); const Configstore = require('electron-config'); -const JsonDB = require('node-json-db'); const isDev = require('electron-is-dev'); const appMenu = require('./menu'); -const {linkIsInternal, skipImages} = require('./link-helper'); const {appUpdater} = require('./autoupdater'); -const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); -const data = db.getData('/'); - // Adds debug features like hotkeys for triggering dev tools and reload require('electron-debug')(); @@ -45,7 +37,6 @@ const isUserAgent = 'ZulipElectron/' + app.getVersion() + ' ' + userOS(); // Prevent window being garbage collected let mainWindow; -let targetLink; // Load this url in main window const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html'); @@ -211,30 +202,6 @@ function createMainWindow() { return win; } -// TODO - fix certificate errors - -// app.commandLine.appendSwitch('ignore-certificate-errors', 'true'); - -// For self-signed certificate -ipc.on('certificate-err', (e, domain) => { - const detail = `URL: ${domain} \n Error: Self-Signed Certificate`; - dialog.showMessageBox(mainWindow, { - title: 'Certificate error', - message: `Do you trust certificate from ${domain}?`, - // eslint-disable-next-line object-shorthand - detail: detail, - type: 'warning', - buttons: ['Yes', 'No'], - cancelId: 1 - // eslint-disable-next-line object-shorthand - }, response => { - if (response === 0) { - // eslint-disable-next-line object-shorthand - db.push('/domain', domain); - mainWindow.loadURL(domain); - } - }); -}); // eslint-disable-next-line max-params app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { event.preventDefault(); @@ -275,7 +242,7 @@ app.on('ready', () => { electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => { page.send('forward'); }); - + page.on('dom-ready', () => { mainWindow.show(); }); @@ -289,9 +256,9 @@ app.on('ready', () => { }); checkConnection(); - ipc.on('reload-main', () =>{ + ipc.on('reload-main', () => { page.reload(); - }) + }); }); app.on('will-quit', () => { diff --git a/app/renderer/index.html b/app/renderer/index.html deleted file mode 100644 index 8c9c6adb..00000000 --- a/app/renderer/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - Login - Zulip - - - -
-
-
- -

Zulip Login

-
-
-
- - - -

-
-
-
-
- - diff --git a/app/renderer/js/domain.js b/app/renderer/js/domain.js deleted file mode 100644 index dd45b6d7..00000000 --- a/app/renderer/js/domain.js +++ /dev/null @@ -1,80 +0,0 @@ -const {app} = require('electron').remote; -const ipcRenderer = require('electron').ipcRenderer; -const JsonDB = require('node-json-db'); -const request = require('request'); - -const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); - -window.addDomain = function () { - const el = sel => { - return document.querySelector(sel); - }; - - const $el = { - error: el('#error'), - main: el('#main'), - section: el('section') - }; - - const event = sel => { - return { - on: (event, callback) => { - document.querySelector(sel).addEventListener(event, callback); - } - }; - }; - - const displayError = msg => { - $el.error.innerText = msg; - $el.error.classList.add('show'); - $el.section.classList.add('shake'); - }; - - let newDomain = document.getElementById('url').value; - newDomain = newDomain.replace(/^https?:\/\//, ''); - if (newDomain === '') { - displayError('Please input a valid URL.'); - } else { - el('#main').innerHTML = 'Checking...'; - if (newDomain.indexOf('localhost:') >= 0) { - const domain = 'http://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - document.getElementById('main').innerHTML = 'Connect'; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else { - $el.main.innerHTML = 'Connect'; - displayError('Not a valid Zulip local server'); - } - }); - // }); - } else { - const domain = 'https://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - $el.main.innerHTML = 'Connect'; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { - $el.main.innerHTML = 'Connect'; - ipcRenderer.send('certificate-err', domain); - } else { - $el.main.innerHTML = 'Connect'; - displayError('Not a valid Zulip server'); - } - }); - } - } - - event('#url').on('input', () => { - el('#error').classList.remove('show'); - }); - - event('section').on('animationend', function () { - this.classList.remove('shake'); - }); -}; diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index 469f564a..33e49a36 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -1,11 +1,13 @@ 'use strict'; -const path = require("path"); -const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); -const { linkIsInternal, skipImages } = require(path.resolve(('app/main/link-helper'))); -const { shell, ipcRenderer, webFrame } = require('electron'); +const path = require('path'); + require(path.resolve(('app/renderer/js/tray.js'))); +const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); +const {linkIsInternal, skipImages} = require(path.resolve(('app/main/link-helper'))); +const {shell, ipcRenderer} = require('electron'); + class ServerManagerView { constructor() { this.$tabsContainer = document.getElementById('tabs-container'); @@ -13,221 +15,220 @@ class ServerManagerView { const $actionsContainer = document.getElementById('actions-container'); this.$addServerButton = $actionsContainer.querySelector('#add-action'); this.$settingsButton = $actionsContainer.querySelector('#settings-action'); - this.$content = document.getElementById('content'); + this.$content = document.getElementById('content'); - this.isLoading = false; - this.settingsTabIndex = -1; - this.activeTabIndex = -1; - this.zoomFactors = []; + this.isLoading = false; + this.settingsTabIndex = -1; + this.activeTabIndex = -1; + this.zoomFactors = []; } init() { this.domainUtil = new DomainUtil(); this.initTabs(); - this.initActions(); - this.registerIpcs(); + this.initActions(); + this.registerIpcs(); } initTabs() { const servers = this.domainUtil.getDomains(); - if (servers.length) { - for (let server of servers) { - this.initTab(server); - } - - this.activateTab(0); - } else { - this.openSettings(); - } + if (servers.length > 0) { + for (const server of servers) { + this.initTab(server); + } + + this.activateTab(0); + } else { + this.openSettings(); + } } initTab(tab) { const { - alias, url, - icon + icon } = tab; - const tabTemplate = tab.template || ` -
-
-
`; - const $tab = this.__insert_node(tabTemplate); - const index = this.$tabsContainer.childNodes.length; - this.$tabsContainer.appendChild($tab); + const tabTemplate = tab.template || ` +
+
+
`; + const $tab = this.insertNode(tabTemplate); + const index = this.$tabsContainer.childNodes.length; + this.$tabsContainer.appendChild($tab); $tab.addEventListener('click', this.activateTab.bind(this, index)); } - initWebView(url, index, nodeIntegration = false) { - const webViewTemplate = ` - - - `; - const $webView = this.__insert_node(webViewTemplate); + initWebView(url, index, nodeIntegration = false) { + const webViewTemplate = ` + + + `; + const $webView = this.insertNode(webViewTemplate); this.$content.appendChild($webView); - this.isLoading = true; - $webView.addEventListener('dom-ready', this.endLoading.bind(this, index)); - this.registerListeners($webView); - this.zoomFactors[index] = 1; - } - - startLoading(url, index) { - const $activeWebView = document.getElementById(`webview-${this.activeTabIndex}`); - if ($activeWebView) { - $activeWebView.classList.add('disabled'); - } - const $webView = document.getElementById(`webview-${index}`); - if (!$webView) { - this.initWebView(url, index, this.settingsTabIndex == index); - } else { - $webView.classList.remove('disabled'); - } - } - - endLoading(index) { - const $webView = document.getElementById(`webview-${index}`); - this.isLoading = false; - $webView.classList.remove('loading'); - } - - initActions() { - this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); - this.$settingsButton.addEventListener('click', this.openSettings.bind(this)); + this.isLoading = true; + $webView.addEventListener('dom-ready', this.endLoading.bind(this, index)); + this.registerListeners($webView); + this.zoomFactors[index] = 1; } - openSettings() { - if (this.settingsTabIndex != -1) { - this.activateTab(this.settingsTabIndex); - return; - } - const url = 'file:///' + path.resolve(('app/renderer/preference.html')); + startLoading(url, index) { + const $activeWebView = document.getElementById(`webview-${this.activeTabIndex}`); + if ($activeWebView) { + $activeWebView.classList.add('disabled'); + } + const $webView = document.getElementById(`webview-${index}`); + if ($webView === null) { + this.initWebView(url, index, this.settingsTabIndex === index); + } else { + $webView.classList.remove('disabled'); + } + } - const settingsTabTemplate = ` -
-
- settings -
-
`; + endLoading(index) { + const $webView = document.getElementById(`webview-${index}`); + this.isLoading = false; + $webView.classList.remove('loading'); + } + + initActions() { + this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); + this.$settingsButton.addEventListener('click', this.openSettings.bind(this)); + } + + openSettings() { + if (this.settingsTabIndex !== -1) { + this.activateTab(this.settingsTabIndex); + return; + } + const url = 'file:///' + path.resolve(('app/renderer/preference.html')); + + const settingsTabTemplate = ` +
+
+ settings +
+
`; this.initTab({ - alias: 'Settings', - url: url, - template: settingsTabTemplate - }); + alias: 'Settings', + url, + template: settingsTabTemplate + }); - this.settingsTabIndex = this.$tabsContainer.childNodes.length - 1; - this.activateTab(this.settingsTabIndex); - } + this.settingsTabIndex = this.$tabsContainer.childNodes.length - 1; + this.activateTab(this.settingsTabIndex); + } - activateTab(index) { - if (this.isLoading) return; + activateTab(index) { + if (this.isLoading) { + return; + } - if (this.activeTabIndex != -1) { - if (this.activeTabIndex == index) { - return; - } else { - this.__get_tab_at(this.activeTabIndex).classList.remove('active'); - } - } + if (this.activeTabIndex !== -1) { + if (this.activeTabIndex === index) { + return; + } else { + this.getTabAt(this.activeTabIndex).classList.remove('active'); + } + } - const $tab = this.__get_tab_at(index); + const $tab = this.getTabAt(index); $tab.classList.add('active'); - const domain = $tab.getAttribute('domain'); - this.startLoading(domain, index); - this.activeTabIndex = index; - } + const domain = $tab.getAttribute('domain'); + this.startLoading(domain, index); + this.activeTabIndex = index; + } - __insert_node(html) { - let wrapper= document.createElement('div'); - wrapper.innerHTML= html; - return wrapper.firstElementChild; - } + insertNode(html) { + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + return wrapper.firstElementChild; + } - __get_tab_at(index) { - return this.$tabsContainer.childNodes[index]; - } + getTabAt(index) { + return this.$tabsContainer.childNodes[index]; + } - registerListeners($webView) { - $webView.addEventListener('new-window', (event) => { - const {url} = event; - const domainPrefix = this.domainUtil.getDomain(this.activeTabIndex).url; - if (linkIsInternal(domainPrefix, url) && url.match(skipImages) === null) { - event.preventDefault(); - return $webView.loadURL(url); - } - event.preventDefault(); - shell.openExternal(url); - }); - } - - registerIpcs() { + registerListeners($webView) { + $webView.addEventListener('new-window', event => { + const {url} = event; + const domainPrefix = this.domainUtil.getDomain(this.activeTabIndex).url; + if (linkIsInternal(domainPrefix, url) && url.match(skipImages) === null) { + event.preventDefault(); + return $webView.loadURL(url); + } + event.preventDefault(); + shell.openExternal(url); + }); + } - ipcRenderer.on('reload', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - activeWebview.reload(); - }); + registerIpcs() { + ipcRenderer.on('reload', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.reload(); + }); - ipcRenderer.on('back', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - if (activeWebview.canGoBack()) { - activeWebview.goBack(); - } - }); + ipcRenderer.on('back', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + if (activeWebview.canGoBack()) { + activeWebview.goBack(); + } + }); - ipcRenderer.on('forward', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - if (activeWebview.canGoForward()) { - activeWebview.goForward(); - } - }); + ipcRenderer.on('forward', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + if (activeWebview.canGoForward()) { + activeWebview.goForward(); + } + }); - // Handle zooming functionality - ipcRenderer.on('zoomIn', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - this.zoomFactors[this.activeTabIndex] += 0.1; - activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); - }); + // Handle zooming functionality + ipcRenderer.on('zoomIn', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] += 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); - ipcRenderer.on('zoomOut', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - this.zoomFactors[this.activeTabIndex] -= 0.1; - activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); - }); + ipcRenderer.on('zoomOut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] -= 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); - ipcRenderer.on('zoomActualSize', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - this.zoomFactors[this.activeTabIndex] = 1; - activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); - }); + ipcRenderer.on('zoomActualSize', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] = 1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); - ipcRenderer.on('log-out', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - activeWebview.executeJavaScript('logout()'); - }); + ipcRenderer.on('log-out', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('logout()'); + }); - ipcRenderer.on('shortcut', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - activeWebview.executeJavaScript('shortcut()'); - }); + ipcRenderer.on('shortcut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('shortcut()'); + }); - ipcRenderer.on('open-settings', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - if (this.settingsTabIndex == -1) { - this.openSettings(); - } else { - this.activateTab(this.settingsTabIndex); - } - }); - } + ipcRenderer.on('open-settings', () => { + if (this.settingsTabIndex === -1) { + this.openSettings(); + } else { + this.activateTab(this.settingsTabIndex); + } + }); + } } window.onload = () => { const serverManagerView = new ServerManagerView(); serverManagerView.init(); -} +}; diff --git a/app/renderer/js/pref.js b/app/renderer/js/pref.js deleted file mode 100644 index 7624ad52..00000000 --- a/app/renderer/js/pref.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; -// eslint-disable-next-line import/no-extraneous-dependencies -const {remote} = require('electron'); - -const prefWindow = remote.getCurrentWindow(); - -document.getElementById('close-button').addEventListener('click', () => { - prefWindow.close(); -}); - -document.addEventListener('keydown', event => { - if (event.key === 'Escape' || event.keyCode === 27) { - prefWindow.close(); - } -}); -// eslint-disable-next-line no-unused-vars -window.prefDomain = function () { - const request = require('request'); - // eslint-disable-next-line import/no-extraneous-dependencies - const ipcRenderer = require('electron').ipcRenderer; - const JsonDB = require('node-json-db'); - // eslint-disable-next-line import/no-extraneous-dependencies - const {app} = require('electron').remote; - - const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); - - let newDomain = document.getElementById('url').value; - newDomain = newDomain.replace(/^https?:\/\//, ''); - newDomain = newDomain.replace(/^http?:\/\//, ''); - - if (newDomain === '') { - document.getElementById('urladded').innerHTML = 'Please input a value'; - } else { - document.getElementById('main').innerHTML = 'Checking...'; - if (newDomain.indexOf('localhost:') >= 0) { - const domain = 'http://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Not a valid Zulip Local Server.'; - } - }); - } else { - const domain = 'https://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { - document.getElementById('main').innerHTML = 'Switch'; - ipcRenderer.send('certificate-err', domain); - document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain; - } else { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Not a valid Zulip Server.'; - } - }); - } - } -}; diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js index 7e0bca50..664b7092 100644 --- a/app/renderer/js/preference.js +++ b/app/renderer/js/preference.js @@ -1,142 +1,144 @@ 'use strict'; -const { ipcRenderer } = require('electron'); -const path = require("path"); +const {ipcRenderer} = require('electron'); +const path = require('path'); + const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); + class PreferenceView { - constructor() { - this.$newServerButton = document.getElementById('new-server-action'); - this.$saveServerButton = document.getElementById('save-server-action'); - this.$reloadServerButton = document.getElementById('reload-server-action'); - this.$serverInfoContainer = document.querySelector('.server-info-container'); - } + constructor() { + this.$newServerButton = document.getElementById('new-server-action'); + this.$saveServerButton = document.getElementById('save-server-action'); + this.$reloadServerButton = document.getElementById('reload-server-action'); + this.$serverInfoContainer = document.querySelector('.server-info-container'); + } - init() { - this.domainUtil = new DomainUtil(); - this.initServers(); - this.initActions(); - } + init() { + this.domainUtil = new DomainUtil(); + this.initServers(); + this.initActions(); + } - initServers() { - const servers = this.domainUtil.getDomains(); - this.$serverInfoContainer.innerHTML = servers.length? '': 'Add your first server to get started!'; + initServers() { + const servers = this.domainUtil.getDomains(); + this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!'; - this.initNewServerForm(); - - for (let i in servers) { - this.initServer(servers[i], i); - } - } + this.initNewServerForm(); - initServer(server, index) { - const { + for (const i in servers) { + this.initServer(servers[i], i); + } + } + + initServer(server, index) { + const { alias, url, - icon + icon } = server; - const serverInfoTemplate = ` -
-
- -
-
-
- Name - -
-
- Url - -
-
- Icon - -
-
- Actions -
- indeterminate_check_box - Delete -
-
-
-
`; - this.$serverInfoContainer.appendChild(this.__insert_node(serverInfoTemplate)); - document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { - this.domainUtil.removeDomain(index); - this.initServers(); - alert('Success. Reload to apply changes.') - this.$reloadServerButton.classList.remove('hidden'); - }); - } + const serverInfoTemplate = ` +
+
+ +
+
+
+ Name + +
+
+ Url + +
+
+ Icon + +
+
+ Actions +
+ indeterminate_check_box + Delete +
+
+
+
`; + this.$serverInfoContainer.appendChild(this.insertNode(serverInfoTemplate)); + document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { + this.domainUtil.removeDomain(index); + this.initServers(); + alert('Success. Reload to apply changes.'); + this.$reloadServerButton.classList.remove('hidden'); + }); + } - initNewServerForm() { - const newServerFormTemplate = ` - - `; - this.$serverInfoContainer.appendChild(this.__insert_node(newServerFormTemplate)); + initNewServerForm() { + const newServerFormTemplate = ` + + `; + this.$serverInfoContainer.appendChild(this.insertNode(newServerFormTemplate)); - this.$newServerForm = document.querySelector('.server-info.active'); - this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0]; - this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1]; - this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2]; - } + this.$newServerForm = document.querySelector('.server-info.active'); + this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0]; + this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1]; + this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2]; + } - initActions() { - this.$newServerButton.addEventListener('click', () => { - this.$newServerForm.classList.remove('hidden'); - this.$saveServerButton.classList.remove('hidden'); - this.$newServerButton.classList.add('hidden'); - }); - this.$saveServerButton.addEventListener('click', () => { - this.domainUtil.checkDomain(this.$newServerUrl.value).then((domain) => { - const server = { - alias: this.$newServerAlias.value, - url: domain, - icon: this.$newServerIcon.value - }; - this.domainUtil.addDomain(server); - this.$saveServerButton.classList.add('hidden'); - this.$newServerButton.classList.remove('hidden'); - this.$newServerForm.classList.add('hidden'); + initActions() { + this.$newServerButton.addEventListener('click', () => { + this.$newServerForm.classList.remove('hidden'); + this.$saveServerButton.classList.remove('hidden'); + this.$newServerButton.classList.add('hidden'); + }); + this.$saveServerButton.addEventListener('click', () => { + this.domainUtil.checkDomain(this.$newServerUrl.value).then(domain => { + const server = { + alias: this.$newServerAlias.value, + url: domain, + icon: this.$newServerIcon.value + }; + this.domainUtil.addDomain(server); + this.$saveServerButton.classList.add('hidden'); + this.$newServerButton.classList.remove('hidden'); + this.$newServerForm.classList.add('hidden'); - this.initServers(); - alert('Success. Reload to apply changes.') - this.$reloadServerButton.classList.remove('hidden'); - }, (errorMessage) => { - alert(errorMessage); - }); - }); - this.$reloadServerButton.addEventListener('click', () => { - ipcRenderer.send('reload-main'); - }); - } - __insert_node(html) { - let wrapper= document.createElement('div'); - wrapper.innerHTML= html; - return wrapper.firstElementChild; - } + this.initServers(); + alert('Success. Reload to apply changes.'); + this.$reloadServerButton.classList.remove('hidden'); + }, errorMessage => { + alert(errorMessage); + }); + }); + this.$reloadServerButton.addEventListener('click', () => { + ipcRenderer.send('reload-main'); + }); + } + insertNode(html) { + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + return wrapper.firstElementChild; + } } window.onload = () => { const preferenceView = new PreferenceView(); preferenceView.init(); -} +}; diff --git a/app/renderer/js/preload.js b/app/renderer/js/preload.js index 2a9ce077..794a446b 100644 --- a/app/renderer/js/preload.js +++ b/app/renderer/js/preload.js @@ -1,12 +1,6 @@ 'use strict'; -const ipcRenderer = require('electron').ipcRenderer; const {spellChecker} = require('./spellchecker'); -process.once('loaded', () => { - global.logout = logout; - global.shortcut = shortcut; -}); - const logout = () => { // Create the menu for the below document.querySelector('.dropdown-toggle').click(); @@ -27,6 +21,11 @@ const shortcut = () => { } }; +process.once('loaded', () => { + global.logout = logout; + global.shortcut = shortcut; +}); + // To prevent failing this script on linux we need to load it after the document loaded document.addEventListener('DOMContentLoaded', () => { // Init spellchecker diff --git a/app/renderer/js/tray.js b/app/renderer/js/tray.js index d4391788..8c7aade7 100644 --- a/app/renderer/js/tray.js +++ b/app/renderer/js/tray.js @@ -124,7 +124,7 @@ const createTray = function () { type: 'separator' }, { - label: 'Change Zulip server', + label: 'Manage Zulip servers', click() { sendAction('open-settings'); } @@ -136,7 +136,6 @@ const createTray = function () { label: 'Reload', click() { sendAction('reload'); - // window.tray.destroy(); } }, { diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 8a806f68..f9384c04 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -5,58 +5,58 @@ const JsonDB = require('node-json-db'); const request = require('request'); class DomainUtil { - constructor() { - this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); - if (!this.getDomains()) { - this.db.push("/domains", []); - } - } + constructor() { + this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); + if (!this.getDomains()) { + this.db.push('/domains', []); + } + } - getDomains() { - return this.db.getData('/domains'); - } + getDomains() { + return this.db.getData('/domains'); + } - getDomain(index) { - return this.db.getData(`/domains[${index}]`); - } + getDomain(index) { + return this.db.getData(`/domains[${index}]`); + } - addDomain(server) { - server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; - this.db.push("/domains[]", server, true); - } + addDomain(server) { + server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; + this.db.push('/domains[]', server, true); + } - removeDomains() { - this.db.delete("/domains"); - } + removeDomains() { + this.db.delete('/domains'); + } - removeDomain(index) { - this.db.delete(`/domains[${index}]`); - } + removeDomain(index) { + this.db.delete(`/domains[${index}]`); + } - checkDomain(domain) { - const hasPrefix = (domain.indexOf('http') == 0); - if (!hasPrefix) { - domain = (domain.indexOf('localhost:') >= 0)? `http://${domain}` : `https://${domain}`; - } + checkDomain(domain) { + const hasPrefix = (domain.indexOf('http') === 0); + if (!hasPrefix) { + domain = (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`; + } const checkDomain = domain + '/static/audio/zulip.ogg'; - return new Promise((res, rej) => { - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - res(domain); - } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { - if (window.confirm(`Do you trust certificate from ${domain}?`)) { - res(domain); - } else { - rej('Untrusted Certificate.'); - } - } else { - rej('Not a valid Zulip server'); - } - }); - }) - } + return new Promise((resolve, reject) => { + request(checkDomain, (error, response) => { + if (!error && response.statusCode !== 404) { + resolve(domain); + } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { + if (window.confirm(`Do you trust certificate from ${domain}?`)) { + resolve(domain); + } else { + reject('Untrusted Certificate.'); + } + } else { + reject('Not a valid Zulip server'); + } + }); + }); + } } -module.exports = DomainUtil; \ No newline at end of file +module.exports = DomainUtil; diff --git a/app/renderer/preference.html b/app/renderer/preference.html index 99ae71f1..0772ffb3 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -27,7 +27,7 @@
From 1c012c7a28c4921aba3fa4eebaf796fb45382df0 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sun, 30 Apr 2017 23:08:45 +0800 Subject: [PATCH 23/52] Update Save Server button text. --- app/renderer/preference.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/renderer/preference.html b/app/renderer/preference.html index c0b71b49..dd2588c8 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -27,7 +27,7 @@
- > - - + />
From 0bfa2027638816392e05f0a119ff7cbc9f97c329 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sat, 22 Apr 2017 23:33:23 +0800 Subject: [PATCH 30/52] Change domain config schema and update DomainUtil. --- app/renderer/js/utils/domain-util.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 53103063..fa3d3c54 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -15,10 +15,14 @@ class DomainUtil { addDomain() { const servers = { url: 'https://chat.zulip.org', - alias: 'Zulip 2', - avatar: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png' + alias: 'Zulip 2333', + icon: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png' } - db.push("/domains[]", servers, true); + this.db.push("/domains[]", servers, true); + } + + removeDomains() { + this.db.delete("/domains"); } } From 6b29139805140171b6e5e96aa11e802827d6abb0 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sat, 22 Apr 2017 23:35:03 +0800 Subject: [PATCH 31/52] Finish interactions of switching servers in ServerManagerView. --- .vscode/settings.json | 3 + app/renderer/css/servermanager.css | 21 ++++++ app/renderer/img/loading.gif | Bin 0 -> 22272 bytes app/renderer/js/main.js | 115 +++++++++++++++++++++++------ app/renderer/main.html | 13 +--- 5 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 app/renderer/img/loading.gif diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..20af2f68 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +// Place your settings in this file to overwrite default and user settings. +{ +} \ No newline at end of file diff --git a/app/renderer/css/servermanager.css b/app/renderer/css/servermanager.css index 1b6834dc..18b809e9 100644 --- a/app/renderer/css/servermanager.css +++ b/app/renderer/css/servermanager.css @@ -6,6 +6,9 @@ html, body { #content { display: flex; height: 100%; + background: #eee url(../img/loading.gif) no-repeat; + background-size: 60px 60px; + background-position: center; } #sidebar { @@ -32,8 +35,12 @@ html, body { .action-button i { color: #6c8592; font-size: 28px; + cursor: default; } +.action-button:hover i { + color: #98a9b3; +} #servers-container { display: flex; @@ -75,6 +82,20 @@ html, body { opacity: 0.6; } +.server-button .server-name:hover{ + opacity: 0.8; +} + .server-button.active .server-name{ opacity: 1; +} + +#webview { + opacity: 1; + transition: opacity 0.3s; +} + +#webview.loading { + opacity: 0; + transition: opacity 0.3s; } \ No newline at end of file diff --git a/app/renderer/img/loading.gif b/app/renderer/img/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..60d63d367b4a5bae3cbac49665e2516efe84a2d0 GIT binary patch literal 22272 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 literal 0 HcmV?d00001 diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index a538106d..aeea5827 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -3,37 +3,104 @@ const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); class ServerManagerView { - constructor() { - this.serverButtonTemplate = ` -
-
-
`; - this.$serversContainer = document.getElementById('servers-container'); + constructor() { + this.$serversContainer = document.getElementById('servers-container'); - const $actionsContainer = document.getElementById('actions-container'); - this.$addServerButton = $actionsContainer.querySelector('#add-action'); - this.$settingsButton = $actionsContainer.querySelector('#settings-action'); - } - - init() { - this.domainUtil = new DomainUtil(); - console.log(this.domainUtil.getDomains()); + const $actionsContainer = document.getElementById('actions-container'); + this.$addServerButton = $actionsContainer.querySelector('#add-action'); + this.$settingsButton = $actionsContainer.querySelector('#settings-action'); + this.$content = document.getElementById('content'); + + this.isLoading = false; + } + + init() { + this.domainUtil = new DomainUtil(); + this.initServers(); + } + + initServers() { + const servers = this.domainUtil.getDomains(); + for (let server of servers) { + this.initServer(server); + } + + const $firstServerButton = this.$serversContainer.firstChild; + $firstServerButton.classList.add('active'); + this.$activeServerButton = $firstServerButton; + this.initWebView($firstServerButton.getAttribute('domain')); + } + + initServer(server) { + const { + alias, + url + } = server; + const icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; + const serverButtonTemplate = ` +
+
+
`; + const $serverButton = this.__insert_node(serverButtonTemplate); + this.$serversContainer.appendChild($serverButton); + $serverButton.addEventListener('click', () => { + if (this.isLoading || this.$activeServerButton == $serverButton) return; + + this.$activeServerButton.classList.remove('active'); + $serverButton.classList.add('active'); + const url = $serverButton.getAttribute('domain'); + this.$activeServerButton = $serverButton; + this.startLoading(url); + }); + } + + initWebView(url) { + const webViewTemplate = ` + + + `; + this.$webView = this.__insert_node(webViewTemplate); + this.$content.appendChild(this.$webView); + this.$webView.addEventListener('dom-ready', this.endLoading.bind(this)); } - initServers() { + startLoading(url) { + this.$webView.loadURL(url); + this.isLoading = true; + this.$webView.classList.add('loading'); + } + + endLoading() { + this.isLoading = false; + this.$webView.classList.remove('loading'); + } + + initActions() { + + } + + addServer() { + + } + + openSettings() { } - initActions() { - - } - - addServer() { - + __insert_node(html) { + let wrapper= document.createElement('div'); + wrapper.innerHTML= html; + return wrapper.firstElementChild; } } window.onload = () => { - const serverManagerView = new ServerManagerView(); - serverManagerView.init(); -} \ No newline at end of file + const serverManagerView = new ServerManagerView(); + serverManagerView.init(); +} diff --git a/app/renderer/main.html b/app/renderer/main.html index 3e77eaad..2363b5a5 100644 --- a/app/renderer/main.html +++ b/app/renderer/main.html @@ -10,17 +10,7 @@
- />
From 3fe23e84b346b45b08a98de029cebfe61de9cbe4 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Mon, 24 Apr 2017 00:01:33 +0800 Subject: [PATCH 32/52] Refactor ServerManagerView and setup layout for PreferenceView. --- app/renderer/css/preference.css | 49 ++++++++++++++++ app/renderer/css/servermanager.css | 25 +++++--- app/renderer/js/main.js | 92 +++++++++++++++++++++--------- app/renderer/js/preference.js | 0 app/renderer/main.html | 2 +- app/renderer/preference.html | 23 ++++++++ 6 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 app/renderer/css/preference.css create mode 100644 app/renderer/js/preference.js create mode 100644 app/renderer/preference.html diff --git a/app/renderer/css/preference.css b/app/renderer/css/preference.css new file mode 100644 index 00000000..70a44115 --- /dev/null +++ b/app/renderer/css/preference.css @@ -0,0 +1,49 @@ +html, body { + height: 100%; + margin: 0; + cursor: default; +} + +#content { + display: flex; + height: 100%; + font-family: sans-serif; + background: #fff; +} + +#sidebar { + width: 80px; + padding: 40px; + display: flex; + flex-direction: column; + font-size: 16px; +} + +#tabs-container { + padding: 20px 0; +} + +.tab { + padding: 5px 0; + color: #999; + cursor: pointer; +} + +.tab.active { + color: #464e5a; + cursor: default; +} + +.tab.active::before { + color: #464e5a; + cursor: default; +} +#settings-header { + font-size: 24px; + color: #5c6166; +} + +.settings-container { + width: 100%; + display: flex; +} \ No newline at end of file diff --git a/app/renderer/css/servermanager.css b/app/renderer/css/servermanager.css index 18b809e9..260f2580 100644 --- a/app/renderer/css/servermanager.css +++ b/app/renderer/css/servermanager.css @@ -1,6 +1,7 @@ html, body { height: 100%; margin: 0; + cursor: default; } #content { @@ -35,25 +36,24 @@ html, body { .action-button i { color: #6c8592; font-size: 28px; - cursor: default; } .action-button:hover i { color: #98a9b3; } -#servers-container { +#tabs-container { display: flex; align-items: center; flex-direction: column; } -.server-button { +.tab { position: relative; margin: 5px 0; } -.server-button.active::before{ +.tab.active::before{ content: ""; background: #fff; border-radius: 0 3px 3px 0; @@ -64,7 +64,7 @@ html, body { top: 5px; } -.server-button .server-name{ +.tab .server-tab{ background: #a4d3c4; background-size: 100%; border-radius: 4px; @@ -74,7 +74,7 @@ html, body { margin: 5px 0; z-index: 11; line-height: 44px; - font-size: 24px; + font-size: 32px; font-family: sans-serif; color: #194a2b; text-align: center; @@ -82,11 +82,20 @@ html, body { opacity: 0.6; } -.server-button .server-name:hover{ +.tab .server-tab:hover{ opacity: 0.8; } -.server-button.active .server-name{ +.tab .settings-tab{ + background: #eee; +} + +.tab .settings-tab i{ + font-size: 28px; + line-height: 44px; +} + +.tab.active .server-tab{ opacity: 1; } diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index aeea5827..751a1f7e 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -4,7 +4,7 @@ const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); class ServerManagerView { constructor() { - this.$serversContainer = document.getElementById('servers-container'); + this.$tabsContainer = document.getElementById('tabs-container'); const $actionsContainer = document.getElementById('actions-container'); this.$addServerButton = $actionsContainer.querySelector('#add-action'); @@ -12,46 +12,38 @@ class ServerManagerView { this.$content = document.getElementById('content'); this.isLoading = false; + this.settingsTabIndex = -1; } init() { this.domainUtil = new DomainUtil(); - this.initServers(); + this.initTabs(); + this.initActions(); } - initServers() { + initTabs() { const servers = this.domainUtil.getDomains(); for (let server of servers) { - this.initServer(server); + this.initTab(server); } - - const $firstServerButton = this.$serversContainer.firstChild; - $firstServerButton.classList.add('active'); - this.$activeServerButton = $firstServerButton; - this.initWebView($firstServerButton.getAttribute('domain')); + + this.activateTab(0); } - initServer(server) { + initTab(tab) { const { alias, url - } = server; - const icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; - const serverButtonTemplate = ` -
-
+ } = tab; + const icon = tab.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; + const tabTemplate = tab.template || ` +
+
`; - const $serverButton = this.__insert_node(serverButtonTemplate); - this.$serversContainer.appendChild($serverButton); - $serverButton.addEventListener('click', () => { - if (this.isLoading || this.$activeServerButton == $serverButton) return; - - this.$activeServerButton.classList.remove('active'); - $serverButton.classList.add('active'); - const url = $serverButton.getAttribute('domain'); - this.$activeServerButton = $serverButton; - this.startLoading(url); - }); + const $tab = this.__insert_node(tabTemplate); + const index = this.$tabsContainer.childNodes.length; + this.$tabsContainer.appendChild($tab); + $tab.addEventListener('click', this.activateTab.bind(this, index)); } initWebView(url) { @@ -79,18 +71,62 @@ class ServerManagerView { endLoading() { this.isLoading = false; this.$webView.classList.remove('loading'); + this.$webView.openDevTools(); } initActions() { - + this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); } addServer() { - + } openSettings() { + if (this.settingsTabIndex != -1) { + this.activateTab(this.settingsTabIndex); + return; + } + const url = 'file:///' + path.resolve(('app/renderer/preference.html')); + console.log(url); + const settingsTabTemplate = ` +
+
+ settings +
+
`; + this.initTab({ + alias: 'Settings', + url: url, + template: settingsTabTemplate + }); + + this.settingsTabIndex = this.$tabsContainer.childNodes.length - 1; + this.activateTab(this.settingsTabIndex); + } + + activateTab(index) { + if (this.isLoading) return; + const $tab = this.$tabsContainer.childNodes[index]; + + if (this.$activeTab) { + if (this.$activeTab == $tab) { + return; + } else { + this.$activeTab.classList.remove('active'); + } + } + + $tab.classList.add('active'); + this.$activeTab = $tab; + + const domain = $tab.getAttribute('domain'); + if (this.$webView){ + this.startLoading(domain); + } else { + this.initWebView(domain); + } } __insert_node(html) { diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js new file mode 100644 index 00000000..e69de29b diff --git a/app/renderer/main.html b/app/renderer/main.html index 2363b5a5..f15e2709 100644 --- a/app/renderer/main.html +++ b/app/renderer/main.html @@ -10,7 +10,7 @@
`; this.$serverInfoContainer.appendChild(this.__insert_node(serverInfoTemplate)); + document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { + this.domainUtil.removeDomain(index); + this.initServers(); + }); } + initNewServerForm() { + const newServerFormTemplate = ` + + `; + this.$serverInfoContainer.appendChild(this.__insert_node(newServerFormTemplate)); + + this.$newServerForm = document.querySelector('.server-info.active'); + this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0]; + this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1]; + this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2]; + } + + initActions() { + this.$newServerButton.addEventListener('click', () => { + this.$newServerForm.classList.remove('hidden'); + this.$saveServerButton.classList.remove('hidden'); + this.$newServerButton.classList.add('hidden'); + }); + this.$saveServerButton.addEventListener('click', () => { + this.domainUtil.checkDomain(this.$newServerUrl.value).then((domain) => { + const server = { + alias: this.$newServerAlias.value, + url: domain, + icon: this.$newServerIcon.value + }; + this.domainUtil.addDomain(server); + this.$saveServerButton.classList.add('hidden'); + this.$newServerButton.classList.remove('hidden'); + this.$newServerForm.classList.add('hidden'); + + this.initServers(); + }, (errorMessage) => { + alert(errorMessage); + }); + }); + } __insert_node(html) { let wrapper= document.createElement('div'); wrapper.innerHTML= html; diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index fa3d3c54..f98d75cc 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -2,6 +2,7 @@ const {app} = require('electron').remote; const JsonDB = require('node-json-db'); +const request = require('request'); class DomainUtil { constructor() { @@ -12,18 +13,37 @@ class DomainUtil { return this.db.getData('/domains'); } - addDomain() { - const servers = { - url: 'https://chat.zulip.org', - alias: 'Zulip 2333', - icon: 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png' - } + addDomain(server) { + server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; this.db.push("/domains[]", servers, true); } removeDomains() { this.db.delete("/domains"); } + + removeDomain(index) { + this.db.delete(`/domains[${index}]`); + } + + checkDomain(domain) { + const hasPrefix = (domain.indexOf('http') == 0); + if (!hasPrefix) { + domain = (domain.indexOf('localhost:') >= 0)? `http://${domain}` : `https://${domain}`; + } + + const checkDomain = domain + '/static/audio/zulip.ogg'; + + return new Promise((res, rej) => { + request(checkDomain, (error, response) => { + if (!error && response.statusCode !== 404) { + res(domain); + } else { + rej('Not a valid Zulip server'); + } + }); + }) + } } module.exports = DomainUtil; \ No newline at end of file diff --git a/app/renderer/preference.html b/app/renderer/preference.html index 03226fa7..1a1f61e3 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -25,7 +25,7 @@ add_box New Server
-
+ From 865553fa45439cfda33575961544f87408350781 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Wed, 26 Apr 2017 13:32:51 +0800 Subject: [PATCH 36/52] Redirect to server settings page when domains are empty. --- app/renderer/js/main.js | 21 +++++++++++---------- app/renderer/js/preference.js | 14 +++++++++----- app/renderer/js/utils/domain-util.js | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index 56fd964d..3a540504 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -24,11 +24,15 @@ class ServerManagerView { initTabs() { const servers = this.domainUtil.getDomains(); - for (let server of servers) { - this.initTab(server); - } - - this.activateTab(0); + if (servers.length) { + for (let server of servers) { + this.initTab(server); + } + + this.activateTab(0); + } else { + this.openSettings(); + } } initTab(tab) { @@ -87,10 +91,7 @@ class ServerManagerView { initActions() { this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); - } - - addServer() { - + this.$settingsButton.addEventListener('click', this.openSettings.bind(this)); } openSettings() { @@ -99,7 +100,7 @@ class ServerManagerView { return; } const url = 'file:///' + path.resolve(('app/renderer/preference.html')); - console.log(url); + const settingsTabTemplate = `
diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js index 35e40d61..4e197233 100644 --- a/app/renderer/js/preference.js +++ b/app/renderer/js/preference.js @@ -16,12 +16,14 @@ class PreferenceView { } initServers() { - this.$serverInfoContainer.innerHTML = ''; - this.initNewServerForm(); const servers = this.domainUtil.getDomains(); - for (let i in servers) { - this.initServer(servers[i], i); - } + this.$serverInfoContainer.innerHTML = servers.length? '': 'Add your first server to get started!'; + + this.initNewServerForm(); + + for (let i in servers) { + this.initServer(servers[i], i); + } } initServer(server, index) { @@ -61,6 +63,7 @@ class PreferenceView { document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { this.domainUtil.removeDomain(index); this.initServers(); + alert('Success. Reload to apply changes.') }); } @@ -113,6 +116,7 @@ class PreferenceView { this.$newServerForm.classList.add('hidden'); this.initServers(); + alert('Success. Reload to apply changes.') }, (errorMessage) => { alert(errorMessage); }); diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index f98d75cc..4272adb9 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -15,7 +15,7 @@ class DomainUtil { addDomain(server) { server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; - this.db.push("/domains[]", servers, true); + this.db.push("/domains[]", server, true); } removeDomains() { From de34a22740ebb35eecf468b68aee7bcbc952e11c Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Thu, 27 Apr 2017 00:07:22 +0800 Subject: [PATCH 37/52] Move methods from mainWindow.webContents (index.js) to WebView (main.js). --- app/main/index.js | 98 +++------------------------ app/renderer/css/pref.css | 74 -------------------- app/renderer/css/preload.css | 1 - app/renderer/img/topography.png | Bin 41424 -> 0 bytes app/renderer/js/main.js | 40 ++++++++++- app/renderer/js/preload.js | 4 +- app/renderer/js/utils/domain-util.js | 12 +++- app/renderer/pref.html | 19 ------ 8 files changed, 59 insertions(+), 189 deletions(-) delete mode 100644 app/renderer/css/pref.css delete mode 100644 app/renderer/css/preload.css delete mode 100644 app/renderer/img/topography.png delete mode 100644 app/renderer/pref.html diff --git a/app/main/index.js b/app/main/index.js index 142a1ce7..6c4dd915 100644 --- a/app/main/index.js +++ b/app/main/index.js @@ -48,49 +48,7 @@ let mainWindow; let targetLink; // Load this url in main window -const staticURL = 'file://' + path.join(__dirname, '../renderer', 'index.html'); - -const targetURL = function () { - if (data.domain === undefined) { - return staticURL; - } - // TODO: Use new main window - return 'file://' + path.join(__dirname, '../renderer', 'main.html'); - return data.domain; -}; - -function serverError(targetURL) { - // TODO: disabled - return; - if (targetURL.indexOf('localhost:') < 0 && data.domain) { - const req = https.request(targetURL + '/static/audio/zulip.ogg', res => { - console.log('Server StatusCode:', res.statusCode); - console.log('You are connected to:', res.req._headers.host); - if (res.statusCode >= 500 && res.statusCode <= 599) { - return dialog.showErrorBox('SERVER IS DOWN!', 'We are getting a ' + res.statusCode + ' error status from the server ' + res.req._headers.host + '. Please try again after some time or you may switch server.'); - } - }); - req.on('error', e => { - if (e.toString().indexOf('Error: self signed certificate') >= 0) { - const url = targetURL.replace(/^https?:\/\//, ''); - console.log('Server StatusCode:', 200); - console.log('You are connected to:', url); - } else { - console.error(e); - } - }); - req.end(); - } else if (data.domain) { - const req = http.request(targetURL + '/static/audio/zulip.ogg', res => { - console.log('Server StatusCode:', res.statusCode); - console.log('You are connected to:', res.req._headers.host); - }); - req.on('error', e => { - console.error(e); - }); - req.end(); - } -} +const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html'); function checkConnectivity() { return dialog.showMessageBox({ @@ -117,6 +75,7 @@ const connectivityERR = [ 'ERR_NAME_NOT_RESOLVED' ]; +// TODO function checkConnection() { // eslint-disable-next-line no-unused-vars mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => { @@ -142,13 +101,6 @@ if (isAlreadyRunning) { app.quit(); } -function checkWindowURL() { - if (data.domain !== undefined) { - return data.domain; - } - return targetLink; -} - function isWindowsOrmacOS() { return process.platform === 'darwin' || process.platform === 'win32'; } @@ -201,9 +153,7 @@ function createMainWindow() { win.show(); }); - serverError(targetURL()); - - win.loadURL(targetURL(), { + win.loadURL(mainURL, { userAgent: isUserAgent + ' ' + win.webContents.getUserAgent() }); @@ -314,36 +264,22 @@ app.on('ready', () => { // TODO - use global shortcut instead electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => { - mainWindow.reload(); - mainWindow.webContents.send('destroytray'); + page.send('reload'); + // page.send('destroytray'); }); electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => { - if (page.canGoBack()) { - page.goBack(); - } + page.send('back'); }); electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => { - if (page.canGoForward()) { - page.goForward(); - } + page.send('forward'); }); - + page.on('dom-ready', () => { - page.insertCSS(fs.readFileSync(path.join(__dirname, '../renderer/css/preload.css'), 'utf8')); mainWindow.show(); }); - page.on('new-window', (event, url) => { - if (linkIsInternal(checkWindowURL(), url) && url.match(skipImages) === null) { - event.preventDefault(); - return mainWindow.loadURL(url); - } - event.preventDefault(); - electron.shell.openExternal(url); - }); - page.once('did-frame-finish-load', () => { const checkOS = isWindowsOrmacOS(); if (checkOS && !isDev) { @@ -362,21 +298,3 @@ app.on('will-quit', () => { // Unregister all the shortcuts so that they don't interfare with other apps electronLocalshortcut.unregisterAll(mainWindow); }); - -ipc.on('new-domain', (e, domain) => { - // MainWindow.loadURL(domain); - if (!mainWindow) { - mainWindow = createMainWindow(); - mainWindow.loadURL(domain); - mainWindow.webContents.send('destroytray'); - } else if (mainWindow.isMinimized()) { - mainWindow.webContents.send('destroytray'); - mainWindow.loadURL(domain); - mainWindow.show(); - } else { - mainWindow.webContents.send('destroytray'); - mainWindow.loadURL(domain); - serverError(domain); - } - targetLink = domain; -}); diff --git a/app/renderer/css/pref.css b/app/renderer/css/pref.css deleted file mode 100644 index fcd67827..00000000 --- a/app/renderer/css/pref.css +++ /dev/null @@ -1,74 +0,0 @@ -body{ - background-color: #6BB6C7; -} - -.form { - position: absolute; - top: 35%; - width: 300px; - left: 9%; -} - -.close { - background: transparent url('../img/close.png') no-repeat 4px 4px; - background-size: 24px 24px; - cursor: pointer; - display: inline-block; - height: 32px; - position: absolute; - right: 6px; - text-indent: -10000px; - top: 6px; - width: 32px; - z-index: 1; - -webkit-app-region: no-drag; -} - -input[type="text"] { - display: block; - margin: 0; - width: 100%; - font-family: sans-serif; - font-size: 18px; - appearance: none; - box-shadow: none; - border-radius: none; - color: #646464; -} -input[type="text"]:focus { - outline: none; -} - -.form input[type="text"] { - padding: 10px; - border: solid 1px #dcdcdc; - transition: box-shadow 0.3s, border 0.3s; -} -.form input[type="text"]:focus, -.form input[type="text"].focus { - border: solid 1px #707070; - box-shadow: 0 0 5px 1px #969696; -} -button { - border: none; - color: #fff; - padding: 12px 32px; - text-align: center; - text-decoration: none; - margin-left: 107px; - margin-top: 24px; - display: inline-block; - font-size: 16px; - background: #137b86; -} -button:focus { - outline: 0; -} -#urladded { - font-size: 20px; - position: absolute; - font-family: 'opensans'; - top: -61%; - left: 25%; - text-align: center; -} \ No newline at end of file diff --git a/app/renderer/css/preload.css b/app/renderer/css/preload.css deleted file mode 100644 index 1fa30f28..00000000 --- a/app/renderer/css/preload.css +++ /dev/null @@ -1 +0,0 @@ -/* We'll be overriding default styling so that app look more native * / diff --git a/app/renderer/img/topography.png b/app/renderer/img/topography.png deleted file mode 100644 index a009e12004f6e77e1cf93df878bc0916889bad3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41424 zcmbTcbC55=voARI%o*GEcWm3XZQHib*tTu^jLkE)ZSDEp`|jKQ+@RAZDNtzN3`f8Jy|7$FHC&YK&0O3KolJoQ zP3((vFw-rmK&b(9(pLSe;dtLDoUo)WTB2%gI#POHReu%i5UJgqWX?kjI1T zp8#7^7ehi1TN^uPE)QPf|B}n~ul*k~fSB;VSX``miT~Ru4Os<3VS6W2LRNYjdPY`GCJq)_LM8@AHUI+)fRU4qk(uk?&B8$Vzb@i`+MGYvL19)=D8MtTN-t?hs2^&}C@lP+sI$_u5>m<<8e7`^hfMXKQ2qDJ zMNFM6-Aqlyoa}80|I620mj4Gk{=fO)zwsvje|-q}XB6N+3G@F*p#LHL%gX;W|93h3 z>-_J6GPV0xMNa?9WfBrT@n5OoE6Avb{`~wrKR>^{z1`g0yuZKS-Q9hCeLX%tUR_;X zTwKVXHdF!u9N z+;;Cko;=^reYq?6X?z>skbDc|jfL0qyMBYbcYhOqdF0J|ce?`mxL@1&?lBJydGaD( zUnpMP2R>lqYj==y(=cZVg#6IF@XsH9%?@3CP7CPA@b5&h`+kqV59HOoe!}fOJb%8t z`B3|5`o6p`i{XT`a#eC3tJi#_2lgp_hWW2m=&R#jJwU#>npXi<1oIgBVtVH-CV@|< z`M{1By**DiPjqZd`4Qa2UO`uHB0e(-;}J#(XZdfj>R$%Y%|n0?j~_1Ha5n5G4uWo8 z=UzDXbNtWW3_WX|Z~JaBaJ|aOa|l-B9!;Ei`GY^3J{exyXY5WYd^n4bdWVHyhJGKX ze~_HrLms;4)s2>k_r60G$a7+R_Lhq)$A7=tiO<7H?A#qpj0*d5kJ#z@BAwfI$)Y?( z1a-pu*?qn}wvOeAeLoAl!ye@=a_^?)%FFvy=-j@Yw}pT45{Nahz4=>6fZd-yaYcJ4 zD0_5Y@>a$rVzGj?gmals8wQOBo%h%iL{r)RBNs>Q??PHg# zj^XUmTK8IATNY<8-m!DviCLS})7s1IfNSW^^op9<1JQ>;$eZgp3i+P;i{+{zf6X-g zfMB*P_Z2r+^RICk2ho1g74V&-k#*J3GQ~Ckc#sz4>I5yY4VJ0TgaDN{CWgn5VZqkr z?yaA_crqnb*ixThb9ukKS3Va+8?0~kWxs9%lK17%Gjfc@Y5lLc;hWK{cj&LIL&nQD zXOv0iySfjZiEZ!2a=N>MT=BMrw5S9BT4=gHz(A({?wibPmISFYKRx<>->~gOTbngc zz!uB<5L2|sV}~x%XRk=Y$`<{x#lh7Z&9vQ1Ha~lw{5NINY#X-{_k!KZreyjz*sCy) z{@&Y9WH4`PwyQ5)y}dr~MSrH7&nNaebjs_-WYbFCop=2xWFPB0?$lkp+C2#`^d-|< zO|ChmAx!j5j4;ty;%o`;*@+(!w`H%*B;J{Qx#SzE5`p^ZK zBKw_(dD*pMxh+0TC*Qn-Pc$W9QVRYvT;qMd#N@G?x{KzJCJ?|qDtq+? z@~&KgP&X;Rlc{ez?y(2h;e? z5b0CP`1*Z^v}cV1b9U4ghaEhK;X_jfbJORw^t4_~UHU2El;+{?yq|(y5Y#Li{paIS&8MlRAn39(GV@ z%loU{(;2|5J-vT!DzOF7T4W*x<&TABhM#UvZqHA7(3i-0Ny~)4GYQA^@h1yQ4r(3P zCM+hr1-!?pMt(PCm^zD4TNQH$hE7zA?Ts}_LuIvnSq z_WnjA_5Kr4pUo(qm4CnU_36m{iIAskJohvyg-u%T@b@OxsLJ@<^W)|y6{&1$%Izk_7hhL+p&2e(2L$gU;S(F z-V`JF^=<(m@l}0tbu^5`5{CNdHW%hyKk4;(xzwz`z)+{3o7w(HoW4w)FGphvpYBUY znn~yJ$|#N7cr7&>lzy=TYW|{nxiY)_jZ<&8Nw5C)AO~*32v@pS8?HP4)C1TaSr-Q1O$MajHG2`*fm zQuK7higQQjdfzkh&Ow(iorc@moW>|L z2X?14?J|o$XS1xo?!a!|5yReTlk4iuTS}x&>dKfw#v`S>`~%(!Ceqq=XxB|{w!FZsem%yo zF=x^aLN!h$o>|4=pKUM~o)H|95p-d_=EvB%l}aeh(kv3?Nx z;%AEB%c2t9N}6^yHoZsl^jS7`@e8-S%f@u|_gUG@`C9rg(08usCl*-pzS((N*}kMZ z->blrv5mA_&lHNrmMCF10s7k4={xNiIZNM3>Teg^koTqy%=5NbCdc~98c_Son*3J? z*7dCYyG!tp*6DO_lgJWSA4v>*omdyA)VX2}6Sb%z4<&dT*{?EodqkctUyZ>23E7H^ zYG=g}*OR4$_}~zG3H$!2q^Azja7Yb}>K(zl?CvA(TfNfNWfkr=)h{+Ecv`s8?N{bX z$wtqW$^0%qTg3WP2VO9ZYwu5XJd0i8gQp4hVDnS}cdwK3nl*6x4z^x$ycReO)VXS& z{{=LwGj5*s09=$f&if>P(A_7l8yad*Uth9flxu;iVIUcQZt5USK`Nt9Se0}5PW(k; zWn!2jX{W9rC%;bI>ong^Al$XD9>ka?sgqTISqy2K<>l9#wzK}Fr2C;CT-?#@R|wqI z<;XBYf&o>^J}=>rqVg*!_7qN_F`O(GZ8^=hp>75~;2sPMSAJ!T&3eE2jY_^XOWco_ zV1>U?$(6&erY4FO@tdQM5=p7<#1J5vJIQOpkvh5dkGqxpj} za!i53WLsk9qaEm(Gm2C$4A2v;Gd`w2U} zG@ze%=kU_>3a8-4QgDhd=c^)HBAZF!X1q8{%_`iz=hq9So*B}gM?qb;7sBX5N%#bD z=buChiEx5;=D5YcU$Fc!+SE#{t|!fdt+CUwVgf^C^h7 zkl~@-5kl=9hg=1mVD1v+Nw2|1nn=q`D>w)f!L!6q4jfT)?}ylJbNoyosFNsgA2O2IwqZRvW*1P@$aOBSaJ7USZy}YWrc{)N@?&ut2y|E-84hD zN5t29Yz=~Q6_*|SSjX>CEnq#y>D`<1JR=7x8Xm2{WM8F}CNmb=1w3P8WVa4^Hrm~chEFLy!hhx&&ubyGAldm(Yv|T zLmC9>_jW?a6OesIpTHaAnPYJwP3AGr9%4WlmVw{|p~@^oxR_as2vN@Cn=*=YM&9*Y zZe_FV^-rFW|4p6Wxs8Nyarh_w)1+RNEoMBg=b5&9{U~Og0B9m>@!S;OVCApQZ>Da{ z;sNflaA%)3#$=nosqTBfJHzi~Bo7Kn&1hK%$g1Ce z$ibVPD|RlBi^CrPX0OMLMZV=tc>HS=S3NXO6*Xa4`k0+B#J6JNAL(_ThBX;Ci}QHm zQs5Tkkc;_3^hV$`q4a5_-`Sd_vDh>IPCEA4-66QBKrnserV3xQYToHo=Z49~L*Ww| zo1fLR+1!jE{yLL18Lgk@ztlard2x4FWL~Z`>SFBcNEg_K(JFby;X}P&Db!q79bx>ZNrKD;a{Bpp^+1h+5vq0a|dv2Kt=b_)n({HP5Zd{Ie zZ-PA}8bFbUwGgloRN%+ao$M+`EC@C3L6L3AYU>*LagO+fa9p_y6pHXgbgFlYj~esO zGL;TpzgrF^wZYzMV+R#yVqU4~(vdA2v5~EBmZtc=x`Yf121XovV#dhjETDj?4(U?9 zI0RFjF-QE=7pbD#(&?(^huW497?ahpC_=rj|7P&0{K+qeh}>kNk6F5kLS2X;Fd6ZL zHDNjgodEr<$Ycy13&$v9*$LKA9B2OOAyvto<0V zNg>tnR$aD)2y{eO_Yg?3@8SMAewQO<1H^)lD$*}{2RkEvE%!awZvi03ab6kF;_hKw z4#S7(NPb0M4AI~+*9aWjC5+=Z=uLIr!qlTp4!8q`al_O8c5p(^35|PZ%rYYlST7}U zKMT9FOSlAtow%G@f4a73%ZHwWL$|#LHW&jMUG2x{k>Sd*MbkS1-r{_`47ESi< zlzlBb;{IU9?;p^38))ok+qE199txT=N}TyC7ATsX*66!>S|zYF3*qb4>Lhb56OKp< z8QRs-Eo&RWn7Wm(8KKY2oQS8_iVf{j+P@vLmg7`@KIx1x1WB{Ih-ft0=%zC*Ow^mh zXo)G+pxMePZOP1v1q^OJZyl5y$P#1pC9;@Ox(sMl3{4ZZc!`6n1P>=s4YV87tQWOn zC74){&v@wkw(q!hO`PmoW$O3HQL`g>VPOg`ZwflN5A$wdNM0t2A-gbP6PNoMjP3V+ zP?YkIuU5osf_rc_S!KQF!bhrxZX?s>xtB7b0#M#cc-9%wZai$icfnd#&R;KH>uRw% z1-fK07jD`wN$BhG6t5N8GFe}J?w2+i^uP}ZTukzwwZHd3JR$duZ=qgB;lx^^ysGu@y*5r#5tgmBx5mDi z+sVdjt8$8+T}h(o7&kd;!k|a;740gIK#pyVdMs*+!Y!>(D)Dq)H==wKd(a3YRIbvv zIeNfpZHn1S!(TSC=4o~6B_1*05Sn@7=dhleA<-CiF2lXo-M13M4d`0_gcxebxp*u4 zndU79eM%&QfX7vTNliuQ#uH0BQeg3AZ%}~=0f(fHY_>4jpkM7Q2RH|V>nx?9q3UR4T6L-V7 zpJ%e-D%@Y(I)-efTFq&%Kv=00lZh$x=isIyLON=z_DUyCa5~fU`3Tf~mr>j>KG$oq4ZAtc->D0mDn-3)A2379uT56P8Qj913G!ah(M+g|J&yd6?O(3Th^~ z52_7vVHh4?^d{eut&G+5+Nq6Y_Ljb8%OV~k+xF2k^!ChbZYg;88QFonen*hbI)+&{ z)er<&NZyU>!^Y?d)BP3NtddVVWgx`o*?H<z#dLn_&MIdAMy4t6C)sSvkpJ?iKDJl)T zn7__ai>Rnw0){-*oR25=&1Qj`UkF{*2#OSck5RLL#k-2H5DA7;VSdn!K|dQX^+$Dp!4Qj z9~x7(=9n46U_ocn@m&K4^Vdw#ABrgf8_09)A6X6)GijxhdO+9kn(XAE9lWAi++@mOYn1Frvo@r&ze8@2j~&CS;JIF+9yQ! z>775p{LyeZ?vp7_An|fvSqG8Kq%wo*Dj$}ZDlutxz^vIYsBjX8HClK}qzPCaOeJL(_hpi_$tBmS`wHq?!<%>2;+;%)0*?b{dRe=40FB+n2oXI4=NduL!z6Ti|FU z(<*A_jlWAewxuuVy1bAlmx{T#4aWXR-lG^sur1%BkjUoMKG$<`;|N_V#fw8Hrn?jUT0-Ji(hW^H*6;%~)tUzh3!Hop=alvbIkgm`|tIf%c;*&(fVfiAE+1`78(?u{+d_BD&p!9SKWLUTfg~__3o`$nFk5C?7c6Ru z90YQ-cw>O=xVViFa%LNc{g~YHvbo3Za$z4Q%)L)=HNP!?SB-w>^t{^fv*P??eW?^V zNAI~1pFL#_dB51xW>!C}5`q0EV?Q;l-Jtdm9Kw$QEI72$+G=@Jw53gYqqui&+m;R$ zq<#ZHA5PwVo|h@hs*j5)5*V$}jrsJvs6o<+{@ZklAdvU+R(u;)a2-wKfOL;dIo>W6 zU)4Kd0)0O2mEV8j=g)AhjM6*GSJf$fRdNBANQ;U@Xori!`UANykNrYvDvwEO8HonU zzSEkDGfLNK(<+=JNGDkI)a7}|yWlwo{@=Fp#}2paUyckq@b{U(n9b%YvmxVEIIQ$n z4!qS$(#uTLh6|q*!M>&%^VEX{Qr@aq8s`W~Ju&nMPL&8MH)3{51dMtJHG3?%fBytS zbm9v&L{kmz@UA0Heq!AS!#Jd#_LVht_5S6@G=W%4Ih@KAZH+&ppk-x~YVwsnW4?jK zP&4p|gxvZrQES~|L%uDxXlmf-BH`kaLvS60{gMY{ankid^j+NlROzELsqok7o~NFm zm}Z7)(Cn;!A)yQTRCd%BLx~t$MWoP{`|E9VsZtZ`oLwrKm#6P7kkN||k`eZsbmz@3 zm_`S_TA{raaT^_`7|iJf^8MubEmH;f#hoO`5UEquPc?osM0~p8wEQGIOW?`d7j4f? z3l&Sw|G|f6@oRm>GmCHCQJgE#NNQ(i6ui@%{=4b%aAtw5j{QQHlWc(yCX08EU6!;^ z|4pz{RmdQ9)t_MqESX)>iU_i;qZXj?esY{h$ht*+&m*af(5Wi@GzDq0)F4JA*Sv)P z^{U22#3dpfN?|-U-i}Z@_dwGcgigy#JU*-<%`6E47NEN?*u<~`$cS_QqIim)J~8MZ zPMPT#-*-_KX+g^BgAbw$PJ~L*bk~`AS*o!3o8P5WeDfw!y#2UbY#743p^17Pg?w57 z&ML|$MBeBLJ6ZJz*ucU{-H&C@D+xOTIm?{Mmjb0VQOH^x@MjsPRF+AjxhHi9lo*~$ z^!j?qkBb%*Yqf+Az2e1H12AcZ(RPL*`_V&4o=o`v+da4m#oO?X)eus%xhrJWxSp<7`Mpy@QgYHbu4%oH@q-*Dwj|4 zh_Ik2=ym~p=LnzT=w$o!!44a&P(kAjeR<)IU5oOL8cUogR2Z{k`V}xv+i6t1kYDvy zBu$$u-MCXg;}%9k;A+@>;E#@reihF+Y9CB1Oa$1PFbG%Ayz83S}f^N2SB1NEXWk)U!0w3oOD0@s4_+VKGXZ`{4zJXTq~ZF~ZsYyw!Ka zJN8~qIv_=sBvNd74~Yx2Hrfr0K$PZ(#ankE^c*Jfr@E*jacZP=UvV8sCEuCuZ27_D2oJ4r%guztDSlTwUBC+ zFw_!d8|s9435pNt+0csqQUIrel=K$G=%#+r5dLWl(;N`LD9&kHI4v^4r-w3*03He> z*o*!bUU!5uP$!h}r4Jf?p9B%JS*(LZcvviJ3oWmFTU-@(b|Y;nzfyx*`VZeub3(0Q zXD4nfgFU2`-;RQij--NJUWZi*-uVzSc0P)K|2rCY#WD#`-kA8hgezUoN2RmK?2#$< zne*Qbx-pmBAl0n=Apc&%aW86Ski9uEGEd%9rH@W7*(Q|EE|^4ni;v7Td}KWjKd38E zu~oG)jvd|+D*~IV68wsxg5Q^*HOUcIf$nMV_utzu=1^{1OI+56k%>G!&Kmo(me%s~ zqUeLZ1S7Y}181$5kPi%ZNx79=O!LiCT{YChhIon6LyK07x|aqQNFwQ(i7Zy~YqjAW zB#~Fm7~x7u7N@oPaQae;C7Xum`i^9uEpIdAJ(YxWd>Ak{NipUYG2OMGQZhZI7n0vN$6#epwzsh3cxoZPK|4{m&zp#~ zlB85+q^n3TwU1|@q%0eMu?XY8MExu=U1H&_YbXTQdO$QidKT#OZjAJ$uUU4j36qAQ z@djS=*eKL4Zjp+Kv$-gzq!=IN2j+OXX6YesT{HW%B?D=bJ7dv%g`FBQfWMBh6y

8m{6g*rkwMZi8krqz8i~${2Xu*zdRu@vVVn!Ut0qC45=!9gyXu> zjvRw*vbrq)*-NJ0%1HB;O<~8fwupMPac1=w{zd0HXJ(^2-A~ZZX8dYLC5Y|ZM;SZ{ z){y>$Qsh)BF^MplNzbNmfbw{l^3skJXeZc%?>7nK2m72{O=2(CkSXK~uOErxZHc0@ zA<7c5^aQUBI~Y~56MCbIOZQ6*R%P!}D#@jOChXs8ql!mT5b07mFEgpL!B;kwDH#XE zHNFL-H{ZH}2PuMk>ZtRVu#qGG%*;FJH*Y8gM7KR#Rj(DEQEb{Ia@|lLi>6aFa4nu` z{D|^PsAQGZRtAPWpis|_pyb-+O%UZFwwPCpW`eAt1rB#|na}`dQdvY9pAwB%El+pp z2u0(W{eblM*1d%mZ(eLDY0`yq7yfaGx_xR632eIH6BQTvt$i+h)@EM44V(>TH(X` z2A8;LfBU<-hv_DB;cpQ|uTyZ<;U?ZUlwwTPuMpjA*Xu8{>F-~_q+At~rw+e#No?CH zOJf9k*?=Kc=gUM8XN5ka+)$`fAC2b%evrZGQ+gVHAQbRna$-bm#8*oh4CIT!U zs{SRXrA~G#eDtL6TPU?zkkgsM^EXO?#Zla!1hHSB7bIBxpiqb0wMHgIYM%&p0p>yw z0H$PYGP>0$zJLR(pq{#i(8;|7h$6y1=3==bMbi*MO_uT!7%}C1m9T9ymtYy@Uq6;I zg?}At)(nw;r9r+FVTVU=zIzU`_Vh|>Zb0_Qu!ZCQ3@Y@xR~4C;V4DXTc zgA0k@9a{w9(?YQruHMZLQS{zgx5{CBU$s(o5Iemm0*&`EX6YfbL+@Gq5*!2$(8;Si zh@@$@hrewnCPe0_{9)iNNr&XC$lDNycui z5W+c?wS`l_iIm)zQ4Z#K)vD0qjdqREg(ekM0MjQ5EobMmOOJMCGLCId1n`7Bgwd1O zw}lfaUvC03!Xz;-8b_;FPiRr5FgE{-4VfdB4W%@E?fOg_gtmx;WUf6nB3WMoJ$&-M zA9z#QFOr@*aQ#J}3ix1044A4bV-ZFt-K#Svr zW!aqLcp9ch>>(}VJM}B8m^`Zq*Qkm~E=dxhmj-WPS-MMu^kYCA^5kn^jk&0i zQ$d8-^P!HfO=whMQK=%Y_x*vuEpF#r*QS=5Ob&?SvefFbm9~3b<;DiuEPS@xU=)ts z>nk^d4Sx|FAP2vOwH6chQ&JlBV!R zjdj(4w1l{?y4hwrLs~IrhPCI+SrLu>?GArR zv=~?C^XkNt)`XQYs6=#t{mev*axO(E#VH9ZduTNl3`iIZbjq$tZZI9U6dk!K)WHNw zm<4InB1i@0ng@ld-DofCA2%cmDS#;dh3O6{DCNF=pT-4$7WZ)M<&mZrzw^GM?f4fY zY0roMAXCxB)=JOfI9}=}YFwMmkgw@MCOzkN03MD?*z*)IXH%9ClF)ZI> zsZctn%ht0yP^Go_nI8)6@Dkv2qc3ZdF9P_}#fANt;QD?I&$#$ij)CshTzhfqG5zN3 zFE6T#`)6ad$S#VNE<;$#LMiW#GU3VwODjPHuevL%ljiw9g*T#&9T!>z9UgI6NR^R~`07@=o#rj=x1?r}-2=KMfO1N@rg} zR!unx`v9@W!U1B68SJg=F{u^`dGGF9Fm15vt?io>t)YSW>uq+IbLX_PP+Gn-x6z-b;ADf;{)QcZ zKL|VqA!`D3{BjW0riH;jML2skTyOLP6=}LTCn9gE(Ny?{@PUJ#ilKvkkA7!>&a6ql zjlp|M8N~ZoC1BJ-Ci<$>q&vu436z#S+K!Z5(RZREATy-Vl(6sdnssuj=F&iAE7#a_ zl~jEyIW}abPlC#L1K!7`a@aDhhhI~GuYK5z!*PxaYlxzYy57cLI2*@ZW~g{O<(W~P zwgp3#Go!`?F{~G+f=OJ_7T)o1&gnky2!|^=+743#BR z*e2$)FplRFaq7?+4;3+V*zB2AY25fHw2G|}0}Wh&Yfc;@i_fI1`pZjb51gF_`$JSy zQS!8fMrYLwQYtn@6|I5#Q|6nBxg+#~p|)Bl@e?LM-^f`Ua*`^Bs7TykPp5C>q|oHf zz4e7SuBKNww98!-f)=ZsXA^m6*7%xd+*I|yb)Uq8XFF*16J!nL2SFwU zd2q;rm~$IhH)SXNC&p$iz_5zUaZS@q0mw65O6Zg?v!w?(U$LYl`a1mgF2G`@-s^k^ zr8Q$UTrz00mXW13c+2us{DjqII;=6psTMH9+5|h+Asv$GUdot&=HDIVTCskRdcS(6 znNUWqlsVY7M!ER^4q0n9BII#@mMBoKZQB{xY$npKv$vJk)(PitplNAQ4=9tZ0`AT@ zx=UW<(a}`3)Qz~YTP6O`(pFPQmmkknBD#{eKh=ybpVOGN8VEEax-x#BfTLS>P5B(N z(su=NXN(i55Li#(rdiv%#Hm8gQwudfV+)vM9m$$hr#yR5t>sGN@iyw%K-S`y2RqjH z>Cc~|Epn!aEigK2N~KcQ#E>Bb7Y8j?BUgDS!`{NG9>LuO=mOV4ctS4&=U012vJ<*e zphULO#bT4hhIfU}}~gk2j-2pT={LR8RX`J;*|I)eV2x4!#AH0d4FffONmD7B%<{)@j3>s!fE){Q%_UX#tPLp zO=xJq$68`KH$Ju7z6a+r=ruiXhDdyQGu3FU0#E8`KB>zyqThM70iQaGFKn}QWoY$J zU6?D>o$j4%Dn=Id#a!cj%p7P*gR$l%_o5QHPyqi0&yE2(JuorBg<~eb&9}i45_@sa zi3r=Ng4F>dtXwlY8+YFE;3n_ye~Y)O;j{mK#GgJ@7^llarIWy^raAm18YQTunCZ}V zL@GipeKR%%X|_bskPU*ywmqI z{^4pK_ilvn=77_UR?olj@3q~~0l6V7Du*L&$&5A~U?248H?u4?w$5TR3)z}i6JBF_ zg)~;1kh>P@yvl8Y>#hZ5j>jHXlpy6h82f95{q8+}HW6g{=`SBDHis$pu?MN6`#fCt zU=^tzDggDHFnLN37<4%o%k&Habtu&kl%Gp+!*PvA~b?$xW_kA3e~n;&+u3Fwxmf)NTNxGF?Fs{+r+p_RYKUb$c9m5_xhP=W{gB{R-i2Uc=jk z!Qq`)L`AyLkTi>)Bxm%cl3w~v%vP-Nx_u*!sf>=*YXZG_W zEaX&=QNO}qLWqg3Q1EJ=Y;xg~T?mT)o(fQ-(`=PP{b#Q7*6nQk(R89=8i$EuR^jLO zZ~UUmb15o%aWJwZ!#zV1SLaEv6xC17WFkuM%vOw3ZC?Ito47m@F$k7q-XQY`K zPCX_O4Kp&e>?TknbyZu942p%7YEK%MczDEOu)3-#=z6i(eO>v*Q&LW<`hpt85gbQDt6zOyut7V<^ZdP5WUG*H3ZEbF7-6nhXg(M+3K{Iht)z zhmjXG2wNvpRuZp2>iVVz$oA{S4E<8K)U(8r9S#e3)mEqSP_o^Ubwos=Hl+d2chp-L zC-zoUMWqYl)R&N{umBJUy5k2SS_|9zet&pAs4DTx!Lhc6;F!`o@O)YiD6})%ie9sE zEbxeajvDl{<56$)WkG^AYsnR^DGT7ctp}!SR!J@d2%Vipz-n7DMnXaT!^$l>mRKr} zD0!(#OT+mnQD{99gmymVzKbG2c%OH8LaB9-4E{#1tL>@Tfl>bP49#_S3vL>wCH{`~ zM#Yw^TkB||MD{@6@#+%Fgy#qHZS~1oEPH4@Bt`IuJ?+X}d#W4@@1z2o3V-JUD!3Ke zwsmn#rWy8@d#>10og!2H$41p*87D(=C}m^o>Q+E(eB~e!ye_3!OIRyl5SLVC@|@2noU zFh>9_Qwa;R=yzC4_EWhk7-tGEUEEDF{oAJF4WefSWNtt4 za->w9>zc{5cUPzq*s4Kk(%7kF{sc|+qW%;~oGf{xAtJjbPaf@Hh8RWk~>`YcYl_!toO(Y_rr#8X-p}j+KP>I_d$|k+Bt#8^Ih9_ z#SS~gbG>FW^FCss#S~$}pnQ-T56o^uIgYWhFNCQ*5u8Be*^D@#OD&!+!^^ z#SOq<@!L!|S0L~oe@^FwQ4eFtH)Q34fL>s5>&nF=UERxsH4j5)(y)tb!mG^2_$XPipov_Y+p9$RO!j=030oaWqs z%>7ZaG#bCh9w_m>B~q)K2iSx(zS{9Jhc|X--wySuT!tV?eo+;DYc8ZNt9fuG;@{9J zR{x~uHZSv4JRF95;T|h2Nv+vAA3*2OS(^*(y@WbkdFtEFB|ou`)g-NZr`4Hx%OvT% zpo1MUc*|BK=MtoaL=C#nMQUQ_L5IZ^M7CrY^v^{A$o+o z)K99LXNU+M$=a|umzERmdCw9QOkG$Pz6gl}V*uRIBzvjid5_iW!hhxd9=SewOCgra z&DBdZgmt$Wl^u5aC6MG2qx~Xi7#YAtk|?^-6%cydU2a6TI1{)%+h+(175dB6r-t=| zQVC zPy^wBe9q7M0l#K+xryaCx2vz4t%+HX0~`I9Af&hc9%{VUzB`f_=cRCfaf4yynS!iA zf&xWWL!T>d>a6IA-PS3F)`Pc*^w}w?oPT9?7hm$=&Qq?y10}NOFVzA_-H)SXP~H8O-jcoqe7%zGH9{l!T-S`-;jy}E{PTsG7VYu_ihpMj<9}T- zJgBD=DtANLrp(1WM_%0bX%yQ&-;UL5TA|59kDuFrRT><59x$nds5YKi3tv=t;4nMX zuRY@5wU(2}+&18S#xJ^+UY)V{MUUTl?w{*um+B!P5EwT?Va}>q)YO)c(h0FLGSuvc z;lY$e2UK}>nB?Y`q5?aS)y}bplFnydjMwsl&_elWS?%Cmkq(GV_C+fBru}Z_3 zy8;LxkRH7**$Uuq)y+zx!zB73ap4FMKeAK_cP+ws9E>2jYdPQ;DGD7`9J6p+j2IWBo0 zeiitAUQ{fnPv(Bk^?awy$AopW&f@F6Wc_fo^Y~mI1G;&IfZdC({H2AInw-Pex4Iym zW~Rfy$q%_7#i1KW9&SKzWJy9^tl=CR-W52Ra1s@;Cd>x4UI|P;*yG~ZW4y2|^2tnz zHI%@lM`2YquaZ4W6Tknp2i}+?yXfz|#K8aaSVfD?gom0JpWXqKRSPR4EemxkZf)2@ zuIFB0J2`I~pyY2%o}g+AqxjS<`UN{?y!VZYWP@w6BeSDNW+ice_%>dJ={W9-e4May zKDvIle6~e8yg3UR#>cZfyKvhk`0eJBFN;ki4PTh$-AQ*8GT@)RWAc2Y z2}Axz&#AJuy8L_LBpiVkJ0A7g@!Q{Dn36?aHO}_cW#sYiL0pg5U(6IaLW4>RSv?04 zYRgo=q(emk4WS6sc#+kr92(c*YxFG3u+RgBbuuNjd0dn={s}31EY+}o z!Dl|^W{QqHjk4Y}JN&(A9H94i{BePqbNEI=M&4z47HZ^z-Ip9Hl^VGS<|Cx`i z@v8fx)ZQyEOV5PeQSz16{uL^$5}R8-@G~}Ge16*FA`;Y*Jh?VZ*eM}2B)6z}h;SCf zy5sGaJ6kk!2Ln{sb`Ss^uKxF`hT-k&V#g>}l!`D7VINPDSku_JT!9;`28a+^;@`jj z7dWYI8&b0sBHm3Vb+_%NDf=`3>00b5@D#;5xK}RSJ^0>+9Ev_Gb9B0lL`6gVM6lzc zNEnUyDxQ0aCFSg=bSsyLRDpeQs!A&ill-f`FdSKH$I_u~5_QYDQroDic4aQkFu ziS`d_g=|t!A3;h3@Tyy(&#|hy^?7>^x#keJ3{|u+@uCn57M^e6_(64g>S%bbi*g7P zlT1qDC+wNWH_A_iuMB7&r02#8pTKl$mn_qxP5*tk{URIDKAFDOzs1g~(AC{n88@=_ zRJe8ZhK9xzu^;+xMF0EW8E2%UABtE_2381H#?`(HunW~lr&-f^Rz_YOvAUq+${{>) zQ5y%Hhd~Lr?*&qC&b-akj&wn03?fuhKB)&CtuV44t`<*4RuC?>oXW^dtu0#3ZNa!g zA#a;>U%1mdnW4;ZOtz|p##oS3SUA7&f^z(i5y>29z%4TM&%l58G&Y2as(y7!J7)lG zI;l z5d6J9e2hBl;OLAztA>U3O;szq*W6C18(RWVbVo9J)+ApdTZEA~cU}Cp>55moeFVGEYN3^bcd;7J4G zb~66<9LE(8t9ITfpiov%vY>AWE*dlLuJfUxy)y0FOf{W8FKB+m zxU;Yl>k3heas$%8a@F;i^rq#pF^E3x4KDa5&MY4tQ3|NetY3nVOIK9aJyuY%fM(9- zx5p_D_@jyJ5O>JE`Wc`c7fjcep-}zH&vBwx7I2pKan4Ph)(!gF2`@k>C3VaARQy`= zn{z0vkt40q>zi|I61iT5l@%sL@LuI5`AV@>=Thqqq@zeDy_mXx3LG>hiH{YzOJ6x( zHCj$Tnt(oOR1rqtDg}AL;%4H=OJR1gKVY|k(tHX}Z7pC$PChpkY-i4N|AObF9ibpY z94iO$q@IFgL>LiCIuHL5j^{3tXXBfXp-mN_T2WNnjMZA|y6mLmU&0=j9_+MZtHS8} zZXwk7?2zkU{6zud`oM!jff=IG4nw|Lj7LDo|#G*oBRk?mkB**{MRIk@=@R zyDD37nr4-79ED?=ura*Nxmd3)wb9>s;#@{P*>1wTN*zXF@XKPEk8 zNOU%6TaDH*&p@yyG5vpKkg^k7+qa=WXMLfg4w|{NLq%ywYGOM_8c&M6>e%0}o4lNDggZVY?fJF^D@~GZv&pmq zuBZH)clo8q9f4n}Lh)D@LIi#{!DNp+o}RF!x-O<84G`ao!?6L-=CmbU zL*_oTbmUxD_R>6c$P+?HdsR5g6E(Cm>VMMo3|t@At`nXHDbl4LAls6d=x28){W-P^ z44(z#w*H^~>StkC#3qH@;QDy9=o>jpuF%RM+9}9iY3qAyIua|4OStFb!fd<2Ks5jID-$ z_Uz%DVVf!_-v0#H|9}@e)c|dy7k2P=#r;d7;G5C1>KM!LM2RRt;I@#iO2Vq`+5@`T z3K^!siAlKZcp?Gm%s( zon9-ALG%xUIBotz=Uu=cBf;7RYhPLO%22{&y`P=lOGgC51$6Yn&_zpIQ|6IRSWMrK-zO0StxdjbjQ>FQDeziX$ zKK7-b3g~rS803vr>9wGja^v0fIeOrdt{i&4AM_`xs_pB+>q@;QWjzH^=4AHLE#nt zFo+gb?Me!WMm&tt6KTV14nU8y7;AEtT&1BpEL4ICR}zo2B3@e*{iZNTq^7zep&4_C zlpj%`pbC{z+rYW3g-5+LRSo_h^4k9vR`j{_mLAK`8dAaKe;7ocgJS$t=gw6=uMYld z<}8|=_{jzDN)oNAYM&I6>H^9)@lH~Ccjjgd4(p^#$(LWqWbGl;@-s{h&c zB6reZ5Em22NZ9#uH2VA(%Na`tcbjb_TY)uJgLqZww8c0|zwRe4no%~KqYIe@r-LoC?a@^p34i-~;nXjOwMKwq!P1ti4#I*Gs zQhRCwY1|?f((8+OB(;hlrV@{!DKX0Pzessw>B!3~bg(|GQ>$Q=SlxBP4+Nm@v#_Z> z84(9v4u~q|5hgOlFVz-nL0ok}d^>p47J>|U{)z^R4a5%_QR^^>q$5-BL=b3TT1tm4 z2^1rXIN$v+v#mLbYP3#eShwVVk@DJ4F2gTtDcNX22Z!zRwG=4ZvOh3@ln0zG)a+oI z{7vzaAxo#gLD!l`9_U=w_=Ovk&&N&_j3Pwd7?)Bc4T9St)TQxa5N&%6-}EAHd#G`S zC}dfBrE6ePoEv&X^w;{A@V8=y>BGf}NS#-&{AXWQg6L}i{bE9b1ix4*OWnC4IQ6b~%1r5U2N|X@7id*8jAibiGze>w6g#063 zgNTo5XWv9}9w=*t`!~#uxL7PsP2I*Vfjbw|qbgQ}sN8@qNc{q)>Zn=WQ zsubY`ubtpE<>l$d)KwHR@MUTrK8f!4@1i#0a%(=?it%|zMv(ST8pK)w2Ax`@D!`-2 zRv4lW{xsiB=j&3Cw((Z|5L&B9_fP$g!M4m_6oZVuE#~Ta=cf<3SkqST#{_%pk(g{M z`_}@U+)de?itg?;<=yAY#?2_BWCRyRj&+X|B0-U!a^3-DbL3xmcmW3vDH zi_~na>hO)a`>?*ax-fIIyi~@U#rB*fSBC}|i@eL+|YdDo9B4EaGHfPq%|5}#LUpQuw znzgVFP+b=;pqVYnG9oq_%JNl{u>0aQ3_{783rof43WsgNZNW%n^`I$DWiJZ3e4WcC z??TLo54V;e*Od1iQ>|NA1f=vab330hDj!02uEL~QJE~id;j)Fz`5(-qjQ)neyJ&DZ zP$NcN!yxfshvSl>v>4K@iJ6mn=bU z5&b88o0UOI^+Bt=FFmba2*Au1x1lv~#(#Z5^aJ1iIM(S9@0ZQM5|i8Ff68ZQ_lwm;f` zA?P0RY}$I-gNQExH5?AK^$d~v{x=uIQ{S=ss^!gFFCh+{RBVp{GYIqtC3g4?CL~>i;u1J$92!FJXk;ZHc!C zVQL8JRsbjjC$I$B@l$n!aU+2JTPT^8g4{NW-8@k5Jb?D?I(>@A>IwP=Hz^3=7&+Eh z=%@S6P8LHkrC-T;M_)GWl;L2+!i>?Mk2c6jqW%-t!jh^gYzVQDmk~Y&GxWgv*|E!^ z3uJXhqQuGZUjUm@E2`w@wwN~swCWl_U-{%099>3CdI7q~aI$K2ICv+bk)x+vhph8qbXy8nHWwVX7U?cQVVM^r5fGsSK5 zTbeb%=Wku+3}r{tf>9@i6)H_uYkn@OW`E;aJ^R2Rov2kbe%Jr0LGt4RY$%1#$n_H6l^e!DI%k5~ zjLlP|0GQ%SW$~LC2PnZ)%V2%mh$c9A-_wAt0x*F|zihtUnX~=STGTHye3Frmq|H!n z(6(ZESPjBWYM(&}xa9xVAXMMqLq?LFKfwj729XM|E4ONdpT)Y3Gd698Y$)asg|0$F z)!=I>+aarn_D@*$v;~Ne&tnf)YXm83t}CU*>Igp^w@sDiwzbpDGhaH;@^1!Vl=;6h z$lMmn7Y<+$CWdA1L1)#xTse+TZ#77STF{(i@moq8hE9<)IE0h>^*?Ud(~$l@jshm+ zo;D4^b1}#!jj=@#^zCr_g>x3a%^!_RvW(<-yRwo^4kQiYhxs@|i7t%GBqo=Jg?{GZUsT3c@6*c1>({g)kKeDVD>J|OYd(ba9clpuPWi6Zng|Ylu z1f)ck@qrN(!XRA@lA~5vBxLEz%3>r(dV6<=pJ3=B)nH65KPM@>y~4z|8hz_em8EE5 z6rhXaajeKXIT|fvoTW4icc0j<6Z38iRhc*H$vl@K#K)*(?pCeGsDrn;(I-%Cv>7*p z=scaOKi?oUAF*)=U5a+Y_I$jyP%4k4FKcW?eErl0>tPubEzw{H`5Ji z*CD^5W09I_YNd{!li=_+b*lb%(k{fbK}Hb+v2r!%a%3VZYYlEPS#-n@b9=t&@&Zoz zDk3*rDY)HVK@t1eIz@%hXpNJV?B;Q|U?Rxz1eq<@t&aR5mtht!VEwqAr9)5oSjz zq+Cw9HVxw7ygc(>YcP-{YFon~a~E?21_g|N3LOQWsu>c%0@j-BHI{K-mEWZ9L!Qmh ze@~VjM!+L(P&;>|0y|@}-D{N&R8gBr)gZI{@6{mEycCodjLMD*VPS3h$0a=572uq_ z;>x#;gJhKmpC#Vu3`_gKIb2|t7_VDh?kUBb@vQ?!zD1kq%oeiaH!PpDjg6XPTU7j( zo2;W4E6%Hd3q};EHP%Af8$BMwlx=$j6#S!XjHC^nuKQ~t?BiM;AhC`5`boH07AfO<7|q+ zc6+nVg!#8Yoc=w~nugdM%<_g-u2N}l?Yqpev_kHlh5WPY3C7L{Z6N$>urs>$J!f99 zT^lF_^PAa;>jqPhYxQP3L)|dR>YG=5qOWbI?Njvw^KlTX9|pO}%A*$K&BR`tBEN+s zHRo0!oq5olh02pqlWpVv7f`MYAq7PI+axg>PT8l9gr=A6Sj9i~7)zhIcuMe|@U>sq z##xRh{`IZAcB^GfCpXL`wonv~XyZPu#77 zL1e3-WEan`3&W^`Wh&&WVDPOhA;@-cFG?KoSWx$d>L$ZDAF}``(aqQPiL~^mWm3>@ zu=A2;`bC&VGn$D!IteBpLuChI-#QR`QEzcX7)lb|l_2BLX#HR?7lTlb1%}h7HSebQlt+#-c^=n9^Xw&AQWXOr%vB3>#4nJk4+H_6e zOH1^~5s?BbE)Q|}yrwj6b6wU-mrRRnyVo(?RlS3W-dJc2&V~&Q{uGx+=)bBz``w{+ z32p-1bY}>yQ$cIR^LR~P5Z+Vzz`llR+xdEUy)45Dsj-4p5Lag0YqH4Sajo$ynZbuE zaVuAbi6a$ymNo{w!A^buVYiG4vr}zYpmT`rF4F(}j^y-HtAx5q`i{LZek2rgBdaf< z#YZn>p44yQ*8BQm@jmi2I4;Om%x~n>taRt>F3)l!+F$k}*L*6U+tLYv#kul?qTqWz z1Jp5KctsIZ9)s%e|7sB2B3_6C*)Y9(SNNx*t|mjghZAZQhsIbMXFqgDmr^^^)XI4` z@j6FhW)7i5hd#S8;b^qXmYmlWxk046xBj8?U)IJW6y5h-V`~U0C-A-Avr%Q> z4r$XyzI#y_{8S~b#rb7>PN>eF4z@q|v5TBLXQm>06X)4Rw#QoZ?isgf_6p}`FF~$kJnu_L5eOzugf@bp23f{6W&lNNHWRa}CKQAv z)$F`7`04!}5NrN2c(C`Q7 z@wwwctWmulvvDPbs0a*gM=-Ks8CRq*75_FHR{pNDJzWQ$b?ylNFuxsCoEzT+>hba1 zU`aoiE|2B41>BJluW(r|G!f6Bg)-ot?#H$nd@pg2OA2)V?*;CcEtIw^cpS0f_&1EF z>$ZSpB`Nev_lwZsGmh3Axq8>l4Zc~1(T3+2VjTvAU+n;>5=Uu}vB>VMJ(Zkf(M4z- z78OO*{Ru%6%_>PT)2$NEd3#HVZ$z`X^R32y=ttRHKZD8QE&3ZFWS66t{ZGm_63_&z zziI7t^{%#HxDw$Y(xvw>b?@IjA)#Mz=K>}cXE3&GSQHCUIul`R&-j~bl8tCp_0;U# zTD1r4M!9Zy-ky`LG9E8vjFxVkWZ(G8WbwgVxL%PHaverET18;y?-q-#w(BfY1qxV6 z>cz`CkDq&4e;d6{J9(Lagh9rE@8BW~=&7JWYh+rns+i&1dJPS?I;q{y#~fwA;kZJ4 zAMaY7^>aIHHTpLNFy{$}ujMg*{{;CF;2$&^bySc|;lV^F)x@I}JQc6{n+)*JvADBx zmvO0H-`M=*oo0OkQ-?OeC1i{Uw6H<6XukMQ!@b!kKvL@&c(5Fv2s>d4$V8_7bGtVc&MH zK4OM$lnQ0IM2;$t_$r)HN^Fr?uKvfgxb~-Zn@1~kJoYnvvPMk!K$|=7FBS4YQF4`i zaw1ix!~3cM^G7#EH$n3Me2nKbb=S2{9I?xKb%gSrrw|u2!FJDF??GP;awa`Vv1J^8 zc!k)M??5&uEDW-lJ(*eUA=}z~0<^Ns%tLm3g`ZNZvwYaqY!j9%Vs5JA=N-8eh@VH& zdp|W`-n)*j7;GzR*=g`;;f;-2p<-)qDjS3A8!9PDp=Z=O<-m8^9^aa4a9F6*`cw9$ zn*d#1l4GykE63Njwat3IZ4i7cAL2-AX;B-Fg^L%;yADU@&RO@$`2GCnzlI7Yt?yII z5}ke%(16n{PLcJz(}jC!RXdgZaA8{4CBAthauY;}J&~A`=e}BhO3o&7m>-irnQBT~ zYTGKAB*Q{06qlth=_J3M&#+8yr*i$?J4+Cz=*waI0VBN{f}NMa1(FOOWkV(i-5@N! zB&5J^M3Fsth-V-VwFa>|va!OL+2&<--V)zDhPe2HR4HBMFu~+cbYZSZ3qsbXbr1AH zD`*9nKe{yXN!wbCgeJ`%XsB&HPknZAAz!94hr4_fL3_ZW2#r)@X{) z0x?N=_j#fD)rU%A{3Gs9I)EOU$rrPKMGr!XA6nz``Np6-B&Ae!f2Ca_>nE?sdfi2{ zDCpO98v*dq7E5SHZ5PxK63DW}q@l3XKet?`b^p~YirmD%_gE_hK}~)IOyitu>Uf?9 zul&63HoX%`nu5uDjw6G)dT&~sxL*#vhQcCbr!<@Jk$Ic_)X_rawcCUNoh={_;um48tnUp#p)saA8zD-9(Q-1$bhNod8NoKvjF z)f81;-X7);j7TuWm`}6uZY_Ws$~9!@K)0+(lK>)|TSm3h4i@oO=*3&r{^D9vONXoy zMo-F36%o+WXw^vY^$O8;{v*?IYc7UPkHk;}3(K;GNmy{85gwB`<{xzC>T&k@^iPc+ zoL6(8o5T5=3QtL5p_ik}E$d)8uE{sf+Ce!yB(_SXC>W!`1*I<07)VE&d+}Lz5NCn2 z3e}chg-!c~|Aa9=c2un7JX6aK3m%|m*ZEc7&{PIpg)yXkxFLYB$OarOo7^5ePk=AF zW?_zQhr(LQAnSI%8bHJps(`T3k%L3;h0dQU2;K-J%`-oDY&eB8@CNVwz1zl3eZJ zVUMHLBeL`yG=P#`hDcqTmu*wQu8rA`(uddO^X0p3TcEri^Im`0Wj51Z2z!7eHJW5) z5OLy=A_ke3&r$pPh78tC%t^?<(P8mrYpv_aLSd6YvC9=iza&*Vve>uE=TuTbIj7>X zx(Vq>GBKkErvq1fM~21#n-a;Z!52k3`*+VQ&{fIn{92R{rn&7j-o+P9h72;J35FDM z7^BFx0M46&QAf;#@K=h}aJk4gV8Tg)P>c!RH;cRryRBvO!5P0cx4$B;!;N;kA2I#T z$`<rfa+jz!A7jSb(gwXQ(|4tJ3mhV}G*)|r0UXbC!qk#^ zMEk-OICFncBuE_oxKhxx{k=!=t(_RN4AdFl#rKyHp{laDgGVtZ4>$Tt{)9aIFDY&A zJEA|t6K7iea_PwCT9S(KlOjI2PAz49;d*JIC^D3020)9l;e&!l~Q$mz&Ctr#=fHrbc zKtR}Oj3HEy#rM4+Ft0YL@jk?JL)YJPJgL6B5%bv|ne@&DDOk$%jlXH^k(~20dF6#3 z)Z4!+yZB>w*htxPS|L^(BM-@1k(6zA`vn>8kA{sN2eP{{r}1rdj(W%zLdG-%@Uk<& zpvkB{?T_!fuo6a?$D(lu{n-quLl4SV=75z2dU1r=J!q8iy zS#59LqT&N#iZfC>KwFWt@3{40<7*nU(A)sNWhzW6P{t}CQbJG;knr<#e`9iFfk31=$g&|0XchrbCUk3mdbJ#yx5W(Q$ z=TwAbT^2+v^Yn($37g>Ikui2pxYlT=9RBeyO}48EgmmHE(FC!V5fTF}T22ooe_&+Q zA5l)Jy>7Hgr6fznQNJX|Cvnv0sO6$hQ~Fh1ZR(06%I z4UcNkui0VF+%#<`(Fb-eY>OzW(k0KB8w2!pTfX{ixX}1_{?Y0l*t2^C--2uPmwiZ{ zSxf^hsh8PT?AxcjPENxv<>M+|I#mQ&3u;BFBjeBTqXVDnX17ot;mlig;V)+? zvRo-AWWai^AN4=pKCie?rI|Euyn+hk`eF!*MTXyh(rTa@wF@3|Ux?)YrNHttOH>;L zYuCzTeOyo@3niyhRu1vLG+wmQq`LbULpyU>Se9j+$}OYkK&(B5F@s*$w(rVs-+c#t z4_=p#Q{1xkJZOcc=S8~cXX-sSgD7|5`>)t+CQUPeV{B(kPE=wcg!Ql;;trBvv(f=q zj5hUI21EuS8@5PR*bxq4Kg;$ZZCDI(zf}S@M|xoULeAK6b7ULRMO@Px#CA2GL%`bH zpewPU-2A-lLEizXs+4-C+->^&XR_*n4wOxyIizwyINNz^Vl&}J_TVjdGca0a^VpD^ z0F+>K93NvBO)=i~I90w9RPa|SH$jK6m1Tc%F4idlpFwnUU}=IFiEh)Q1S+0umPz9` z_WjXZPfm~)w!RQaFrZxtk&IUUJ~QntI!KoB9GBL@vK( zs#b@o*Ok`D?z_Zs^nH)f%(G8@^Ip5hnwNn$m%9BWkS&zw(huhQI)<`&f*vKCG>LCB zZ&9;_<*O&}#nF$g-ho!bNSLFtz@yz1lBJ=Q3bg8@ys57*{Lgg^)XH1%?RxwYE#RxHfWEajF_4XBTL_L&U2Rm;5z}XW^B47n#YK#E_--wzR7A!1={ZXUemL`` zM6=4yS8!HAz|9~>XT!zJ=S`$E?t{~$b#!OdA>%v34{FMd;Fab>;%ql?%SgxGt?RK; zAXHaIZSELUZQGBQR^v#BbHtfj4N{Kc3ox^~h;%^?Cd#cao3NV)$f)-l=4~5wJkk#9 zK*GNn#Jq1G_d|71lRnyFVo?r68*5@UjVU=U@80B%g1otaz_zT&<{E;QC!l^7KRG7( z^i6PiB2y<@fH;Os9Kz#oGxTOi2YCW$dE21K;j`dk_kw;|>>30!<2-GP(<9muN9?Or5 z;|VVnsxwJG|J(&pm+xPJ^;pKCZp)Ueo0wji4%INn(A?1W4()Yz$;o5;!L6W}x7-6} zQTpFU+~a!4Vqu0hL-B4Z2(9TT=&Xo(ErzgMN@$LhukeR4atldW-(=fMzalC++-F&D zbG-6x{Kb89vM)s+p9}T&aM=s85(a^~|C1NHeZ5|f%BcV;18S6`A}iyEWKlalFYD&Y zLZWOQRlHXNmoVa#?V*zhQy(DPgJgyLlWLW{ztNmgOe?p76#A3uwt5dxJ0|0uZHXn( zU$`O`Kk?-mEIwd<4-gnIl$u%G8(lEMU;zBw3Y@x~v*@t-+WTIFQu)GfQu6EF8Epx( zb@;$>%NRgIg$xuk5WQ!P>UKq@5Cr>^Vlk@NbrMr={zkURWKg~QOoTvqB-6Q6^Ie76 zzV|5CfvS#-`K(_o@&gG5S3wk30Gb@DMiJY0y9h%rez2{Kco#2EQ-Wuty^QuQ(X*74 znVtk3K4MEYsxwcICgweTjH6rAh0^Gk zT(mt`+H_1hEt{q;SwaZ$AW2Lee1bNm4o>nsUC#1C^BeivwZL zwFK{64_eqF{l}`V1DOvSx_-PaOH`-Ta!=mNKG8PTaJy8QUizSY6EQ%*q+mr#6=`V3@IdTF>b9 zGR`eN$Dk9GWQEQvazv3LG4s7P@6aXbM2nT|fD9d#!br~_{J>K7Kt_rQF~)j*#|L5Ckgl zU`JoyZ1-cA{my6D>K0kA_ecM%SLE33QTRY7^9}pb6|ooRWzzilr;P(qS-4in2Svgw z3W>7Nq%AI4;QvH}QV2$G=MpzKmFQ z%KAaFOIvWzKDSNU;brDJLG6G>4x=sN)-J^Xg-LLkhwWtwH+PuJmUZK{KsVJ0j}vr& zSlv@@UuaFa@OtO)q>{i%!j5T>ue57rn@leMixGfD#z}{M9{W6|8FHJ1Pa^l&f?+m_ zyt)~lX~lG8zzdx`tI>yYnX>Vw#744RWxflQ(ft`7*-E3RuUD&=|D71sfvqQ9TnNMK`p7Q{%~;TTc~B z&tTdVoMn#kOnrZ_!wcLv#cjFD<)KBU)R)n6H%Nj`f$Oeo^TutBoaLCcPos`WHxp>h z{bdgM@O}<%0zbhn0dQ&;$B_O?0?Nc2j+F00a?zm_qfz;Gzv27V?7<@1t_H0mb^moa zKH+BvQc>~H7n3B*wyFUJc+}ro`?#`IV6%^F*C&{HB>`QR;mo<1oXWp-yAdi&plcIz z8QlI1owBFUr0JwmyEZ-w{pa|kuyb*2M#w(Bl_QJOLe*HH((#_L5GjtswVo_nX?^{I zGVK6sDRCz!xPSElr?!#KAmQE%OB$~Ae?Wrkol~FZLidlmVq9prr#;9CUth8?9i8-u z-d{NM8MKd2S~f4Jnne~MPU55-($>XJiCV4C<%Gkr641CnhD@6=%wvspdF3tc z1`Q2XoDXJcl=y#MhbRX0yUd;a*B$ID9Ivj#ny+a$XLYe#gk0thO`#|VN*7LGDK*Xy zM#8#+T$SL1bjGcZt4>m`c|7r%MgOjS?p+{`J`QmXgqWQNLqth8M-4kr_}0l-6~P>T zKW4me=?+7X4-k&Q)4hpduBvR+pzNp(JMx`|;V4Hb9TKsc!jmn8g_7~n8?<(1GmOk< zUEalSi684aHJVzcq0!^X>gr#I?AT&4+)B=n8mGm9l*I+wQ^0Hg@(tS_MT^gG zNhtZo_9$Lr;&jQNzhLPK{5h^>lJyu23qViXZw86#(nZJ@rQ%XW9`jLuevnOEcS%oY zTG!&Zn8OB?YJFkdf<8Ykxq>39COEN-vGk0gd0E@IlVOHQHOZa?9E;P6#3bAn`IHAG z^MIDwZTYz47yGdVL_5iWxJjBxoewjoHlf6FPaCUN1I#0*T=-==`a~z5GR_N<+abKi zBmqRPNFUq2ZEYAdAI~4c`Iu5N>NI3LpWuF0IHDLt0Fh1b&&P8|>5Rn4cVVT|OTgrA zZWA@MIgK0ej1eqJ;=wp;gt7NWZd@pEOIp1rRIgBfTu3e$pd97`hT)8Sv z{+t-VLU1@{;E11cPX+qY`ypIT6pP4oN#&u+GK9GZ#pRsTA=E=ILsX`w!)aA$6F zn0Q}U|Bt`C@sI`?M@5Hyfr}xyqV^TWVh`B*IenUZ_h0iiQO3}POX20JG;-yNs06B0 zc9ym<*$}D;6iEjd++)4+1mdFYOBW))Do&_|2TyNpYOdk<{MWy9G~9*=Y^u*iVZ_V? z(6@l`^?td1x(P%;`@M?; z?dMe;^g|ot+(NX~Q@)3X>sKB!ZcY{2egeK}5$T|!>bIIS<#^#IV7rtf@dp#DUiMhz z&zb5026UpavK_dcHc;f4h7^=i>|*DccNJk#3D?Ty&CPk-Ssvhg-xZFa>ui<3Prp3I zW)NDszg`{cZVUzx@n}3jy(8ND9YR&ABrDOU9z#|0flAZ&Ohu$Q+*%@YIQs(scq zB;Ly7VX?hRt=fuo?4Jt-_R>wGAb^xzK<%}#Qh+-8I5(ex_uVVWCYGS@QADqZp@&Fk zg@vZb!}mqRiex~5%l0%0*87Ra1@YW5bgU13)N(=4#e@$kcCVux|1v4reayon`d<|t zfK7a@+}9W3=(VdBw!@dE>CU+-MM*{x8BDSyM8~bpQjWfPK_ha+|6Vy}A0ayv=qdkJ~75RqM0YfEfy&diu=;vD- zDLTh_*QZHM4cmsh2pX4x^AXK#y7?WbPNmbvS%a&X>AT?2{yRXQt7S_?6hne%wHAHL z2;ZAvr=3IkqQpPvx1i%dl@E6Z?9&kpumzB~k&!MzeeZqOb%f4n6pu9sKB|jw$r2=> zxk5hbB{5lD`YI3O7Ap(RT)RCzc!BaQ#%@3_7p|+0ydRBgrm#FH1{8$;F7xz2&DOD&xV-0hi+0zQ~?kT!u;-b_p zDH41Eh^9eej#{LlsBG=6qfx7p!ta`FCCy6PK*oXATlh7WsCkaEE_Z(Rf?2=D=F8F{ z?ItW{*o0>Cnomu#IIKVSjZ9$Y?kZHz%kPI=3t40?uV#;zp<4((NZSk2+z_(~Svj)g z!Y-ra-=nQM8Bq42=M^O7Xx}uOM~k0WP>6LCSR{j_ZIFqHFP*VCobIW|HR-J(1mDtB zY1_{6M5h&1kf9`Dlq0-k>bi7W(yFi1+u-Xfg#$9zoq2P8$E50wtLSgY(NVb}NHsC& z^^f4n-r#rF`JR3(bSn$eue$F?W8@Qa0!uTi)*=1J3KfL+mT6g-z6D)xT<}HIflMw$ zc>D!r7ukHZh&HCWF%WzKl1olAc?MXj##U??+uZwsc(H%M65)RD9(Aw%Qo(%kc~?2{3f`a1$(2(P(rQ69Bk z@_V3q;_nTtk^j}^P<})ZF88~MOpK4o(r444x<;S0T9+WKRKH?~`2b)95F5QQ`jKu^3kI z9OjFVje7V_P6Si$D;Z+Z$a{em?5Od*&D^4is&$N?j69Gvg?f3`i}>qXsyjBIwoT*O zkX?YW_c(%b;yf-Y@oap9xUuEn%r}GB4+~ow?K-l;TvVT>v2=WQQqLu2Cc7SCJV}1( zxq&L}O zS{cFaq_3YVwaBtOgfjIDQ)|QVJOtIAYUF38-@mC}v1yXr!~UCv`AwlO9o=Zx{Cs1| zds|>a$;ZaNyR26M)Eg5K>r_*bH3At0dOZ(*V9g>VUQ3bDM24zxl?}jBN$~AMy$-^U z$aj5Lt3Lx7@aj&q%a$YkT+@-pE&?#p`n`x7PS`mMH31Mivz$bhz5@tDoQQHDK zBz!Hsrp^O=dLG6F7C;@q%G6>6io+rVPf5QJu`uU`(ex%~y(1VtLGaKgG%jrrAr7`$ zp4({VPa)o@3jI;`Tp}iVxWWw|O1xIhxwbT3Ujm7)Tv5BY29@4NgHS5lU|hw1&ADrL zD9^Eqw336HcP-zZSzok{=LH+B(+PZfgN3J1oGUqdu?7?mMa*&8R*1HIS-IWn zo&PFAJ>T_&qD3o6MXyEj^V9QO&R)>A*Fa)!%eP(QXHh|xMGHiWYuLYzU7qixp|U^f z)5T%oeUpQSmST}ioc9QiJtc?eRz>vn3+ZQYi%8}RuQ})DPBEn63wR(K)}?#3L7zsQ zH4$D2$#6!n#BGuItZDl5)vev#mhau1k!_J)M4G;_^AakS7-HJ+v9V6`4c7t@o6g<&$OYf2_O4R{PxPP_0@7moevh z3P+4LoYFrr13iih&#<6tZ_)asJ-onQDA=mDcgcGf=29(L12w|w;-%m;c)LGe`UR8a zC7E@86Fr)WFdQqM@EVEI9bv(DJ}xdf3ENgXC$rg8d07!NOZub|kl&+h&x?0y;xF@l zTbd?utzpV=SbOd_qf7o!hsCGf20$yZu%);fy8f;~c|2qbR*|h*H}HB{Aw-}xs{7LB zxS2z_gndDpS@R%BoOqul7>w71#1JZFN8>jZeDs6`EN-C=UoxhQ9I;XXVkV%8Pm zO*KgLgUtSU$CE6K&ys@wwFkl+Duybs!`P;gN!|JHMZL~+g8pblJF!<5Z);??72HcP z7HPSVtWjV2|B-erTCU_a&ZH>G`Trk|MS>5hWZ%xoWM{LD=Eo^#Rx^UAzErP|{63=lW6ifGb zh`m0IS{U&Pj9Gc5_Fs63j}i~U@5q|6nYHl^PpZ2l&h7u#03Q&e>&&oh#H>gq#13j{Q9muf$m4zDNm9g@>J|I>7bZNjXybbdadltk-xNwf)c~xU zoZR|Jf|7KV7fTG%c!xTIxooEq*6S&HXrXFmRBjb_zCde&PEsnN`eMG6BT%nKgFfsB zGFICZgAS{0Mzw+%VMToPA;9IjZg*aX>v156QZ9|`j@ZiKjBw%JdnJi%g&Zbe{2ahY zgnUCJT!Q`Wjgh%nM<($*!lI$+hgzMkW^01yw%a}5DVTaU-bSI&Z z4l<~^o%5;fKp;;xp5gLd2)JacmQqNZ>+6KfU&!GVgBqfu6ZX*fF(0p8Jbyx;r?=q6 z*%-8pF8Qf>>8ek`Dsi!9j3e5-e@*}7;;jQel&=m%#k0hwM|r37aInBe+lUgoXxuhZ z_Sbj3`40!OLZhVok2`^;#4YE@-f*%qcD*wH#-hmT44+G`AVdj{EdUErZxdHChVy`H z-Y8kl*CgYYqS7|OT75hxA$Q`5gaK#VQ4q} zY(+*WD-%Us<^4}c=WJT)R$$OcD!6iP7?;IWVw%H1P{ub}*4wNOtRK__q63qeT49`1 ztTfAj(>VwSmzyOmgyV)MlPydU-y{B)PTjKv$uTI-TesmcIAb7V?@U}jnrdhmkSWAc zh-qY@&1u_3=`T83unG7dQ)=Hc z#=M*1Y^z6=c6tKs}aLzk#vCeY(DP>2JzoKObJ7mrZ)u;Bz z;R|1Qh}(KVCZH~R600XNwxXjpvE15qogj@ijfOTr_bGVs=0n(16r{<+8E_wAhySo1UkMi-l@FK*3$_J61V%J+au_SrcUD zO^M9!B3IGMQYuuKOt#u%)RlUn+kP1)QYK<=4tnBj2Ds0(t)!bFubkiT1TfX^4%Vrf z4A6+!i!zmAWY)-d3tIRRm_7JfpvteA`M65gco?>FORMQUjl^a-BBN$^ddAqQii>XBN=onbUuM3y7TB zh@}y+&-1oC&JF%o=A&rziBVO0Jv(~eJdRZ-wfvz(uEv|s2koV6B#{|sc8F9i4GumV zv#Z`8Q9r0)uhY5vfkMwb3ZF#r*CRxxm0#+TS}LQ{vUG zW0^j}L2vN;wD45X>74-o9AMPKega>Di(RCF&<+N{F*i+IU|4MnSU?2$o0{dX4oR&sTVJ) zEP;8+^k;{(**89bAc6iv@9zK6Q|psnZvO65l5 zgBqWMmzEa5aUjpr#zdz%S&K~ySfKec+#p<^S*zq^dF}_F-MipGzGy9FpW>Hl9!#Tz zTm~i&v7XB{zw2~+(Fr8pCG(CYh8TZ&#PcM8c?uz`lMVZbEONlhy1X1_FyYpxODAh{ zQM&gE_O#SWWfmfgKT+74UU1z=PG0>iDd1Mna*`MxQZ03)T^qD-0Qne)PEY~6dVawz zIaOSb@$2aArA0N{ra91)(c~3ev$Yk+y^43hu7S8dP*g;a%U^SPwgdT8 zVe2Q?!qu#ixA)T8#a!SX5N)1#rjQD|NRFB27BJ-2pq=auamOXNB-8GTTY<@^fsWyN z*r@k@&~FFdMaBHRVpoObZK=85RD`26;&!D$H`+tP2RUC_Yg;QKY1nuKhoJZ}Mv zQA^~@H`0t#zG77NYcLc-z>z8WbL@xo~S0SHYzyd3Y~% z_;4WB*N^zSY$CmRM<2^-VN{&M*To!pzz&Gd8s4s^{@8;?nT4%N?!Rboh61_|WvfA} z9jR_ZOFKBKLl5Lr_OA3XKMz+o=EA0Un@%_aY0GqXS8Qt+K<6zMx`lX>PMwmpAQO0;gw z63b-9OV!uub|B0F(X0b`slv>sqhGv8y*8)OD+c-@i1_UlvD!WDxA{lelwaL3ft2VP zSDC9NLo?0qb;q7gXrO*sxyil{+z+IWZaj=G5jC~$P2r&)0uDJW{>mL9efnBr_1xS> z639}cpW`3#1JNupmOUa5hz-qWk?(&O7sxx`FEF;x|M}d&t__{52jrbd`pa`Pe=2#_ z2H*Ik^5`dTDV?zaZC)tWl-jEhh1TLcS5gcJ4bnI6-ea&K`4i2on-=6UeZ3vZQt!)K zW8SlRATl+JEWtXxE36hv<2mc!{4amd1N1PgMf45qGN>FU)!dmd?(dq4W9`8#8<*y` zq>b_ouWMaarI}-f(LZz%z}5(Tf1EC5miJ!?hCIe)x>=3bYg>=8#?#pO&u`ggdgY1? z9LUlPTn92`!6#Fd1dTw9K9ciigE9iEZTcQXWm}2WplurP>q$H_4?;N(F;{u_>(Ils zPgBTwBAvHy_>^if$I#OIX@#eW%Yv2rZPBC5^1#43b=r!aRRLzk@_;%Yc}mb*t)`XAToJ9vQ=}Lb*;5k$eey4R3!C~RaFz0R3+Kv z^9-3>8wRIjgvJ1<^Xq`dcOHz9q>tURPA@8Q01S(w8ulMbJ=J$rYX;d#16A@T3=#7f z%1}xgu9oB~Z(1D#VCTZgWd8z3~)V8}`mQ38dU;m3QD&GEy&PDurDNJafKcWNN;Qb;jNFyJKs{T zNr9Q@4CXfsvXqveDsAkQFOZXAIpOlp(>@zH2tVOqCS)k7m2^_9kbWA@sJl_Bjz4 z@9}G!@3@D(rzIeHui8fjrG1hem6JarQ4&S44 zvHW;Dy?$$KS&8MC;n)fyLAS2L{HsPVrzkGXdMp}RpiPARAFHMK0$TZXkH!t}-H7G5 z0=j_^*vIJBhO7&+U>;MUeF)RLLu$~Uem{h&TeuhyW!Ky0^1g41!Ss8$p(M>>uO8If z-cCx7q%zBomgtpRV5YhuCl;f50I?NQHLP;tQ-SO zc0=K*WMc?I3;IH@dROz`lR|tXb>?z&PF?~l;gruQwv0+zA$%!cG-|$BN6n>?og;6b z*JZ3{ja~&T*-)bWR)D`1&vb;*keIm)*~gVAd_T9&bf5Zl-o4NJw1U%Kw*j`~na`xV z6|i?8mP;t5%6BtTtIV*{yTpJ$f0NFq#L{}s-z0ZRnQK0;BxO>qebqePYAed+#$al& zGy3|X)(&HlGTkEq|)r%}yHbCa0YE8I93OE0T{p06!>tS!&-QFElWjvTf&-~iDZk}03R zjWe^WT8HN8zqUyN%84t|{d!Dy0Pp3RAOXfKRTDUCnKkBAnP#*hQAf?xi#+%DhuQEQ zL)QnT2T!z1SSk?hnV8I2nJuR|9nDpt=acPL1G_leu3Xr0o`x5Aflp?CqA6W_9EhUX z%1p^Ni96FjMV{N{8PvDy!Pb8CvVEm5ra&%oDQUi-Jo_oMI%$5ldlUb-lsOkam4dF6 zoW~$E)J_$a zDUdX8%s+U%|JCzk_vG~WFbIuhQ7S1}e|>WyQJ(6g^a<5%sQ$3|gx6eA-GFyb?4O1B z!LlOYlGq~d;qCxoh!tE_RcEdn0=Ay%u8uOgyY!&t4bB(27ZJGVhpT{(llf2G<9mu)_b@kkqkxVO(jF7azK621!dy$r6RJD$A zEOVA!wy|qojviiTbwnpYV_|oAW@8DeHpH7=fBkC@s9p_Nc{&|QWCSTwYP2NM_B0AZ zD^feYPlKaR`+nmdkx!2??4kS0Q*4ttSvU1o;W_o*j;9n>JvU-z(CSeIj z0w%(135>vAh>`BbWr8D4!&0!>Mts!WJgWWOZ@Colk?4!fkgv1jdUz7y-4Jt4$yVjt zj3|d5o%unjmx^o#N=*xC2)3Ax->o|1y|jvV zmX^y9)m}$iHP8*_opkJVsV==nn3lgDNj{#uA^U+~Bslu0UC1Xe*wHLw&;^#b;er_3 zWX4^Z?$AmVfA-1-j`2TS%Im&`NzV<8BUy{v60)`fVbTMkLf8AQKb`F~1`UN{Nl~r0 zFwdi81X)g(!m=|-_mH!1p(VzD$|CaPK$Idfk|Eq5R691Byc)i#g{Tdy!Wl4es@Dx| zzdOdu&WBbCO{!Vw4x{xVG)6DQfo)40_vP~+)+Cur_bBflFJ?3;PH+w4r%PD*P68pF zNaXYpNN9k4tKN2|c}ptC%@KI^EsExW>?fn0y&nP=^|v+ueCHZOfHlQ-^E|Fz=H;Qw z@AS6lnK~0~eX~UAzGH|dWRJPw-{U)IiN4t0hNUThGYtNH-Cc2aQpJcuM{KO(=2)%7 z*XDrHd#eMBJcVm7eC`@xio&5kU%&o|Et`eRl>5oJpfAI8XyjITu_MOhU2WRcj1L?l zp4W*e{gTX4ITSS>egPoLI!i*aRHXuJwUo51<)TT(T4k|uEY@h>f|~rBQ1Qxy=K?&K zw~!_1@lG8o1z{cz(doSf8A$~Jt*eQsJC7$o{TyWXaW(}#6k1^1tBHnVt2|V!tk9cg z;yNv6Sq-in>Uf#>AkcUlvU0UTBFX)7WO`967I4!Z z2l6Lyxw-kMT|*$Grr{FXDsFi}hXJ-bYQ0i-NlBD3&mSAnC5WsWQ9f8_ZG)TK_(pa! zsYg?_LL@cx29PlCuev&CfPdNLNM=(}@CA2dDzAJ1v)ja4vH2fE@K2?9+$rLPUfrN zv!6q%a|RPUHmK}$`0|~!gs|0~RFbzM?Iz?Uv;!9&E?q_X8sx)T**BXkNp~b`)LA7t z`GF8modifL5|<}q0m-mPi1>=04T$x~Z6a(9A4;hMq;sK7_GCQLSETh4=K|IGQr~!C z+WQ7KxiyCJ*3-D;p%U{ptt=|$p3s>odqA|l=3Ko&eB!=tzLB2NPzDynt3jvU`3VXnw9g@>G_kEcUZnb6xUMW|iE>XA?wzp^f+RR646|0* z!xKSej?H)tqZyUIQ4ItC=ML)4DP_LtoZ&#MVr(|vG2NDNpKnyFk1EV1FyO#O6$kEc zj|dc2jzL+AV_F}u2dowzGIA6~=un0UMTD%3!(AO?q&_X$zPWY3G1N>RKjJ7) zn+#gOl5J@gDSyx)bNfXjv2o3x9wVI|2eL4iVGx}Jo<6Q1Ap#Q0iW0M%FRhnNP_KZ` zHAEkZ1`XIfaPYsl#p%q~c_8@1dVpN|xa6oR&bU^*}1^Y?fa=I_Hom>XX zbLfaip})xSw)6%?kofnW*T$ z$&6l6yD*pTJFpM#T_v=!??#xagr@j)5%;3~iZU`~QrG#b*+LnYCUli3E06KWhHcb~ z7?mr(AwJe5_$sMqpzd9fVUy3tc`SM!Jp@5FjF5FkTY44;+F4vc@hY4EU+dYcjH>Z& z2fPW|0}bY~OlOK;1DX`F@LUYS8qgdv6+(^tP?hj87KH2M9Lha;P`2YBfB>{h|D+@F zwxk7)&NU4Y<*{UBY(?~U=4jH1b~iA7`Y5)#sF1H9*eCzK2{&Cue!8@OX5gJ~vxN9P zuNPAfgZPVk$N97N0i6at&>3icwIm|V-vEO0Avf%x_B@cWAIM#Qnu2Ykf9SAoSCozk z<45Ef`XU-cC;zX|Q+i8#Es3?QW34g6l&jMXe+JYNMfoaB6!?HwyZAj(Dh=1AI$Z_d zNz8J^*Pz>b2kqO&5(B#lL{X1s#OUc@_cF4$>Zpk|Cfkb8m!QglFfd`}woq)PV}dY@ zOY}bA*xgR83NBH$oHzyBV0S2W(K53qovkhvf!;C@v?BO?-8oC5OpM7A4V)M>%{v*c zETwsKPh2$BKR@U$&k{~~3e9+h?OZ(NzaN5J-q6K?$VU7AG8duP(>6@bbchu^HZqZb z5Y(%gdO2bDci{cqiz7knqnBjlZrn1CV#=&OHp52?!}g|vp!IDw-?+>>!5|ET(l$_j zRJ<)_;63*a=O}>B03i*&H{KB4(5x(0elM>_Y5uZAc_8=QjC{XMC>VzFeI8*yZMS41;}q2ldzBz3VaIX8AV2cMLQ7ZQ1UMZYwo*D241EL zYzx=K0o&CIWlY+uDHrW(KWG}=9fVAB;*!j)%6Ef^B4GS_?AxBB;QVLOmF3npnN)Fe z=^U=5CwxsnZsC5GKoEC!{8WjdKxsPCwYAo7N;Zi3KxntlE`LsCANcx>MmR-coNmM0 zX#-&l#HfNIa|c}ldzrsTy8!Rmr5TSSflF!Mu16TW_t*E36KfZ(yF5)lr-K!7-|pZ;pNRw?lt1_eAh0)4b6MSyaI(p`6d@GQwV-hE?r9)(d!1v z`XA$ke&Y%DbU6SclIU4O*cvdVm9ZQ}z;uhZ#=xA%J&}Ct)ZXk-5TJSUE+D_PZ0Fs; z=^qX9h|DPpUsgHrX2yAZt$|!d)8BW0hPqZdFb$CWEDkB>##bm;9AQW(%WEnHc`TTS zIlO;Q>MfMJB*eEp^FD68v|^@UahLv36%--AxY*ViVm6~+YW&C1dg9WPTD@{L?qMvS5nh0M)p$ zrvky59PJ1BimkWvOWCLD?I6+2T>YX~B|r&8F~mQYkH6QZP17n?1-^p?k57S#8O;!u zNb3FjpK1X4-C8Du40A}}er5SqtWEWG7&pu0+QHQ@e!ENMpG>NrZ~rGiCD$%tVlrxA zA|tR{wvP3<7Auo(oxtLEWk5sy%YGTG-Pdco8mYzSCwSoy1i=E=;YAn=HRt4FQvbs) zVZ{h$-6S>1?zVL_U((T9WwH#QoG^C;<6~ikzHC3NMQq-3hVl zh~AP&*OqUe1mX#^OzhN+p59tE>K`Zf#0Gs!@qe91GX-g|H&W@qMKz*)jAtP8Z>t7d zf-KdmimiIFB&U-2{lN~vQ(_nh%R^OV)2LL@dze9V6Soa0F2#2cv#d3Q!^sWppIooV zTKEOw#HCXo$2ZEpmkR1i&Ki_Wi9JSwp2yJ8v$*5E*@Z_Q0omLTzD+nk&_J612<^wr zI$JEdm7z_3%n1IMzt(FqP0L7;l1)SBb3pIDb96_Lxvl5gX+)Q7XcEu$ig2tcouwZa z8#g1NkHR$ZSA{UUmuXf$(=YsjutoYBYY|Nkp) z(YO~G&bF9Y3L%34CiMUES}?NbS=O^q!RPP-JO}l@`Vf zlZKAw^w^?$*5S2Gt^W^i@?%taIS~(B{W`Ky3}kJcL9LjG|DOZ%I-p_pj(NQH#F&_m z>-=c_Lz?ESThoX|n$+h!tnln*j^*5rHD%Pb9OMZPtY*zirgYWu8GCGYqzkF!OBZD8 zug5{emq+yk{vRjerSaoHhKW0}Y=5b;+FuT3R7cQKIQ3+aO^VqsiPNj5+=uduhh=K1 zE45p@*0k=`ceBB6=rhEC}*}Omwd=w3pBZ z{S&m-#@0F(ZkyK6F-N7KXkgnp2BsrQb2f#zp;ts!$TB1aRcXC^YG1O{&25bEbx$?+ z4o+L1TfK7XG^M&|*UzQ+fiw`SKm-J{vv!jgOoQeSke zNBeRhH$o2JV11MC|2J{bSNI0Y=62*y~iXU-@ZGD13|`Phwe(zTZlK z?TUP_qAB*U1(Y5I&_^Lg_z6o*BI+S9yp+`BAY*RH6(fkKx8$`&fNFE(sxV%+n`>m*csZT0qR^sR8q zaaM1+JB~{u)tw{1D$Os7C8Z7s+LtSW$50P3OWNz{R{Sb8+UL(%xnup{+f?dzxsLr? zIwDQ#%bBA!u^BUsI1TPoY(_92v(81M8vkzg=#eE#=kXn!+P2E({Y`9v3p`$xkXQEA z=P1;BU3(^uu~cY%h|eY&+YW$h7fd*OKe5Y?$#i#$537vFMBQ~6{D)ggFED8{kW}xx zwjdg>);Y}2tF#bPx;^-UIx(SLem#e_mnDbt&++^V2z(>v$6O&1T{9%JfTdxFdd&T1 zecU*VW}43iNs^NgDb#}uZx8&6sA#^1)%txNuY<+Db`b*H&R$$_88WHPDX}p5} zA*#bojS*+Nt=9xGIw{=kcM0|VK=yWJD<+b{5QOcH+_IMDU*HkPoou2(@+J`gOz@w5PlyhQjWA9C-xphrD z5uII=lMyENH=S5oKRxZM!cAwn?WF>CF(kPd8|F>oZnhV& zVJiz${H}0?+^VMUs<`KNa$7QD#g8FmP;WYRraT^+4VFxx<{`Cax5@fyHM28R3z}{x zB5&M};bSLGHQM^=`(eGI-Jr_SvQdE`*K?l8k5`fr3I;r_yg|HP0cz2J+f}3RO^+-Q zRfVE}^e)27|0tlYweJ2Zd*@eNWEE|2Z&T~wxTXGh-^od|+Ky1jFhe_EaE6BinT9GP zIaVJ6kqlUkZ`&}(6V~CoN;w$AQ@OWtVFi2{x}}W!{EzYNU8P#I^PBN}?82K6t8LfO zsH~AR$Lz6)aoWJum^J=-AjgYEJ5|NrBaN#Q!V}>}?6M%$yZBPGi@!67c*E_%*IAew mjLGM_m|%IGjIj4#0R{j$>4OiKGe02!0000 { + const {url} = event; + const domainPrefix = this.domainUtil.getDomain(this.activeTabIndex).url; + if (linkIsInternal(domainPrefix, url) && url.match(skipImages) === null) { + event.preventDefault(); + return $webView.loadURL(url); + } + event.preventDefault(); + shell.openExternal(url); + }); + } + + registerIpcs() { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + + ipcRenderer.on('reload', () => { + activeWebview.reload(); + }); + + ipcRenderer.on('back', () => { + if (activeWebview.canGoBack()) { + activeWebview.goBack(); + } + }); + + ipcRenderer.on('forward', () => { + if (activeWebview.canGoForward()) { + activeWebview.goForward(); + } + }); + } } window.onload = () => { diff --git a/app/renderer/js/preload.js b/app/renderer/js/preload.js index 355bc41e..8318c1e6 100644 --- a/app/renderer/js/preload.js +++ b/app/renderer/js/preload.js @@ -11,9 +11,7 @@ process.once('loaded', () => { }); // eslint-disable-next-line import/no-unassigned-import -require('./domain'); -// eslint-disable-next-line import/no-unassigned-import -require('./tray.js'); +// require('./tray.js'); // Calling Tray.js in renderer process everytime app window loads // Handle zooming functionality diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 4272adb9..622f9d5c 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -13,6 +13,10 @@ class DomainUtil { return this.db.getData('/domains'); } + getDomain(index) { + return this.db.getData(`/domains[${index}]`); + } + addDomain(server) { server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; this.db.push("/domains[]", server, true); @@ -38,7 +42,13 @@ class DomainUtil { request(checkDomain, (error, response) => { if (!error && response.statusCode !== 404) { res(domain); - } else { + } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { + if (window.confirm(`Do you trust certificate from ${domain}?`)) { + res(domain); + } else { + rej('Untrusted Certificate.'); + } + } else { rej('Not a valid Zulip server'); } }); diff --git a/app/renderer/pref.html b/app/renderer/pref.html deleted file mode 100644 index 954c66e8..00000000 --- a/app/renderer/pref.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -
Close
-
-
- - -
-

-

- - - From ae3c595d824e2e5ab1b68ecc104335e6dafc5c69 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Fri, 28 Apr 2017 00:05:17 +0800 Subject: [PATCH 38/52] Integrate actions from menu and tray with webview. --- app/main/menu.js | 22 +++++++++-------- app/main/windowmanager.js | 5 ---- app/renderer/js/main.js | 48 +++++++++++++++++++++++++++++++++++--- app/renderer/js/preload.js | 45 +++++------------------------------ app/renderer/js/tray.js | 18 ++++++++++---- 5 files changed, 77 insertions(+), 61 deletions(-) diff --git a/app/main/menu.js b/app/main/menu.js index ddc81051..35710916 100644 --- a/app/main/menu.js +++ b/app/main/menu.js @@ -8,9 +8,8 @@ const app = electron.app; const BrowserWindow = electron.BrowserWindow; const shell = electron.shell; const appName = app.getName(); -// Const tray = require('./tray'); -const {addDomain, about} = require('./windowmanager'); +const {about} = require('./windowmanager'); function sendAction(action) { const win = BrowserWindow.getAllWindows()[0]; @@ -35,8 +34,7 @@ const viewSubmenu = [ label: 'Reload', click(item, focusedWindow) { if (focusedWindow) { - focusedWindow.reload(); - focusedWindow.webContents.send('destroytray'); + sendAction('reload'); } } }, @@ -136,10 +134,12 @@ const darwinTpl = [ type: 'separator' }, { - label: 'Change Zulip Server', + label: 'Manage Zulip Servers', accelerator: 'Cmd+,', - click() { - addDomain(); + click(item, focusedWindow) { + if (focusedWindow) { + sendAction('open-settings'); + } } }, { @@ -269,10 +269,12 @@ const otherTpl = [ type: 'separator' }, { - label: 'Change Zulip Server', + label: 'Manage Zulip Servers', accelerator: 'Ctrl+,', - click() { - addDomain(); + click(item, focusedWindow) { + if (focusedWindow) { + sendAction('open-settings'); + } } }, { diff --git a/app/main/windowmanager.js b/app/main/windowmanager.js index a730aa01..005d8a6f 100644 --- a/app/main/windowmanager.js +++ b/app/main/windowmanager.js @@ -89,11 +89,6 @@ ipc.on('trayabout', event => { } }); -ipc.on('traychangeserver', event => { - if (event) { - addDomain(); - } -}); module.exports = { addDomain, about diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index ede7ac58..469f564a 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -3,7 +3,7 @@ const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); const { linkIsInternal, skipImages } = require(path.resolve(('app/main/link-helper'))); -const { shell, ipcRenderer } = require('electron'); +const { shell, ipcRenderer, webFrame } = require('electron'); require(path.resolve(('app/renderer/js/tray.js'))); class ServerManagerView { @@ -18,6 +18,7 @@ class ServerManagerView { this.isLoading = false; this.settingsTabIndex = -1; this.activeTabIndex = -1; + this.zoomFactors = []; } init() { @@ -72,7 +73,8 @@ class ServerManagerView { this.$content.appendChild($webView); this.isLoading = true; $webView.addEventListener('dom-ready', this.endLoading.bind(this, index)); - this.registerListeners($webView); + this.registerListeners($webView); + this.zoomFactors[index] = 1; } startLoading(url, index) { @@ -165,23 +167,63 @@ class ServerManagerView { } registerIpcs() { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); ipcRenderer.on('reload', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); activeWebview.reload(); }); ipcRenderer.on('back', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); if (activeWebview.canGoBack()) { activeWebview.goBack(); } }); ipcRenderer.on('forward', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); if (activeWebview.canGoForward()) { activeWebview.goForward(); } }); + + // Handle zooming functionality + ipcRenderer.on('zoomIn', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] += 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); + + ipcRenderer.on('zoomOut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] -= 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); + + ipcRenderer.on('zoomActualSize', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] = 1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); + + ipcRenderer.on('log-out', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('logout()'); + }); + + ipcRenderer.on('shortcut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('shortcut()'); + }); + + ipcRenderer.on('open-settings', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + if (this.settingsTabIndex == -1) { + this.openSettings(); + } else { + this.activateTab(this.settingsTabIndex); + } + }); } } diff --git a/app/renderer/js/preload.js b/app/renderer/js/preload.js index 8318c1e6..2a9ce077 100644 --- a/app/renderer/js/preload.js +++ b/app/renderer/js/preload.js @@ -1,54 +1,21 @@ 'use strict'; const ipcRenderer = require('electron').ipcRenderer; -const {webFrame} = require('electron'); const {spellChecker} = require('./spellchecker'); -const _setImmediate = setImmediate; -const _clearImmediate = clearImmediate; process.once('loaded', () => { - global.setImmediate = _setImmediate; - global.clearImmediate = _clearImmediate; + global.logout = logout; + global.shortcut = shortcut; }); -// eslint-disable-next-line import/no-unassigned-import -// require('./tray.js'); -// Calling Tray.js in renderer process everytime app window loads - -// Handle zooming functionality -const zoomIn = () => { - webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1); -}; - -const zoomOut = () => { - webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1); -}; - -const zoomActualSize = () => { - webFrame.setZoomFactor(1); -}; - -// Get zooming actions from main process -ipcRenderer.on('zoomIn', () => { - zoomIn(); -}); - -ipcRenderer.on('zoomOut', () => { - zoomOut(); -}); - -ipcRenderer.on('zoomActualSize', () => { - zoomActualSize(); -}); - -ipcRenderer.on('log-out', () => { +const logout = () => { // Create the menu for the below document.querySelector('.dropdown-toggle').click(); const nodes = document.querySelectorAll('.dropdown-menu li:last-child a'); nodes[nodes.length - 1].click(); -}); +}; -ipcRenderer.on('shortcut', () => { +const shortcut = () => { // Create the menu for the below const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]'); // Additional check @@ -58,7 +25,7 @@ ipcRenderer.on('shortcut', () => { // Atleast click the dropdown document.querySelector('.dropdown-toggle').click(); } -}); +}; // To prevent failing this script on linux we need to load it after the document loaded document.addEventListener('DOMContentLoaded', () => { diff --git a/app/renderer/js/tray.js b/app/renderer/js/tray.js index 50311e63..d4391788 100644 --- a/app/renderer/js/tray.js +++ b/app/renderer/js/tray.js @@ -5,7 +5,7 @@ const electron = require('electron'); const {ipcRenderer, remote} = electron; -const {Tray, Menu, nativeImage} = remote; +const {Tray, Menu, nativeImage, BrowserWindow} = remote; const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray'); @@ -102,6 +102,16 @@ const renderNativeImage = function (arg) { }); }; +function sendAction(action) { + const win = BrowserWindow.getAllWindows()[0]; + + if (process.platform === 'darwin') { + win.restore(); + } + + win.webContents.send(action); +} + const createTray = function () { window.tray = new Tray(iconPath()); const contextMenu = Menu.buildFromTemplate([{ @@ -116,7 +126,7 @@ const createTray = function () { { label: 'Change Zulip server', click() { - ipcRenderer.send('traychangeserver'); + sendAction('open-settings'); } }, { @@ -125,8 +135,8 @@ const createTray = function () { { label: 'Reload', click() { - remote.getCurrentWindow().reload(); - window.tray.destroy(); + sendAction('reload'); + // window.tray.destroy(); } }, { From 0b17dbb014ff06f796d82f2b76cd40465398eec4 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Fri, 28 Apr 2017 22:41:36 +0800 Subject: [PATCH 39/52] Initialize domains on first use. --- app/renderer/js/utils/domain-util.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 622f9d5c..8a806f68 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -7,6 +7,9 @@ const request = require('request'); class DomainUtil { constructor() { this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); + if (!this.getDomains()) { + this.db.push("/domains", []); + } } getDomains() { From 42fedf2d73d45cbf4e907289e5afdb5cbe408f44 Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Fri, 28 Apr 2017 22:58:14 +0800 Subject: [PATCH 40/52] Add reload button to preference page. --- app/main/index.js | 4 ++++ app/renderer/js/preference.js | 7 +++++++ app/renderer/preference.html | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/app/main/index.js b/app/main/index.js index 6c4dd915..9f361b98 100644 --- a/app/main/index.js +++ b/app/main/index.js @@ -292,6 +292,10 @@ app.on('ready', () => { mainWindow.webContents.send('destroytray'); }); checkConnection(); + + ipc.on('reload-main', () =>{ + page.reload(); + }) }); app.on('will-quit', () => { diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js index 4e197233..7e0bca50 100644 --- a/app/renderer/js/preference.js +++ b/app/renderer/js/preference.js @@ -1,11 +1,13 @@ 'use strict'; +const { ipcRenderer } = require('electron'); const path = require("path"); const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); class PreferenceView { constructor() { this.$newServerButton = document.getElementById('new-server-action'); this.$saveServerButton = document.getElementById('save-server-action'); + this.$reloadServerButton = document.getElementById('reload-server-action'); this.$serverInfoContainer = document.querySelector('.server-info-container'); } @@ -64,6 +66,7 @@ class PreferenceView { this.domainUtil.removeDomain(index); this.initServers(); alert('Success. Reload to apply changes.') + this.$reloadServerButton.classList.remove('hidden'); }); } @@ -117,10 +120,14 @@ class PreferenceView { this.initServers(); alert('Success. Reload to apply changes.') + this.$reloadServerButton.classList.remove('hidden'); }, (errorMessage) => { alert(errorMessage); }); }); + this.$reloadServerButton.addEventListener('click', () => { + ipcRenderer.send('reload-main'); + }); } __insert_node(html) { let wrapper= document.createElement('div'); diff --git a/app/renderer/preference.html b/app/renderer/preference.html index 1a1f61e3..99ae71f1 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -29,6 +29,10 @@ check_box Verify & Save

+
From 0271ada591d51af236f88ec64a60b7d5c4691f6b Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sat, 29 Apr 2017 01:10:21 +0800 Subject: [PATCH 41/52] Fix linter warnings. --- app/main/index.js | 39 +-- app/renderer/index.html | 30 --- app/renderer/js/domain.js | 80 ------ app/renderer/js/main.js | 351 ++++++++++++++------------- app/renderer/js/pref.js | 69 ------ app/renderer/js/preference.js | 246 +++++++++---------- app/renderer/js/preload.js | 11 +- app/renderer/js/tray.js | 3 +- app/renderer/js/utils/domain-util.js | 88 +++---- app/renderer/preference.html | 2 +- package.json | 5 +- tests/index.js | 2 +- 12 files changed, 359 insertions(+), 567 deletions(-) delete mode 100644 app/renderer/index.html delete mode 100644 app/renderer/js/domain.js delete mode 100644 app/renderer/js/pref.js diff --git a/app/main/index.js b/app/main/index.js index 9f361b98..ab8ba88a 100644 --- a/app/main/index.js +++ b/app/main/index.js @@ -1,24 +1,16 @@ 'use strict'; const path = require('path'); -const fs = require('fs'); const os = require('os'); const electron = require('electron'); const {app} = require('electron'); const ipc = require('electron').ipcMain; const {dialog} = require('electron'); -const https = require('https'); -const http = require('http'); const electronLocalshortcut = require('electron-localshortcut'); const Configstore = require('electron-config'); -const JsonDB = require('node-json-db'); const isDev = require('electron-is-dev'); const appMenu = require('./menu'); -const {linkIsInternal, skipImages} = require('./link-helper'); const {appUpdater} = require('./autoupdater'); -const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); -const data = db.getData('/'); - // Adds debug features like hotkeys for triggering dev tools and reload require('electron-debug')(); @@ -45,7 +37,6 @@ const isUserAgent = 'ZulipElectron/' + app.getVersion() + ' ' + userOS(); // Prevent window being garbage collected let mainWindow; -let targetLink; // Load this url in main window const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html'); @@ -211,30 +202,6 @@ function createMainWindow() { return win; } -// TODO - fix certificate errors - -// app.commandLine.appendSwitch('ignore-certificate-errors', 'true'); - -// For self-signed certificate -ipc.on('certificate-err', (e, domain) => { - const detail = `URL: ${domain} \n Error: Self-Signed Certificate`; - dialog.showMessageBox(mainWindow, { - title: 'Certificate error', - message: `Do you trust certificate from ${domain}?`, - // eslint-disable-next-line object-shorthand - detail: detail, - type: 'warning', - buttons: ['Yes', 'No'], - cancelId: 1 - // eslint-disable-next-line object-shorthand - }, response => { - if (response === 0) { - // eslint-disable-next-line object-shorthand - db.push('/domain', domain); - mainWindow.loadURL(domain); - } - }); -}); // eslint-disable-next-line max-params app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { event.preventDefault(); @@ -275,7 +242,7 @@ app.on('ready', () => { electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => { page.send('forward'); }); - + page.on('dom-ready', () => { mainWindow.show(); }); @@ -293,9 +260,9 @@ app.on('ready', () => { }); checkConnection(); - ipc.on('reload-main', () =>{ + ipc.on('reload-main', () => { page.reload(); - }) + }); }); app.on('will-quit', () => { diff --git a/app/renderer/index.html b/app/renderer/index.html deleted file mode 100644 index 8c9c6adb..00000000 --- a/app/renderer/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - Login - Zulip - - - -
-
-
- -

Zulip Login

-
-
-
- - - -

-
-
-
-
- - diff --git a/app/renderer/js/domain.js b/app/renderer/js/domain.js deleted file mode 100644 index dd45b6d7..00000000 --- a/app/renderer/js/domain.js +++ /dev/null @@ -1,80 +0,0 @@ -const {app} = require('electron').remote; -const ipcRenderer = require('electron').ipcRenderer; -const JsonDB = require('node-json-db'); -const request = require('request'); - -const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); - -window.addDomain = function () { - const el = sel => { - return document.querySelector(sel); - }; - - const $el = { - error: el('#error'), - main: el('#main'), - section: el('section') - }; - - const event = sel => { - return { - on: (event, callback) => { - document.querySelector(sel).addEventListener(event, callback); - } - }; - }; - - const displayError = msg => { - $el.error.innerText = msg; - $el.error.classList.add('show'); - $el.section.classList.add('shake'); - }; - - let newDomain = document.getElementById('url').value; - newDomain = newDomain.replace(/^https?:\/\//, ''); - if (newDomain === '') { - displayError('Please input a valid URL.'); - } else { - el('#main').innerHTML = 'Checking...'; - if (newDomain.indexOf('localhost:') >= 0) { - const domain = 'http://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - document.getElementById('main').innerHTML = 'Connect'; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else { - $el.main.innerHTML = 'Connect'; - displayError('Not a valid Zulip local server'); - } - }); - // }); - } else { - const domain = 'https://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - $el.main.innerHTML = 'Connect'; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { - $el.main.innerHTML = 'Connect'; - ipcRenderer.send('certificate-err', domain); - } else { - $el.main.innerHTML = 'Connect'; - displayError('Not a valid Zulip server'); - } - }); - } - } - - event('#url').on('input', () => { - el('#error').classList.remove('show'); - }); - - event('section').on('animationend', function () { - this.classList.remove('shake'); - }); -}; diff --git a/app/renderer/js/main.js b/app/renderer/js/main.js index 469f564a..33e49a36 100644 --- a/app/renderer/js/main.js +++ b/app/renderer/js/main.js @@ -1,11 +1,13 @@ 'use strict'; -const path = require("path"); -const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); -const { linkIsInternal, skipImages } = require(path.resolve(('app/main/link-helper'))); -const { shell, ipcRenderer, webFrame } = require('electron'); +const path = require('path'); + require(path.resolve(('app/renderer/js/tray.js'))); +const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); +const {linkIsInternal, skipImages} = require(path.resolve(('app/main/link-helper'))); +const {shell, ipcRenderer} = require('electron'); + class ServerManagerView { constructor() { this.$tabsContainer = document.getElementById('tabs-container'); @@ -13,221 +15,220 @@ class ServerManagerView { const $actionsContainer = document.getElementById('actions-container'); this.$addServerButton = $actionsContainer.querySelector('#add-action'); this.$settingsButton = $actionsContainer.querySelector('#settings-action'); - this.$content = document.getElementById('content'); + this.$content = document.getElementById('content'); - this.isLoading = false; - this.settingsTabIndex = -1; - this.activeTabIndex = -1; - this.zoomFactors = []; + this.isLoading = false; + this.settingsTabIndex = -1; + this.activeTabIndex = -1; + this.zoomFactors = []; } init() { this.domainUtil = new DomainUtil(); this.initTabs(); - this.initActions(); - this.registerIpcs(); + this.initActions(); + this.registerIpcs(); } initTabs() { const servers = this.domainUtil.getDomains(); - if (servers.length) { - for (let server of servers) { - this.initTab(server); - } - - this.activateTab(0); - } else { - this.openSettings(); - } + if (servers.length > 0) { + for (const server of servers) { + this.initTab(server); + } + + this.activateTab(0); + } else { + this.openSettings(); + } } initTab(tab) { const { - alias, url, - icon + icon } = tab; - const tabTemplate = tab.template || ` -
-
-
`; - const $tab = this.__insert_node(tabTemplate); - const index = this.$tabsContainer.childNodes.length; - this.$tabsContainer.appendChild($tab); + const tabTemplate = tab.template || ` +
+
+
`; + const $tab = this.insertNode(tabTemplate); + const index = this.$tabsContainer.childNodes.length; + this.$tabsContainer.appendChild($tab); $tab.addEventListener('click', this.activateTab.bind(this, index)); } - initWebView(url, index, nodeIntegration = false) { - const webViewTemplate = ` - - - `; - const $webView = this.__insert_node(webViewTemplate); + initWebView(url, index, nodeIntegration = false) { + const webViewTemplate = ` + + + `; + const $webView = this.insertNode(webViewTemplate); this.$content.appendChild($webView); - this.isLoading = true; - $webView.addEventListener('dom-ready', this.endLoading.bind(this, index)); - this.registerListeners($webView); - this.zoomFactors[index] = 1; - } - - startLoading(url, index) { - const $activeWebView = document.getElementById(`webview-${this.activeTabIndex}`); - if ($activeWebView) { - $activeWebView.classList.add('disabled'); - } - const $webView = document.getElementById(`webview-${index}`); - if (!$webView) { - this.initWebView(url, index, this.settingsTabIndex == index); - } else { - $webView.classList.remove('disabled'); - } - } - - endLoading(index) { - const $webView = document.getElementById(`webview-${index}`); - this.isLoading = false; - $webView.classList.remove('loading'); - } - - initActions() { - this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); - this.$settingsButton.addEventListener('click', this.openSettings.bind(this)); + this.isLoading = true; + $webView.addEventListener('dom-ready', this.endLoading.bind(this, index)); + this.registerListeners($webView); + this.zoomFactors[index] = 1; } - openSettings() { - if (this.settingsTabIndex != -1) { - this.activateTab(this.settingsTabIndex); - return; - } - const url = 'file:///' + path.resolve(('app/renderer/preference.html')); + startLoading(url, index) { + const $activeWebView = document.getElementById(`webview-${this.activeTabIndex}`); + if ($activeWebView) { + $activeWebView.classList.add('disabled'); + } + const $webView = document.getElementById(`webview-${index}`); + if ($webView === null) { + this.initWebView(url, index, this.settingsTabIndex === index); + } else { + $webView.classList.remove('disabled'); + } + } - const settingsTabTemplate = ` -
-
- settings -
-
`; + endLoading(index) { + const $webView = document.getElementById(`webview-${index}`); + this.isLoading = false; + $webView.classList.remove('loading'); + } + + initActions() { + this.$addServerButton.addEventListener('click', this.openSettings.bind(this)); + this.$settingsButton.addEventListener('click', this.openSettings.bind(this)); + } + + openSettings() { + if (this.settingsTabIndex !== -1) { + this.activateTab(this.settingsTabIndex); + return; + } + const url = 'file:///' + path.resolve(('app/renderer/preference.html')); + + const settingsTabTemplate = ` +
+
+ settings +
+
`; this.initTab({ - alias: 'Settings', - url: url, - template: settingsTabTemplate - }); + alias: 'Settings', + url, + template: settingsTabTemplate + }); - this.settingsTabIndex = this.$tabsContainer.childNodes.length - 1; - this.activateTab(this.settingsTabIndex); - } + this.settingsTabIndex = this.$tabsContainer.childNodes.length - 1; + this.activateTab(this.settingsTabIndex); + } - activateTab(index) { - if (this.isLoading) return; + activateTab(index) { + if (this.isLoading) { + return; + } - if (this.activeTabIndex != -1) { - if (this.activeTabIndex == index) { - return; - } else { - this.__get_tab_at(this.activeTabIndex).classList.remove('active'); - } - } + if (this.activeTabIndex !== -1) { + if (this.activeTabIndex === index) { + return; + } else { + this.getTabAt(this.activeTabIndex).classList.remove('active'); + } + } - const $tab = this.__get_tab_at(index); + const $tab = this.getTabAt(index); $tab.classList.add('active'); - const domain = $tab.getAttribute('domain'); - this.startLoading(domain, index); - this.activeTabIndex = index; - } + const domain = $tab.getAttribute('domain'); + this.startLoading(domain, index); + this.activeTabIndex = index; + } - __insert_node(html) { - let wrapper= document.createElement('div'); - wrapper.innerHTML= html; - return wrapper.firstElementChild; - } + insertNode(html) { + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + return wrapper.firstElementChild; + } - __get_tab_at(index) { - return this.$tabsContainer.childNodes[index]; - } + getTabAt(index) { + return this.$tabsContainer.childNodes[index]; + } - registerListeners($webView) { - $webView.addEventListener('new-window', (event) => { - const {url} = event; - const domainPrefix = this.domainUtil.getDomain(this.activeTabIndex).url; - if (linkIsInternal(domainPrefix, url) && url.match(skipImages) === null) { - event.preventDefault(); - return $webView.loadURL(url); - } - event.preventDefault(); - shell.openExternal(url); - }); - } - - registerIpcs() { + registerListeners($webView) { + $webView.addEventListener('new-window', event => { + const {url} = event; + const domainPrefix = this.domainUtil.getDomain(this.activeTabIndex).url; + if (linkIsInternal(domainPrefix, url) && url.match(skipImages) === null) { + event.preventDefault(); + return $webView.loadURL(url); + } + event.preventDefault(); + shell.openExternal(url); + }); + } - ipcRenderer.on('reload', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - activeWebview.reload(); - }); + registerIpcs() { + ipcRenderer.on('reload', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.reload(); + }); - ipcRenderer.on('back', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - if (activeWebview.canGoBack()) { - activeWebview.goBack(); - } - }); + ipcRenderer.on('back', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + if (activeWebview.canGoBack()) { + activeWebview.goBack(); + } + }); - ipcRenderer.on('forward', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - if (activeWebview.canGoForward()) { - activeWebview.goForward(); - } - }); + ipcRenderer.on('forward', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + if (activeWebview.canGoForward()) { + activeWebview.goForward(); + } + }); - // Handle zooming functionality - ipcRenderer.on('zoomIn', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - this.zoomFactors[this.activeTabIndex] += 0.1; - activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); - }); + // Handle zooming functionality + ipcRenderer.on('zoomIn', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] += 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); - ipcRenderer.on('zoomOut', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - this.zoomFactors[this.activeTabIndex] -= 0.1; - activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); - }); + ipcRenderer.on('zoomOut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] -= 0.1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); - ipcRenderer.on('zoomActualSize', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - this.zoomFactors[this.activeTabIndex] = 1; - activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); - }); + ipcRenderer.on('zoomActualSize', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + this.zoomFactors[this.activeTabIndex] = 1; + activeWebview.setZoomFactor(this.zoomFactors[this.activeTabIndex]); + }); - ipcRenderer.on('log-out', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - activeWebview.executeJavaScript('logout()'); - }); + ipcRenderer.on('log-out', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('logout()'); + }); - ipcRenderer.on('shortcut', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - activeWebview.executeJavaScript('shortcut()'); - }); + ipcRenderer.on('shortcut', () => { + const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); + activeWebview.executeJavaScript('shortcut()'); + }); - ipcRenderer.on('open-settings', () => { - const activeWebview = document.getElementById(`webview-${this.activeTabIndex}`); - if (this.settingsTabIndex == -1) { - this.openSettings(); - } else { - this.activateTab(this.settingsTabIndex); - } - }); - } + ipcRenderer.on('open-settings', () => { + if (this.settingsTabIndex === -1) { + this.openSettings(); + } else { + this.activateTab(this.settingsTabIndex); + } + }); + } } window.onload = () => { const serverManagerView = new ServerManagerView(); serverManagerView.init(); -} +}; diff --git a/app/renderer/js/pref.js b/app/renderer/js/pref.js deleted file mode 100644 index 7624ad52..00000000 --- a/app/renderer/js/pref.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; -// eslint-disable-next-line import/no-extraneous-dependencies -const {remote} = require('electron'); - -const prefWindow = remote.getCurrentWindow(); - -document.getElementById('close-button').addEventListener('click', () => { - prefWindow.close(); -}); - -document.addEventListener('keydown', event => { - if (event.key === 'Escape' || event.keyCode === 27) { - prefWindow.close(); - } -}); -// eslint-disable-next-line no-unused-vars -window.prefDomain = function () { - const request = require('request'); - // eslint-disable-next-line import/no-extraneous-dependencies - const ipcRenderer = require('electron').ipcRenderer; - const JsonDB = require('node-json-db'); - // eslint-disable-next-line import/no-extraneous-dependencies - const {app} = require('electron').remote; - - const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); - - let newDomain = document.getElementById('url').value; - newDomain = newDomain.replace(/^https?:\/\//, ''); - newDomain = newDomain.replace(/^http?:\/\//, ''); - - if (newDomain === '') { - document.getElementById('urladded').innerHTML = 'Please input a value'; - } else { - document.getElementById('main').innerHTML = 'Checking...'; - if (newDomain.indexOf('localhost:') >= 0) { - const domain = 'http://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Not a valid Zulip Local Server.'; - } - }); - } else { - const domain = 'https://' + newDomain; - const checkDomain = domain + '/static/audio/zulip.ogg'; - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain; - db.push('/domain', domain); - ipcRenderer.send('new-domain', domain); - } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { - document.getElementById('main').innerHTML = 'Switch'; - ipcRenderer.send('certificate-err', domain); - document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain; - } else { - document.getElementById('main').innerHTML = 'Switch'; - document.getElementById('urladded').innerHTML = 'Not a valid Zulip Server.'; - } - }); - } - } -}; diff --git a/app/renderer/js/preference.js b/app/renderer/js/preference.js index 7e0bca50..664b7092 100644 --- a/app/renderer/js/preference.js +++ b/app/renderer/js/preference.js @@ -1,142 +1,144 @@ 'use strict'; -const { ipcRenderer } = require('electron'); -const path = require("path"); +const {ipcRenderer} = require('electron'); +const path = require('path'); + const DomainUtil = require(path.resolve(('app/renderer/js/utils/domain-util.js'))); + class PreferenceView { - constructor() { - this.$newServerButton = document.getElementById('new-server-action'); - this.$saveServerButton = document.getElementById('save-server-action'); - this.$reloadServerButton = document.getElementById('reload-server-action'); - this.$serverInfoContainer = document.querySelector('.server-info-container'); - } + constructor() { + this.$newServerButton = document.getElementById('new-server-action'); + this.$saveServerButton = document.getElementById('save-server-action'); + this.$reloadServerButton = document.getElementById('reload-server-action'); + this.$serverInfoContainer = document.querySelector('.server-info-container'); + } - init() { - this.domainUtil = new DomainUtil(); - this.initServers(); - this.initActions(); - } + init() { + this.domainUtil = new DomainUtil(); + this.initServers(); + this.initActions(); + } - initServers() { - const servers = this.domainUtil.getDomains(); - this.$serverInfoContainer.innerHTML = servers.length? '': 'Add your first server to get started!'; + initServers() { + const servers = this.domainUtil.getDomains(); + this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!'; - this.initNewServerForm(); - - for (let i in servers) { - this.initServer(servers[i], i); - } - } + this.initNewServerForm(); - initServer(server, index) { - const { + for (const i in servers) { + this.initServer(servers[i], i); + } + } + + initServer(server, index) { + const { alias, url, - icon + icon } = server; - const serverInfoTemplate = ` -
-
- -
-
-
- Name - -
-
- Url - -
-
- Icon - -
-
- Actions -
- indeterminate_check_box - Delete -
-
-
-
`; - this.$serverInfoContainer.appendChild(this.__insert_node(serverInfoTemplate)); - document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { - this.domainUtil.removeDomain(index); - this.initServers(); - alert('Success. Reload to apply changes.') - this.$reloadServerButton.classList.remove('hidden'); - }); - } + const serverInfoTemplate = ` +
+
+ +
+
+
+ Name + +
+
+ Url + +
+
+ Icon + +
+
+ Actions +
+ indeterminate_check_box + Delete +
+
+
+
`; + this.$serverInfoContainer.appendChild(this.insertNode(serverInfoTemplate)); + document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => { + this.domainUtil.removeDomain(index); + this.initServers(); + alert('Success. Reload to apply changes.'); + this.$reloadServerButton.classList.remove('hidden'); + }); + } - initNewServerForm() { - const newServerFormTemplate = ` - - `; - this.$serverInfoContainer.appendChild(this.__insert_node(newServerFormTemplate)); + initNewServerForm() { + const newServerFormTemplate = ` + + `; + this.$serverInfoContainer.appendChild(this.insertNode(newServerFormTemplate)); - this.$newServerForm = document.querySelector('.server-info.active'); - this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0]; - this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1]; - this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2]; - } + this.$newServerForm = document.querySelector('.server-info.active'); + this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0]; + this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1]; + this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2]; + } - initActions() { - this.$newServerButton.addEventListener('click', () => { - this.$newServerForm.classList.remove('hidden'); - this.$saveServerButton.classList.remove('hidden'); - this.$newServerButton.classList.add('hidden'); - }); - this.$saveServerButton.addEventListener('click', () => { - this.domainUtil.checkDomain(this.$newServerUrl.value).then((domain) => { - const server = { - alias: this.$newServerAlias.value, - url: domain, - icon: this.$newServerIcon.value - }; - this.domainUtil.addDomain(server); - this.$saveServerButton.classList.add('hidden'); - this.$newServerButton.classList.remove('hidden'); - this.$newServerForm.classList.add('hidden'); + initActions() { + this.$newServerButton.addEventListener('click', () => { + this.$newServerForm.classList.remove('hidden'); + this.$saveServerButton.classList.remove('hidden'); + this.$newServerButton.classList.add('hidden'); + }); + this.$saveServerButton.addEventListener('click', () => { + this.domainUtil.checkDomain(this.$newServerUrl.value).then(domain => { + const server = { + alias: this.$newServerAlias.value, + url: domain, + icon: this.$newServerIcon.value + }; + this.domainUtil.addDomain(server); + this.$saveServerButton.classList.add('hidden'); + this.$newServerButton.classList.remove('hidden'); + this.$newServerForm.classList.add('hidden'); - this.initServers(); - alert('Success. Reload to apply changes.') - this.$reloadServerButton.classList.remove('hidden'); - }, (errorMessage) => { - alert(errorMessage); - }); - }); - this.$reloadServerButton.addEventListener('click', () => { - ipcRenderer.send('reload-main'); - }); - } - __insert_node(html) { - let wrapper= document.createElement('div'); - wrapper.innerHTML= html; - return wrapper.firstElementChild; - } + this.initServers(); + alert('Success. Reload to apply changes.'); + this.$reloadServerButton.classList.remove('hidden'); + }, errorMessage => { + alert(errorMessage); + }); + }); + this.$reloadServerButton.addEventListener('click', () => { + ipcRenderer.send('reload-main'); + }); + } + insertNode(html) { + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + return wrapper.firstElementChild; + } } window.onload = () => { const preferenceView = new PreferenceView(); preferenceView.init(); -} +}; diff --git a/app/renderer/js/preload.js b/app/renderer/js/preload.js index 2a9ce077..794a446b 100644 --- a/app/renderer/js/preload.js +++ b/app/renderer/js/preload.js @@ -1,12 +1,6 @@ 'use strict'; -const ipcRenderer = require('electron').ipcRenderer; const {spellChecker} = require('./spellchecker'); -process.once('loaded', () => { - global.logout = logout; - global.shortcut = shortcut; -}); - const logout = () => { // Create the menu for the below document.querySelector('.dropdown-toggle').click(); @@ -27,6 +21,11 @@ const shortcut = () => { } }; +process.once('loaded', () => { + global.logout = logout; + global.shortcut = shortcut; +}); + // To prevent failing this script on linux we need to load it after the document loaded document.addEventListener('DOMContentLoaded', () => { // Init spellchecker diff --git a/app/renderer/js/tray.js b/app/renderer/js/tray.js index d4391788..8c7aade7 100644 --- a/app/renderer/js/tray.js +++ b/app/renderer/js/tray.js @@ -124,7 +124,7 @@ const createTray = function () { type: 'separator' }, { - label: 'Change Zulip server', + label: 'Manage Zulip servers', click() { sendAction('open-settings'); } @@ -136,7 +136,6 @@ const createTray = function () { label: 'Reload', click() { sendAction('reload'); - // window.tray.destroy(); } }, { diff --git a/app/renderer/js/utils/domain-util.js b/app/renderer/js/utils/domain-util.js index 8a806f68..f9384c04 100644 --- a/app/renderer/js/utils/domain-util.js +++ b/app/renderer/js/utils/domain-util.js @@ -5,58 +5,58 @@ const JsonDB = require('node-json-db'); const request = require('request'); class DomainUtil { - constructor() { - this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); - if (!this.getDomains()) { - this.db.push("/domains", []); - } - } + constructor() { + this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true); + if (!this.getDomains()) { + this.db.push('/domains', []); + } + } - getDomains() { - return this.db.getData('/domains'); - } + getDomains() { + return this.db.getData('/domains'); + } - getDomain(index) { - return this.db.getData(`/domains[${index}]`); - } + getDomain(index) { + return this.db.getData(`/domains[${index}]`); + } - addDomain(server) { - server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; - this.db.push("/domains[]", server, true); - } + addDomain(server) { + server.icon = server.icon || 'https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png'; + this.db.push('/domains[]', server, true); + } - removeDomains() { - this.db.delete("/domains"); - } + removeDomains() { + this.db.delete('/domains'); + } - removeDomain(index) { - this.db.delete(`/domains[${index}]`); - } + removeDomain(index) { + this.db.delete(`/domains[${index}]`); + } - checkDomain(domain) { - const hasPrefix = (domain.indexOf('http') == 0); - if (!hasPrefix) { - domain = (domain.indexOf('localhost:') >= 0)? `http://${domain}` : `https://${domain}`; - } + checkDomain(domain) { + const hasPrefix = (domain.indexOf('http') === 0); + if (!hasPrefix) { + domain = (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`; + } const checkDomain = domain + '/static/audio/zulip.ogg'; - return new Promise((res, rej) => { - request(checkDomain, (error, response) => { - if (!error && response.statusCode !== 404) { - res(domain); - } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { - if (window.confirm(`Do you trust certificate from ${domain}?`)) { - res(domain); - } else { - rej('Untrusted Certificate.'); - } - } else { - rej('Not a valid Zulip server'); - } - }); - }) - } + return new Promise((resolve, reject) => { + request(checkDomain, (error, response) => { + if (!error && response.statusCode !== 404) { + resolve(domain); + } else if (error.toString().indexOf('Error: self signed certificate') >= 0) { + if (window.confirm(`Do you trust certificate from ${domain}?`)) { + resolve(domain); + } else { + reject('Untrusted Certificate.'); + } + } else { + reject('Not a valid Zulip server'); + } + }); + }); + } } -module.exports = DomainUtil; \ No newline at end of file +module.exports = DomainUtil; diff --git a/app/renderer/preference.html b/app/renderer/preference.html index 99ae71f1..0772ffb3 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -27,7 +27,7 @@
From 4b4e3a3d013c84f28f0289a07b649678f4b5546c Mon Sep 17 00:00:00 2001 From: Zhongyi Tong Date: Sun, 30 Apr 2017 23:08:45 +0800 Subject: [PATCH 48/52] Update Save Server button text. --- app/renderer/preference.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/renderer/preference.html b/app/renderer/preference.html index c0b71b49..dd2588c8 100644 --- a/app/renderer/preference.html +++ b/app/renderer/preference.html @@ -27,7 +27,7 @@