create-content-sdk-app 2.0.0-canary.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE.MD +202 -202
  2. package/dist/initializers/angular/args.js +2 -0
  3. package/dist/initializers/angular/index.js +30 -0
  4. package/dist/initializers/angular/prompts.js +20 -0
  5. package/dist/templates/angular/.postcssrc.json +5 -0
  6. package/dist/templates/angular/.vscode/extensions.json +4 -0
  7. package/dist/templates/angular/README.md +3 -0
  8. package/dist/templates/angular/angular.json +79 -0
  9. package/dist/templates/angular/package.json +58 -0
  10. package/dist/templates/angular/public/favicon.ico +0 -0
  11. package/dist/templates/angular/src/app/app.config.server.ts +12 -0
  12. package/dist/templates/angular/src/app/app.config.ts +31 -0
  13. package/dist/templates/angular/src/app/app.css +0 -0
  14. package/dist/templates/angular/src/app/app.html +1 -0
  15. package/dist/templates/angular/src/app/app.routes.server.ts +15 -0
  16. package/dist/templates/angular/src/app/app.routes.ts +28 -0
  17. package/dist/templates/angular/src/app/app.ts +12 -0
  18. package/dist/templates/angular/src/app/loaders/error.loader.ts +12 -0
  19. package/dist/templates/angular/src/app/loaders/index.ts +14 -0
  20. package/dist/templates/angular/src/app/loaders/not-found.loader.ts +12 -0
  21. package/dist/templates/angular/src/app/loaders/page.loader.ts +15 -0
  22. package/dist/templates/angular/src/app/loaders/stub-utils.ts +83 -0
  23. package/dist/templates/angular/src/app/pages/error.component.ts +124 -0
  24. package/dist/templates/angular/src/app/pages/not-found.component.ts +85 -0
  25. package/dist/templates/angular/src/app/pages/page.component.ts +58 -0
  26. package/dist/templates/angular/src/app/shared/layout.component.ts +106 -0
  27. package/dist/templates/angular/src/index.html +13 -0
  28. package/dist/templates/angular/src/main.server.ts +8 -0
  29. package/dist/templates/angular/src/main.ts +6 -0
  30. package/dist/templates/angular/src/server.ts +65 -0
  31. package/dist/templates/angular/src/styles.css +3 -0
  32. package/dist/templates/angular/tsconfig.json +38 -0
  33. package/dist/templates/angular/tsconfig.spec.json +10 -0
  34. package/dist/templates/nextjs/.cursor/rules/general.mdc +81 -81
  35. package/dist/templates/nextjs/.cursor/rules/javascript.mdc +112 -112
  36. package/dist/templates/nextjs/.cursor/rules/project-setup.mdc +100 -100
  37. package/dist/templates/nextjs/.cursor/rules/sitecore.mdc +150 -150
  38. package/dist/templates/nextjs/.env.container.example +27 -27
  39. package/dist/templates/nextjs/.env.remote.example +51 -51
  40. package/dist/templates/nextjs/.gitattributes +11 -11
  41. package/dist/templates/nextjs/.prettierrc +8 -8
  42. package/dist/templates/nextjs/.vscode/extensions.json +8 -8
  43. package/dist/templates/nextjs/.vscode/launch.json +15 -15
  44. package/dist/templates/nextjs/.windsurfrules +186 -186
  45. package/dist/templates/nextjs/LICENSE.txt +202 -202
  46. package/dist/templates/nextjs/LLMs.txt +179 -179
  47. package/dist/templates/nextjs/eslint.config.mjs +81 -81
  48. package/dist/templates/nextjs/gitignore +28 -28
  49. package/dist/templates/nextjs/package.json +68 -68
  50. package/dist/templates/nextjs/sitecore.config.ts.example +40 -40
  51. package/dist/templates/nextjs/tsconfig.json +40 -40
  52. package/dist/templates/nextjs-app-router/.cursor/rules/app-router-setup.mdc +116 -116
  53. package/dist/templates/nextjs-app-router/.cursor/rules/general.mdc +80 -80
  54. package/dist/templates/nextjs-app-router/.cursor/rules/javascript.mdc +112 -112
  55. package/dist/templates/nextjs-app-router/.cursor/rules/sitecore.mdc +174 -174
  56. package/dist/templates/nextjs-app-router/.env.container.example +27 -27
  57. package/dist/templates/nextjs-app-router/.env.remote.example +51 -51
  58. package/dist/templates/nextjs-app-router/.gitattributes +11 -11
  59. package/dist/templates/nextjs-app-router/.windsurfrules +290 -290
  60. package/dist/templates/nextjs-app-router/LLMs.txt +236 -236
  61. package/dist/templates/nextjs-app-router/eslint.config.mjs +29 -29
  62. package/dist/templates/nextjs-app-router/gitignore +31 -31
  63. package/dist/templates/nextjs-app-router/package.json +54 -54
  64. package/dist/templates/nextjs-app-router/postcss.config.mjs +5 -5
  65. package/dist/templates/nextjs-app-router/sitecore.config.ts.example +40 -40
  66. package/dist/templates/nextjs-app-router/src/app/globals.css +1 -1
  67. package/dist/templates/nextjs-app-router/tsconfig.json +48 -48
  68. package/package.json +2 -2
