diff --git a/resources/original_messages.json b/resources/original_messages.json index e87f6bc88a..17001f33de 100644 --- a/resources/original_messages.json +++ b/resources/original_messages.json @@ -537,6 +537,14 @@ "description": "The success message after renaming a server in the application.", "message": "Server renamed" }, + "server_share": { + "description": "The text of an option displayed in a server card's options menu to tell the application to share the server's access key with another application.", + "message": "Share" + }, + "server_share_text": { + "description": "The text of a message that appears when the user clicks the Share option in a server card's options menu.", + "message": "This is an access key for an Outline Server. To use it, download the Outline app from the App Store or Google Play." + }, "servers_menu_item": { "description": "The menu item text to navigate to the list of servers.", "message": "Servers" diff --git a/src/www/app/app.ts b/src/www/app/app.ts index 65e712d965..3c272108fb 100644 --- a/src/www/app/app.ts +++ b/src/www/app/app.ts @@ -130,6 +130,8 @@ export class App { this.rootEl.addEventListener('DisconnectPressed', this.disconnectServer.bind(this)); this.rootEl.addEventListener('ForgetPressed', this.forgetServer.bind(this)); this.rootEl.addEventListener('RenameRequested', this.renameServer.bind(this)); + this.rootEl.addEventListener('ForgetPressed', this.forgetServer.bind(this)); + this.rootEl.addEventListener('ShareServer', this.shareServer.bind(this)); this.rootEl.addEventListener('QuitPressed', this.quitApplication.bind(this)); this.rootEl.addEventListener('AutoConnectDialogDismissed', this.autoConnectDialogDismissed.bind(this)); this.rootEl.addEventListener('ShowServerRename', this.rootEl.showServerRename.bind(this.rootEl)); @@ -378,6 +380,23 @@ export class App { this.serverRepo.rename(serverId, newName); } + private async shareServer(event: CustomEvent) { + const {serverId} = event.detail; + const server = this.getServerByServerId(serverId); + + // TODO: fallback to copying to clipboard if share is not available + if (!navigator.share) { + console.warn('Web Share API not available'); + return; + } + + await navigator.share({ + title: server.name || 'Outline Server', + text: this.localize('share-server-text'), + url: server.accessKey, + }); + } + private async connectServer(event: CustomEvent) { event.stopImmediatePropagation(); @@ -614,6 +633,10 @@ export class App { email: extraParams.email, }; } + + if (extraParams.share) { + serverListItem.canShare = true; + } } return serverListItem; diff --git a/src/www/app/outline_server_repository/server.ts b/src/www/app/outline_server_repository/server.ts index b5d76f5ff6..238978c912 100644 --- a/src/www/app/outline_server_repository/server.ts +++ b/src/www/app/outline_server_repository/server.ts @@ -45,6 +45,7 @@ export class OutlineServer implements Server { break; case ServerType.STATIC_CONNECTION: default: + this.accessKey = accessKey; this.sessionConfig = staticKeyToShadowsocksSessionConfig(accessKey); break; } diff --git a/src/www/messages/en.json b/src/www/messages/en.json index db0ffcdecd..40acc4e0e0 100644 --- a/src/www/messages/en.json +++ b/src/www/messages/en.json @@ -103,6 +103,8 @@ "server-forgotten-undo": "Server “{serverName}” has been restored.", "server-rename": "Rename", "server-rename-complete": "Server renamed", + "server-share": "Share", + "server-share-text": "This is an access key for an Outline Server. To use it, download the Outline app from the App Store or Google Play.", "servers-menu-item": "Servers", "servers-page-title": "Outline", "submit": "Submit", diff --git a/src/www/model/server.ts b/src/www/model/server.ts index 4fee4bb8ae..bf9ef0a405 100644 --- a/src/www/model/server.ts +++ b/src/www/model/server.ts @@ -33,6 +33,9 @@ export interface Server { // A type specifying the manner in which the Server connects. readonly type: ServerType; + // The access key used to connect to the server. + accessKey: string; + // The name of this server, as given by the user. name: string; diff --git a/src/www/views/servers_view/server_list_item/index.ts b/src/www/views/servers_view/server_list_item/index.ts index c518284ba3..1c6689ca02 100644 --- a/src/www/views/servers_view/server_list_item/index.ts +++ b/src/www/views/servers_view/server_list_item/index.ts @@ -21,6 +21,7 @@ export enum ServerListItemEvent { DISCONNECT = 'DisconnectPressed', FORGET = 'ForgetPressed', RENAME = 'ShowServerRename', + SHARE = 'ShareServer', } /** @@ -41,6 +42,7 @@ export interface ServerListItem { contact?: { email: string; }; + canShare?: boolean; } /** diff --git a/src/www/views/servers_view/server_list_item/server_card/index.ts b/src/www/views/servers_view/server_list_item/server_card/index.ts index b1f278a263..e9dfb8c79c 100644 --- a/src/www/views/servers_view/server_list_item/server_card/index.ts +++ b/src/www/views/servers_view/server_list_item/server_card/index.ts @@ -16,7 +16,7 @@ import '@material/mwc-icon-button'; import '@material/mwc-menu'; import '../../server_connection_indicator'; -import {css, html, LitElement} from 'lit'; +import {css, html, LitElement, nothing} from 'lit'; import {customElement, property} from 'lit/decorators.js'; import {createRef, Ref, ref} from 'lit/directives/ref.js'; @@ -200,6 +200,10 @@ const getSharedComponents = (element: ServerListItemElement & LitElement) => { element.dispatchEvent( new CustomEvent(ServerListItemEvent.FORGET, {detail: {serverId: server.id}, bubbles: true, composed: true}) ), + share: () => + element.dispatchEvent( + new CustomEvent(ServerListItemEvent.SHARE, {detail: {serverId: server.id}, bubbles: true, composed: true}) + ), connectToggle: () => element.dispatchEvent( new CustomEvent(isConnectedState ? ServerListItemEvent.DISCONNECT : ServerListItemEvent.CONNECT, { @@ -237,6 +241,9 @@ const getSharedComponents = (element: ServerListItemElement & LitElement) => { `, menu: html` + ${server.canShare + ? html`${localize('server-share')}` + : nothing} ${localize('server-rename')} ${localize('server-forget')}