From a89b3865d7026ed30d92d1a0ff83a32b477091c1 Mon Sep 17 00:00:00 2001 From: "(Holloway) Chew, Kean Ho" Date: Mon, 16 Dec 2024 17:11:48 +0800 Subject: [PATCH] Angular: enhanced workspace to be SEO ready Since this new Angular/ workspace is ready for static site deployment, we can proceed to enhance it for SEO ready. Hence, let's do this. This patch enhances Angular/ workspace to be SEO ready. Co-authored-by: Shuralyov, Jean Co-authored-by: Galyna, Cory Co-authored-by: (Holloway) Chew, Kean Ho Signed-off-by: (Holloway) Chew, Kean Ho --- Angular/.ci/clean_unix-any.sh | 1 + Angular/.ci/clean_windows-any.ps1 | 1 + Angular/.ci/test_unix-any.sh | 18 +- Angular/.gitignore | 11 + Angular/README.md | 135 ++-- Angular/app.config.server.ts | 3 +- Angular/app.config.ts | 42 +- Angular/app.routes.ts | 13 +- Angular/build.sh.ps1 | 22 +- Angular/contents/404/Page.ts | 35 + Angular/contents/404/data-i18n.ts | 49 ++ Angular/contents/404/page.css | 3 - Angular/contents/404/page.html | 11 +- Angular/contents/404/page.spec.ts | 13 +- Angular/contents/404/page.ts | 18 - Angular/contents/Page.ts | 35 + Angular/contents/data-i18n.ts | 50 ++ Angular/contents/lang/Page.ts | 35 + Angular/contents/lang/data-i18n.ts | 47 ++ Angular/contents/lang/page.css | 17 + Angular/contents/lang/page.html | 6 +- Angular/contents/lang/page.spec.ts | 12 +- Angular/contents/lang/page.ts | 62 -- Angular/contents/page.css | 3 - Angular/contents/page.html | 4 +- Angular/contents/page.spec.ts | 11 +- Angular/contents/page.ts | 18 - Angular/init.sh.ps1 | 64 -- Angular/main.server.ts | 75 ++- Angular/main.ts | 6 +- Angular/package-lock.json | 185 ++---- Angular/package.json | 6 +- Angular/serve.sh.ps1 | 22 +- Angular/server.ts | 35 +- Angular/services/app/Definitions.ts | 276 ++++++++ Angular/services/app/{footer.ts => Footer.ts} | 0 .../services/app/{metadata.ts => Metadata.ts} | 599 ++++++++++++++---- Angular/services/app/{root.ts => Root.ts} | 0 Angular/services/app/URL.ts | 29 + Angular/services/app/metadata-definitions.ts | 142 ----- Angular/services/app/noscript.html | 24 + Angular/services/app/root.css | 4 +- Angular/services/app/root.html | 160 ----- Angular/services/app/seo/Start.ts | 32 + .../{init => app/seo}/browserconfig-xml.ts | 30 +- Angular/services/{init => app/seo}/cname.ts | 15 +- Angular/services/app/seo/html.ts | 274 ++++++++ .../services/{init => app/seo}/no-jekyll.ts | 9 +- Angular/services/{init => app/seo}/pwa.ts | 105 +-- Angular/services/{init => app/seo}/seo.ts | 54 +- Angular/services/app/shell/build.ps1 | 12 + Angular/services/app/shell/build.sh | 13 + Angular/services/app/shell/init.ps1 | 106 ++++ Angular/services/app/shell/init.sh | 103 +++ Angular/services/app/shell/serve.ps1 | 12 + Angular/services/app/shell/serve.sh | 13 + Angular/services/app/shell/test.ps1 | 12 + Angular/services/app/shell/test.sh | 15 + Angular/services/app/shell/watch.ps1 | 12 + Angular/services/app/shell/watch.sh | 13 + Angular/services/init/url.ts | 51 -- Angular/test.sh.ps1 | 22 +- Angular/watch.sh.ps1 | 21 +- 63 files changed, 2140 insertions(+), 1086 deletions(-) create mode 100644 Angular/contents/404/Page.ts create mode 100644 Angular/contents/404/data-i18n.ts delete mode 100644 Angular/contents/404/page.ts create mode 100644 Angular/contents/Page.ts create mode 100644 Angular/contents/data-i18n.ts create mode 100644 Angular/contents/lang/Page.ts create mode 100644 Angular/contents/lang/data-i18n.ts delete mode 100644 Angular/contents/lang/page.ts delete mode 100644 Angular/contents/page.ts delete mode 100644 Angular/init.sh.ps1 create mode 100644 Angular/services/app/Definitions.ts rename Angular/services/app/{footer.ts => Footer.ts} (100%) rename Angular/services/app/{metadata.ts => Metadata.ts} (58%) rename Angular/services/app/{root.ts => Root.ts} (100%) create mode 100644 Angular/services/app/URL.ts delete mode 100644 Angular/services/app/metadata-definitions.ts create mode 100644 Angular/services/app/noscript.html delete mode 100644 Angular/services/app/root.html create mode 100644 Angular/services/app/seo/Start.ts rename Angular/services/{init => app/seo}/browserconfig-xml.ts (54%) rename Angular/services/{init => app/seo}/cname.ts (53%) create mode 100644 Angular/services/app/seo/html.ts rename Angular/services/{init => app/seo}/no-jekyll.ts (67%) rename Angular/services/{init => app/seo}/pwa.ts (62%) rename Angular/services/{init => app/seo}/seo.ts (79%) create mode 100644 Angular/services/app/shell/build.ps1 create mode 100644 Angular/services/app/shell/build.sh create mode 100644 Angular/services/app/shell/init.ps1 create mode 100644 Angular/services/app/shell/init.sh create mode 100644 Angular/services/app/shell/serve.ps1 create mode 100644 Angular/services/app/shell/serve.sh create mode 100644 Angular/services/app/shell/test.ps1 create mode 100644 Angular/services/app/shell/test.sh create mode 100644 Angular/services/app/shell/watch.ps1 create mode 100644 Angular/services/app/shell/watch.sh delete mode 100644 Angular/services/init/url.ts diff --git a/Angular/.ci/clean_unix-any.sh b/Angular/.ci/clean_unix-any.sh index f56216c0..ba89d7a7 100644 --- a/Angular/.ci/clean_unix-any.sh +++ b/Angular/.ci/clean_unix-any.sh @@ -31,6 +31,7 @@ cd "${PROJECT_PATH_ROOT}/${PROJECT_ANGULAR}" FS_Remove_Silently "dist" FS_Remove_Silently "node_modules" +FS_Remove_Silently ".angular" cd "$__current_path" unset __current_path diff --git a/Angular/.ci/clean_windows-any.ps1 b/Angular/.ci/clean_windows-any.ps1 index b674830c..8f3549b7 100644 --- a/Angular/.ci/clean_windows-any.ps1 +++ b/Angular/.ci/clean_windows-any.ps1 @@ -30,6 +30,7 @@ $null = Set-Location "${env:PROJECT_PATH_ROOT}\${env:PROJECT_ANGULAR}" $null = FS-Remove-Silently "dist" $null = FS-Remove-Silently "node_modules" +$null = FS-Remove-Silently ".angular" $null = Set-Location "${__current_path}" $null = Remove-Variable __current_path diff --git a/Angular/.ci/test_unix-any.sh b/Angular/.ci/test_unix-any.sh index e38964f2..2ec5bbe8 100644 --- a/Angular/.ci/test_unix-any.sh +++ b/Angular/.ci/test_unix-any.sh @@ -45,25 +45,13 @@ if [ $(OS_Is_Run_Simulated) -eq 0 ]; then I18N_Simulate_Testing return 0 else - ___old_IFS="$IFS" - while IFS="" read -r ___line || [ -n "$___line" ]; do - ___browser="$(type -p chromium)" - if [ ! -z "$___browser" ]; then - break - fi - done << EOF -chromium -google-chrome -brave-browser -EOF - IFS="$___old_IFS" && unset ___old_IFS ___line - - if [ "$___browser" = "" ]; then + ___browser="$(type -p chromium)" + if [ $(STRINGS_Is_Empty "$___browser") -eq 0 ]; then I18N_Run_Failed return 1 fi - CHROME_BIN="$___browser" ./test.sh.ps1 + CHROME_BIN="${___browser}" ./test.sh.ps1 if [ $? -ne 0 ]; then I18N_Run_Failed return 1 diff --git a/Angular/.gitignore b/Angular/.gitignore index 1874c1ee..236a5680 100644 --- a/Angular/.gitignore +++ b/Angular/.gitignore @@ -3,6 +3,15 @@ # (1) Specify your project specific files and directories here. You should # # only touch this section. # ################################################################################ +services/HestiaFONT_NOTO_EMOJI_COLOR/ +services/HestiaFONT_NOTO_SANS/ +services/HestiaGUI_ANCHOR/ +services/HestiaGUI_BUTTON/ +services/HestiaGUI_CORE/ +services/HestiaGUI_SOCIAL/ +services/HestiaGUI_SOCIAL_BLUESKY/ +services/HestiaGUI_ULIST/ +services/HestiaSOCIAL/ @@ -19,6 +28,8 @@ assets/manifest.webmanifest assets/robots.txt assets/sitemap.xml assets/sitemaps/ +services/app/root.html +services/app/.root.html diff --git a/Angular/README.md b/Angular/README.md index 05e6005c..6df8c42c 100644 --- a/Angular/README.md +++ b/Angular/README.md @@ -13,17 +13,17 @@ components directories: 1. `contents/` - organize page components with respect to pathing hirarchy. 2. `services/` - where your libraries and service components stays. -3. `services/app/` - where `app-root` and `app-footer` components are located. -4. `services/init/` - where your project init components are located. +3. `services/app/` - where `app-root`, `app-footer`, and `Init.ts` components + are located. 5. `assets/` - any static files located at the root of the site. -The root directory for the workspace is where both `app.ts` and `app.server.ts` -are located. +The root directory for the workspace is where both `main.ts` and +`main.server.ts` are located. For internationalization (i18n), it is best to keep it as a service component libraries while keeping the `contents/` directory as the page template. You can -pass the language code using the `app.routes.ts` routing mechanism and have -them rendered accordingly. +definite and pass the language code using the `app.routes.ts` routing mechanism. +Then, the page components can render the specific language accordingly. To avoid path conflicts, you should always check the `assets/` availability before creating the content inside `content/` directory. @@ -51,22 +51,33 @@ $ cd Angular/ $ ./serve.sh.ps1 # for development $ ./test.sh.ps1 # for test run $ ./build.sh.ps1 # for build +$ ./watch.sh.ps1 # for watch ``` This is mainly due to Angular does not have any pre-initialization function -where the workspace's critical data files can be updated dynamically -(example: `assets/manifest.webmanifest`, `assets/CNAME`, and etc). To workaround -this, a 2-steps execution is done inside these scripts where the `init.sh.ps1` -(sourced by the all shell scripts) is responsible for building and updating -these critical data files using the server-side-rendering. +to update critical SEO files generations autonomously & dynamically. Affected +files are: -Those are Polygot scripts which means it works on both UNIX and Windows OSes -natively. +* `assets/browserconfig.xml` +* `assets/CNAME` +* `assets/.nojekyll` +* `assets/sitemap.xml` +* `assets/robots.txt` +* `assets/manifest.webmanifest` +* `services/app/root.html` +This includes the `index.html` (`services/app/root.html`) that `angular.json` +depends on. To workaround this issue, a 2-steps execution is done using the +Shell (and PowerShell on Windows) scripts where Stage-1 is to prepare these +criticaly files dynamically while Stage-2 is your designated execution. +Those shell scripts are Polygot in nature so the same script works on both +UNIX and Windows OSes natively. -## Server-Side Rendering (SSR) or Static Site Generation (SSG) First + + +## Server-Side Rendering (SSR) or Static Site Generation (SSG) Enabled AutomataCI prioritizes the SSR and SSG (pre-rendering) facilities over other modes. There is a high chance this project is likely being used to generate @@ -103,17 +114,19 @@ To set the base URL, update the `baseHref` and `deployUrl` data inside the `baseHref` is used as the website base URL while `deployUrl` is for the asset-only base URL. -**DO NOT SEND IN ANY OF THEM VIA COMMAND ARGUMENTS**. The workspace -initialization is only sourcing from `angular.json` data file. Failure can -cause unknown and time-consuming concequences. +> **DO NOT SEND IN ANY OF THEM VIA COMMAND ARGUMENTS** +> +> The workspace initialization is only sourcing from `angular.json` data file. +> Failure can cause unknown and time-consuming concequences. ## Site-Level Metadata -You just need to edit `services/app/metadata.ts` data file that houses the -site-level metadata. Each fields are documented with inline specifications. +You just need to edit `services/app/Metadata.ts` data file that houses the +site-level metadata. Each fields are documented with its inline +specifications. @@ -121,8 +134,9 @@ site-level metadata. Each fields are documented with inline specifications. ## PWA Web Manifest By default, the workspace engine prepares and generates the -`manifest.webmanifest` file dynamically via the 2-steps operation using the -`services/app/metadata.ts` data. +`assets/manifest.webmanifest` file dynamically in the Stage-1 execution. + +It generally uses `services/app/Metadata.ts` data for dynamic configurations. @@ -130,11 +144,12 @@ By default, the workspace engine prepares and generates the ## Site Favicons, Logos, and Screenshots This workspace defines the favicons, logos, and screenshots metadata in the -`services/app/metadata.ts` data file ("Icons" section). You can supply the -media files in the `assets/` directory. +`services/app/Metadata.ts` data file. You can supply the media files in the +`assets/` directory (e.g. `assets/screenshots/`, `assets/thumbnails/`, +`assets/logos/`). -The default media files are located in `assets/logos/` directory. On the -first run, you can just update these media files to match your project. +If you just want to dive in with site construction, simply override the +existing template files in the `assets/` directory is suffice. @@ -143,9 +158,15 @@ first run, you can just update these media files to match your project. By default, AutomataCI setup the default image thumbnails located inside the `assets/thumbnails/` directory. These images serve as the fall back images -in case the page-level ones failed. +in case the page-level ones failed. These default thumbnails are configurable +in the `services/app/Metadata.ts` file. -In practice, these Open-Graph metadata must be updated at page-level. +The `services/app/root.html` (main template file) is dynamically generated +in Stage-1 execution with the site-level thumbnails meta included. + +Currently, Angular updates the meta tags dynamically using `Meta` and `Title` +services from `@angular/platform-browser` library. There is no way to statically +generate/patch the output `index.html` file yet. Recommended media dimension would be: @@ -153,45 +174,15 @@ Recommended media dimension would be: 2. `1200x1200` - square presentation 3. `480x480` - fallback presentation -You are free to alter the thumbnails located in `services/app/root.html` head -section. -For image, please use: -``` - - - - - -``` -For video, please use: - -``` - - - - - -``` +## LD+JSON Search Engine Optimization Schematic Data -For audio, please use: +This feature is pending and under development. ``` - - -``` - -For more technical specification, please refer to the following: - -1. https://ogp.me/ - - - - -## LD+JSON Search Engine Optimization Schematic Data - +SPEC: By default, AutomataCI only setup the default empty tag in the `services/app/root.html`. @@ -200,16 +191,21 @@ For more technical specifications, please refer to the following: 1. https://schema.org/docs/full.html +For SEO, the default page **MUST** be the root page since Angular only +pre-render the landing page HTML once and let its Javascript to take over +the others. +``` + ## Sitemaps & `Robots.txt` Search Engine Optimization (SEO) -By default, the engine generates both the sitemaps and the `robots.txt` -autonomously using the `services/app/metadata.ts` data file via the 2-steps -operation. +By default, the engine generates both the sitemaps (`assets/sitemap.xml` and +the `assets/sitemaps/` directory) and the `assets/robots.txt` autonomously +using the `services/app/Metadata.ts` data file via Stage-1 execution. -The engine is designed to utilize sitemap index for large scale contents. +It uses the sitemap index methodology for large scale contents mapping. @@ -217,7 +213,8 @@ The engine is designed to utilize sitemap index for large scale contents. ## `browserconfig.xml` fallback configuration While deemed obselete, this file is generated autonomously for backward -compatibilities and request silencing via the 2-steps operation. +compatibilities and silencing the request using the `services/app/Metadata.ts` +data file via Stage-1 execution. @@ -226,7 +223,7 @@ compatibilities and request silencing via the 2-steps operation. Specific to GitHub Pages, the engine generates the `assets/.nojekyll` configuration file for instructing the facility not to use -[Jekyll](https://jekyllrb.com/) via the 2-steps operation. +[Jekyll](https://jekyllrb.com/) via Stage-1 execution. @@ -234,5 +231,7 @@ configuration file for instructing the facility not to use ## `CNAME` configuration file Specific to GitHub Pages, the engine generates the `assets/CNAME` config -file based on the `services/app/metadata.ts` data file. This is used by -GitHub Pages to implement custom domain configurations. +file based on the `services/app/Metadata.ts` data file via Stage-1 execution. + +This file is used by GitHub Pages to implement custom domain configurations +persistently. diff --git a/Angular/app.config.server.ts b/Angular/app.config.server.ts index 1420c354..c15b6c68 100644 --- a/Angular/app.config.server.ts +++ b/Angular/app.config.server.ts @@ -3,6 +3,7 @@ */ import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; import { provideServerRendering } from '@angular/platform-server'; + import { appConfig } from './app.config'; @@ -10,7 +11,7 @@ import { appConfig } from './app.config'; const serverConfig: ApplicationConfig = { providers: [ - provideServerRendering() + provideServerRendering(), ] }; export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/Angular/app.config.ts b/Angular/app.config.ts index 79b174de..ec1959b3 100644 --- a/Angular/app.config.ts +++ b/Angular/app.config.ts @@ -3,11 +3,43 @@ */ import { ApplicationConfig, isDevMode } from '@angular/core'; import { provideRouter } from '@angular/router'; - -import { routes } from './app.routes'; +import { APP_BASE_HREF, PlatformLocation } from '@angular/common'; import { provideClientHydration } from '@angular/platform-browser'; import { provideServiceWorker } from '@angular/service-worker'; +import { routes } from './app.routes'; + + + + +export function Parse_URL_Base(target: PlatformLocation): string { + // try parsing from DOM + var output = target.getBaseHrefFromDOM().replace(/\/$/, ""); + if (output != '') { + return output; + } + + + // no choice - construct from protocol, hostname, and port + switch (target.protocol) { + case 'http:': + case 'https:': + output = `${target.protocol}//`; + break; + default: + output = `${target.protocol}`; + break; + } + + output += target.hostname; + + if (target.port != "") { + output += `:${target.port}`; + } + + return output; +} + @@ -18,6 +50,10 @@ export const appConfig: ApplicationConfig = { provideServiceWorker('ngsw-worker.js', { enabled: !isDevMode(), registrationStrategy: 'registerWhenStable:30000' - }), + }), { + provide: APP_BASE_HREF, + useFactory: Parse_URL_Base, + deps: [PlatformLocation], + }, ] }; diff --git a/Angular/app.routes.ts b/Angular/app.routes.ts index b1366a78..30759f2a 100644 --- a/Angular/app.routes.ts +++ b/Angular/app.routes.ts @@ -3,9 +3,11 @@ */ import { Routes } from '@angular/router'; -import { Page_Root } from "./contents/page"; -import { Page_404 } from "./contents/404/page"; -import { Page_Lang } from "./contents/lang/page"; +import { METADATA_SITE } from "services/app/Metadata"; + +import { Page_Root } from "contents/Page"; +import { Page_404 } from "contents/404/Page"; +import { Page_Lang } from "contents/lang/Page"; @@ -16,7 +18,7 @@ export const routes: Routes = [ path: '', component: Page_Root, data: { - lang: '', + lang: 'en', }, }, @@ -35,5 +37,8 @@ export const routes: Routes = [ { path: '**', component: Page_404, + data: { + lang: 'en', + }, }, ]; diff --git a/Angular/build.sh.ps1 b/Angular/build.sh.ps1 index bce75bd6..2df867ea 100755 --- a/Angular/build.sh.ps1 +++ b/Angular/build.sh.ps1 @@ -24,17 +24,13 @@ echo \" <<'RUN_AS_POWERSHELL' >/dev/null # " | Out-Null ################################################################################ # Windows POWERSHELL Codes # ################################################################################ -# execute -$null = . ".\init.sh.ps1" -$null = Write-Host "initializing the repository..." -$null = ng build --aot --configuration production --server main.server.ts | Out-Null -$null = Remove-Item -Recurse -Force ".\dist" -ErrorAction SilentlyContinue -$null = [System.IO.File]::FlushAll() -$null = ng build --aot --configuration production +${env:WORKSPACE_ROOT} = Get-Location +${env:WORKSPACE_RUN} = 'production' +$___process = . "${env:WORKSPACE_ROOT}\services\app\shell\build.ps1" ################################################################################ # Windows POWERSHELL Codes # ################################################################################ -exit +exit $___process <# RUN_AS_POWERSHELL @@ -44,13 +40,9 @@ RUN_AS_POWERSHELL ################################################################################ # Unix Main Codes # ################################################################################ -# execute -. "./init.sh.ps1" -1>&2 printf -- "%s\n" "initializing the repository..." -ng build --aot --configuration production --server main.server.ts &> /dev/null -rm -rf "./dist/" &> /dev/null -sync -ng build --aot --configuration production +WORKSPACE_ROOT="$PWD" +WORKSPACE_RUN="production" +. "${WORKSPACE_ROOT}/services/app/shell/build.sh" ################################################################################ # Unix Main Codes # ################################################################################ diff --git a/Angular/contents/404/Page.ts b/Angular/contents/404/Page.ts new file mode 100644 index 00000000..39c0f426 --- /dev/null +++ b/Angular/contents/404/Page.ts @@ -0,0 +1,35 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { Component } from '@angular/core'; +import { RouterOutlet, ActivatedRoute } from '@angular/router'; + +import { Data_Page } from './data-i18n'; + + + + +@Component({ + selector: 'page-404', + imports: [RouterOutlet], + templateUrl: './page.html', + styleUrl: './page.css' +}) +export class Page_404 { + public constructor( + private route: ActivatedRoute, + public Metadata: Data_Page, + ) { + this.Metadata.Init(route); + } + + + public ngOnInit() { + if (!this.Metadata.Mode_Browser) { + // server mode (SSR|SSG|pre-render) + return; + } + + // browser mode + } +} diff --git a/Angular/contents/404/data-i18n.ts b/Angular/contents/404/data-i18n.ts new file mode 100644 index 00000000..aec06d48 --- /dev/null +++ b/Angular/contents/404/data-i18n.ts @@ -0,0 +1,49 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { Injectable, PLATFORM_ID } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; +import { ActivatedRoute } from '@angular/router'; + +import { Metadata_Page } from 'services/app/Definitions'; +import { Service_Page, METADATA_SITE } from 'services/app/Metadata'; + + + + +@Injectable({ + providedIn: 'root', +}) +export class Data_Page extends Metadata_Page { + public constructor( + private service: Service_Page, + ) { + super(); + } + + + public Init(route: ActivatedRoute) { + // setting functional metadata + this.Mode_Browser = isPlatformBrowser(PLATFORM_ID); + this.URL = route.snapshot.url.toString(); + this.Site = METADATA_SITE; + + + // setting language-specific metadata + switch (route.snapshot.data['lang']) { + case 'en': + case 'en-Latn': + default: + // English (en) + this.Lang = 'en'; + this.Title = '404'; + this.Description = 'Page Not Found'; + this.Keywords = 'website, 404'; + break; + } + + + // initiate the page + this.service.Init(this); + } +} diff --git a/Angular/contents/404/page.css b/Angular/contents/404/page.css index 0e6b958a..e69de29b 100644 --- a/Angular/contents/404/page.css +++ b/Angular/contents/404/page.css @@ -1,3 +0,0 @@ -/* - * COPYRIGHT LICENSE NOTICE HERE - */ diff --git a/Angular/contents/404/page.html b/Angular/contents/404/page.html index a201fdca..d8220db5 100644 --- a/Angular/contents/404/page.html +++ b/Angular/contents/404/page.html @@ -1,8 +1,5 @@ - -
-

