feat(docs-infra): add shortcuts for the angular.io PWA (#40393)
This commits adds some shortcut definitions for the angular.io PWA. The user agent can use them to assemble a context menu to be displayed by the operating system when a user engages with the app's icon. (In addition, shortcuts provide an easy way for users to add links to specific pages on their home screen.) See [here][1] for more details on the `shortcuts` property of the PWA manifest. The choice of pages to create shortcuts to was influenced by the following facts/criteria: - It seems that only the first 4 shortcuts are displayed by Chrome (at least on my Android phone). - Since the PWA is mostly used on mobile, I omitted pages that are less likely to be useful for mobile users (such as pages related to CLI). [1]: https://developer.mozilla.org/en-US/docs/Web/Manifest/shortcuts PR Close #40393
This commit is contained in:
parent
4065c98054
commit
96690ed3a4
|
@ -22,5 +22,31 @@
|
||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": "/?utm_source=homescreen"
|
"start_url": "/?utm_source=homescreen",
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "Go to API Reference",
|
||||||
|
"short_name": "API",
|
||||||
|
"description": "Go to the Angular API reference page.",
|
||||||
|
"url": "/api?utm_source=homescreen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Go to Glossary",
|
||||||
|
"short_name": "Glossary",
|
||||||
|
"description": "Go to the glossary page: A list of common Angular terms and their explanation.",
|
||||||
|
"url": "/guide/glossary?utm_source=homescreen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Go to Resources",
|
||||||
|
"short_name": "Resources",
|
||||||
|
"description": "Go to the resources page: A list of Angular resouces, such as development tooling, UI libraries, books, courses, community publications, podcasts, etc.",
|
||||||
|
"url": "/resources?utm_source=homescreen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Go to Tutorial: Tour of Heroes",
|
||||||
|
"short_name": "Tutorial",
|
||||||
|
"description": "Go to the \"Tour of Heroes\" tutorial page: Learn how to create your first Angular application.",
|
||||||
|
"url": "/tutorial?utm_source=homescreen"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ export class SitePage {
|
||||||
|
|
||||||
async navigateTo(pageUrl: string) {
|
async navigateTo(pageUrl: string) {
|
||||||
// Navigate to the page, disable animations, and wait for Angular.
|
// Navigate to the page, disable animations, and wait for Angular.
|
||||||
await browser.get(`/${pageUrl}`);
|
await browser.get(`/${pageUrl.replace(/^\//, '')}`);
|
||||||
await browser.executeScript('document.body.classList.add(\'no-animations\')');
|
await browser.executeScript('document.body.classList.add(\'no-animations\')');
|
||||||
await browser.waitForAngular();
|
await browser.waitForAngular();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { PwaManifestPage, PwaShortcutItem } from './pwa-manifest.po';
|
||||||
|
|
||||||
|
|
||||||
|
describe('PWA manifest', () => {
|
||||||
|
const page = new PwaManifestPage();
|
||||||
|
|
||||||
|
describe('shortcuts', () => {
|
||||||
|
let shortcuts: PwaShortcutItem[];
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
const pageExists = async (url: string) => {
|
||||||
|
await page.navigateTo(url);
|
||||||
|
const content = await page.getDocViewerText();
|
||||||
|
return !/page not found/i.test(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
shortcuts = await page.getPwaShortcuts();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should exist', async () => {
|
||||||
|
for (const {short_name, url} of shortcuts) {
|
||||||
|
expect(await pageExists(url)).toBe(
|
||||||
|
true,
|
||||||
|
`Page for shortcut '${short_name}' (from '${page.pwaManifestUrl}') does not exist. (URL: ${url})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { get as httpGet } from 'http';
|
||||||
|
import { get as httpsGet } from 'https';
|
||||||
|
import { browser } from 'protractor';
|
||||||
|
import { SitePage } from './app.po';
|
||||||
|
|
||||||
|
|
||||||
|
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape of a PWA manifest.
|
||||||
|
* For simplicity, we only define types for the properties we care about in tests.
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/Manifest
|
||||||
|
*/
|
||||||
|
export type PwaManifest = Json & {
|
||||||
|
shortcuts?: PwaShortcutItem[],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape of an item in a PWA manifest's `shortcuts` list.
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/Manifest/shortcuts
|
||||||
|
*/
|
||||||
|
export type PwaShortcutItem = Json & {
|
||||||
|
url: string,
|
||||||
|
name: string,
|
||||||
|
short_name?: string,
|
||||||
|
description?: string,
|
||||||
|
icons?: PwaImageResource[],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shape of an item in a PWA manifest's icons list (such as the value of the top-level `icons` property or that of
|
||||||
|
* the `icons` property of a shortcut item).
|
||||||
|
* @see https://w3c.github.io/manifest/#manifestimageresource-and-its-members
|
||||||
|
*/
|
||||||
|
export type PwaImageResource = Json & {
|
||||||
|
src: string,
|
||||||
|
sizes?: string,
|
||||||
|
type?: string,
|
||||||
|
purpose?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export class PwaManifestPage extends SitePage {
|
||||||
|
/** The base URL with the trailing `/` stripped off (if any). */
|
||||||
|
baseUrl = browser.baseUrl.replace(/\/$/, '');
|
||||||
|
|
||||||
|
/** The URL to the app's PWA manifest. */
|
||||||
|
pwaManifestUrl = `${this.baseUrl}/pwa-manifest.json`;
|
||||||
|
|
||||||
|
private pwaManifestText: string | null = null;
|
||||||
|
|
||||||
|
/** Get the app's PWA manifest as an object. */
|
||||||
|
async getPwaManifest(): Promise<PwaManifest> {
|
||||||
|
if (this.pwaManifestText === null) {
|
||||||
|
const get = /^https:/.test(this.pwaManifestUrl) ? httpsGet : httpGet;
|
||||||
|
|
||||||
|
this.pwaManifestText = await new Promise<string>((resolve, reject) => {
|
||||||
|
let responseText = '';
|
||||||
|
get(this.pwaManifestUrl, res => res
|
||||||
|
.on('data', chunk => responseText += chunk)
|
||||||
|
.on('end', () => resolve(responseText))
|
||||||
|
.on('error', reject));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(this.pwaManifestText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get a list of PWA shortcuts as extracted from the app's PWA manifest. */
|
||||||
|
async getPwaShortcuts(): Promise<PwaShortcutItem[]> {
|
||||||
|
const {shortcuts = []} = await this.getPwaManifest();
|
||||||
|
return shortcuts;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue