mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
docs(docs-infra): pull version picker info from shared versions.json
fixes #64842
This commit is contained in:
parent
b2c79793c8
commit
05ab1dd76e
11 changed files with 368 additions and 330 deletions
|
|
@ -37,6 +37,7 @@ TEST_FILES = APPLICATION_FILES + [
|
|||
|
||||
APPLICATION_ASSETS = [
|
||||
"//adev/src/assets/images",
|
||||
"//adev/src/assets/others",
|
||||
"//adev/src/assets/previews",
|
||||
"//adev/src/assets:tutorials",
|
||||
"//adev/src/assets/icons",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {AppComponent} from './app.component';
|
|||
import {provideRouter, withComponentInputBinding} from '@angular/router';
|
||||
import {routes} from './routes';
|
||||
import {Search, WINDOW} from '@angular/docs';
|
||||
import {CURRENT_MAJOR_VERSION} from './core/providers/current-version';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
const fakeSearch = {};
|
||||
|
|
@ -30,10 +29,6 @@ describe('AppComponent', () => {
|
|||
provide: Search,
|
||||
useValue: fakeSearch,
|
||||
},
|
||||
{
|
||||
provide: CURRENT_MAJOR_VERSION,
|
||||
useValue: fakeCurrentMajorVersion,
|
||||
},
|
||||
],
|
||||
});
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import {CustomErrorHandler} from './core/services/errors-handling/error-handler'
|
|||
import {ExampleContentLoader} from './core/services/example-content-loader.service';
|
||||
import {ReuseTutorialsRouteStrategy} from './features/tutorial/tutorials-route-reuse-strategy';
|
||||
import {routes} from './routes';
|
||||
import {CURRENT_MAJOR_VERSION} from './core/providers/current-version';
|
||||
import {AppScroller} from './app-scroller';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
|
|
@ -78,10 +77,6 @@ export const appConfig: ApplicationConfig = {
|
|||
provideEnvironmentInitializer(() => inject(AppScroller)),
|
||||
provideEnvironmentInitializer(() => inject(AnalyticsService)),
|
||||
provideAlgoliaSearchClient(environment),
|
||||
{
|
||||
provide: CURRENT_MAJOR_VERSION,
|
||||
useValue: Number(VERSION.major),
|
||||
},
|
||||
{provide: ENVIRONMENT, useValue: environment},
|
||||
{provide: ErrorHandler, useClass: CustomErrorHandler},
|
||||
{provide: PREVIEWS_COMPONENTS, useValue: PREVIEWS_COMPONENTS_MAP},
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
export const VERSIONS_CONFIG = {
|
||||
aDevVersionsLinkPattern: 'https://{{prefix}}{{version}}angular.dev',
|
||||
aioVersions: [
|
||||
{
|
||||
version: 'v18',
|
||||
url: 'https://v18.angular.dev/overview',
|
||||
},
|
||||
{
|
||||
version: 'v17',
|
||||
url: 'https://v17.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v16',
|
||||
url: 'https://v16.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v15',
|
||||
url: 'https://v15.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v14',
|
||||
url: 'https://v14.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v13',
|
||||
url: 'https://v13.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v12',
|
||||
url: 'https://v12.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v11',
|
||||
url: 'https://v11.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v10',
|
||||
url: 'https://v10.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v9',
|
||||
url: 'https://v9.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v8',
|
||||
url: 'https://v8.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v7',
|
||||
url: 'https://v7.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v6',
|
||||
url: 'https://v6.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v5',
|
||||
url: 'https://v5.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v4',
|
||||
url: 'https://v4.angular.io/docs',
|
||||
},
|
||||
{
|
||||
version: 'v2',
|
||||
url: 'https://v2.angular.io/docs',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -62,9 +62,9 @@
|
|||
<nav
|
||||
class="adev-nav-primary docs-scroll-hide"
|
||||
[class.adev-nav-primary--open]="isMobileNavigationOpened()"
|
||||
[class.adev-nav-primary--rc]="currentDocsVersionMode === 'rc'"
|
||||
[class.adev-nav-primary--next]="currentDocsVersionMode === 'next'"
|
||||
[class.adev-nav-primary--deprecated]="currentDocsVersionMode === 'deprecated'"
|
||||
[class.adev-nav-primary--rc]="currentDocsVersionMode() === 'rc'"
|
||||
[class.adev-nav-primary--next]="currentDocsVersionMode() === 'next'"
|
||||
[class.adev-nav-primary--deprecated]="currentDocsVersionMode() === 'deprecated'"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -83,58 +83,65 @@
|
|||
>
|
||||
<a aria-label="Angular homepage" routerLink="/">
|
||||
<!-- Logo Symbol -->
|
||||
@if(!isUwu) {
|
||||
<svg class="angular-logo" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 223 236" width="32">
|
||||
<g clip-path="url(#a)">
|
||||
<path
|
||||
fill="url(#b)"
|
||||
d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#c)"
|
||||
d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="49.009"
|
||||
x2="225.829"
|
||||
y1="213.75"
|
||||
y2="129.722"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#E40035" />
|
||||
<stop offset=".24" stop-color="#F60A48" />
|
||||
<stop offset=".352" stop-color="#F20755" />
|
||||
<stop offset=".494" stop-color="#DC087D" />
|
||||
<stop offset=".745" stop-color="#9717E7" />
|
||||
<stop offset="1" stop-color="#6C00F5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="c"
|
||||
x1="41.025"
|
||||
x2="156.741"
|
||||
y1="28.344"
|
||||
y2="160.344"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FF31D9" />
|
||||
<stop offset="1" stop-color="#FF5BE1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h223v236H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
} @else {
|
||||
<img
|
||||
src="assets/images/uwu.png"
|
||||
style="width: auto; margin: 0"
|
||||
class="uwu-logo"
|
||||
alt="Angular logo"
|
||||
height="34"/>
|
||||
}
|
||||
@if (!isUwu) {
|
||||
<svg
|
||||
class="angular-logo"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 223 236"
|
||||
width="32"
|
||||
>
|
||||
<g clip-path="url(#a)">
|
||||
<path
|
||||
fill="url(#b)"
|
||||
d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#c)"
|
||||
d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="49.009"
|
||||
x2="225.829"
|
||||
y1="213.75"
|
||||
y2="129.722"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#E40035" />
|
||||
<stop offset=".24" stop-color="#F60A48" />
|
||||
<stop offset=".352" stop-color="#F20755" />
|
||||
<stop offset=".494" stop-color="#DC087D" />
|
||||
<stop offset=".745" stop-color="#9717E7" />
|
||||
<stop offset="1" stop-color="#6C00F5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="c"
|
||||
x1="41.025"
|
||||
x2="156.741"
|
||||
y1="28.344"
|
||||
y2="160.344"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FF31D9" />
|
||||
<stop offset="1" stop-color="#FF5BE1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h223v236H0z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
} @else {
|
||||
<img
|
||||
src="assets/images/uwu.png"
|
||||
style="width: auto; margin: 0"
|
||||
class="uwu-logo"
|
||||
alt="Angular logo"
|
||||
height="34"
|
||||
/>
|
||||
}
|
||||
</a>
|
||||
|
||||
<!-- Version picker for v18+ -->
|
||||
|
|
@ -150,7 +157,7 @@
|
|||
(cdkMenuClosed)="closeMenu()"
|
||||
(click)="openVersionMenu($event)"
|
||||
>
|
||||
{{ currentDocsVersion()?.displayName }}
|
||||
{{ currentDocsVersion().displayName }}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="15"
|
||||
|
|
@ -165,16 +172,16 @@
|
|||
<ng-template #docsVersionMiniMenu>
|
||||
<ul class="adev-mini-menu adev-version-picker" cdkMenu>
|
||||
@for (item of versions(); track item) {
|
||||
<li>
|
||||
<a
|
||||
type="button"
|
||||
cdkMenuItem
|
||||
[href]="item.url"
|
||||
[attr.aria-label]="item.displayName"
|
||||
>
|
||||
<span>{{ item.displayName }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
type="button"
|
||||
cdkMenuItem
|
||||
[href]="item.url"
|
||||
[attr.aria-label]="item.displayName"
|
||||
>
|
||||
<span>{{ item.displayName }}</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
|
@ -363,8 +370,9 @@
|
|||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.468 1.948C5.303 3.325 7.276 6.118 8 7.616c.725-1.498 2.697-4.29 4.532-5.668C13.855.955 16 .186 16 2.632c0 .489-.28 4.105-.444 4.692-.572 2.04-2.653 2.561-4.504 2.246 3.236.551 4.06 2.375 2.281 4.2-3.376 3.464-4.852-.87-5.23-1.98-.07-.204-.103-.3-.103-.218 0-.081-.033.014-.102.218-.379 1.11-1.855 5.444-5.231 1.98-1.778-1.825-.955-3.65 2.28-4.2-1.85.315-3.932-.205-4.503-2.246C.28 6.737 0 3.12 0 2.632 0 .186 2.145.955 3.468 1.948Z"></path>
|
||||
<path
|
||||
d="M3.468 1.948C5.303 3.325 7.276 6.118 8 7.616c.725-1.498 2.697-4.29 4.532-5.668C13.855.955 16 .186 16 2.632c0 .489-.28 4.105-.444 4.692-.572 2.04-2.653 2.561-4.504 2.246 3.236.551 4.06 2.375 2.281 4.2-3.376 3.464-4.852-.87-5.23-1.98-.07-.204-.103-.3-.103-.218 0-.081-.033.014-.102.218-.379 1.11-1.855 5.444-5.231 1.98-1.778-1.825-.955-3.65 2.28-4.2-1.85.315-3.932-.205-4.503-2.246C.28 6.737 0 3.12 0 2.632 0 .186 2.145.955 3.468 1.948Z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
|
|
@ -403,21 +411,27 @@
|
|||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a [href]="DISCORD" cdkMenuItem title="Angular Discord" target="_blank" rel="noopener">
|
||||
<a
|
||||
[href]="DISCORD"
|
||||
cdkMenuItem
|
||||
title="Angular Discord"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<!-- Discord Icon -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 127.14 96.36"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,110.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
|
||||
/>
|
||||
</svg>
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 127.14 96.36"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,110.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -434,13 +448,17 @@
|
|||
(cdkMenuOpened)="openMenu('theme-picker')"
|
||||
>
|
||||
<docs-icon role="presentation">
|
||||
@switch (theme()) { @case ('light') {
|
||||
{{ 'light_mode' }}
|
||||
} @case ('dark') {
|
||||
{{ 'dark_mode' }}
|
||||
} @case ('auto') {
|
||||
{{ 'routine' }}
|
||||
} }
|
||||
@switch (theme()) {
|
||||
@case ('light') {
|
||||
{{ 'light_mode' }}
|
||||
}
|
||||
@case ('dark') {
|
||||
{{ 'dark_mode' }}
|
||||
}
|
||||
@case ('auto') {
|
||||
{{ 'routine' }}
|
||||
}
|
||||
}
|
||||
</docs-icon>
|
||||
</button>
|
||||
|
||||
|
|
@ -487,15 +505,16 @@
|
|||
|
||||
<!-- Tablet: Second horizontal nav layer which opens the secondary nav -->
|
||||
@if (activeRouteItem() === DOCS_ROUTE || activeRouteItem() === REFERENCE_ROUTE) {
|
||||
<div class="adev-secondary-tablet-bar">
|
||||
<button type="button" (click)="openMobileNav($event)">
|
||||
<docs-icon class="docs-icon_high-contrast">menu</docs-icon>
|
||||
@if (activeRouteItem() === DOCS_ROUTE) {
|
||||
<span>Docs</span>
|
||||
} @if (activeRouteItem() === REFERENCE_ROUTE) {
|
||||
<span>API</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div class="adev-secondary-tablet-bar">
|
||||
<button type="button" (click)="openMobileNav($event)">
|
||||
<docs-icon class="docs-icon_high-contrast">menu</docs-icon>
|
||||
@if (activeRouteItem() === DOCS_ROUTE) {
|
||||
<span>Docs</span>
|
||||
}
|
||||
@if (activeRouteItem() === REFERENCE_ROUTE) {
|
||||
<span>API</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ export class Navigation implements OnInit {
|
|||
openedMenu: MenuType | null = null;
|
||||
|
||||
currentDocsVersion = this.versionManager.currentDocsVersion;
|
||||
currentDocsVersionMode = this.versionManager.currentDocsVersion()?.version;
|
||||
currentDocsVersionMode = this.versionManager.currentDocsVersionMode;
|
||||
|
||||
// Set the values of the search label and title only on the client, because the label is user-agent specific.
|
||||
searchLabel = this.isBrowser
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {InjectionToken} from '@angular/core';
|
||||
|
||||
export const CURRENT_MAJOR_VERSION = new InjectionToken<number>('CURRENT_MAJOR_VERSION');
|
||||
|
|
@ -6,54 +6,100 @@
|
|||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {Injectable, VERSION, computed, inject} from '@angular/core';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {httpResource} from '@angular/common/http';
|
||||
|
||||
import {INITIAL_ADEV_DOCS_VERSION, VersionManager} from './version-manager.service';
|
||||
import {WINDOW} from '@angular/docs';
|
||||
import {CURRENT_MAJOR_VERSION} from '../providers/current-version';
|
||||
import versionJson from '../../../assets/others/versions.json';
|
||||
|
||||
describe('VersionManager', () => {
|
||||
const fakeWindow = {location: {hostname: 'angular.dev'}};
|
||||
const fakeCurrentMajorVersion = 19;
|
||||
export interface Version {
|
||||
displayName: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
let service: VersionManager;
|
||||
export type VersionMode = 'stable' | 'deprecated' | 'rc' | 'next' | number;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: WINDOW,
|
||||
useValue: fakeWindow,
|
||||
},
|
||||
{
|
||||
provide: CURRENT_MAJOR_VERSION,
|
||||
useValue: fakeCurrentMajorVersion,
|
||||
},
|
||||
],
|
||||
});
|
||||
service = TestBed.inject(VersionManager);
|
||||
export const INITIAL_ADEV_DOCS_VERSION = 18;
|
||||
export const VERSION_PLACEHOLDER = '{{version}}';
|
||||
export const MODE_PLACEHOLDER = '{{prefix}}';
|
||||
|
||||
type VersionJson = {version: string; url: string};
|
||||
|
||||
/**
|
||||
* This service will rely on 2 sources of data for the list of versions.
|
||||
*
|
||||
* To have an up-to-date list of versions, it will fetch a json from the deployed website.
|
||||
* As fallback it will use a local json file that is bundled with the app.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class VersionManager {
|
||||
private document = inject(DOCUMENT);
|
||||
|
||||
readonly currentDocsVersionMode = computed<VersionMode>(() => {
|
||||
const hostname = this.document.location.hostname;
|
||||
if (hostname.startsWith('v')) return 'deprecated';
|
||||
if (hostname.startsWith('rc')) return 'rc';
|
||||
if (hostname.startsWith('next')) return 'next';
|
||||
|
||||
return 'stable';
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
private localVersions = (versionJson as VersionJson[]).map((v) => {
|
||||
return {
|
||||
displayName: v.version,
|
||||
url: v.url,
|
||||
};
|
||||
});
|
||||
|
||||
it('should contain correct number of Angular Docs versions', () => {
|
||||
// Note: From v2 to v18 (inclusive), there were no v3
|
||||
const expectedAioDocsVersionsCount = 16;
|
||||
|
||||
// Last stable version and next
|
||||
const expectedRecentDocsVersionCount = 2;
|
||||
|
||||
const expectedPreviousAdevVersionsCount = fakeCurrentMajorVersion - INITIAL_ADEV_DOCS_VERSION;
|
||||
|
||||
expect(service['getAioVersions']().length).toBe(expectedAioDocsVersionsCount);
|
||||
expect(service['getRecentVersions']().length).toBe(expectedRecentDocsVersionCount);
|
||||
expect(service['getAdevVersions']().length).toBe(expectedPreviousAdevVersionsCount);
|
||||
expect(service.versions().length).toBe(
|
||||
expectedAioDocsVersionsCount +
|
||||
expectedRecentDocsVersionCount +
|
||||
expectedPreviousAdevVersionsCount,
|
||||
);
|
||||
// This handle the fallback if the resource fails.
|
||||
readonly versions = computed(() => {
|
||||
return this.remoteVersions.hasValue() ? this.remoteVersions.value() : this.localVersions;
|
||||
});
|
||||
});
|
||||
|
||||
// Yes this will trigger a cors error on localhost
|
||||
// but this is fine as we'll fallback to the local versions.json
|
||||
// which is the most up-to-date anyway.
|
||||
remoteVersions = httpResource(
|
||||
() => ({
|
||||
url: 'https://angular.dev/assets/others/versions.json',
|
||||
transferCache: false,
|
||||
cache: 'no-cache',
|
||||
}),
|
||||
{
|
||||
parse: (json: unknown) => {
|
||||
if (!Array.isArray(json)) {
|
||||
throw new Error('Invalid version data');
|
||||
}
|
||||
return json.map((v: unknown) => {
|
||||
if (
|
||||
v === undefined ||
|
||||
v === null ||
|
||||
typeof v !== 'object' ||
|
||||
!('version' in v) ||
|
||||
!('url' in v) ||
|
||||
typeof v.version !== 'string' ||
|
||||
typeof v.url !== 'string'
|
||||
) {
|
||||
throw new Error('Invalid version data');
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: v.version,
|
||||
url: v.url,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
readonly currentDocsVersion = computed(() => {
|
||||
// In devmode the version is 0, so we'll target next (which is first on the list)
|
||||
if (VERSION.major === '0') {
|
||||
return this.versions()[0];
|
||||
}
|
||||
|
||||
return this.versions().find((v) => v.displayName.includes(VERSION.major)) ?? this.versions()[0];
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Injectable, computed, inject, signal} from '@angular/core';
|
||||
import {VERSIONS_CONFIG} from '../constants/versions';
|
||||
import {WINDOW} from '@angular/docs';
|
||||
import {CURRENT_MAJOR_VERSION} from '../providers/current-version';
|
||||
import {Injectable, VERSION, computed, inject} from '@angular/core';
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {httpResource} from '@angular/common/http';
|
||||
|
||||
import versionJson from '../../../assets/others/versions.json';
|
||||
|
||||
export interface Version {
|
||||
displayName: string;
|
||||
version: VersionMode;
|
||||
url: string;
|
||||
}
|
||||
|
||||
|
|
@ -23,107 +23,83 @@ export const INITIAL_ADEV_DOCS_VERSION = 18;
|
|||
export const VERSION_PLACEHOLDER = '{{version}}';
|
||||
export const MODE_PLACEHOLDER = '{{prefix}}';
|
||||
|
||||
type VersionJson = {version: string; url: string};
|
||||
|
||||
/**
|
||||
* This service will rely on 2 sources of data for the list of versions.
|
||||
*
|
||||
* To have an up-to-date list of versions, it will fetch a json from the deployed website.
|
||||
* As fallback it will use a local json file that is bundled with the app.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class VersionManager {
|
||||
private readonly currentMajorVersion = inject(CURRENT_MAJOR_VERSION);
|
||||
private readonly window = inject(WINDOW);
|
||||
private document = inject(DOCUMENT) as Document;
|
||||
|
||||
// Note: We can assume that if the URL starts with v{{version}}, it is documentation for previous versions of Angular.
|
||||
// Based on URL we can indicate as well if it's rc or next Docs version.
|
||||
private get currentVersionMode(): VersionMode {
|
||||
const hostname = this.window.location.hostname;
|
||||
readonly currentDocsVersionMode = computed<VersionMode>(() => {
|
||||
const hostname = this.document.location.hostname;
|
||||
if (hostname.startsWith('v')) return 'deprecated';
|
||||
if (hostname.startsWith('rc')) return 'rc';
|
||||
if (hostname.startsWith('next')) return 'next';
|
||||
|
||||
return 'stable';
|
||||
}
|
||||
|
||||
versions = signal<Version[]>([
|
||||
...this.getRecentVersions(),
|
||||
...this.getAdevVersions(),
|
||||
...this.getAioVersions(),
|
||||
]);
|
||||
|
||||
currentDocsVersion = computed(() => {
|
||||
return this.versions().find(
|
||||
(version) => version.version.toString() === this.currentVersionMode,
|
||||
);
|
||||
});
|
||||
|
||||
// List of Angular Docs versions which includes current version, next and rc.
|
||||
private getRecentVersions(): Version[] {
|
||||
return [
|
||||
{
|
||||
url: this.getAdevDocsUrl('next'),
|
||||
displayName: `next`,
|
||||
version: 'next',
|
||||
},
|
||||
// Note: 'rc' should not be visible for now
|
||||
// {
|
||||
// url: this.getAdevDocsUrl('rc'),
|
||||
// displayName: `rc`,
|
||||
// version: 'rc',
|
||||
// },
|
||||
{
|
||||
url: 'https://angular.dev/',
|
||||
displayName: this.getVersion(this.currentMajorVersion),
|
||||
version: this.currentVersionMode,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// List of Angular Docs versions hosted on angular.dev domain.
|
||||
private getAdevVersions(): Version[] {
|
||||
const adevVersions: Version[] = [];
|
||||
for (
|
||||
let version = this.currentMajorVersion - 1;
|
||||
version >= INITIAL_ADEV_DOCS_VERSION;
|
||||
version--
|
||||
) {
|
||||
adevVersions.push({
|
||||
url: this.getAdevDocsUrl(version),
|
||||
displayName: this.getVersion(version),
|
||||
version: 'deprecated',
|
||||
});
|
||||
}
|
||||
return adevVersions;
|
||||
}
|
||||
|
||||
// List of Angular Docs versions hosted on angular.io domain.
|
||||
private getAioVersions(): Version[] {
|
||||
return VERSIONS_CONFIG.aioVersions.map((item) =>
|
||||
this.mapToVersion(item as Pick<Version, 'url' | 'version'>),
|
||||
);
|
||||
}
|
||||
|
||||
private mapToVersion(value: Pick<Version, 'url' | 'version'>): Version {
|
||||
private localVersions = (versionJson as VersionJson[]).map((v) => {
|
||||
return {
|
||||
...value,
|
||||
displayName: this.getVersion(value.version),
|
||||
displayName: v.version,
|
||||
url: v.url,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
private getVersion(versionMode: VersionMode): string {
|
||||
if (versionMode === 'stable' || versionMode === 'deprecated') {
|
||||
return `v${this.currentMajorVersion}`;
|
||||
// This handle the fallback if the resource fails.
|
||||
readonly versions = computed(() => {
|
||||
return this.remoteVersions.hasValue() ? this.remoteVersions.value() : this.localVersions;
|
||||
});
|
||||
|
||||
// Yes this will trigger a cors error on localhost
|
||||
// but this is fine as we'll fallback to the local versions.json
|
||||
// which is the most up-to-date anyway.
|
||||
remoteVersions = httpResource(
|
||||
() => ({
|
||||
url: 'https://angular.dev/assets/others/versions.json',
|
||||
transferCache: false,
|
||||
cache: 'no-cache',
|
||||
}),
|
||||
{
|
||||
parse: (json: unknown) => {
|
||||
if (!Array.isArray(json)) {
|
||||
throw new Error('Invalid version data');
|
||||
}
|
||||
return json.map((v: unknown) => {
|
||||
if (
|
||||
v === undefined ||
|
||||
v === null ||
|
||||
typeof v !== 'object' ||
|
||||
!('version' in v) ||
|
||||
!('url' in v) ||
|
||||
typeof v.version !== 'string' ||
|
||||
typeof v.url !== 'string'
|
||||
) {
|
||||
throw new Error('Invalid version data');
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: v.version,
|
||||
url: v.url,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
readonly currentDocsVersion = computed(() => {
|
||||
// In devmode the version is 0, so we'll target next (which is first on the list)
|
||||
if (VERSION.major === '0') {
|
||||
return this.versions()[0];
|
||||
}
|
||||
if (Number.isInteger(versionMode)) {
|
||||
return `v${versionMode}`;
|
||||
}
|
||||
return versionMode.toString();
|
||||
}
|
||||
|
||||
private getAdevDocsUrl(version: VersionMode): string {
|
||||
const docsUrlPrefix = isNaN(Number(version)) ? `` : 'v';
|
||||
|
||||
return VERSIONS_CONFIG.aDevVersionsLinkPattern
|
||||
.replace(MODE_PLACEHOLDER, docsUrlPrefix)
|
||||
.replace(
|
||||
VERSION_PLACEHOLDER,
|
||||
`${version.toString() === 'stable' ? '' : `${version.toString()}.`}`,
|
||||
);
|
||||
}
|
||||
return this.versions().find((v) => v.displayName.includes(VERSION.major)) ?? this.versions()[0];
|
||||
});
|
||||
}
|
||||
|
|
|
|||
16
adev/src/assets/others/BUILD.bazel
Normal file
16
adev/src/assets/others/BUILD.bazel
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "others",
|
||||
srcs = glob(
|
||||
include = ["**/*"],
|
||||
exclude = ["BUILD.bazel"],
|
||||
),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
78
adev/src/assets/others/versions.json
Normal file
78
adev/src/assets/others/versions.json
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
[
|
||||
{
|
||||
"version": "next",
|
||||
"url": "https://next.angular.dev/overview"
|
||||
},
|
||||
{
|
||||
"version": "v20",
|
||||
"url": "https://v20.angular.dev/overview"
|
||||
},
|
||||
{
|
||||
"version": "v19",
|
||||
"url": "https://v19.angular.dev/overview"
|
||||
},
|
||||
{
|
||||
"version": "v18",
|
||||
"url": "https://v18.angular.dev/overview"
|
||||
},
|
||||
{
|
||||
"version": "v17",
|
||||
"url": "https://v17.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v16",
|
||||
"url": "https://v16.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v15",
|
||||
"url": "https://v15.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v14",
|
||||
"url": "https://v14.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v13",
|
||||
"url": "https://v13.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v12",
|
||||
"url": "https://v12.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v11",
|
||||
"url": "https://v11.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v10",
|
||||
"url": "https://v10.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v9",
|
||||
"url": "https://v9.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v8",
|
||||
"url": "https://v8.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v7",
|
||||
"url": "https://v7.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v6",
|
||||
"url": "https://v6.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v5",
|
||||
"url": "https://v5.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v4",
|
||||
"url": "https://v4.angular.io/docs"
|
||||
},
|
||||
{
|
||||
"version": "v2",
|
||||
"url": "https://v2.angular.io/docs"
|
||||
}
|
||||
]
|
||||
Loading…
Reference in a new issue