Hello, {{ title }}

-

Just for extra testing

-
+
+

{{ Metadata.Title }}

+

{{ Metadata.Description }}

+
diff --git a/Angular/contents/404/page.spec.ts b/Angular/contents/404/page.spec.ts index 53d45fab..98ae688d 100644 --- a/Angular/contents/404/page.spec.ts +++ b/Angular/contents/404/page.spec.ts @@ -2,21 +2,16 @@ * COPYRIGHT LICENSE NOTICE HERE */ import { TestBed } from '@angular/core/testing'; -import { Page_404 } from './page'; -describe('Page_404', () => { +describe('Page_Lang', () => { beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [Page_404], - }).compileComponents(); + console.log('run before each test case'); }); - it('should create the app', () => { - const fixture = TestBed.createComponent(Page_404); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); + it('a test case here', () => { + expect(true).toBeTruthy(); }); }); diff --git a/Angular/contents/404/page.ts b/Angular/contents/404/page.ts deleted file mode 100644 index 5173f723..00000000 --- a/Angular/contents/404/page.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * COPYRIGHT LICENSE NOTICE HERE - */ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; - - - - -@Component({ - selector: 'page-404', - imports: [RouterOutlet], - templateUrl: './page.html', - styleUrl: './page.css' -}) -export class Page_404 { - public title: string = "404"; -} diff --git a/Angular/contents/Page.ts b/Angular/contents/Page.ts new file mode 100644 index 00000000..c54c3704 --- /dev/null +++ b/Angular/contents/Page.ts @@ -0,0 +1,35 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { Component } from '@angular/core'; +import { RouterOutlet, ActivatedRoute } from '@angular/router'; + +import { Data_Page } from './data-i18n'; + + + + +@Component({ + selector: 'page-root', + imports: [RouterOutlet], + templateUrl: './page.html', + styleUrl: './page.css' +}) +export class Page_Root { + public constructor( + private route: ActivatedRoute, + public Metadata: Data_Page, + ) { + this.Metadata.Init(route); + } + + + public ngOnInit(): void { + if (!this.Metadata.Mode_Browser) { + // server mode (SSR|SSG|pre-render) + return; + } + + // browser mode + } +} diff --git a/Angular/contents/data-i18n.ts b/Angular/contents/data-i18n.ts new file mode 100644 index 00000000..045ac32d --- /dev/null +++ b/Angular/contents/data-i18n.ts @@ -0,0 +1,50 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { Injectable, PLATFORM_ID } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; +import { ActivatedRoute } from '@angular/router'; + +import { Metadata_Page } from 'services/app/Definitions'; +import { Service_Page, METADATA_SITE } from 'services/app/Metadata'; + + + + +@Injectable({ + providedIn: 'root', +}) +export class Data_Page extends Metadata_Page { + public constructor( + private route: ActivatedRoute, + private service: Service_Page, + ) { + super(); + } + + + public Init(route: ActivatedRoute) { + // setting functional metadata + this.Mode_Browser = isPlatformBrowser(PLATFORM_ID); + this.URL = route.snapshot.url.toString(); + this.Site = METADATA_SITE; + + + // setting language-specific metadata + switch (route.snapshot.data['lang']) { + case 'en': + case 'en-Latn': + default: + // English (en) + this.Lang = 'en'; + this.Title = this.Site.Name[this.Lang]; + this.Description = 'Hello World'; + this.Keywords = 'website, landing, root'; + break; + } + + + // initiate the page + this.service.Init(this); + } +} diff --git a/Angular/contents/lang/Page.ts b/Angular/contents/lang/Page.ts new file mode 100644 index 00000000..5dc53f40 --- /dev/null +++ b/Angular/contents/lang/Page.ts @@ -0,0 +1,35 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { Component } from '@angular/core'; +import { RouterOutlet, ActivatedRoute } from '@angular/router'; + +import { Data_Page } from './data-i18n'; + + + + +@Component({ + selector: 'page-lang', + imports: [RouterOutlet], + templateUrl: './page.html', + styleUrl: './page.css' +}) +export class Page_Lang { + public constructor( + private route: ActivatedRoute, + public Metadata: Data_Page, + ) { + this.Metadata.Init(route); + } + + + public ngOnInit(): void { + if (!this.Metadata.Mode_Browser) { + // server mode (SSR|SSG|pre-render) + return; + } + + // browser mode + } +} diff --git a/Angular/contents/lang/data-i18n.ts b/Angular/contents/lang/data-i18n.ts new file mode 100644 index 00000000..ebdbf4cb --- /dev/null +++ b/Angular/contents/lang/data-i18n.ts @@ -0,0 +1,47 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { Injectable, PLATFORM_ID } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; +import { ActivatedRoute } from '@angular/router'; + +import { Metadata_Page } from 'services/app/Definitions'; +import { Service_Page, METADATA_SITE } from 'services/app/Metadata'; + + + + +@Injectable({ + providedIn: 'root', +}) +export class Data_Page extends Metadata_Page { + public constructor( + private service: Service_Page, + ) { + super(); + } + + + public Init(route: ActivatedRoute) { + // setting functional metadata + this.Mode_Browser = isPlatformBrowser(PLATFORM_ID); + this.URL = route.snapshot.url.toString(); + this.Site = METADATA_SITE; + + // setting language-specific metadata + switch (route.snapshot.data['lang']) { + case 'en': + case 'en-Latn': + default: + // English (en) + this.Lang = 'en'; + this.Title = 'Language-Specific Landing Page'; + this.Description = 'the language-specific landing page.'; + this.Keywords = `website, landing, ${this.Lang}`; + break; + } + + // initiate the page + this.service.Init(this); + } +} diff --git a/Angular/contents/lang/page.css b/Angular/contents/lang/page.css index e69de29b..019a78e3 100644 --- a/Angular/contents/lang/page.css +++ b/Angular/contents/lang/page.css @@ -0,0 +1,17 @@ +section.selector .panel { + margin: 2rem 0; +} + +section.selector .panel .catalog { + display: flex; + flex-direction: column; + justify-content: center; + gap: 2rem; + + margin: 4rem 0; +} + +section.selector .panel .catalog > .social { + min-width: 80vw; + --social-height: 8rem; +} diff --git a/Angular/contents/lang/page.html b/Angular/contents/lang/page.html index d28e09ef..d8220db5 100644 --- a/Angular/contents/lang/page.html +++ b/Angular/contents/lang/page.html @@ -1,5 +1,5 @@ -
-

Hello, {{ Title }}

-

Just for extra testing.

+
+

{{ Metadata.Title }}

+

{{ Metadata.Description }}

diff --git a/Angular/contents/lang/page.spec.ts b/Angular/contents/lang/page.spec.ts index 3af840c4..98ae688d 100644 --- a/Angular/contents/lang/page.spec.ts +++ b/Angular/contents/lang/page.spec.ts @@ -2,22 +2,16 @@ * COPYRIGHT LICENSE NOTICE HERE */ import { TestBed } from '@angular/core/testing'; -import { Page_Lang } from './page'; describe('Page_Lang', () => { beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [Page_Lang], - }).compileComponents(); + console.log('run before each test case'); }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(Page_Lang); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); + it('a test case here', () => { + expect(true).toBeTruthy(); }); }); diff --git a/Angular/contents/lang/page.ts b/Angular/contents/lang/page.ts deleted file mode 100644 index 48b0cf49..00000000 --- a/Angular/contents/lang/page.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * COPYRIGHT LICENSE NOTICE HERE - */ -import { Component, PLATFORM_ID } from '@angular/core'; -import { isPlatformBrowser } from '@angular/common'; -import { RouterOutlet } from '@angular/router'; -import { METADATA_SITE } from 'services/app/metadata'; - - - - -@Component({ - selector: 'page-lang', - imports: [RouterOutlet], - templateUrl: './page.html', - styleUrl: './page.css' -}) -export class Page_Lang { - /* system level settings */ - private is_browser_mode: boolean; - - public Lang: string; - public Title: string; - - - public constructor() { - /* determine runtime mode */ - this.is_browser_mode = isPlatformBrowser(PLATFORM_ID); - - - /* determine page language */ - this.Lang = 'en'; - this.Title = METADATA_SITE.Name[this.Lang]; - } - - - public ngOnInit() { - this.init_common(); - - if (!this.is_browser_mode) { - this.init_server_mode(); - return; - } - - this.init_browser_mode(); - } - - - private init_common() { - return; - } - - - private init_server_mode() { - return; - } - - - private init_browser_mode() { - return; - } -} diff --git a/Angular/contents/page.css b/Angular/contents/page.css index 0e6b958a..e69de29b 100644 --- a/Angular/contents/page.css +++ b/Angular/contents/page.css @@ -1,3 +0,0 @@ -/* - * COPYRIGHT LICENSE NOTICE HERE - */ diff --git a/Angular/contents/page.html b/Angular/contents/page.html index de9bcb5c..d8220db5 100644 --- a/Angular/contents/page.html +++ b/Angular/contents/page.html @@ -1,5 +1,5 @@
-

