@foliokit/cms-ui 0.4.2 → 1.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.
- package/esm2022/index.js +3 -0
- package/esm2022/index.js.map +1 -1
- package/esm2022/lib/about-page/about-page.component.js +81 -0
- package/esm2022/lib/about-page/about-page.component.js.map +1 -0
- package/esm2022/lib/links-page/links-page.component.js +143 -0
- package/esm2022/lib/links-page/links-page.component.js.map +1 -0
- package/esm2022/lib/route-data.js +17 -0
- package/esm2022/lib/route-data.js.map +1 -0
- package/index.d.ts +3 -0
- package/lib/about-page/about-page.component.d.ts +12 -0
- package/lib/links-page/links-page.component.d.ts +14 -0
- package/lib/route-data.d.ts +34 -0
- package/package.json +6 -7
package/esm2022/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export * from './lib/app-shell/app-shell.component';
|
|
2
2
|
export * from './lib/theme.service';
|
|
3
3
|
export * from './lib/shell-config.token';
|
|
4
|
+
export * from './lib/about-page/about-page.component';
|
|
5
|
+
export * from './lib/links-page/links-page.component';
|
|
6
|
+
export * from './lib/route-data';
|
|
4
7
|
//# sourceMappingURL=index.js.map
|
package/esm2022/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/cms-ui/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC","sourcesContent":["export * from './lib/app-shell/app-shell.component';\nexport * from './lib/theme.service';\nexport * from './lib/shell-config.token';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/cms-ui/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC;AACtD,cAAc,kBAAkB,CAAC","sourcesContent":["export * from './lib/app-shell/app-shell.component';\nexport * from './lib/theme.service';\nexport * from './lib/shell-config.token';\nexport * from './lib/about-page/about-page.component';\nexport * from './lib/links-page/links-page.component';\nexport * from './lib/route-data';\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, effect, inject, PLATFORM_ID, } from '@angular/core';
|
|
2
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
3
|
+
import { ActivatedRoute } from '@angular/router';
|
|
4
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { map } from 'rxjs';
|
|
6
|
+
import { Meta, Title } from '@angular/platform-browser';
|
|
7
|
+
import { MarkdownModule } from 'ngx-markdown';
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
import * as i1 from "ngx-markdown";
|
|
10
|
+
export class AboutPageComponent {
|
|
11
|
+
route = inject(ActivatedRoute);
|
|
12
|
+
meta = inject(Meta);
|
|
13
|
+
title = inject(Title);
|
|
14
|
+
platformId = inject(PLATFORM_ID);
|
|
15
|
+
page = toSignal(this.route.data.pipe(map((data) => data['page'] ?? null)), { initialValue: this.route.snapshot.data['page'] ?? null });
|
|
16
|
+
constructor() {
|
|
17
|
+
effect(() => {
|
|
18
|
+
const p = this.page();
|
|
19
|
+
if (!p)
|
|
20
|
+
return;
|
|
21
|
+
if (!isPlatformBrowser(this.platformId))
|
|
22
|
+
return;
|
|
23
|
+
this.title.setTitle(p.seo?.title ?? p.title);
|
|
24
|
+
if (p.seo?.description) {
|
|
25
|
+
this.meta.updateTag({ name: 'description', content: p.seo.description });
|
|
26
|
+
}
|
|
27
|
+
if (p.seo?.ogImage) {
|
|
28
|
+
this.meta.updateTag({ property: 'og:image', content: p.seo.ogImage });
|
|
29
|
+
}
|
|
30
|
+
if (p.seo?.noIndex) {
|
|
31
|
+
this.meta.updateTag({ name: 'robots', content: 'noindex' });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.meta.removeTag('name="robots"');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AboutPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
39
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: AboutPageComponent, isStandalone: true, selector: "cms-about-page", ngImport: i0, template: `
|
|
40
|
+
@if (page()) {
|
|
41
|
+
<article class="max-w-3xl mx-auto px-4 py-10">
|
|
42
|
+
@if (page()!.heroImageUrl) {
|
|
43
|
+
<img
|
|
44
|
+
class="w-full rounded-xl object-cover mb-8 max-h-80"
|
|
45
|
+
[src]="page()!.heroImageUrl"
|
|
46
|
+
[alt]="page()!.heroImageAlt || page()!.title"
|
|
47
|
+
/>
|
|
48
|
+
}
|
|
49
|
+
<markdown [data]="page()!.body" class="folio-prose" />
|
|
50
|
+
</article>
|
|
51
|
+
} @else {
|
|
52
|
+
<p class="p-10 text-center opacity-50">No content available.</p>
|
|
53
|
+
}
|
|
54
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: MarkdownModule }, { kind: "component", type: i1.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
55
|
+
}
|
|
56
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AboutPageComponent, decorators: [{
|
|
57
|
+
type: Component,
|
|
58
|
+
args: [{
|
|
59
|
+
selector: 'cms-about-page',
|
|
60
|
+
standalone: true,
|
|
61
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
62
|
+
imports: [MarkdownModule],
|
|
63
|
+
template: `
|
|
64
|
+
@if (page()) {
|
|
65
|
+
<article class="max-w-3xl mx-auto px-4 py-10">
|
|
66
|
+
@if (page()!.heroImageUrl) {
|
|
67
|
+
<img
|
|
68
|
+
class="w-full rounded-xl object-cover mb-8 max-h-80"
|
|
69
|
+
[src]="page()!.heroImageUrl"
|
|
70
|
+
[alt]="page()!.heroImageAlt || page()!.title"
|
|
71
|
+
/>
|
|
72
|
+
}
|
|
73
|
+
<markdown [data]="page()!.body" class="folio-prose" />
|
|
74
|
+
</article>
|
|
75
|
+
} @else {
|
|
76
|
+
<p class="p-10 text-center opacity-50">No content available.</p>
|
|
77
|
+
}
|
|
78
|
+
`,
|
|
79
|
+
}]
|
|
80
|
+
}], ctorParameters: () => [] });
|
|
81
|
+
//# sourceMappingURL=about-page.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"about-page.component.js","sourceRoot":"","sources":["../../../../../../libs/cms-ui/src/lib/about-page/about-page.component.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,SAAS,EAET,MAAM,EACN,MAAM,EACN,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAC3B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;;;AAyB9C,MAAM,OAAO,kBAAkB;IACZ,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;IAC/B,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACtB,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;IAEzC,IAAI,GAAG,QAAQ,CACtB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAE,IAAI,CAAC,MAAM,CAAe,IAAI,IAAI,CAAC,CAAC,EACxE,EAAE,YAAY,EAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAe,IAAI,IAAI,EAAE,CAC1E,CAAC;IAEF;QACE,MAAM,CAAC,GAAG,EAAE;YACV,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC;gBAAE,OAAO;YACf,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC;gBAAE,OAAO;YAChD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;YAC7C,IAAI,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC;gBACvB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;YAC3E,CAAC;YACD,IAAI,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;gBACnB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACxE,CAAC;YACD,IAAI,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;gBACnB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;YACvC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;uGA7BU,kBAAkB;2FAAlB,kBAAkB,0EAjBnB;;;;;;;;;;;;;;;GAeT,2DAhBS,cAAc;;2FAkBb,kBAAkB;kBAtB9B,SAAS;mBAAC;oBACT,QAAQ,EAAE,gBAAgB;oBAC1B,UAAU,EAAE,IAAI;oBAChB,eAAe,EAAE,uBAAuB,CAAC,MAAM;oBAC/C,OAAO,EAAE,CAAC,cAAc,CAAC;oBACzB,QAAQ,EAAE;;;;;;;;;;;;;;;GAeT;iBACF","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n PLATFORM_ID,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { ActivatedRoute } from '@angular/router';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { map } from 'rxjs';\nimport { Meta, Title } from '@angular/platform-browser';\nimport { MarkdownModule } from 'ngx-markdown';\nimport type { AboutPage } from '@foliokit/cms-core';\n\n@Component({\n selector: 'cms-about-page',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MarkdownModule],\n template: `\n @if (page()) {\n <article class=\"max-w-3xl mx-auto px-4 py-10\">\n @if (page()!.heroImageUrl) {\n <img\n class=\"w-full rounded-xl object-cover mb-8 max-h-80\"\n [src]=\"page()!.heroImageUrl\"\n [alt]=\"page()!.heroImageAlt || page()!.title\"\n />\n }\n <markdown [data]=\"page()!.body\" class=\"folio-prose\" />\n </article>\n } @else {\n <p class=\"p-10 text-center opacity-50\">No content available.</p>\n }\n `,\n})\nexport class AboutPageComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly meta = inject(Meta);\n private readonly title = inject(Title);\n private readonly platformId = inject(PLATFORM_ID);\n\n readonly page = toSignal(\n this.route.data.pipe(map((data) => (data['page'] as AboutPage) ?? null)),\n { initialValue: (this.route.snapshot.data['page'] as AboutPage) ?? null },\n );\n\n constructor() {\n effect(() => {\n const p = this.page();\n if (!p) return;\n if (!isPlatformBrowser(this.platformId)) return;\n this.title.setTitle(p.seo?.title ?? p.title);\n if (p.seo?.description) {\n this.meta.updateTag({ name: 'description', content: p.seo.description });\n }\n if (p.seo?.ogImage) {\n this.meta.updateTag({ property: 'og:image', content: p.seo.ogImage });\n }\n if (p.seo?.noIndex) {\n this.meta.updateTag({ name: 'robots', content: 'noindex' });\n } else {\n this.meta.removeTag('name=\"robots\"');\n }\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, effect, inject, PLATFORM_ID, } from '@angular/core';
|
|
2
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
3
|
+
import { ActivatedRoute } from '@angular/router';
|
|
4
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { map } from 'rxjs';
|
|
6
|
+
import { Meta, Title } from '@angular/platform-browser';
|
|
7
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
const PLATFORM_ICONS = {
|
|
10
|
+
youtube: 'fa-brands fa-youtube',
|
|
11
|
+
twitch: 'fa-brands fa-twitch',
|
|
12
|
+
twitter: 'fa-brands fa-x-twitter',
|
|
13
|
+
bluesky: 'fa-brands fa-bluesky',
|
|
14
|
+
github: 'fa-brands fa-github',
|
|
15
|
+
linkedin: 'fa-brands fa-linkedin-in',
|
|
16
|
+
instagram: 'fa-brands fa-instagram',
|
|
17
|
+
tiktok: 'fa-brands fa-tiktok',
|
|
18
|
+
facebook: 'fa-brands fa-facebook',
|
|
19
|
+
email: 'fa-solid fa-envelope',
|
|
20
|
+
website: 'fa-solid fa-globe',
|
|
21
|
+
};
|
|
22
|
+
export class LinksPageComponent {
|
|
23
|
+
route = inject(ActivatedRoute);
|
|
24
|
+
meta = inject(Meta);
|
|
25
|
+
title = inject(Title);
|
|
26
|
+
platformId = inject(PLATFORM_ID);
|
|
27
|
+
page = toSignal(this.route.data.pipe(map((data) => data['page'] ?? null)), { initialValue: this.route.snapshot.data['page'] ?? null });
|
|
28
|
+
sortedLinks = computed(() => [...(this.page()?.links ?? [])].sort((a, b) => a.order - b.order), ...(ngDevMode ? [{ debugName: "sortedLinks" }] : /* istanbul ignore next */ []));
|
|
29
|
+
getIcon(link) {
|
|
30
|
+
if (link.icon)
|
|
31
|
+
return link.icon;
|
|
32
|
+
if (link.platform)
|
|
33
|
+
return PLATFORM_ICONS[link.platform] ?? '';
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
constructor() {
|
|
37
|
+
effect(() => {
|
|
38
|
+
const p = this.page();
|
|
39
|
+
if (!p)
|
|
40
|
+
return;
|
|
41
|
+
if (!isPlatformBrowser(this.platformId))
|
|
42
|
+
return;
|
|
43
|
+
this.title.setTitle(p.seo?.title ?? p.title);
|
|
44
|
+
if (p.seo?.description) {
|
|
45
|
+
this.meta.updateTag({ name: 'description', content: p.seo.description });
|
|
46
|
+
}
|
|
47
|
+
if (p.seo?.ogImage) {
|
|
48
|
+
this.meta.updateTag({ property: 'og:image', content: p.seo.ogImage });
|
|
49
|
+
}
|
|
50
|
+
if (p.seo?.noIndex) {
|
|
51
|
+
this.meta.updateTag({ name: 'robots', content: 'noindex' });
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
this.meta.removeTag('name="robots"');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: LinksPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
59
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: LinksPageComponent, isStandalone: true, selector: "cms-links-page", ngImport: i0, template: `
|
|
60
|
+
@if (page()) {
|
|
61
|
+
<div class="flex flex-col items-center max-w-md mx-auto px-4 py-12 gap-6">
|
|
62
|
+
@if (page()!.avatarUrl) {
|
|
63
|
+
<img
|
|
64
|
+
class="w-24 h-24 rounded-full object-cover shadow-md"
|
|
65
|
+
[src]="page()!.avatarUrl"
|
|
66
|
+
[alt]="page()!.avatarAlt || page()!.title"
|
|
67
|
+
/>
|
|
68
|
+
}
|
|
69
|
+
@if (page()!.headline) {
|
|
70
|
+
<h1 class="text-2xl font-bold text-center">{{ page()!.headline }}</h1>
|
|
71
|
+
}
|
|
72
|
+
@if (page()!.bio) {
|
|
73
|
+
<p class="text-center opacity-70">{{ page()!.bio }}</p>
|
|
74
|
+
}
|
|
75
|
+
<nav class="flex flex-col w-full gap-3">
|
|
76
|
+
@for (link of sortedLinks(); track link.id) {
|
|
77
|
+
<a
|
|
78
|
+
[href]="link.url"
|
|
79
|
+
target="_blank"
|
|
80
|
+
rel="noopener noreferrer"
|
|
81
|
+
[class]="link.highlighted ? 'link-btn-featured' : 'link-btn'"
|
|
82
|
+
class="w-full flex items-center rounded-full px-5 !py-3 text-base font-medium transition-opacity no-underline"
|
|
83
|
+
>
|
|
84
|
+
<span class="w-6 flex-shrink-0 text-lg leading-none">
|
|
85
|
+
@if (getIcon(link)) {
|
|
86
|
+
<i [class]="getIcon(link)"></i>
|
|
87
|
+
}
|
|
88
|
+
</span>
|
|
89
|
+
<span class="flex-1 text-center">{{ link.label }}</span>
|
|
90
|
+
<span class="w-6 flex-shrink-0"></span>
|
|
91
|
+
</a>
|
|
92
|
+
}
|
|
93
|
+
</nav>
|
|
94
|
+
</div>
|
|
95
|
+
} @else {
|
|
96
|
+
<p class="p-10 text-center opacity-50">No content available.</p>
|
|
97
|
+
}
|
|
98
|
+
`, isInline: true, styles: [".link-btn{background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}.link-btn-featured{background-color:var(--mat-sys-tertiary-container);color:var(--mat-sys-on-tertiary-container)}.link-btn:hover,.link-btn-featured:hover{opacity:.92}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
99
|
+
}
|
|
100
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: LinksPageComponent, decorators: [{
|
|
101
|
+
type: Component,
|
|
102
|
+
args: [{ selector: 'cms-links-page', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatButtonModule], template: `
|
|
103
|
+
@if (page()) {
|
|
104
|
+
<div class="flex flex-col items-center max-w-md mx-auto px-4 py-12 gap-6">
|
|
105
|
+
@if (page()!.avatarUrl) {
|
|
106
|
+
<img
|
|
107
|
+
class="w-24 h-24 rounded-full object-cover shadow-md"
|
|
108
|
+
[src]="page()!.avatarUrl"
|
|
109
|
+
[alt]="page()!.avatarAlt || page()!.title"
|
|
110
|
+
/>
|
|
111
|
+
}
|
|
112
|
+
@if (page()!.headline) {
|
|
113
|
+
<h1 class="text-2xl font-bold text-center">{{ page()!.headline }}</h1>
|
|
114
|
+
}
|
|
115
|
+
@if (page()!.bio) {
|
|
116
|
+
<p class="text-center opacity-70">{{ page()!.bio }}</p>
|
|
117
|
+
}
|
|
118
|
+
<nav class="flex flex-col w-full gap-3">
|
|
119
|
+
@for (link of sortedLinks(); track link.id) {
|
|
120
|
+
<a
|
|
121
|
+
[href]="link.url"
|
|
122
|
+
target="_blank"
|
|
123
|
+
rel="noopener noreferrer"
|
|
124
|
+
[class]="link.highlighted ? 'link-btn-featured' : 'link-btn'"
|
|
125
|
+
class="w-full flex items-center rounded-full px-5 !py-3 text-base font-medium transition-opacity no-underline"
|
|
126
|
+
>
|
|
127
|
+
<span class="w-6 flex-shrink-0 text-lg leading-none">
|
|
128
|
+
@if (getIcon(link)) {
|
|
129
|
+
<i [class]="getIcon(link)"></i>
|
|
130
|
+
}
|
|
131
|
+
</span>
|
|
132
|
+
<span class="flex-1 text-center">{{ link.label }}</span>
|
|
133
|
+
<span class="w-6 flex-shrink-0"></span>
|
|
134
|
+
</a>
|
|
135
|
+
}
|
|
136
|
+
</nav>
|
|
137
|
+
</div>
|
|
138
|
+
} @else {
|
|
139
|
+
<p class="p-10 text-center opacity-50">No content available.</p>
|
|
140
|
+
}
|
|
141
|
+
`, styles: [".link-btn{background-color:var(--mat-sys-primary);color:var(--mat-sys-on-primary)}.link-btn-featured{background-color:var(--mat-sys-tertiary-container);color:var(--mat-sys-on-tertiary-container)}.link-btn:hover,.link-btn-featured:hover{opacity:.92}\n"] }]
|
|
142
|
+
}], ctorParameters: () => [] });
|
|
143
|
+
//# sourceMappingURL=links-page.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links-page.component.js","sourceRoot":"","sources":["../../../../../../libs/cms-ui/src/lib/links-page/links-page.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n inject,\n PLATFORM_ID,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { ActivatedRoute } from '@angular/router';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { map } from 'rxjs';\nimport { Meta, Title } from '@angular/platform-browser';\nimport { MatButtonModule } from '@angular/material/button';\nimport type { LinksPage, LinksLink } from '@foliokit/cms-core';\nimport type { SocialPlatform } from '@foliokit/cms-core';\n\nconst PLATFORM_ICONS: Record<SocialPlatform, string> = {\n youtube: 'fa-brands fa-youtube',\n twitch: 'fa-brands fa-twitch',\n twitter: 'fa-brands fa-x-twitter',\n bluesky: 'fa-brands fa-bluesky',\n github: 'fa-brands fa-github',\n linkedin: 'fa-brands fa-linkedin-in',\n instagram: 'fa-brands fa-instagram',\n tiktok: 'fa-brands fa-tiktok',\n facebook: 'fa-brands fa-facebook',\n email: 'fa-solid fa-envelope',\n website: 'fa-solid fa-globe',\n};\n\n@Component({\n selector: 'cms-links-page',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [MatButtonModule],\n styles: [`\n .link-btn {\n background-color: var(--mat-sys-primary);\n color: var(--mat-sys-on-primary);\n }\n .link-btn-featured {\n background-color: var(--mat-sys-tertiary-container);\n color: var(--mat-sys-on-tertiary-container);\n }\n .link-btn:hover, .link-btn-featured:hover {\n opacity: 0.92;\n }\n `],\n template: `\n @if (page()) {\n <div class=\"flex flex-col items-center max-w-md mx-auto px-4 py-12 gap-6\">\n @if (page()!.avatarUrl) {\n <img\n class=\"w-24 h-24 rounded-full object-cover shadow-md\"\n [src]=\"page()!.avatarUrl\"\n [alt]=\"page()!.avatarAlt || page()!.title\"\n />\n }\n @if (page()!.headline) {\n <h1 class=\"text-2xl font-bold text-center\">{{ page()!.headline }}</h1>\n }\n @if (page()!.bio) {\n <p class=\"text-center opacity-70\">{{ page()!.bio }}</p>\n }\n <nav class=\"flex flex-col w-full gap-3\">\n @for (link of sortedLinks(); track link.id) {\n <a\n [href]=\"link.url\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n [class]=\"link.highlighted ? 'link-btn-featured' : 'link-btn'\"\n class=\"w-full flex items-center rounded-full px-5 !py-3 text-base font-medium transition-opacity no-underline\"\n >\n <span class=\"w-6 flex-shrink-0 text-lg leading-none\">\n @if (getIcon(link)) {\n <i [class]=\"getIcon(link)\"></i>\n }\n </span>\n <span class=\"flex-1 text-center\">{{ link.label }}</span>\n <span class=\"w-6 flex-shrink-0\"></span>\n </a>\n }\n </nav>\n </div>\n } @else {\n <p class=\"p-10 text-center opacity-50\">No content available.</p>\n }\n `,\n})\nexport class LinksPageComponent {\n private readonly route = inject(ActivatedRoute);\n private readonly meta = inject(Meta);\n private readonly title = inject(Title);\n private readonly platformId = inject(PLATFORM_ID);\n\n readonly page = toSignal(\n this.route.data.pipe(map((data) => (data['page'] as LinksPage) ?? null)),\n { initialValue: (this.route.snapshot.data['page'] as LinksPage) ?? null },\n );\n\n readonly sortedLinks = computed<LinksLink[]>(() =>\n [...(this.page()?.links ?? [])].sort((a, b) => a.order - b.order),\n );\n\n getIcon(link: LinksLink): string {\n if (link.icon) return link.icon;\n if (link.platform) return PLATFORM_ICONS[link.platform] ?? '';\n return '';\n }\n\n constructor() {\n effect(() => {\n const p = this.page();\n if (!p) return;\n if (!isPlatformBrowser(this.platformId)) return;\n this.title.setTitle(p.seo?.title ?? p.title);\n if (p.seo?.description) {\n this.meta.updateTag({ name: 'description', content: p.seo.description });\n }\n if (p.seo?.ogImage) {\n this.meta.updateTag({ property: 'og:image', content: p.seo.ogImage });\n }\n if (p.seo?.noIndex) {\n this.meta.updateTag({ name: 'robots', content: 'noindex' });\n } else {\n this.meta.removeTag('name=\"robots\"');\n }\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route data key used by AboutPageComponent and LinksPageComponent
|
|
3
|
+
* to read their page from Angular Router resolved data.
|
|
4
|
+
*
|
|
5
|
+
* Use this constant in your route definition's `resolve` map so the key
|
|
6
|
+
* stays in sync with what the components expect:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* {
|
|
10
|
+
* path: 'about',
|
|
11
|
+
* component: AboutPageComponent,
|
|
12
|
+
* resolve: { [CMS_ROUTE_DATA_KEY]: aboutPageResolver }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const CMS_ROUTE_DATA_KEY = 'page';
|
|
17
|
+
//# sourceMappingURL=route-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-data.js","sourceRoot":"","sources":["../../../../../libs/cms-ui/src/lib/route-data.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAe,CAAC","sourcesContent":["import type { AboutPage, LinksPage } from '@foliokit/cms-core';\n\n/**\n * The route data key used by AboutPageComponent and LinksPageComponent\n * to read their page from Angular Router resolved data.\n *\n * Use this constant in your route definition's `resolve` map so the key\n * stays in sync with what the components expect:\n *\n * ```ts\n * {\n * path: 'about',\n * component: AboutPageComponent,\n * resolve: { [CMS_ROUTE_DATA_KEY]: aboutPageResolver }\n * }\n * ```\n */\nexport const CMS_ROUTE_DATA_KEY = 'page' as const;\n\n/**\n * Shape of the resolved route data expected by AboutPageComponent.\n * Use as the return type annotation on your resolver:\n *\n * ```ts\n * export const aboutPageResolver: ResolveFn<AboutPageRouteData[typeof CMS_ROUTE_DATA_KEY]> = ...\n * ```\n */\nexport interface AboutPageRouteData {\n [CMS_ROUTE_DATA_KEY]: AboutPage | null;\n}\n\n/**\n * Shape of the resolved route data expected by LinksPageComponent.\n */\nexport interface LinksPageRouteData {\n [CMS_ROUTE_DATA_KEY]: LinksPage | null;\n}\n"]}
|
package/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export * from './lib/app-shell/app-shell.component';
|
|
2
2
|
export * from './lib/theme.service';
|
|
3
3
|
export * from './lib/shell-config.token';
|
|
4
|
+
export * from './lib/about-page/about-page.component';
|
|
5
|
+
export * from './lib/links-page/links-page.component';
|
|
6
|
+
export * from './lib/route-data';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AboutPage } from '@foliokit/cms-core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class AboutPageComponent {
|
|
4
|
+
private readonly route;
|
|
5
|
+
private readonly meta;
|
|
6
|
+
private readonly title;
|
|
7
|
+
private readonly platformId;
|
|
8
|
+
readonly page: import("@angular/core").Signal<AboutPage>;
|
|
9
|
+
constructor();
|
|
10
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AboutPageComponent, never>;
|
|
11
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<AboutPageComponent, "cms-about-page", never, {}, {}, never, never, true, never>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LinksPage, LinksLink } from '@foliokit/cms-core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class LinksPageComponent {
|
|
4
|
+
private readonly route;
|
|
5
|
+
private readonly meta;
|
|
6
|
+
private readonly title;
|
|
7
|
+
private readonly platformId;
|
|
8
|
+
readonly page: import("@angular/core").Signal<LinksPage>;
|
|
9
|
+
readonly sortedLinks: import("@angular/core").Signal<LinksLink[]>;
|
|
10
|
+
getIcon(link: LinksLink): string;
|
|
11
|
+
constructor();
|
|
12
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LinksPageComponent, never>;
|
|
13
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LinksPageComponent, "cms-links-page", never, {}, {}, never, never, true, never>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AboutPage, LinksPage } from '@foliokit/cms-core';
|
|
2
|
+
/**
|
|
3
|
+
* The route data key used by AboutPageComponent and LinksPageComponent
|
|
4
|
+
* to read their page from Angular Router resolved data.
|
|
5
|
+
*
|
|
6
|
+
* Use this constant in your route definition's `resolve` map so the key
|
|
7
|
+
* stays in sync with what the components expect:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* {
|
|
11
|
+
* path: 'about',
|
|
12
|
+
* component: AboutPageComponent,
|
|
13
|
+
* resolve: { [CMS_ROUTE_DATA_KEY]: aboutPageResolver }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const CMS_ROUTE_DATA_KEY: "page";
|
|
18
|
+
/**
|
|
19
|
+
* Shape of the resolved route data expected by AboutPageComponent.
|
|
20
|
+
* Use as the return type annotation on your resolver:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* export const aboutPageResolver: ResolveFn<AboutPageRouteData[typeof CMS_ROUTE_DATA_KEY]> = ...
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface AboutPageRouteData {
|
|
27
|
+
[CMS_ROUTE_DATA_KEY]: AboutPage | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Shape of the resolved route data expected by LinksPageComponent.
|
|
31
|
+
*/
|
|
32
|
+
export interface LinksPageRouteData {
|
|
33
|
+
[CMS_ROUTE_DATA_KEY]: LinksPage | null;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foliokit/cms-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Angular shell layout components and theme service for FolioKit CMS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -22,12 +22,11 @@
|
|
|
22
22
|
"@angular/common": "^21.2.4",
|
|
23
23
|
"@angular/core": "^21.2.4",
|
|
24
24
|
"@angular/material": "^21.2.2",
|
|
25
|
-
"@
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
}
|
|
25
|
+
"@angular/platform-browser": "^21.2.4",
|
|
26
|
+
"@angular/router": "^21.2.4",
|
|
27
|
+
"@foliokit/cms-core": "^1.0.0",
|
|
28
|
+
"ngx-markdown": "^21.1.0",
|
|
29
|
+
"rxjs": "~7.8.0"
|
|
31
30
|
},
|
|
32
31
|
"sideEffects": false,
|
|
33
32
|
"module": "esm2022/foliokit-cms-ui.js",
|