@@ -0,0 +1,31 @@
1
+ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
2
+ import { provideRouter, withNavigationErrorHandler } from '@angular/router';
3
+ import { provideHttpClient, withFetch } from '@angular/common/http';
4
+ import {
5
+ provideLoaderRegistry,
6
+ handleNavigationError,
7
+ provideSitecoreAngular,
8
+ PreLoaderDataService,
9
+ } from '@sitecore-content-sdk/angular';
10
+ import { routes } from './app.routes';
11
+ import { LOADERS } from './loaders';
12
+
13
+ /**
14
+ * Client hydration is disabled so that RouterLink and other directives attach correctly
15
+ * after bootstrap. With provideClientHydration(), server-rendered DOM is reused and
16
+ * directive event listeners (e.g. RouterLink click) can fail to attach. Without
17
+ * hydration, the client re-renders the app and routing works as expected.
18
+ */
19
+ export const appConfig: ApplicationConfig = {
20
+ providers: [
21
+ provideBrowserGlobalErrorListeners(),
22
+ provideHttpClient(withFetch()),
23
+ provideSitecoreAngular({
24
+ notFoundRoute: '/404',
25
+ errorRoute: '/500',
26
+ }),
27
+ provideLoaderRegistry(LOADERS),
28
+ PreLoaderDataService,
29
+ provideRouter(routes, withNavigationErrorHandler(handleNavigationError())),
30
+ ],
31
+ };
File without changes
@@ -0,0 +1 @@
1
+ <router-outlet />
@@ -0,0 +1,15 @@
1
+ import { RenderMode, ServerRoute } from '@angular/ssr';
2
+
3
+ /**
4
+ * Server route config. Define 404 and 500 with status so that when the loader resolver
5
+ * redirects to the not-found or error route (e.g. after NotFoundNavigationError or LoaderHttpError),
6
+ * the server responds with the correct HTTP status code instead of 200.
7
+ */
8
+ export const serverRoutes: ServerRoute[] = [
9
+ { path: '404', renderMode: RenderMode.Server, status: 404 },
10
+ { path: '500', renderMode: RenderMode.Server, status: 500 },
11
+ {
12
+ path: '**',
13
+ renderMode: RenderMode.Server,
14
+ },
15
+ ];
@@ -0,0 +1,28 @@
1
+ import { Routes } from '@angular/router';
2
+ import { loaderResolver } from '@sitecore-content-sdk/angular';
3
+ import { PageComponent } from './pages/page.component';
4
+ import { NotFoundComponent } from './pages/not-found.component';
5
+ import { ErrorComponent } from './pages/error.component';
6
+
7
+ export const routes: Routes = [
8
+ {
9
+ path: '',
10
+ children: [
11
+ {
12
+ path: '500',
13
+ component: ErrorComponent,
14
+ resolve: { page: loaderResolver('500') },
15
+ },
16
+ {
17
+ path: '404',
18
+ component: NotFoundComponent,
19
+ resolve: { page: loaderResolver('404') },
20
+ },
21
+ {
22
+ path: '**',
23
+ component: PageComponent,
24
+ resolve: { page: loaderResolver('page') },
25
+ },
26
+ ],
27
+ },
28
+ ];
@@ -0,0 +1,12 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { RouterOutlet } from '@angular/router';
3
+
4
+ @Component({
5
+ selector: 'app-root',
6
+ imports: [RouterOutlet],
7
+ templateUrl: './app.html',
8
+ styleUrl: './app.css'
9
+ })
10
+ export class App {
11
+ protected readonly title = signal('angular-sample');
12
+ }
@@ -0,0 +1,12 @@
1
+ import type { LoaderFn, Page } from '@sitecore-content-sdk/angular';
2
+ import { errorPageResult } from './stub-utils';
3
+
4
+ const DEFAULT_MESSAGE = 'Internal Server Error';
5
+
6
+ /**
7
+ * 500 loader. Returns a page with route.fields.error set to the message.
8
+ * The Error component should read layout.sitecore.route.fields?.error?.value for custom text.
9
+ */
10
+ export const errorLoader: LoaderFn<Page> = async (context) => {
11
+ return errorPageResult(context.url, DEFAULT_MESSAGE);
12
+ };
@@ -0,0 +1,14 @@
1
+ export { pageLoader } from './page.loader';
2
+ export { notFoundLoader } from './not-found.loader';
3
+ export { errorLoader } from './error.loader';
4
+
5
+ import { pageLoader } from './page.loader';
6
+ import { notFoundLoader } from './not-found.loader';
7
+ import { errorLoader } from './error.loader';
8
+
9
+ /** Loaders object for use in provideLoaderRegistry() and createLoaderDataServiceMiddleware(). */
10
+ export const LOADERS = {
11
+ page: pageLoader,
12
+ '404': notFoundLoader,
13
+ '500': errorLoader,
14
+ };
@@ -0,0 +1,12 @@
1
+ import type { LoaderFn, Page } from '@sitecore-content-sdk/angular';
2
+ import { errorPageResult } from './stub-utils';
3
+
4
+ const DEFAULT_MESSAGE = 'Page Not Found';
5
+
6
+ /**
7
+ * 404 loader. Returns a page with route.fields.error set to the message.
8
+ * The NotFound component should read layout.sitecore.route.fields?.error?.value for custom text.
9
+ */
10
+ export const notFoundLoader: LoaderFn<Page> = async (context) => {
11
+ return errorPageResult(context.url, DEFAULT_MESSAGE);
12
+ };
@@ -0,0 +1,15 @@
1
+ import type { LoaderFn, Page } from '@sitecore-content-sdk/angular';
2
+ import { NotFoundNavigationError } from '@sitecore-content-sdk/angular';
3
+ import { stubPageResult } from './stub-utils';
4
+
5
+ /**
6
+ * Page loader: fetches layout data from Sitecore for the current URL.
7
+ * Used by the route resolver to enable dynamic route rendering.
8
+ */
9
+ export const pageLoader: LoaderFn<Page> = async (context) => {
10
+ const page = stubPageResult(context.url);
11
+ if (!page) {
12
+ throw new NotFoundNavigationError();
13
+ }
14
+ return page;
15
+ };
@@ -0,0 +1,83 @@
1
+ import type { Page } from '@sitecore-content-sdk/angular';
2
+ import { LayoutServicePageState } from '@sitecore-content-sdk/angular';
3
+
4
+ /**
5
+ * Stub helpers until proper implementation (e.g. Sitecore layout service integration).
6
+ * Used by app loaders to return minimal valid Page shapes.
7
+ */
8
+
9
+ /**
10
+ * Returns a stubbed Page for the page loader: route data, mode data, 'en' locale and basic text fields.
11
+ * Replace with real layout/route data from Sitecore when implemented.
12
+ */
13
+ export function stubPageResult(url: string): Page {
14
+ return {
15
+ layout: {
16
+ sitecore: {
17
+ context: {
18
+ url,
19
+ },
20
+ route: {
21
+ name: url,
22
+ placeholders: {},
23
+ fields: {},
24
+ databaseName: 'web',
25
+ deviceId: 'web',
26
+ itemLanguage: 'en',
27
+ itemVersion: 1,
28
+ layoutId: '123',
29
+ templateId: '123',
30
+ templateName: '123',
31
+ },
32
+ },
33
+ },
34
+ locale: 'en',
35
+ mode: {
36
+ name: LayoutServicePageState.Normal,
37
+ isNormal: true,
38
+ isPreview: false,
39
+ isEditing: false,
40
+ isDesignLibrary: false,
41
+ designLibrary: {
42
+ isVariantGeneration: false,
43
+ },
44
+ },
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Returns a stubbed Page for not-found (404) or error (500) loaders with an error message in route.fields.error.
50
+ * Replace with proper error page handling when implemented.
51
+ */
52
+ export function errorPageResult(url: string, errorMessage: string): Page {
53
+ return {
54
+ layout: {
55
+ sitecore: {
56
+ context: { url },
57
+ route: {
58
+ name: url,
59
+ placeholders: {},
60
+ fields: {
61
+ error: { value: errorMessage },
62
+ },
63
+ databaseName: 'web',
64
+ deviceId: 'web',
65
+ itemLanguage: 'en',
66
+ itemVersion: 1,
67
+ layoutId: '123',
68
+ templateId: '123',
69
+ templateName: '123',
70
+ },
71
+ },
72
+ },
73
+ locale: 'en',
74
+ mode: {
75
+ name: LayoutServicePageState.Normal,
76
+ isNormal: true,
77
+ isPreview: false,
78
+ isEditing: false,
79
+ isDesignLibrary: false,
80
+ designLibrary: { isVariantGeneration: false },
81
+ },
82
+ };
83
+ }
@@ -0,0 +1,124 @@
1
+ import { Component, computed, inject } from '@angular/core';
2
+ import { ActivatedRoute } from '@angular/router';
3
+ import { toSignal } from '@angular/core/rxjs-interop';
4
+
5
+ const DEFAULT_TITLE = 'Internal Server Error';
6
+ const DEFAULT_MESSAGE = 'Something went wrong on our end. Please try again later.';
7
+
8
+ /**
9
+ * 500 error page component.
10
+ * Reads page from the '500' loader; displays route.fields.error.value or default text.
11
+ */
12
+ @Component({
13
+ selector: 'app-error',
14
+ template: `
15
+ <div class="error-container">
16
+ <div class="error-content">
17
+ <h1 class="error-code">{{ errorCode }}</h1>
18
+ <h2 class="error-title">{{ errorTitle() }}</h2>
19
+ <p class="error-message">{{ errorMessage() }}</p>
20
+ <div class="error-actions">
21
+ <button class="btn btn-primary" (click)="goHome()">Go Home</button>
22
+ <button class="btn btn-secondary" (click)="retry()">Retry</button>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ `,
27
+ styles: [
28
+ `
29
+ .error-container {
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ min-height: 100vh;
34
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
35
+ }
36
+
37
+ .error-content {
38
+ text-align: center;
39
+ background: white;
40
+ padding: 3rem;
41
+ border-radius: 8px;
42
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
43
+ max-width: 500px;
44
+ }
45
+
46
+ .error-code {
47
+ font-size: 4rem;
48
+ font-weight: bold;
49
+ color: #e74c3c;
50
+ margin: 0;
51
+ }
52
+
53
+ .error-title {
54
+ font-size: 1.5rem;
55
+ color: #2c3e50;
56
+ margin: 1rem 0;
57
+ }
58
+
59
+ .error-message {
60
+ color: #7f8c8d;
61
+ margin: 1.5rem 0 2rem;
62
+ }
63
+
64
+ .error-actions {
65
+ display: flex;
66
+ gap: 1rem;
67
+ justify-content: center;
68
+ }
69
+
70
+ .btn {
71
+ padding: 0.75rem 1.5rem;
72
+ border: none;
73
+ border-radius: 4px;
74
+ font-size: 1rem;
75
+ cursor: pointer;
76
+ transition: background-color 0.3s;
77
+ }
78
+
79
+ .btn-primary {
80
+ background-color: #3498db;
81
+ color: white;
82
+ }
83
+
84
+ .btn-primary:hover {
85
+ background-color: #2980b9;
86
+ }
87
+
88
+ .btn-secondary {
89
+ background-color: #95a5a6;
90
+ color: white;
91
+ }
92
+
93
+ .btn-secondary:hover {
94
+ background-color: #7f8c8d;
95
+ }
96
+ `,
97
+ ],
98
+ })
99
+ export class ErrorComponent {
100
+ readonly errorCode = 500;
101
+
102
+ private readonly route = inject(ActivatedRoute);
103
+ private readonly routeData = toSignal(this.route.data);
104
+
105
+ private readonly errorFromPage = computed(() => {
106
+ const page = this.routeData()?.['page'] as
107
+ | { layout?: { sitecore?: { route?: { fields?: { error?: { value?: string } } } } } }
108
+ | undefined;
109
+ return page?.layout?.sitecore?.route?.fields?.error?.value;
110
+ });
111
+
112
+ readonly errorTitle = computed(() => DEFAULT_TITLE);
113
+ readonly errorMessage = computed(() =>
114
+ typeof this.errorFromPage() === 'string' ? this.errorFromPage()! : DEFAULT_MESSAGE
115
+ );
116
+
117
+ goHome(): void {
118
+ window.location.href = '/';
119
+ }
120
+
121
+ retry(): void {
122
+ window.location.reload();
123
+ }
124
+ }
@@ -0,0 +1,85 @@
1
+ import { Component, computed, inject } from '@angular/core';
2
+ import { ActivatedRoute } from '@angular/router';
3
+ import { RouterLink } from '@angular/router';
4
+ import { toSignal } from '@angular/core/rxjs-interop';
5
+
6
+ const DEFAULT_TITLE = 'Page Not Found';
7
+ const DEFAULT_MESSAGE = 'Sorry, the page you\'re looking for doesn\'t exist or has been moved.';
8
+
9
+ /**
10
+ * 404 Not Found error page component.
11
+ * Reads page from the '404' loader; displays route.fields.error.value or default text.
12
+ */
13
+ @Component({
14
+ selector: 'app-404',
15
+ standalone: true,
16
+ imports: [RouterLink],
17
+ template: `
18
+ <div class="not-found-container">
19
+ <div class="not-found-content">
20
+ <h1>404</h1>
21
+ <h2>{{ title() }}</h2>
22
+ <p>{{ message() }}</p>
23
+ <a routerLink="/" class="back-link">Return to Home</a>
24
+ </div>
25
+ </div>
26
+ `,
27
+ styles: [`
28
+ .not-found-container {
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ min-height: 100vh;
33
+ background-color: #f5f5f5;
34
+ }
35
+
36
+ .not-found-content {
37
+ text-align: center;
38
+ padding: 2rem;
39
+ }
40
+
41
+ h1 {
42
+ font-size: 6rem;
43
+ margin: 0;
44
+ color: #333;
45
+ }
46
+
47
+ h2 {
48
+ font-size: 2rem;
49
+ margin: 1rem 0;
50
+ color: #666;
51
+ }
52
+
53
+ p {
54
+ font-size: 1.1rem;
55
+ color: #999;
56
+ margin-bottom: 2rem;
57
+ }
58
+
59
+ .back-link {
60
+ display: inline-block;
61
+ padding: 0.75rem 1.5rem;
62
+ background-color: #0061cc;
63
+ color: white;
64
+ text-decoration: none;
65
+ border-radius: 4px;
66
+ transition: background-color 0.3s ease;
67
+ }
68
+
69
+ .back-link:hover {
70
+ background-color: #0052a3;
71
+ }
72
+ `],
73
+ })
74
+ export class NotFoundComponent {
75
+ private readonly route = inject(ActivatedRoute);
76
+ private readonly routeData = toSignal(this.route.data);
77
+
78
+ private readonly errorFromPage = computed(() => {
79
+ const page = this.routeData()?.['page'] as { layout?: { sitecore?: { route?: { fields?: { error?: { value?: string } } } } } } | undefined;
80
+ return page?.layout?.sitecore?.route?.fields?.error?.value;
81
+ });
82
+
83
+ readonly title = computed(() => DEFAULT_TITLE);
84
+ readonly message = computed(() => (typeof this.errorFromPage() === 'string' ? this.errorFromPage()! : DEFAULT_MESSAGE));
85
+ }
@@ -0,0 +1,58 @@
1
+ import { Component, computed, inject } from '@angular/core';
2
+ import { ActivatedRoute, Router } from '@angular/router';
3
+ import { Page } from '@sitecore-content-sdk/angular';
4
+ import { toSignal } from '@angular/core/rxjs-interop';
5
+ import { LayoutComponent } from '../shared/layout.component';
6
+
7
+ /**
8
+ * Page component that sets the Sitecore context and dictionary phrases, then renders the layout.
9
+ * Gets page and dictionary data from route resolvers.
10
+ * Displays current route and fields returned by pageLoader.
11
+ */
12
+ @Component({
13
+ selector: 'app-page',
14
+ standalone: true,
15
+ imports: [LayoutComponent],
16
+ template: `
17
+ @let pageValue = page();
18
+ <section class="page-loader-info" style="margin: 1rem; padding: 1rem; border: 1px solid #ccc; border-radius: 4px;">
19
+ <h2 style="margin-top: 0;">Current route</h2>
20
+ <p><strong>URL:</strong> {{ router.url }}</p>
21
+
22
+ @if (pageValue) {
23
+ <h2>Page loader data</h2>
24
+ <dl style="display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem;">
25
+ <dt>Route name</dt>
26
+ <dd>{{ routeName(pageValue) }}</dd>
27
+ <dt>Locale</dt>
28
+ <dd>{{ pageValue.locale }}</dd>
29
+ <dt>Site name</dt>
30
+ <dd>{{ pageValue.siteName ?? '—' }}</dd>
31
+ <dt>Mode</dt>
32
+ <dd>{{ pageValue.mode?.name ?? '—' }}</dd>
33
+ <dt>Route fields</dt>
34
+ <dd><pre style="margin: 0; font-size: 0.875rem;">{{ routeFieldsJson(pageValue) }}</pre></dd>
35
+ </dl>
36
+ }
37
+ </section>
38
+ @if (pageValue) {
39
+ <app-layout [page]="pageValue"></app-layout>
40
+ }
41
+ `,
42
+ })
43
+ export class PageComponent {
44
+ private readonly activatedRoute = inject(ActivatedRoute);
45
+ readonly router = inject(Router);
46
+ private data = toSignal(this.activatedRoute.data);
47
+ page = computed(() => this.data()?.page as Page | null);
48
+
49
+ routeName(p: Page): string {
50
+ return p?.layout?.sitecore?.route?.name ?? '—';
51
+ }
52
+
53
+ routeFieldsJson(p: Page): string {
54
+ const fields = p?.layout?.sitecore?.route?.fields;
55
+ if (fields == null || Object.keys(fields).length === 0) return '{}';
56
+ return JSON.stringify(fields, null, 2);
57
+ }
58
+ }
@@ -0,0 +1,106 @@
1
+ import { Component, input, computed, effect, inject } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ // import { RouterLink } from '@angular/router';
4
+ import { Title } from '@angular/platform-browser';
5
+ import { Page, Field } from '@sitecore-content-sdk/angular';
6
+ import { RouterLink } from '@angular/router';
7
+
8
+ /**
9
+ * Route fields interface for page title
10
+ */
11
+ interface RouteFields {
12
+ [key: string]: unknown;
13
+ Title?: Field<string>;
14
+ }
15
+
16
+ /**
17
+ * Layout component that provides the main structure for Sitecore pages.
18
+ * Renders header, main content, and footer placeholders.
19
+ * @public
20
+ */
21
+ @Component({
22
+ selector: 'app-layout',
23
+ standalone: true,
24
+ imports: [CommonModule, RouterLink],
25
+ template: `
26
+ <div [class]="mainClass()">
27
+ <!-- <sc-editing-scripts></sc-editing-scripts> -->
28
+ <header>
29
+ <div id="header">
30
+ <a [routerLink]="'/'">Home</a>
31
+ <a [routerLink]="'/about'">About</a>
32
+ </div>
33
+ </header>
34
+ <main>
35
+ <div id="content">Main content</div>
36
+ </main>
37
+ <footer>
38
+ <div id="footer">footer</div>
39
+ </footer>
40
+ </div>
41
+ `,
42
+ styles: `
43
+ :host {
44
+ display: block;
45
+ min-height: 100vh;
46
+ }
47
+
48
+ .editing-mode {
49
+ /* Styles for editing mode */
50
+ }
51
+
52
+ .prod-mode {
53
+ /* Styles for production mode */
54
+ }
55
+
56
+ .loading {
57
+ display: flex;
58
+ justify-content: center;
59
+ align-items: center;
60
+ min-height: 100vh;
61
+ }
62
+
63
+ header {
64
+ width: 100%;
65
+ }
66
+
67
+ main {
68
+ flex: 1;
69
+ width: 100%;
70
+ }
71
+
72
+ footer {
73
+ width: 100%;
74
+ }
75
+ `,
76
+ })
77
+ export class LayoutComponent {
78
+ /**
79
+ * Page data from Sitecore
80
+ */
81
+ readonly page = input.required<Page>();
82
+
83
+ /**
84
+ * Current route data derived from page
85
+ */
86
+ readonly route = computed(() => this.page().layout?.sitecore?.route ?? null);
87
+
88
+ /**
89
+ * Main container CSS class based on editing mode
90
+ */
91
+ readonly mainClass = computed(() => (this.page().mode?.isEditing ? 'editing-mode' : 'prod-mode'));
92
+
93
+ private readonly titleService = inject(Title);
94
+
95
+ constructor() {
96
+ // Effect to update the page title when the page changes
97
+ effect(() => {
98
+ const route = this.route();
99
+ if (route) {
100
+ const fields = route.fields as RouteFields | undefined;
101
+ const title = fields?.Title?.value ?? 'Page';
102
+ this.titleService.setTitle(title);
103
+ }
104
+ });
105
+ }
106
+ }
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Angular Content SDK Sample</title>
6
+ <base href="/">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link rel="icon" type="image/x-icon" href="favicon.ico">
9
+ </head>
10
+ <body>
11
+ <app-root></app-root>
12
+ </body>
13
+ </html>
@@ -0,0 +1,8 @@
1
+ import { BootstrapContext, bootstrapApplication } from '@angular/platform-browser';
2
+ import { App } from './app/app';
3
+ import { config } from './app/app.config.server';
4
+
5
+ const bootstrap = (context: BootstrapContext) =>
6
+ bootstrapApplication(App, config, context);
7
+
8
+ export default bootstrap;
@@ -0,0 +1,6 @@
1
+ import { bootstrapApplication } from '@angular/platform-browser';
2
+ import { appConfig } from './app/app.config';
3
+ import { App } from './app/app';
4
+
5
+ bootstrapApplication(App, appConfig)
6
+ .catch((err) => console.error(err));