Hello, {{ title }}

-

Just for extra testing

+

{{ Metadata.Title }}

+

{{ Metadata.Description }}

diff --git a/Angular/contents/page.spec.ts b/Angular/contents/page.spec.ts index 853041b8..4ef65477 100644 --- a/Angular/contents/page.spec.ts +++ b/Angular/contents/page.spec.ts @@ -2,21 +2,16 @@ * COPYRIGHT LICENSE NOTICE HERE */ import { TestBed } from '@angular/core/testing'; -import { Page_Root } from './page'; describe('Page_Root', () => { beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [Page_Root], - }).compileComponents(); + console.log('run before each test case'); }); - it('should create the app', () => { - const fixture = TestBed.createComponent(Page_Root); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); + it('a test case here', () => { + expect(true).toBeTruthy(); }); }); diff --git a/Angular/contents/page.ts b/Angular/contents/page.ts deleted file mode 100644 index e799e8da..00000000 --- a/Angular/contents/page.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * COPYRIGHT LICENSE NOTICE HERE - */ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; - - - - -@Component({ - selector: 'page-root', - imports: [RouterOutlet], - templateUrl: './page.html', - styleUrl: './page.css' -}) -export class Page_Root { - public title: string = "Root"; -} diff --git a/Angular/init.sh.ps1 b/Angular/init.sh.ps1 deleted file mode 100644 index 98dd254b..00000000 --- a/Angular/init.sh.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -echo \" <<'RUN_AS_BATCH' >/dev/null ">NUL "\" \`" <#" -@ECHO OFF -REM LICENSE CLAUSES HERE -REM ---------------------------------------------------------------------------- - - - - -REM ############################################################################ -REM # Windows BATCH Codes # -REM ############################################################################ -echo "[ ERROR ] --> powershell.exe !!!" -exit /b 1 -REM ############################################################################ -REM # Windows BATCH Codes # -REM ############################################################################ -RUN_AS_BATCH -#> | Out-Null - - - - -echo \" <<'RUN_AS_POWERSHELL' >/dev/null # " | Out-Null -################################################################################ -# Windows POWERSHELL Codes # -################################################################################ -# execute -$null = Write-Host "clean-ing up existing configuration files..." -$null = Remove-Item -Force ".\assets\manifest.webmanifest" -ErrorAction SilentlyContinue -$null = Remove-Item -Force ".\assets\CNAME" -ErrorAction SilentlyContinue -$null = Remove-Item -Recursive -Force ".\assets\sitemaps" -ErrorAction SilentlyContinue -$null = Remove-Item -Force ".\assets\sitemap.xml" -ErrorAction SilentlyContinue -$null = Remove-Item -Force ".\assets\robots.txt" -ErrorAction SilentlyContinue -$null = Remove-Item -Force ".\assets\browserconfig.xml" -ErrorAction SilentlyContinue -$null = Remove-Item -Force ".\assets\.nojekyll" -ErrorAction SilentlyContinue -return -################################################################################ -# Windows POWERSHELL Codes # -################################################################################ -exit -<# -RUN_AS_POWERSHELL - - - - -################################################################################ -# Unix Main Codes # -################################################################################ -# execute -1>&2 printf -- "%s\n" "clean-ing up existing configuration files..." -rm "./assets/manifest.webmanifest" &> /dev/null -rm "./assets/CNAME" &> /dev/null -rm -r "./assets/sitemaps/" &> /dev/null -rm "./assets/sitemap.xml" &> /dev/null -rm "./assets/robots.txt" &> /dev/null -rm "./assets/browserconfig.xml" &> /dev/null -rm "./assets/.nojekyll" &> /dev/null -return 0 -################################################################################ -# Unix Main Codes # -################################################################################ -exit $? -#> diff --git a/Angular/main.server.ts b/Angular/main.server.ts index 00611ab4..c38924f4 100644 --- a/Angular/main.server.ts +++ b/Angular/main.server.ts @@ -1,28 +1,79 @@ /* * COPYRIGHT LICENSE NOTICE HERE */ +import * as fs from 'fs'; +import { join } from 'path'; + +import { Inject, isDevMode } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; + +import { App } from 'services/app/Root'; +import { Init_Site } from 'services/app/seo/Start'; + import { config } from './app.config.server'; -import { App } from './services/app/root'; -import { Create_Web_Manifest } from './services/init/pwa'; -import { Create_CNAME } from './services/init/cname'; -import { Create_SEO } from './services/init/seo'; -import { Create_Browser_Config_XML } from './services/init/browserconfig-xml'; -import { Create_No_Jekyll } from './services/init/no-jekyll'; -/* initializing project's dynamic configurations */ -Create_CNAME(); -Create_Web_Manifest(); -Create_SEO(); -Create_Browser_Config_XML(); -Create_No_Jekyll(); +function get_path_asset(): string { + // this is the workspace pathing + return "assets"; +} + + + + +function get_path_app_root(): string { + // this is the workspace pathing + return "services/app"; +} + + + + +function get_base_url(): string { + if(isDevMode()) { + return ''; + } + + try { + // Read angular.json file + const filepath = join(process.cwd(), 'angular.json'); + const file_content = fs.readFileSync(filepath, 'utf-8'); + + // Parse JSON safely + const json = JSON.parse(file_content); + + // Get first project key if project name not specified + const keys = Object.keys(json.projects || {}); + if (keys.length === 0) { + console.warn('No projects found in angular.json'); + return ''; + } + const project = json.projects["app"]; + + // Safely navigate the object structure + const url = project?.architect?.build?.configurations?.production?.baseHref; + + // make sure the base url is the correct type and without tailing slash + if (typeof url === 'string') { + return url.replace(/\/$/, ""); + } + } catch (error) { + console.warn('Error reading baseHref from angular.json:', error); + } + + return ''; +} /* main code */ +Init_Site(get_path_app_root(), + get_path_asset(), + get_base_url(), + 'prerender-routes.txt', +); const bootstrap = () => bootstrapApplication(App, config); export default bootstrap; diff --git a/Angular/main.ts b/Angular/main.ts index bd434d9e..d80244a5 100644 --- a/Angular/main.ts +++ b/Angular/main.ts @@ -2,9 +2,11 @@ * COPYRIGHT LICENSE NOTICE HERE */ import { bootstrapApplication } from '@angular/platform-browser'; + +import { App } from 'services/app/Root'; +import { Footer } from 'services/app/Footer'; + import { appConfig } from './app.config'; -import { App } from './services/app/root'; -import { Footer } from './services/app/footer'; diff --git a/Angular/package-lock.json b/Angular/package-lock.json index 532db775..163f11d5 100644 --- a/Angular/package-lock.json +++ b/Angular/package-lock.json @@ -18,15 +18,15 @@ "@angular/platform-server": "^19.0.0", "@angular/router": "^19.0.0", "@angular/service-worker": "^19.0.0", - "@angular/ssr": "^19.0.4", + "@angular/ssr": "^19.0.5", "express": "^4.21.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^19.0.4", - "@angular/cli": "^19.0.4", + "@angular-devkit/build-angular": "^19.0.5", + "@angular/cli": "^19.0.5", "@angular/compiler-cli": "^19.0.0", "@types/express": "^4.17.17", "@types/jasmine": "~4.3.0", @@ -54,13 +54,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1900.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1900.4.tgz", - "integrity": "sha512-9XwZ21BPYS2vGOOwVB40fsMyuwJT0H1lWaAMo8Umwi6XbKBVfaWbEhjtR9dlarrySKtFuTz9hmTZkIXHLjXPdA==", + "version": "0.1900.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1900.5.tgz", + "integrity": "sha512-JxgoIxwGw3QNj6e70d04g5yJ8ZK0g/my22UK0TlRJRbYcfFQr8pL7u3wq77iNlgeHMDwBskZEf4TEZOVSbm7mw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.0.4", + "@angular-devkit/core": "19.0.5", "rxjs": "7.8.1" }, "engines": { @@ -70,9 +70,9 @@ } }, "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.4.tgz", - "integrity": "sha512-+imxIj1JLr2hbUYQePHgkTUKr0VmlxNSZvIREcCWtXUcdCypiwhJAtGXv6MfpB4hAx+FJZYEpVWeLwYOS/gW0A==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.5.tgz", + "integrity": "sha512-njBblpYHmlDI+Jtbub9NEm9RH+SBIFmmsgL9uJB8GxQVSo2qo4+f69nTkijRNN8WNKsSkYoRR9+JSl9QXWbyEA==", "dev": true, "license": "MIT", "dependencies": { @@ -163,17 +163,17 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.0.4.tgz", - "integrity": "sha512-n7fcRdNB7ed5j6aZI+qPI/1LylFv1OiRNgBIeJxX3HEmzQxsHHLcxWog2yZK2Fvw3390xFx/VjZaklITj6tBFA==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-19.0.5.tgz", + "integrity": "sha512-Z8GcTBsDGKPIKWtLoRVuss/oGytRaVXZSsXzfCapWjggwuN0B2b26Ms0kfU0kIWRfEzz38wKwug/1l86Q9HqNA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1900.4", - "@angular-devkit/build-webpack": "0.1900.4", - "@angular-devkit/core": "19.0.4", - "@angular/build": "19.0.4", + "@angular-devkit/architect": "0.1900.5", + "@angular-devkit/build-webpack": "0.1900.5", + "@angular-devkit/core": "19.0.5", + "@angular/build": "19.0.5", "@babel/core": "7.26.0", "@babel/generator": "7.26.2", "@babel/helper-annotate-as-pure": "7.25.9", @@ -184,7 +184,7 @@ "@babel/preset-env": "7.26.0", "@babel/runtime": "7.26.0", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "19.0.4", + "@ngtools/webpack": "19.0.5", "@vitejs/plugin-basic-ssl": "1.1.0", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", @@ -238,7 +238,7 @@ "@angular/localize": "^19.0.0", "@angular/platform-server": "^19.0.0", "@angular/service-worker": "^19.0.0", - "@angular/ssr": "^19.0.4", + "@angular/ssr": "^19.0.5", "@web/test-runner": "^0.19.0", "browser-sync": "^3.0.2", "jest": "^29.5.0", @@ -289,9 +289,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.4.tgz", - "integrity": "sha512-+imxIj1JLr2hbUYQePHgkTUKr0VmlxNSZvIREcCWtXUcdCypiwhJAtGXv6MfpB4hAx+FJZYEpVWeLwYOS/gW0A==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.5.tgz", + "integrity": "sha512-njBblpYHmlDI+Jtbub9NEm9RH+SBIFmmsgL9uJB8GxQVSo2qo4+f69nTkijRNN8WNKsSkYoRR9+JSl9QXWbyEA==", "dev": true, "license": "MIT", "dependencies": { @@ -317,14 +317,14 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular/build": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.0.4.tgz", - "integrity": "sha512-ubsNjLb54VkZwcPQ21Ke8aAHiIrRIcv7gG3R6/6XOoWeK1K2+tsv8bnO4mz5cHgzWOspLOT7FDC83NJjrKX3Nw==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-19.0.5.tgz", + "integrity": "sha512-/4msIXebFfDWcsyYGDzcxrhn1G1bWVTVbLYqkDXDVYFTqWRpBA8UtQ6eLM8FrJqrHw9e/1cxkqBNsR0tkDJ9FQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1900.4", + "@angular-devkit/architect": "0.1900.5", "@babel/core": "7.26.0", "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", @@ -363,7 +363,7 @@ "@angular/localize": "^19.0.0", "@angular/platform-server": "^19.0.0", "@angular/service-worker": "^19.0.0", - "@angular/ssr": "^19.0.4", + "@angular/ssr": "^19.0.5", "less": "^4.2.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0", @@ -765,27 +765,14 @@ "fsevents": "~2.3.2" } }, - "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1900.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1900.4.tgz", - "integrity": "sha512-eovr5Am8EwxF7d/y0Hbfz/KYWnOXXVXVwquPUcg8JBI19lLbfctz4+71Vjz2qGroijr2FlZztRpmhd498SLt/A==", + "version": "0.1900.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1900.5.tgz", + "integrity": "sha512-SWrXxVS0u9RXq3bz1+rKfH79nYiqPL9qdJt4lAhTo5O+Uc+qEHLctLvkOYCJHqezLblJG2nGBhHTB0EBmi8pLg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1900.4", + "@angular-devkit/architect": "0.1900.5", "rxjs": "7.8.1" }, "engines": { @@ -799,13 +786,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.0.4.tgz", - "integrity": "sha512-2r6Qs4N5NSPho+qzegCYS8kIgylXyH4DHaS7HJ5+4XvM1I8V8AII8payLWkUK0i29XufVoD5XfPUFnjxZrBfYQ==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.0.5.tgz", + "integrity": "sha512-dhLVBVb0ECfcIP59azoD/5lJMSMU//bo1LEbuE0VrFA9orVxQhgilNuZeVXBr5sOll1PFjxs/fqyX8sAH9xQYw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.0.4", + "@angular-devkit/core": "19.0.5", "jsonc-parser": "3.3.1", "magic-string": "0.30.12", "ora": "5.4.1", @@ -818,9 +805,9 @@ } }, "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.4.tgz", - "integrity": "sha512-+imxIj1JLr2hbUYQePHgkTUKr0VmlxNSZvIREcCWtXUcdCypiwhJAtGXv6MfpB4hAx+FJZYEpVWeLwYOS/gW0A==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.5.tgz", + "integrity": "sha512-njBblpYHmlDI+Jtbub9NEm9RH+SBIFmmsgL9uJB8GxQVSo2qo4+f69nTkijRNN8WNKsSkYoRR9+JSl9QXWbyEA==", "dev": true, "license": "MIT", "dependencies": { @@ -926,18 +913,18 @@ } }, "node_modules/@angular/cli": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.0.4.tgz", - "integrity": "sha512-jxnD9qkhelcRMCrHDCxNsWgn6HQCvMIj8uI0T2eB9Vy93q2YWUo/fWl2Sy4gFlR+VNeF+1hYhPLb/vqLLzjWuA==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.0.5.tgz", + "integrity": "sha512-AalLr1EbgJqBbzk+5ZtXwg6wCwLlRLd+CRrZZcC6QSee69mfsU9jEP2KFlMAecajOCqAGK3H4ZRiTZNeQ3y5AA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1900.4", - "@angular-devkit/core": "19.0.4", - "@angular-devkit/schematics": "19.0.4", + "@angular-devkit/architect": "0.1900.5", + "@angular-devkit/core": "19.0.5", + "@angular-devkit/schematics": "19.0.5", "@inquirer/prompts": "7.1.0", "@listr2/prompt-adapter-inquirer": "2.0.18", - "@schematics/angular": "19.0.4", + "@schematics/angular": "19.0.5", "@yarnpkg/lockfile": "1.1.0", "ini": "5.0.0", "jsonc-parser": "3.3.1", @@ -960,9 +947,9 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.4.tgz", - "integrity": "sha512-+imxIj1JLr2hbUYQePHgkTUKr0VmlxNSZvIREcCWtXUcdCypiwhJAtGXv6MfpB4hAx+FJZYEpVWeLwYOS/gW0A==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.5.tgz", + "integrity": "sha512-njBblpYHmlDI+Jtbub9NEm9RH+SBIFmmsgL9uJB8GxQVSo2qo4+f69nTkijRNN8WNKsSkYoRR9+JSl9QXWbyEA==", "dev": true, "license": "MIT", "dependencies": { @@ -1052,19 +1039,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular/common": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.0.0.tgz", @@ -1292,9 +1266,9 @@ } }, "node_modules/@angular/ssr": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-19.0.4.tgz", - "integrity": "sha512-sK0yvr+qnsois6ocVxbtnY57J77DKoncsPWH34Qx6ypUrtqdbeyt0mtx0xlO8QIT5gP840vC39FIsAtQRDVK1g==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-19.0.5.tgz", + "integrity": "sha512-yQjtlxKcFZF5fL2vOsi7skTY9hHUTfGhByDxZyW9UeqQA1YIGhFHYL0iqB3czNDK7tVSmqnVof2aLvEkfaIvAQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -4399,9 +4373,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.0.4.tgz", - "integrity": "sha512-N3WCbQz5ipdAZoSWHNf81RLET6+isq35+GZu9u0StpFtJCpXAmRRAv4vdMUYL7DLOzRmvEgwww6Rd5AwGeLFSw==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-19.0.5.tgz", + "integrity": "sha512-T8BJQHbGySRkR4JYLcH3YIscbRJI/GNWidNHL5GzRG+3i8Z6XmR0KLTIEoZGaCLpTGR8hcCG5Lfj/uF5pa4Yww==", "dev": true, "license": "MIT", "engines": { @@ -5350,14 +5324,14 @@ ] }, "node_modules/@schematics/angular": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.0.4.tgz", - "integrity": "sha512-1fXBtkA/AjgMPxHLpGlw7NuT/wggCqAwBAmDnSiRnBBV7Pgs/tHorLgh7A9eoUi3c8CYCuAh8zqWNyjBGGigOQ==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.0.5.tgz", + "integrity": "sha512-4nBJZF8HvSdj/RoyIixAfOuKEQaEBsEBtohIow8iHX1wcLax558d70O/ZM6EOh2FQxmEaxUe1x4KwBQIha8RxA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.0.4", - "@angular-devkit/schematics": "19.0.4", + "@angular-devkit/core": "19.0.5", + "@angular-devkit/schematics": "19.0.5", "jsonc-parser": "3.3.1" }, "engines": { @@ -5367,9 +5341,9 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.4.tgz", - "integrity": "sha512-+imxIj1JLr2hbUYQePHgkTUKr0VmlxNSZvIREcCWtXUcdCypiwhJAtGXv6MfpB4hAx+FJZYEpVWeLwYOS/gW0A==", + "version": "19.0.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.5.tgz", + "integrity": "sha512-njBblpYHmlDI+Jtbub9NEm9RH+SBIFmmsgL9uJB8GxQVSo2qo4+f69nTkijRNN8WNKsSkYoRR9+JSl9QXWbyEA==", "dev": true, "license": "MIT", "dependencies": { @@ -11165,9 +11139,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -11175,6 +11149,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -12969,13 +12944,11 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -12983,24 +12956,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", diff --git a/Angular/package.json b/Angular/package.json index 46889960..a1ddf53d 100644 --- a/Angular/package.json +++ b/Angular/package.json @@ -21,15 +21,15 @@ "@angular/platform-server": "^19.0.0", "@angular/router": "^19.0.0", "@angular/service-worker": "^19.0.0", - "@angular/ssr": "^19.0.4", + "@angular/ssr": "^19.0.5", "express": "^4.21.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^19.0.4", - "@angular/cli": "^19.0.4", + "@angular-devkit/build-angular": "^19.0.5", + "@angular/cli": "^19.0.5", "@angular/compiler-cli": "^19.0.0", "@types/express": "^4.17.17", "@types/jasmine": "~4.3.0", diff --git a/Angular/serve.sh.ps1 b/Angular/serve.sh.ps1 index e83c3f85..e6188f28 100755 --- a/Angular/serve.sh.ps1 +++ b/Angular/serve.sh.ps1 @@ -24,17 +24,13 @@ echo \" <<'RUN_AS_POWERSHELL' >/dev/null # " | Out-Null ################################################################################ # Windows POWERSHELL Codes # ################################################################################ -# execute -$null = . ".\init.sh.ps1" -$null = Write-Host "initializing the repository..." -$null = ng build --configuration development --server main.server.ts | Out-Null -$null = Remove-Item -Recurse -Force ".\dist" -ErrorAction SilentlyContinue -$null = [System.IO.File]::FlushAll() -$null = ng serve +${env:WORKSPACE_ROOT} = Get-Location +${env:WORKSPACE_RUN} = 'development' +$___process = . "${env:WORKSPACE_ROOT}\app\shell\serve.ps1" ################################################################################ # Windows POWERSHELL Codes # ################################################################################ -exit +exit $___process <# RUN_AS_POWERSHELL @@ -44,13 +40,9 @@ RUN_AS_POWERSHELL ################################################################################ # Unix Main Codes # ################################################################################ -# execute -. "./init.sh.ps1" -1>&2 printf -- "%s\n" "initializing the repository..." -ng build --configuration development --server main.server.ts &> /dev/null -rm -rf "./dist/" &> /dev/null -sync -ng serve +WORKSPACE_ROOT="$PWD" +WORKSPACE_RUN="development" +. "${WORKSPACE_ROOT}/services/app/shell/serve.sh" ################################################################################ # Unix Main Codes # ################################################################################ diff --git a/Angular/server.ts b/Angular/server.ts index e2ba541d..5114d343 100644 --- a/Angular/server.ts +++ b/Angular/server.ts @@ -6,7 +6,7 @@ import { CommonEngine } from '@angular/ssr/node'; import express from 'express'; import { fileURLToPath } from 'node:url'; import { dirname, join, resolve } from 'node:path'; -import AppServerModule from './main.server'; +import bootstrap from './main.server'; @@ -14,37 +14,40 @@ import AppServerModule from './main.server'; // The Express app is exported so that it can be used by serverless Functions. export function app(): express.Express { const server = express(); - const serverDistFolder = dirname(fileURLToPath(import.meta.url)); - const browserDistFolder = resolve(serverDistFolder, '../browser'); - const indexHtml = join(serverDistFolder, 'index.server.html'); + const dir_dist_server = dirname(fileURLToPath(import.meta.url)); + const dir_dist_browser = resolve(dir_dist_server, '../browser'); + const html_index_server = join(dir_dist_server, 'index.server.html'); const commonEngine = new CommonEngine(); server.set('view engine', 'html'); - server.set('views', browserDistFolder); + server.set('views', dir_dist_browser); + // Example Express Rest API endpoints // server.get('/api/**', (req, res) => { }); // Serve static files from /browser - server.get('**', express.static(browserDistFolder, { + server.get('*.*', express.static(dir_dist_browser, { maxAge: '1y', - index: 'index.html', })); // All regular routes use the Angular engine - server.get('**', (req, res, next) => { - const { protocol, originalUrl, baseUrl, headers } = req; + server.get('*', (req, res, next) => { + const { protocol, originalUrl, baseUrl, headers } = req; - commonEngine - .render({ - bootstrap: AppServerModule, - documentFilePath: indexHtml, + commonEngine.render({ + bootstrap: bootstrap, + documentFilePath: html_index_server, url: `${protocol}://${headers.host}${originalUrl}`, - publicPath: browserDistFolder, + publicPath: dir_dist_browser, providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], }) - .then((html) => res.send(html)) - .catch((err) => next(err)); + .then((html) => { + res.send(html); + }) + .catch((err) => { + next(err); + }); }); return server; diff --git a/Angular/services/app/Definitions.ts b/Angular/services/app/Definitions.ts new file mode 100644 index 00000000..f3b4de62 --- /dev/null +++ b/Angular/services/app/Definitions.ts @@ -0,0 +1,276 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ + + +// IMPORTANT NOTICE +// (1) This is the kernel level source codes defining critical data structures +// for the project's proper operations. +// +// (2) These definitions can be modified to rely on your external libraries +// BUT only isolated at this level. You can use 'extend' to extend from +// your external libraries. +// +// (3) AVOID changing existing definitions at all cost. It can cripple the +// project without overhauling. +export class Application { + public ID: string = ''; + public Platform: string = ''; + public URL: string = ''; +} + +export class Application_Related { + public Prioritized: boolean = false; + public List: Application[] = []; +} + + + + +export class Contact { + public ID: string = ''; + public Type: string = ''; + public URL: string = ''; +} + + + + +export class Display { + public Primary: string = ''; + public Overrides: string[] = []; + public Orientation: string = ''; +} + + + + +export class Media { + public Text: { [lang: string]: string } = {}; + public Width: number = 0; + public Height: number = 0; + public Sources: Source[] = []; + public Is_Decorative?: boolean = false; + public Loading?: string = 'lazy'; + public CORS?: string = 'anonymous'; + public Form_Factor?: string = ''; + public Purpose?: string = ''; + public Relationship?: string = ''; + public Design?: string = ''; + public Preload?: string = ''; + public Control?: boolean = true; + public Autoplay?: boolean = false; + public Loop?: boolean = false; + public Mute?: boolean = false; + public Inline?: boolean = false; + public Tracks?: Track[] = []; +} + + + + +export class Metadata_Page { + public Lang: string = ''; + public Title: string = ''; + public Description: string = ''; + public Keywords: string = ''; + public Others: { [key: number]: any } = {}; + public Thumbnails: Media[] = []; + + public Mode_Browser: boolean = false; + public URL: string = ''; + public Site?: Metadata_Site = undefined; +} + + + + +export class Metadata_Site { + public ID: string = ''; + public ID_CNAME: string = ''; + public ID_APP: string = ''; + public ID_SKU: string = ''; + public Language_Default: string = ''; + public Name: { [lang: string]: string } = {}; + public Description: { [lang: string]: string } = {}; + public Keywords: { [lang: string]: string[] } = {}; + public Color_Theme_Foreground: string = ''; + public Color_Theme_Background: string = ''; + public Owners: Owner[] = []; + public Screenshots: Media[] = []; + public Protocol: Protocol = { + Start: '', + Scope: '', + Handlers: [], + }; + public Related_Application: Application_Related = { + Prioritized: false, + List: [], + }; + public Shortcuts: Shortcut[] = []; + public SEO: SEO = { + Add: [], + Remove: [], + Robot: '', + }; + public Icons: Media[] = []; + public Thumbnails: Media[] = []; + public Display: Display = { + Primary: '', + Overrides: [], + Orientation: '', + }; +} + + + + +export class Owner { + public UUID: string = ''; + public Name_Family: { [lang: string]: string } = {}; + public Name_Given: { [lang: string]: string } = {}; + public Call_Sign: { [lang: string]: string } = {}; + public Title: { [lang: string]: string } = {}; + public Description: { [lang: string]: string } = {}; + public Slogan: { [lang: string]: string } = {}; + public Contacts: { [type: string]: { [lang: string]: Contact } } = {}; + public Roles: string[] = []; +} + + + + +export class Protocol { + public Start: string = ''; + public Scope: string = ''; + public Handlers: Protocol_Handler[] = []; +} + +export class Protocol_Handler { + public Protocol: string = ''; + public URL: string = ''; +} + + + + +export class SEO { + public Add: string[] = []; + public Remove: string[] = []; + public Robot: string = ''; +} + + + + +export class Shortcut { + public URL: string = ''; + public Name_Long: { [lang: string]: string } = {}; + public Name_Short: { [lang: string]: string } = {}; + public Description: { [lang: string]: string } = {}; + public Icons: Media[] = []; +} + + + + +export class Source { + public URL: string = ''; + public Type: string = ''; + public Media?: string = ''; + public Descriptor?: string = ''; +} + + + + +export class Track { + public URL: string = ''; + public Kind: string = ''; + public Label: string = ''; + public Default: boolean = false; +} + + + + +export function Yield_Thumbnails( + media: Media[], + url_base: string, + lang: string, + label: string, +): Media[] { + var list: Media[] = []; + var text_alt = ''; + + + // validate input + if (media.length == 0) { + return list; + } + + + // execute + for (var i = 0; i < media.length; i++) { + if (media[i].Sources.length == 0) { + continue; + } + + var url = Yield_URL(media[i].Sources[0].URL, url_base); + if (url == '') { + continue; + } + + if (media[i].Width == 0 || media[i].Height == 0) { + continue; + } + + text_alt = label; + if (media[i].Text[lang] != '') { + text_alt = media[i].Text[lang]; + } + + if (media[i].Sources[0].Type.startsWith('image/')) { + } else if (media[i].Sources[0].Type.startsWith('video/')) { + } else if (media[i].Sources[0].Type.startsWith('audio/')) { + } else { + continue; + } + + list.push({ + Text: { + [lang]: text_alt, + }, + Sources: [{ + URL: url, + Type: media[i].Sources[0].Type, + }], + Width: media[i].Width, + Height: media[i].Height, + }); + } + + // report status + return list; +} + + + + +export function Yield_URL(url_given: string, url_base: string): string { + // execute + if (url_given == '' && url_base == '') { + return ''; + } + + if (url_given == '' && url_base != '') { + return url_base; + } + + if (url_given.replace(/^.*:/gm, '') != url_given) { + // given is an absolute protocol - return now + return url_given; + } + + return url_base.replace(/\/$/, "") + '/' + url_given.replace(/^\//, ""); +} diff --git a/Angular/services/app/footer.ts b/Angular/services/app/Footer.ts similarity index 100% rename from Angular/services/app/footer.ts rename to Angular/services/app/Footer.ts diff --git a/Angular/services/app/metadata.ts b/Angular/services/app/Metadata.ts similarity index 58% rename from Angular/services/app/metadata.ts rename to Angular/services/app/Metadata.ts index 9068b0cd..53b03e63 100644 --- a/Angular/services/app/metadata.ts +++ b/Angular/services/app/Metadata.ts @@ -1,12 +1,19 @@ /* * COPYRIGHT LICENSE NOTICE HERE */ -import { Owner, Metadata } from './metadata-definitions'; +import { Inject, Injectable, isDevMode, LOCALE_ID, PLATFORM_ID } from '@angular/core'; +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; +import { ActivatedRoute } from '@angular/router'; +import { Meta, Title } from '@angular/platform-browser'; +import { Owner, Metadata_Site, Metadata_Page, Yield_Thumbnails } from './Definitions'; +import { Service_URL } from './URL'; -export const METADATA_SITE: Metadata = { + + +export const METADATA_SITE: Metadata_Site = { // 'ID' is the general unique identification of the web application. A // good example will be the reversed URI like 'com.example.www.project'. // @@ -56,7 +63,7 @@ export const METADATA_SITE: Metadata = { // // Leaving this field empty can cause catastrophic failure. Name: { - "en": "Site Title Here", + "en": "Project Example", }, @@ -80,7 +87,6 @@ export const METADATA_SITE: Metadata = { Keywords: { "en": [ "website", - "webapp", ], }, @@ -88,13 +94,13 @@ export const METADATA_SITE: Metadata = { // 'Color_Theme_Foreground' sets the color of the foreground objects. // // Leaving this field empty can cause catastrophic failure. - Color_Theme_Foreground: "#FFFF00", + Color_Theme_Foreground: "#FFFFFF", // 'Color_Theme_Background' sets the color of the background. // // Leaving this field empty can cause catastrophic failure. - Color_Theme_Background: "#000428", + Color_Theme_Background: "#021B79", // 'Owners' sets the list of owners of this site. The first entry is @@ -154,29 +160,43 @@ export const METADATA_SITE: Metadata = { Owners: [{ UUID: '', Name_Family: { - "en": "Familia", + "en": "Holloway", }, Name_Given: { - "en": "Own Name", + "en": "Kean Ho", }, Call_Sign: { - "en": "Brand Name", + "en": "HollowayKeanHo", }, Title: { "en": "Mr.", }, Description: { - "en": "Simple Pitch", + "en": "Forward-thinking technology explorer.", }, Slogan: { - "en": "Simple speech", + "en": "Learn, Internalize, Grow, and Expand.", }, Contacts: { - website_01: { + bluesky_01: { + "en": { + ID: "hollowaykeanho.com", + Type: "bluesky", + URL: "https://bsky.app/profile/hollowaykeanho.com", + }, + }, + github_01: { "en": { - ID: "Official Website", - Type: "website", - URL: "https://www.example.com", + ID: "HollowayKeanHo", + Type: "github", + URL: "https://github.com/hollowaykeanho", + }, + }, + mastodon_01: { + "en": { + ID: "@hollowaykeanho", + Type: "mastodon", + URL: "https://mastodon.online/@hollowaykeanho", }, }, }, @@ -371,13 +391,15 @@ Allow: / // empty (''). // // - // 'Icons' are the list of display icon for the shortcut. + // 'Icons' are the list of display icon for the shortcut. Only the first + // entry of the 'Sources' is used. 'Sources.0.Text' is unused but is + // defined due to type requirement. // - // (1) 'Source' is the icon's source URL. The engine can convert + // (1) 'Sources.0.URL' is the icon's source URL. The engine can convert // relative URL to its absolute version in production mode // autonomously. It will skip the entry if left empty (''). - // (2) 'Type' is the icon's MIME ID. The engine will skip the entry - // if left empty (''). + // (2) 'Sources.0.Type' is the icon's MIME ID. The engine will skip the + // entry if left empty (''). // (3) 'Purpose' is the icon's PWA use intent. the value can be: // (a) 'any' = use in any mode; OR // (b) 'maskable' = use only for color re-masking. @@ -386,15 +408,18 @@ Allow: / // is invalid (less than or equal to 0). // // Example: - // Icons: [{ - // Source: '/logos/icon_57x57.png', - // Type: 'image/png', - // Purpose: 'any', - // Width: 57, - // Height: 57, - // }, { - // ... - // }], + // Icons: [{ + // Text: {}, + // Sources: [{ + // URL: '/logos/icon_1200x1200.png', + // Type: 'image/png', + // }], + // Width: 1200, + // Height: 1200, + // Purpose: 'any', + // }, { + // ... + // }], Shortcuts: [{ URL: '/', Name_Long: { @@ -407,11 +432,14 @@ Allow: / "en": "To the main overview page.", }, Icons: [{ - Source: '/logos/icon_1200x1200.png', - Type: 'image/png', - Purpose: 'any', + Text: {}, + Sources: [{ + URL: '/logos/icon_1200x1200.png', + Type: 'image/png', + }], Width: 1200, Height: 1200, + Purpose: 'any', }], }], @@ -419,7 +447,7 @@ Allow: / // 'Screenshots' are the list of marketing screenshots for the // application. // - // (1) 'Label' is the text label of the media. It can be used as the + // (1) 'Text' is the text label of the media. It can be used as the // alternate text if required. This field is internationalized // (i18n). The engine will skip the entry if left empty (''). // (2) 'Form_Factor' is the setting for displaying the screenshot on @@ -427,54 +455,133 @@ Allow: / // (a) empty ('') = always display the media; OR // (b) 'wide' = only display on horizontal widescreen; OR // (c) 'narrow' = only display on vertical narrow screen. - // (3) 'Source' is the screenshot's source URL. The engine can - // convert relative URL to its absolute version in production - // mode autonomously. If left empty (''), the engine will skip - // the entry. - // (4) 'Type' is the icon's MIME ID. The engine will skip the entry - // if left empty. - // (5) 'Width' and 'Height' are the default dimension + // (3) 'Sources' are the list of screenshot's source URL. Only the + // first entry (0) is used. The engine can convert relative URL + // to its absolute version in production mode autonomously. If + // left empty (''), the engine will skip the entry. + // (4) 'Sources.0.URL' is the icon's Source URL. The engine will skip + // the entry if left empty. + // (5) 'Sources.0.Type' is the icon's MIME ID. The engine will skip + // the entry if left empty. + // (6) 'Width' and 'Height' are the default dimension // of the icon in pixel. The engine will skip the entry if any // of them is invalid (less than or equal to 0). // // Example: // Screenshots: [{ - // Label: { + // Text: { // "en": 'Welcome', // }, // Form_Factor: 'wide', - // Source: '/screenshots/welcome_1200x630.png', - // Type: 'image/png', + // Sources: [{ + // URL: '/screenshots/welcome_1200x630.png', + // Type: 'image/png', + // }], // Width: 1200, // Height: 630, // }, { // ... // }], Screenshots: [{ - Label: { + Text: { "en": 'Welcome', }, Form_Factor: 'wide', - Source: '/screenshots/welcome_1200x630.png', - Type: 'image/png', + Sources: [{ + URL: '/screenshots/welcome_1200x630.png', + Type: 'image/png', + }], Width: 1200, Height: 630, }, { - Label: { + Text: { "en": 'Welcome', }, Form_Factor: 'narrow', - Source: '/screenshots/welcome_630x1200.png', - Type: 'image/png', + Sources: [{ + URL: '/screenshots/welcome_630x1200.png', + Type: 'image/png', + }], Width: 630, Height: 1200, }, { - Label: { + Text: { "en": 'Welcome', }, - Form_Factor: '', - Source: '/screenshots/welcome_1200x1200.png', - Type: 'image/png', + Sources: [{ + URL: '/screenshots/welcome_1200x1200.png', + Type: 'image/png', + }], + Width: 1200, + Height: 1200, + }], + + + // 'Thumbnails' are the list of default page thumbnails for fallback + // purposes. + // + // (1) 'Text' is the text label of the media content (not a caption). + // This field is internationalized (i18n). The engine will use + // the page or site title if left empty (''). + // (3) 'Sources' are the list of screenshot's source URL. Only the + // first entry (0) is used. The engine can convert relative URL + // to its absolute version in production mode autonomously. If + // left empty (''), the engine will skip the entry. + // (4) 'Sources.0.URL' is the icon's Source URL. The engine will skip + // the entry if left empty. + // (5) 'Sources.0.Type' is the icon's MIME ID. The engine only + // accepts 'image/*', 'video/*', and 'audio/*' types currently. + // It will skip the entry if other types are supplied or left + // empty. + // (6) 'Width' and 'Height' are the default dimension + // of the icon in pixel. The engine will skip the entry if any + // of them is invalid (less than or equal to 0). + // + // Example: + // Thumbnails: [{ + // Text: { + // "en": 'A thumbnail image', + // }, + // Form_Factor: 'wide', + // Sources: [{ + // URL: '/thumbnails/default_1200x630.png', + // Type: 'image/png', + // }], + // Width: 1200, + // Height: 630, + // }, { + // ... + // }], + Thumbnails: [{ + Text: { + "en": 'A thumbnail image', + }, + Form_Factor: 'wide', + Sources: [{ + URL: '/thumbnails/default_480x480.png', + Type: 'image/png', + }], + Width: 480, + Height: 480, + }, { + Text: { + "en": 'A thumbnail image', + }, + Form_Factor: 'narrow', + Sources: [{ + URL: '/thumbnails/default_1200x630.png', + Type: 'image/png', + }], + Width: 1200, + Height: 630, + }, { + Text: { + "en": 'A thumbnail image', + }, + Sources: [{ + URL: '/thumbnails/default_1200x1200.png', + Type: 'image/png', + }], Width: 1200, Height: 1200, }], @@ -482,160 +589,232 @@ Allow: / // 'Icons' are the list of display icon for the site. // - // (1) 'Source' is the icon's source URL. The engine can convert - // relative URL to its absolute version in production mode - // autonomously. It will skip the entry if left empty (''). - // (2) 'Type' is the icon's MIME ID. The engine will skip the entry - // if left empty (''). - // (3) 'Purpose' is the icon's PWA use intent. the value can be: + // (1) 'Sources' are the list icon's source URL. Only the first + // entry is used. The engine can convert relative URL to its + // absolute version in production mode autonomously. It will + // skip the entry if left empty ([]). + // (2) 'Source.0.URL' is the icon's source URL. The engine will skip + // the entry if left empty (''). + // (3) 'Source.0.Type' is the icon's MIME ID. The engine will skip + // the entry if left empty (''). + // (4) 'Purpose' is the icon's PWA use intent. the value can be: // (a) 'any' = use in any mode; OR // (b) 'maskable' = use only for color re-masking. - // (4) 'Width' and 'Height' are the default dimension of the icon + // (5) 'Width' and 'Height' are the default dimension of the icon // in pixel unit. The engine will skip the entry if any of them // is invalid (less than or equal to 0). // // Example: // Icons: [{ - // Source: '/logos/icon_57x57.png', - // Type: 'image/png', - // Purpose: 'any', + // Text: {}, + // Sources: [{ + // URL: '/logos/icon_57x57.png', + // Type: 'image/png', + // }], // Width: 57, // Height: 57, + // Purpose: 'any', // }, { // ... // }], Icons: [{ - Source: '/logos/icon_57x57.png', - Type: 'image/png', - Purpose: 'any', + Text: {}, + Sources: [{ + URL: '/logos/icon_57x57.png', + Type: 'image/png', + }], Width: 57, Height: 57, - }, { - Source: '/logos/icon_60x60.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_60x60.png', + Type: 'image/png', + }], Width: 60, Height: 60, - }, { - Source: '/logos/icon_70x70.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_70x70.png', + Type: 'image/png', + }], Width: 70, Height: 70, - }, { - Source: '/logos/icon_72x72.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_72x72.png', + Type: 'image/png', + }], Width: 72, Height: 72, - }, { - Source: '/logos/icon_76x76.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_76x76.png', + Type: 'image/png', + }], Width: 76, Height: 76, - }, { - Source: '/logos/icon_96x96.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_96x96.png', + Type: 'image/png', + }], Width: 96, Height: 96, - }, { - Source: '/logos/icon_114x114.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_114x114.png', + Type: 'image/png', + }], Width: 114, Height: 114, - }, { - Source: '/logos/icon_120x120.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_120x120.png', + Type: 'image/png', + }], Width: 120, Height: 120, - }, { - Source: '/logos/icon_128x128.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_128x128.png', + Type: 'image/png', + }], Width: 128, Height: 128, - }, { - Source: '/logos/icon_144x144.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_144x144.png', + Type: 'image/png', + }], Width: 144, Height: 144, - }, { - Source: '/logos/icon_150x150.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_150x150.png', + Type: 'image/png', + }], Width: 150, Height: 150, - }, { - Source: '/logos/icon_152x152.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_152x152.png', + Type: 'image/png', + }], Width: 152, Height: 152, - }, { - Source: '/logos/icon_180x180.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_180x180.png', + Type: 'image/png', + }], Width: 180, Height: 180, - }, { - Source: '/logos/icon_192x192.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_192x192.png', + Type: 'image/png', + }], Width: 192, Height: 192, - }, { - Source: '/logos/icon_310x310.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_310x310.png', + Type: 'image/png', + }], Width: 310, Height: 310, - }, { - Source: '/logos/icon_384x384.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_384x384.png', + Type: 'image/png', + }], Width: 384, Height: 384, - }, { - Source: '/logos/icon_480x480.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_480x480.png', + Type: 'image/png', + }], Width: 480, Height: 480, - }, { - Source: '/logos/icon_512x512.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_512x512.png', + Type: 'image/png', + }], Width: 512, Height: 512, - }, { - Source: '/logos/icon_1024x1024.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_1024x1024.png', + Type: 'image/png', + }], Width: 1024, Height: 1024, - }, { - Source: '/logos/icon_1200x1200.svg', - Type: 'image/svg+xml', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_1200x1200.svg', + Type: 'image/svg+xml', + }], Width: 1200, Height: 1200, - }, { - Source: '/logos/icon_1200x1200.png', - Type: 'image/png', Purpose: 'any', + }, { + Text: {}, + Sources: [{ + URL: '/logos/icon_1200x1200.png', + Type: 'image/png', + }], Width: 1200, Height: 1200, + Purpose: 'any', }, { - Source: '/logos/icon-monochrome_1200x1200.svg', - Type: 'image/svg+xml', - Purpose: 'maskable', + Text: {}, + Sources: [{ + URL: '/logos/icon-monochrome_1200x1200.svg', + Type: 'image/svg+xml', + }], Width: 1200, Height: 1200, + Purpose: 'maskable', }], @@ -677,3 +856,167 @@ Allow: / Orientation: 'any', }, } + + + + +@Injectable({ + providedIn: 'root', +}) +export class Service_Page { + public constructor( + @Inject(DOCUMENT) private document: Document, + @Inject(LOCALE_ID) private locale: string, + private service_url: Service_URL, + private meta: Meta, + private title: Title, + ) {} + + + public Init(metadata: Metadata_Page): number { + // validate input + if (metadata.Lang == '') { + metadata.Lang = 'en'; + } + + if (metadata.Title == '') { + return 1; + } + + if (metadata.Description == '') { + return 1; + } + + if (metadata.Keywords == '') { + return 1; + } + + if (!metadata.Site) { + metadata.Site = METADATA_SITE; + } + + if (metadata.Site.Color_Theme_Foreground == '') { + return 1; + } + + if (metadata.Site.Color_Theme_Background == '') { + return 1; + } + + + // initiate page language + this.locale = metadata.Lang; + this.document.documentElement.lang = metadata.Lang; + + + // setup page-level metadata + this.title.setTitle(metadata.Title); + + this.meta.updateTag({ + name: 'description', + content: metadata.Description, + }); + + this.meta.updateTag({ + name: 'keywords', + content: metadata.Keywords, + }); + + this.meta.updateTag({ + name: 'theme-color', + content: metadata.Site.Color_Theme_Foreground, + }); + + this.meta.updateTag({ + name: 'msapplication-TileColor', + content: metadata.Site.Color_Theme_Background, + }); + + + // setup open-graph metadata + this.meta.updateTag({ + property: 'og:title', + content: metadata.Title, + }); + + this.meta.updateTag({ + property: 'og:site_name', + content: metadata.Site.Name[metadata.Lang], + }); + + this.meta.updateTag({ + property: 'og:locale', + content: metadata.Lang, + }); + + this.meta.updateTag({ + property: 'og:description', + content: metadata.Description, + }); + + this.meta.updateTag({ + property: 'og:url', + content: metadata.URL, + }); + + var thumbnails = Yield_Thumbnails( + metadata.Thumbnails, + this.service_url.Get_Base_URL(), + metadata.Lang, + metadata.Title, + ); + + for (var i = 0; i < thumbnails.length; i++) { + if (thumbnails[i].Sources[0].Type.startsWith('image/')) { + // an image + this.meta.addTags([{ + property: 'og:image', + content: thumbnails[i].Sources[0].URL, + }, { + property: 'og:image:type', + content: thumbnails[i].Sources[0].Type, + }, { + property: 'og:image:width', + content: `${thumbnails[i].Width}`, + }, { + property: 'og:image:height', + content: `${thumbnails[i].Height}`, + }, { + property: 'og:image:alt', + content: thumbnails[i].Text[metadata.Lang], + }]); + } else if (thumbnails[i].Sources[0].Type.startsWith('video/')) { + // a video + this.meta.addTags([{ + property: 'og:video', + content: thumbnails[i].Sources[0].URL, + }, { + property: 'og:video:type', + content: thumbnails[i].Sources[0].Type, + }, { + property: 'og:video:width', + content: `${thumbnails[i].Width}`, + }, { + property: 'og:video:height', + content: `${thumbnails[i].Height}`, + }, { + property: 'og:video:alt', + content: thumbnails[i].Text[metadata.Lang], + }]); + } else if (thumbnails[i].Sources[0].Type.startsWith('video/')) { + // an audio + this.meta.addTags([{ + property: 'og:audio', + content: thumbnails[i].Sources[0].URL, + }, { + property: 'og:audio:type', + content: thumbnails[i].Sources[0].Type, + }]); + }; + } + + + // report status + return 0; + } +} diff --git a/Angular/services/app/root.ts b/Angular/services/app/Root.ts similarity index 100% rename from Angular/services/app/root.ts rename to Angular/services/app/Root.ts diff --git a/Angular/services/app/URL.ts b/Angular/services/app/URL.ts new file mode 100644 index 00000000..f5a30873 --- /dev/null +++ b/Angular/services/app/URL.ts @@ -0,0 +1,29 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { Inject, Injectable } from '@angular/core'; +import { APP_BASE_HREF } from '@angular/common'; + +import { Yield_URL } from 'services/app/Definitions'; + + + + +@Injectable({ + providedIn: 'root', +}) +export class Service_URL { + public constructor( + @Inject(APP_BASE_HREF) private url_base: string, + ) {} + + + public Get_Base_URL(): string { + return this.url_base; + } + + + public To_Absolute(url_given: string): string { + return Yield_URL(url_given, this.url_base); + } +} diff --git a/Angular/services/app/metadata-definitions.ts b/Angular/services/app/metadata-definitions.ts deleted file mode 100644 index 1503aeaf..00000000 --- a/Angular/services/app/metadata-definitions.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* - * COPYRIGHT LICENSE NOTICE HERE - */ -export class Application { - ID: string = ''; - Platform: string = ''; - URL: string = ''; -} - -export class Application_Related { - Prioritized: boolean = false; - List: Application[] = []; -} - - - - -export class Display { - public Primary: string = ''; - public Overrides: string[] = []; - public Orientation: string = ''; -} - - - - -export class Icon { - public Source: string = ''; - public Type: string = ''; - public Purpose: string = ''; - public Width: number = 0; - public Height: number = 0; -} - - - - -export class Contact { - public ID: string = ''; - public Type: string = ''; - public URL: string = ''; -} - - - - -export class Owner { - public UUID: string = ''; - public Name_Family: { [lang: string]: string } = {}; - public Name_Given: { [lang: string]: string } = {}; - public Call_Sign: { [lang: string]: string } = {}; - public Title: { [lang: string]: string } = {}; - public Description: { [lang: string]: string } = {}; - public Slogan: { [lang: string]: string } = {}; - public Contacts: { [type: string]: { [lang: string]: Contact } } = {}; - public Roles: string[] = []; -} - - - - -export class Protocol_Handler { - public Protocol: string = ''; - public URL: string = ''; -} - -export class Protocol { - public Start: string = ''; - public Scope: string = ''; - public Handlers: Protocol_Handler[] = []; -} - - - - -export class SEO { - public Add: string[] = []; - public Remove: string[] = []; - public Robot: string = ''; -} - - - - -export class Screenshot { - public Label: { [lang: string]: string } = {}; - public Form_Factor: string = ''; - public Source: string = ''; - public Type: string = ''; - public Width: number = 0; - public Height: number = 0; -} - - - - -export class Shortcut { - public URL: string = ''; - public Name_Long: { [lang: string]: string } = {}; - public Name_Short: { [lang: string]: string } = {}; - public Description: { [lang: string]: string } = {}; - public Icons: Icon[] = []; -} - - - - -export class Metadata { - public ID: string = ''; - public ID_CNAME: string = ''; - public ID_APP: string = ''; - public ID_SKU: string = ''; - public Language_Default: string = ''; - public Name: { [lang: string]: string } = {}; - public Description: { [lang: string]: string } = {}; - public Keywords: { [lang: string]: string[] } = {}; - public Color_Theme_Foreground: string = ''; - public Color_Theme_Background: string = ''; - public Owners: Owner[] = []; - public Screenshots: Screenshot[] = []; - public Protocol: Protocol = { - Start: '', - Scope: '', - Handlers: [], - }; - public Related_Application: Application_Related = { - Prioritized: false, - List: [], - }; - public Shortcuts: Shortcut[] = []; - public SEO: SEO = { - Add: [], - Remove: [], - Robot: '', - }; - public Icons: Icon[] = []; - public Display: Display = { - Primary: '', - Overrides: [], - Orientation: '', - }; -} diff --git a/Angular/services/app/noscript.html b/Angular/services/app/noscript.html new file mode 100644 index 00000000..950427c6 --- /dev/null +++ b/Angular/services/app/noscript.html @@ -0,0 +1,24 @@ +
+
+ + + +
+
+

JavaScript???

+
+
diff --git a/Angular/services/app/root.css b/Angular/services/app/root.css index 34afdd31..e298ba9a 100644 --- a/Angular/services/app/root.css +++ b/Angular/services/app/root.css @@ -1,4 +1,6 @@ body { /* define all your font family here */ - font-family: var(--font-family-web-safe-sans-serif); + font-family: var(--font-family-noto-emoji-color), + var(--font-family-noto-sans), + var(--font-family-web-safe-sans-serif); } diff --git a/Angular/services/app/root.html b/Angular/services/app/root.html deleted file mode 100644 index 1d90d202..00000000 --- a/Angular/services/app/root.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - TITLE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
- - diff --git a/Angular/services/app/seo/Start.ts b/Angular/services/app/seo/Start.ts new file mode 100644 index 00000000..e08f1611 --- /dev/null +++ b/Angular/services/app/seo/Start.ts @@ -0,0 +1,32 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import { METADATA_SITE } from 'services/app/Metadata'; + +import { Create_Web_Manifest } from './pwa'; +import { Create_CNAME } from './cname'; +import { Create_HTML_Index } from './html'; +import { Create_SEO } from './seo'; +import { Create_Browser_Config_XML } from './browserconfig-xml'; +import { Create_No_Jekyll } from './no-jekyll'; + + + + +export function Init_Site ( + path_app_root: string, + path_asset: string, + url_base: string, + path_prerender: string, +) { + Create_Browser_Config_XML( + path_asset, + url_base, + METADATA_SITE.Color_Theme_Background, + ); + Create_CNAME(path_asset, METADATA_SITE.ID_CNAME); + Create_No_Jekyll(path_asset); + Create_SEO(path_asset, url_base, path_prerender, METADATA_SITE.SEO); + Create_Web_Manifest(path_asset, url_base, METADATA_SITE); + Create_HTML_Index(path_app_root, url_base, METADATA_SITE); +} diff --git a/Angular/services/init/browserconfig-xml.ts b/Angular/services/app/seo/browserconfig-xml.ts similarity index 54% rename from Angular/services/init/browserconfig-xml.ts rename to Angular/services/app/seo/browserconfig-xml.ts index 649d514a..62845123 100644 --- a/Angular/services/init/browserconfig-xml.ts +++ b/Angular/services/app/seo/browserconfig-xml.ts @@ -1,17 +1,20 @@ /* * COPYRIGHT LICENSE NOTICE HERE */ -import { METADATA_SITE } from '../app/metadata'; -import { Get_Base_URL, Yield_URL } from './url'; import * as fs from 'fs'; +import { Yield_URL } from 'services/app/Definitions'; + /* exported function for creating the browserconfig.xml file */ -export function Create_Browser_Config_XML() { - const dir_build = 'assets'; - const filepath = dir_build + '/' + 'browserconfig.xml'; +export function Create_Browser_Config_XML( + path_build: string, + url_base: string, + color_background: string, +) { + const filepath = path_build + '/' + 'browserconfig.xml'; /* bail if file exists */ @@ -25,23 +28,22 @@ export function Create_Browser_Config_XML() { /* create directory */ - if (!fs.existsSync(dir_build)) { - fs.mkdirSync(dir_build, { recursive: true }); + if (!fs.existsSync(path_build)) { + fs.mkdirSync(path_build, { recursive: true }); } /* create new file */ - let data = Get_Base_URL(); fs.writeFileSync(filepath,` - - - - - - ${METADATA_SITE.Color_Theme_Background || '#021B79'} + + + + + + ${color_background || '#021B79'} diff --git a/Angular/services/init/cname.ts b/Angular/services/app/seo/cname.ts similarity index 53% rename from Angular/services/init/cname.ts rename to Angular/services/app/seo/cname.ts index daa2b4a7..b7834922 100644 --- a/Angular/services/init/cname.ts +++ b/Angular/services/app/seo/cname.ts @@ -1,21 +1,18 @@ /* * COPYRIGHT LICENSE NOTICE HERE */ -import { METADATA_SITE } from '../app/metadata'; -import { Get_Base_URL } from './url'; import * as fs from 'fs'; /* exported function for creating the CNAME configuration file */ -export function Create_CNAME() { - const dir_build = 'assets'; - const filepath = dir_build + '/' + 'CNAME'; +export function Create_CNAME(path_build: string, cname: string) { + const filepath = path_build + '/' + 'CNAME'; /* check for requirement */ - if (!METADATA_SITE.ID_CNAME) { + if (!cname) { return; } @@ -31,13 +28,13 @@ export function Create_CNAME() { /* create directory */ - if (!fs.existsSync(dir_build)) { - fs.mkdirSync(dir_build, { recursive: true }); + if (!fs.existsSync(path_build)) { + fs.mkdirSync(path_build, { recursive: true }); } /* create new file */ - fs.writeFileSync(filepath, METADATA_SITE.ID_CNAME); + fs.writeFileSync(filepath, cname); /* report status */ diff --git a/Angular/services/app/seo/html.ts b/Angular/services/app/seo/html.ts new file mode 100644 index 00000000..76480939 --- /dev/null +++ b/Angular/services/app/seo/html.ts @@ -0,0 +1,274 @@ +/* + * COPYRIGHT LICENSE NOTICE HERE + */ +import * as fs from 'fs'; + +import { + Metadata_Site, + Yield_Thumbnails, + Yield_URL, +} from 'services/app/Definitions'; + + + + +function _process_favicons( + url_base: string, + metadata: Metadata_Site, + color_foreground: string, +): string { + var html = ''; + var mask_icon = ''; + var sizes = ''; + var url = ''; + var icon_type = ''; + + + // execute + for (var i = 0; i < metadata.Icons.length; i++) { + if (metadata.Icons[i].Sources.length == 0) { + continue; + } + + url = Yield_URL(metadata.Icons[i].Sources[0].URL, url_base); + if (url == '') { + continue; + } + + if (metadata.Icons[i].Width == 0 || metadata.Icons[i].Height == 0) { + continue; + } + sizes = `${metadata.Icons[i].Width}x${metadata.Icons[i].Height}`; + + if (!metadata.Icons[i].Sources[0].Type.startsWith('image/')) { + continue; + } + icon_type = `${metadata.Icons[i].Sources[0].Type}`; + + + // render icon meta tag + if (metadata.Icons[i].Purpose == 'maskable') { + html += ` + + + +` + } else { + html += ` + + + +`; + } + + switch (sizes) { + case "57x57": + case "60x60": + case "72x72": + case "76x76": + case "114x114": + case "120x120": + case "144x144": + case "152x152": + case "180x180": + html += ` +`; + + if (sizes == '144x144') { + html += ` +`; + } + break; + default: + break; + } + } + + + // report status + return html; +} + + + + +function _process_thumbnails(url_base: string, metadata: Metadata_Site): string { + var html = ''; + var text = metadata.Name[metadata.Language_Default] || '$TITLE'; + + var thumbnails = Yield_Thumbnails( + metadata.Thumbnails, + url_base, + metadata.Language_Default, + text, + ); + + + var set_image_alt = false; + var set_video_alt = false; + for (var i = 0; i < thumbnails.length; i++) { + if (thumbnails[i].Sources[0].Type.startsWith('image/')) { + html += ` + + + + + ` + if (!set_image_alt) { + html += ` + `; + set_image_alt = true; + } + } else if (thumbnails[i].Sources[0].Type.startsWith('video/')) { + html += ` + + + + + ` + if (!set_video_alt) { + html += ` + `; + set_image_alt = true; + } + } else if (thumbnails[i].Sources[0].Type.startsWith('audio/')) { + html += ` + + + ` + } + } + + + // report status + return html; +} + + + + +/* exported function for creating the web manifest file */ +export function Create_HTML_Index(path_build: string, url_base: string, metadata: Metadata_Site) { + const filepath = path_build + '/' + '.root.html'; + const filepath_noscript = path_build + "/" + "noscript.html"; + + + /* bail if file exists */ + try { + if (fs.existsSync(filepath)) { + return; + } + } catch { + ; + } + + + /* create directory */ + if (!fs.existsSync(path_build)) { + fs.mkdirSync(path_build, { recursive: true }); + } + + + /* parse notscript.html content */ + var noscript = ''; + if (fs.existsSync(filepath_noscript)) { + noscript = fs.readFileSync(filepath_noscript, { encoding: 'utf8', flag: 'r' }); + } + + if (noscript == '') { + noscript = '

Javascript???

'; + } + + + /* process required data */ + var lang: string = metadata.Language_Default || 'en'; + var title: string = metadata.Name[lang] || '$TITLE'; + var description: string = metadata.Description[lang] || '$DESCRIPTION'; + var color_foreground: string = metadata.Color_Theme_Foreground || '#FFFF00'; + var color_background: string = metadata.Color_Theme_Background || '#000428'; + + var keywords_list: string[] = metadata.Keywords[lang] || []; + if (keywords_list.length == 0) { + keywords_list = [ 'KEYWORDS' ]; + } + var keywords: string = keywords_list.join(', '); + var og_thumbnails: string = _process_thumbnails(url_base, metadata); + var favicons: string = _process_favicons(url_base, metadata, color_foreground); + + + /* create new file */ + fs.writeFileSync(filepath, ` + + + + + + ${title} + + + + + + + + + + + +${favicons} + + + + + + + + +${og_thumbnails} + + + + + + + + + +
+ +
+
+
+ + +`); + + + /* report status */ + console.log(`✓ ${filepath} generated successfully.`); +} diff --git a/Angular/services/init/no-jekyll.ts b/Angular/services/app/seo/no-jekyll.ts similarity index 67% rename from Angular/services/init/no-jekyll.ts rename to Angular/services/app/seo/no-jekyll.ts index 1be8d615..edd4000b 100644 --- a/Angular/services/init/no-jekyll.ts +++ b/Angular/services/app/seo/no-jekyll.ts @@ -7,9 +7,8 @@ import * as fs from 'fs'; /* exported function for creating the .nojekyll file */ -export function Create_No_Jekyll() { - const dir_build = 'assets'; - const filepath = dir_build + '/' + '.nojekyll'; +export function Create_No_Jekyll(path_build: string) { + const filepath = path_build + '/' + '.nojekyll'; /* bail if file exists */ @@ -23,8 +22,8 @@ export function Create_No_Jekyll() { /* create directory */ - if (!fs.existsSync(dir_build)) { - fs.mkdirSync(dir_build, { recursive: true }); + if (!fs.existsSync(path_build)) { + fs.mkdirSync(path_build, { recursive: true }); } diff --git a/Angular/services/init/pwa.ts b/Angular/services/app/seo/pwa.ts similarity index 62% rename from Angular/services/init/pwa.ts rename to Angular/services/app/seo/pwa.ts index efa0ac98..e886865c 100644 --- a/Angular/services/init/pwa.ts +++ b/Angular/services/app/seo/pwa.ts @@ -1,25 +1,31 @@ /* * COPYRIGHT LICENSE NOTICE HERE */ -import { Icon, Application, Protocol_Handler, Screenshot, Shortcut } from '../app/metadata-definitions'; -import { METADATA_SITE } from '../app/metadata'; -import { Get_Base_URL, Yield_URL } from './url'; import * as fs from 'fs'; +import { + Application, + Media, + Metadata_Site, + Protocol_Handler, + Shortcut, + Yield_URL, +} from 'services/app/Definitions'; -function _process_icons(items: Icon[], url_base: string): any[] { + +function _process_icons(items: Media[], url_base: string): any[] { var list = []; // process list data for (var i = 0; i < items.length; i++) { - if (items[i].Source == '') { + if (!items[i].Sources || items[i].Sources.length == 0) { continue; } - if (items[i].Type == '') { + if (items[i].Sources[0].Type == '') { continue; } @@ -32,9 +38,9 @@ function _process_icons(items: Icon[], url_base: string): any[] { } list.push({ - src: Yield_URL(items[i].Source, url_base), + src: Yield_URL(items[i].Sources[0].URL, url_base), sizes: `${items[i].Width}x${items[i].Height}`, - type: items[i].Type, + type: items[i].Sources[0].Type, purpose: items[i].Purpose, }); } @@ -77,7 +83,7 @@ function _process_protocol_handlers(items: Protocol_Handler[], name_short: strin -function _process_related_applications(items: Application[], url_base: string): any[] { +function _process_related_applications(items: Application[], url_base: string, sku: string): any[] { var list = []; @@ -90,7 +96,7 @@ function _process_related_applications(items: Application[], url_base: string): if (items[i].ID == '') { continue; } else if (items[i].ID == '/') { - items[i].ID = METADATA_SITE.ID_SKU; + items[i].ID = sku; } switch (items[i].Platform) { @@ -121,41 +127,45 @@ function _process_related_applications(items: Application[], url_base: string): -function _process_screenshots(items: Screenshot[], lang: string, url_base: string): any[] { +function _process_screenshots(items: Media[], lang: string, url_base: string): any[] { var list = []; // process list data for (var i = 0; i < items.length; i++) { + if (!items[i].Sources || items[i].Sources.length == 0) { + continue; + } + switch (items[i].Form_Factor) { case "NARROW": case "Narrow": case "narrow": list.push({ - label: items[i].Label[lang], + label: items[i].Text[lang] || '', form_factor: "narrow", - src: Yield_URL(items[i].Source, url_base), + src: Yield_URL(items[i].Sources[0].URL, url_base), sizes: `${items[i].Width}x${items[i].Height}`, - type: items[i].Type, + type: items[i].Sources[0].Type, }); break; case "WIDE": case "Wide": case "wide": list.push({ - label: items[i].Label[lang], + label: items[i].Text[lang] || '', form_factor: "wide", - src: Yield_URL(items[i].Source, url_base), + src: Yield_URL(items[i].Sources[0].URL, url_base), sizes: `${items[i].Width}x${items[i].Height}`, - type: items[i].Type, + type: items[i].Sources[0].Type, }); break; default: list.push({ - label: items[i].Label[lang], - src: Yield_URL(items[i].Source, url_base), + label: items[i].Text[lang] || '', + src: Yield_URL(items[i].Sources[0].URL, url_base), sizes: `${items[i].Width}x${items[i].Height}`, - type: items[i].Type, + type: items[i].Sources[0].Type, }); break; } @@ -205,47 +215,47 @@ function _process_shortcuts(items: Shortcut[], lang: string, url_base: string): /* generate manifest.webmanifest with site data */ -function generate_web_manifest(): any { - const url_base = Get_Base_URL(); - const lang = METADATA_SITE.Language_Default; +function generate_web_manifest(url_base: string, metadata: Metadata_Site): any { + const lang = metadata.Language_Default; /* create settings content */ return { - name: METADATA_SITE.Name[lang], - short_name: METADATA_SITE.ID_SKU, + name: metadata.Name[lang], + short_name: metadata.ID_SKU, lang: lang, - description: METADATA_SITE.Description[lang], - categories: METADATA_SITE.Keywords[lang], - id: METADATA_SITE.ID, - start_url: METADATA_SITE.Protocol.Start, - scope: METADATA_SITE.Protocol.Scope, + description: metadata.Description[lang], + categories: metadata.Keywords[lang], + id: metadata.ID, + start_url: metadata.Protocol.Start, + scope: metadata.Protocol.Scope, protocol_handlers: _process_protocol_handlers( - METADATA_SITE.Protocol.Handlers, - METADATA_SITE.ID_APP, + metadata.Protocol.Handlers, + metadata.ID_APP, ), - display: METADATA_SITE.Display.Primary, - display_override: METADATA_SITE.Display.Overrides, - orientation: METADATA_SITE.Display.Orientation, - theme_color: METADATA_SITE.Color_Theme_Foreground, - background_color: METADATA_SITE.Color_Theme_Background, - prefer_related_applications: METADATA_SITE.Related_Application.Prioritized, + display: metadata.Display.Primary, + display_override: metadata.Display.Overrides, + orientation: metadata.Display.Orientation, + theme_color: metadata.Color_Theme_Foreground, + background_color: metadata.Color_Theme_Background, + prefer_related_applications: metadata.Related_Application.Prioritized, related_applications: _process_related_applications( - METADATA_SITE.Related_Application.List, + metadata.Related_Application.List, url_base, + metadata.ID_SKU, ), screenshots: _process_screenshots( - METADATA_SITE.Screenshots, + metadata.Screenshots, lang, url_base, ), shortcuts: _process_shortcuts( - METADATA_SITE.Shortcuts, + metadata.Shortcuts, lang, url_base, ), icons: _process_icons( - METADATA_SITE.Icons, + metadata.Icons, url_base, ), } @@ -255,9 +265,8 @@ function generate_web_manifest(): any { /* exported function for creating the web manifest file */ -export function Create_Web_Manifest() { - const dir_build = 'assets'; - const filepath = dir_build + '/' + 'manifest.webmanifest'; +export function Create_Web_Manifest(path_build: string, url_base: string, metadata: Metadata_Site) { + const filepath = path_build + '/' + 'manifest.webmanifest'; /* bail if file exists */ @@ -271,13 +280,13 @@ export function Create_Web_Manifest() { /* create directory */ - if (!fs.existsSync(dir_build)) { - fs.mkdirSync(dir_build, { recursive: true }); + if (!fs.existsSync(path_build)) { + fs.mkdirSync(path_build, { recursive: true }); } /* create new file */ - const data = generate_web_manifest(); + const data = generate_web_manifest(url_base, metadata); fs.writeFileSync(filepath, JSON.stringify(data, null, 2)); diff --git a/Angular/services/init/seo.ts b/Angular/services/app/seo/seo.ts similarity index 79% rename from Angular/services/init/seo.ts rename to Angular/services/app/seo/seo.ts index bf44e8f1..0ce4700c 100644 --- a/Angular/services/init/seo.ts +++ b/Angular/services/app/seo/seo.ts @@ -1,11 +1,11 @@ /* * COPYRIGHT LICENSE NOTICE HERE */ -import { METADATA_SITE } from '../app/metadata'; -import { Get_Base_URL, Yield_URL } from './url'; import * as fs from 'fs'; import * as path from 'path'; +import { SEO, Yield_URL } from 'services/app/Definitions'; + @@ -159,30 +159,34 @@ function create_sitemaps_index(filepath: string, list: string[]) { -function create_robots(filepath: string, sitemap_url: string) { +function create_robots(filepath: string, sitemap_url: string, seo: SEO) { // write sitemap if available if (sitemap_url) { fs.appendFileSync(filepath, `Sitemap: ${sitemap_url}`); } // append the policy - fs.appendFileSync(filepath, METADATA_SITE.SEO.Robot); + fs.appendFileSync(filepath, seo.Robot); } /* exported function for creating the seo configuration files */ -export function Create_SEO() { - const source_file = 'prerender-routes.txt'; +export function Create_SEO( + path_build: string, + url_base: string, + source_file: string, + seo: SEO, +) { const filename_sitemap = 'sitemap.xml'; const filename_robots = 'robots.txt'; const dir_sitemaps = 'sitemaps'; - const dir_sitemaps_build = `assets/${dir_sitemaps}`; + const dir_sitemaps_build = `${path_build}/${dir_sitemaps}`; - const filepath_sitemap = `assets/${filename_sitemap}`; - const filepath_robots = `assets/${filename_robots}`; + const filepath_sitemap = `${path_build}/${filename_sitemap}`; + const filepath_robots = `${path_build}/${filename_robots}`; /* bail if source file is missing */ @@ -214,7 +218,6 @@ export function Create_SEO() { /* parse urls from prerender-routes.txt */ - var base = Get_Base_URL(); var sources = fs.readFileSync(source_file).toString().split("\n"); var list: string[] = []; for (var i = 0; i < sources.length; i++) { @@ -222,17 +225,17 @@ export function Create_SEO() { continue } - list[i] = Yield_URL(sources[i], base); + list[i] = Yield_URL(sources[i], url_base); } - /* add urls from METADATA_SITE.SEO.Add */ - for (var i = 0; i < METADATA_SITE.SEO.Add.length; i++) { - if (!METADATA_SITE.SEO.Add[i]) { + /* add urls from METADATA_SITE.SEO.Add list */ + for (var i = 0; i < seo.Add.length; i++) { + if (!seo.Add[i]) { continue } - list.push(Yield_URL(sources[i], base)); + list.push(Yield_URL(sources[i], url_base)); } @@ -240,27 +243,26 @@ export function Create_SEO() { list.filter((x, i, a) => a.indexOf(x) == i); - /* remove urls from METADATA_SITE.SEO.Remove */ - for (var i = 0; i < METADATA_SITE.SEO.Remove.length; i++) { - if (!METADATA_SITE.SEO.Remove[i]) { + /* remove urls from METADATA_SITE.SEO.Remove list */ + for (var i = 0; i < seo.Remove.length; i++) { + if (!seo.Remove[i]) { continue } - let target = Yield_URL(METADATA_SITE.SEO.Remove[i], base); + let target = Yield_URL(seo.Remove[i], url_base); list = list.filter((sample) => sample != target); } /* validate list before proceeding */ if (list.length <= 0) { - create_robots(filepath_robots, ''); + create_robots(filepath_robots, '', seo); return; /* no url - bail */ } /* update sitemap baseline url */ - let url_sitemap = Yield_URL(filename_sitemap, base); - base = Yield_URL(`/${dir_sitemaps}`, base); + let url_sitemap = Yield_URL(filename_sitemap, url_base); /* create directory */ @@ -270,7 +272,11 @@ export function Create_SEO() { /* create page sitemaps */ - var sitemaps = create_sitemaps_page(dir_sitemaps_build, base, list); + var sitemaps = create_sitemaps_page( + dir_sitemaps_build, + Yield_URL(`/${dir_sitemaps}`, url_base), + list, + ); /* create index sitemaps */ @@ -278,7 +284,7 @@ export function Create_SEO() { /* create robots.txt */ - create_robots(filepath_robots, url_sitemap); + create_robots(filepath_robots, url_sitemap, seo); /* report status */ diff --git a/Angular/services/app/shell/build.ps1 b/Angular/services/app/shell/build.ps1 new file mode 100644 index 00000000..9f0aaf3c --- /dev/null +++ b/Angular/services/app/shell/build.ps1 @@ -0,0 +1,12 @@ +# +# COPYRIGHT LICENSE NOTICE HERE +# +if ("${env:WORKSPACE_RUN}" -eq "") { + ${env:WORKSPACE_RUN} = "production" +} + + +# execute +. "${WORKSPACE_ROOT}/services/app/shell/init.sh" +ng build --aot --configuration "$WORKSPACE_RUN" +exit $? diff --git a/Angular/services/app/shell/build.sh b/Angular/services/app/shell/build.sh new file mode 100644 index 00000000..7025b059 --- /dev/null +++ b/Angular/services/app/shell/build.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# +# COPYRIGHT LICENSE NOTICE HERE +# +if [ "$WORKSPACE_RUN" = "" ]; then + WORKSPACE_RUN="production" +fi + + +# execute +. "${WORKSPACE_ROOT}/services/app/shell/init.sh" +ng build --aot --configuration "$WORKSPACE_RUN" +return $? diff --git a/Angular/services/app/shell/init.ps1 b/Angular/services/app/shell/init.ps1 new file mode 100644 index 00000000..9cdfef67 --- /dev/null +++ b/Angular/services/app/shell/init.ps1 @@ -0,0 +1,106 @@ +# +# COPYRIGHT LICENSE NOTICE HERE +# +if ("${env:WORKSPACE_ROOT}" -eq "") { + $null = Write-Host "[ ERROR ] Missing `${env:WORKSPACE_ROOT}" + return 1 +} +$null = Write-Host "Workspace: ${env:WORKSPACE_ROOT}" + + + + +if ("${env:WORKSPACE_RUN}" -eq "") { + $null = Write-Host "[ ERROR ] Missing `${env:WORKSPACE_RUN}" + return 1 +} +$null = Write-Host "Run Mode : ${env:WORKSPACE_RUN}" + + + + +$null = Write-Host "STAGE-0: Cleaning up existing configuration files..." +foreach ($___line in [ + "assets\manifest.webmanifest", + "assets\CNAME", + "assets\sitemaps", + "assets\robots.txt", + "assets\browserconfig.xml", + "assets\.nojekyll", + "services\app\.root.html" +]) { + $null = Remove-Item -Force "${env:WORKSPACE_ROOT}\${___line}" ` + -ErrorAction SilentlyContinue +} + + + + +# setup CHROME_BIN +foreach ($___line in [ + "chromium.exe", + "chrome.exe", + "brave.exe", +]) { + $___browser = Get-Command $___line -ErrorAction SilentlyContinue + if ($___browser -ne "") { + $env:CHROME_BIN = $___browser + break + } +} +return + + + + +# initialize repository +$null = Write-Host "STAGE-1: Initializing repository..." + +if (-not (Test-Path -Path "${env:WORKSPACE_ROOT}\services\app\root.html")) { + @" + + + + + + + + +
+ +
+
+
+ +"@ ` + | Out-File -FilePath "${env:WORKSPACE_ROOT}\services\app\root.html" ` + -Encoding UTF8 +} + +$null = ng build --aot --configuration ${env:WORKSPACE_RUN} --server main.server.ts ` +$null = Remove-Item -Recurse -Force "${env:WORKSPACE_ROOT}\dist" ` + -ErrorAction SilentlyContinue +$null = [System.IO.File]::FlushAll() +$null = ng test --no-watch --code-coverage --browsers ChromeHeadless + + + + +# export critical files +if (-not $(Test-Path -Path "${env:WORKSPACE_ROOT}\services\app\.root.html")) { + $null = Write-Error "[ ERROR ] failed to compile 'services\app\root.html'" + exit 1 +} +$null = Move-Item -Force ` + -Path "${env:WORKSPACE_ROOT}\services\app\.root.html" ` + -Destination "${env:WORKSPACE_ROOT}\services\app\root.html" +$null = [System.IO.File]::FlushAll() + + + + +# done +$null = Write-Host "STAGE-2: Executing job..." +return diff --git a/Angular/services/app/shell/init.sh b/Angular/services/app/shell/init.sh new file mode 100644 index 00000000..3c5b58f9 --- /dev/null +++ b/Angular/services/app/shell/init.sh @@ -0,0 +1,103 @@ +#!/bin/sh +# +# COPYRIGHT LICENSE NOTICE HERE +# +if [ "$WORKSPACE_ROOT" = "" ]; then + 1>&2 printf -- "[ ERROR ] %s\n" "Missing WORKSPACE_ROOT!" + return 1 +fi +1>&2 printf -- "Workspace: %s\n" "$WORKSPACE_ROOT" + + + + +if [ "$WORKSPACE_RUN" = "" ]; then + 1>&2 printf -- "[ ERROR ] %s\n" "Missing WORKSPACE_RUN!" + return 1 +fi +1>&2 printf -- "Run mode : %s\n" "$WORKSPACE_RUN" + + + + +1>&2 printf -- "%s\n" "STAGE-0: Cleaning up existing configuration files..." +___old_IFS="$IFS" +while IFS="" read -r ___line || [ -n "$___line" ]; do + rm -rf "${WORKSPACE_ROOT}/${___line}" &> /dev/null +done << EOF +assets/manifest.webmanifest +assets/CNAME +assets/sitemaps +assets/sitemap.xml +assets/robots.txt +assets/browserconfig.xml +assets/.nojekyll +services/app/.root.html +EOF +IFS="$___old_IFS" && unset ___old_IFS ___line + + + + +# setup CHROME_BIN +___old_IFS="$IFS" +while IFS="" read -r ___line || [ -n "$___line" ]; do + if [ ! -z "$(type -p "$___line")" ]; then + CHROME_BIN="$(type -p "$___line")" + break + fi +done << EOF +chromium +google-chrome +brave-browser +EOF +IFS="$___old_IFS" && unset ___old_IFS ___line + + + + +# initialize repository +1>&2 printf -- "%s\n" "STAGE-1: Initializing repository..." + +if [ ! -f "${WORKSPACE_ROOT}/services/app/root.html" ]; then + printf -- "%s" "\ + + + + + + + + +
+ +
+
+ + +" > "${WORKSPACE_ROOT}/services/app/root.html" +fi + +ng build --aot --configuration "${WORKSPACE_RUN:-production}" --server main.server.ts +rm -rf "${WORKSPACE_ROOT}/dist" &> /dev/null +sync + + + + +# export critical files +if [ ! -f "${WORKSPACE_ROOT}/services/app/.root.html" ]; then + 1>&2 printf -- "[ ERROR ] failed to compile '%s'!\n" "services/app/root.html" + exit 1 +fi +mv "${WORKSPACE_ROOT}/services/app/.root.html" "${WORKSPACE_ROOT}/services/app/root.html" +sync + + + + +# done +1>&2 printf -- "STAGE-2: Executing job...\n" +return 0 diff --git a/Angular/services/app/shell/serve.ps1 b/Angular/services/app/shell/serve.ps1 new file mode 100644 index 00000000..0a02f4ad --- /dev/null +++ b/Angular/services/app/shell/serve.ps1 @@ -0,0 +1,12 @@ +# +# COPYRIGHT LICENSE NOTICE HERE +# +if ("${env:WORKSPACE_RUN}" -eq "") { + ${env:WORKSPACE_RUN} = "development" +} + + +# execute +$null = . "${env:WORKSPACE_ROOT}\services\app\shell\init.ps1" +$null = ng serve +exit $? diff --git a/Angular/services/app/shell/serve.sh b/Angular/services/app/shell/serve.sh new file mode 100644 index 00000000..f8d891aa --- /dev/null +++ b/Angular/services/app/shell/serve.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# +# COPYRIGHT LICENSE NOTICE HERE +# +if [ "$WORKSPACE_RUN" = "" ]; then + WORKSPACE_RUN="development" +fi + + +# execute +. "${WORKSPACE_ROOT}/services/app/shell/init.sh" +ng serve +return $? diff --git a/Angular/services/app/shell/test.ps1 b/Angular/services/app/shell/test.ps1 new file mode 100644 index 00000000..0232f542 --- /dev/null +++ b/Angular/services/app/shell/test.ps1 @@ -0,0 +1,12 @@ +# +# COPYRIGHT LICENSE NOTICE HERE +# +if ("${env:WORKSPACE_RUN}" -eq "") { + ${env:WORKSPACE_RUN} = "development" +} + + +# execute +$null = . "${env:WORKSPACE_ROOT}\services\app\shell\init.ps1" +$null = ng test --no-watch --code-coverage --browsers ChromeHeadless +return $? diff --git a/Angular/services/app/shell/test.sh b/Angular/services/app/shell/test.sh new file mode 100644 index 00000000..97ded6e8 --- /dev/null +++ b/Angular/services/app/shell/test.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# +# COPYRIGHT LICENSE NOTICE HERE +# +if [ "$WORKSPACE_RUN" = "" ]; then + WORKSPACE_RUN="development" +fi + + +# execute command +. "${WORKSPACE_ROOT}/services/app/shell/init.sh" +CHROME_BIN="$CHROME_BIN" ng test --no-watch \ + --code-coverage \ + --browsers ChromeHeadless +return $? diff --git a/Angular/services/app/shell/watch.ps1 b/Angular/services/app/shell/watch.ps1 new file mode 100644 index 00000000..233644da --- /dev/null +++ b/Angular/services/app/shell/watch.ps1 @@ -0,0 +1,12 @@ +# +# COPYRIGHT LICENSE NOTICE HERE +# +if ("${env:WORKSPACE_RUN}" -eq "") { + ${env:WORKSPACE_RUN} = "development" +} + + +# execute +$null = . "${env:WORKSPACE_ROOT}\services\app\shell\init.ps1" +$null = ng build --watch --configuration ${env:WORKSPACE_RUN} +return $? diff --git a/Angular/services/app/shell/watch.sh b/Angular/services/app/shell/watch.sh new file mode 100644 index 00000000..4924afe1 --- /dev/null +++ b/Angular/services/app/shell/watch.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# +# COPYRIGHT LICENSE NOTICE HERE +# +if [ "$WORKSPACE_RUN" = "" ]; then + WORKSPACE_RUN="development" +fi + + +# execute command +. "${WORKSPACE_ROOT}/services/app/shell/init.sh" +ng build --watch --configuration "$WORKSPACE_RUN" +return $? diff --git a/Angular/services/init/url.ts b/Angular/services/init/url.ts deleted file mode 100644 index 96207d76..00000000 --- a/Angular/services/init/url.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * COPYRIGHT LICENSE NOTICE HERE - */ -import { isDevMode } from '@angular/core'; -import * as fs from 'fs'; -import { join } from 'path'; - - - - -export function Get_Base_URL(): string { - if(isDevMode()) { - return ''; - } - - try { - // Read angular.json file - const filepath = join(process.cwd(), 'angular.json'); - const file_content = fs.readFileSync(filepath, 'utf-8'); - - // Parse JSON safely - const json = JSON.parse(file_content); - - // Get first project key if project name not specified - const keys = Object.keys(json.projects || {}); - if (keys.length === 0) { - console.warn('No projects found in angular.json'); - return ''; - } - const project = json.projects["app"]; - - // Safely navigate the object structure - const url = project?.architect?.build?.configurations?.production?.baseHref; - - // make sure the base url is the correct type and without tailing slash - if (typeof url === 'string') { - return url.replace(/\/$/, ""); - } - } catch (error) { - console.warn('Error reading baseHref from angular.json:', error); - } - - return ''; -} - - - - -export function Yield_URL(path: string, base_url: string): string { - return base_url.replace(/\/$/, "") + '/' + path.replace(/^\//, ""); -} diff --git a/Angular/test.sh.ps1 b/Angular/test.sh.ps1 index b7644386..6e2b80cc 100755 --- a/Angular/test.sh.ps1 +++ b/Angular/test.sh.ps1 @@ -24,17 +24,13 @@ echo \" <<'RUN_AS_POWERSHELL' >/dev/null # " | Out-Null ################################################################################ # Windows POWERSHELL Codes # ################################################################################ -# execute -$null = . ".\init.sh.ps1" -$null = Write-Host "initializing the repository..." -$null = ng build --configuration development --server main.server.ts | Out-Null -$null = Remove-Item -Recurse -Force ".\dist" -ErrorAction SilentlyContinue -$null = [System.IO.File]::FlushAll() -$null = ng test --no-watch --code-coverage --browsers ChromeHeadless +${env:WORKSPACE_ROOT} = Get-Location +${env:WORKSPACE_RUN} = 'development' +$___process = . "${env:WORKSPACE_ROOT}\services\app\shell\test.ps1" ################################################################################ # Windows POWERSHELL Codes # ################################################################################ -exit +exit $___process <# RUN_AS_POWERSHELL @@ -44,13 +40,9 @@ RUN_AS_POWERSHELL ################################################################################ # Unix Main Codes # ################################################################################ -# execute -. "./init.sh.ps1" -1>&2 printf -- "%s\n" "initializing the repository..." -ng build --aot --configuration development --server main.server.ts &> /dev/null -rm -rf "./dist/" &> /dev/null -sync -ng test --no-watch --code-coverage --browsers ChromeHeadless +WORKSPACE_ROOT="$PWD" +WORKSPACE_RUN="development" +. "${WORKSPACE_ROOT}/services/app/shell/test.sh" ################################################################################ # Unix Main Codes # ################################################################################ diff --git a/Angular/watch.sh.ps1 b/Angular/watch.sh.ps1 index d441a45a..2a27b98c 100755 --- a/Angular/watch.sh.ps1 +++ b/Angular/watch.sh.ps1 @@ -25,16 +25,13 @@ echo \" <<'RUN_AS_POWERSHELL' >/dev/null # " | Out-Null # Windows POWERSHELL Codes # ################################################################################ # execute -$null = . ".\init.sh.ps1" -$null = Write-Host "initializing the repository..." -$null = ng build --aot --configuration development --server main.server.ts | Out-Null -$null = Remove-Item -Recurse -Force ".\dist" -ErrorAction SilentlyContinue -$null = [System.IO.File]::FlushAll() -$null = ng build --watch --configuration development +${env:WORKSPACE_ROOT} = Get-Location +${env:WORKSPACE_RUN} = 'development' +$___process = . "${env:WORKSPACE_ROOT}\services\app\shell\watch.ps1" ################################################################################ # Windows POWERSHELL Codes # ################################################################################ -exit +exit $___process <# RUN_AS_POWERSHELL @@ -44,13 +41,9 @@ RUN_AS_POWERSHELL ################################################################################ # Unix Main Codes # ################################################################################ -# execute -. "./init.sh.ps1" -1>&2 printf -- "%s\n" "initializing the repository..." -ng build --aot --configuration development --server main.server.ts &> /dev/null -rm -rf "./dist/" &> /dev/null -sync -ng build --watch --configuration development +WORKSPACE_ROOT="$PWD" +WORKSPACE_RUN="development" +. "${WORKSPACE_ROOT}/services/app/shell/watch.sh" ################################################################################ # Unix Main Codes # ################################################################################