@doug-williamson/ng-rhombus 1.0.7 → 2.0.0-beta.1
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/README.md +29 -0
- package/fesm2022/doug-williamson-ng-rhombus.mjs +474 -217
- package/fesm2022/doug-williamson-ng-rhombus.mjs.map +1 -1
- package/package.json +1 -1
- package/theme/_index.scss +20 -0
- package/theme/theme-colors.scss +199 -0
- package/types/doug-williamson-ng-rhombus.d.ts +203 -37
|
@@ -1,58 +1,56 @@
|
|
|
1
1
|
import * as i1$3 from '@angular/cdk/layout';
|
|
2
2
|
import { Breakpoints } from '@angular/cdk/layout';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, Injectable, PLATFORM_ID, signal, computed, input, output, EventEmitter, Output, Component, Input,
|
|
5
|
-
import * as i3
|
|
4
|
+
import { inject, Injectable, InjectionToken, PLATFORM_ID, signal, computed, input, output, EventEmitter, Output, Component, Input, ViewChild, effect } from '@angular/core';
|
|
5
|
+
import * as i3 from '@angular/material/sidenav';
|
|
6
6
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
7
7
|
import { BehaviorSubject, filter, of, Subject, takeUntil, firstValueFrom } from 'rxjs';
|
|
8
8
|
import * as i1$1 from '@angular/material/button';
|
|
9
9
|
import { MatButtonModule } from '@angular/material/button';
|
|
10
|
-
import * as
|
|
10
|
+
import * as i2 from '@angular/material/icon';
|
|
11
11
|
import { MatIconModule } from '@angular/material/icon';
|
|
12
12
|
import * as i4 from '@angular/material/menu';
|
|
13
13
|
import { MatMenuModule } from '@angular/material/menu';
|
|
14
|
-
import * as
|
|
14
|
+
import * as i5 from '@angular/material/toolbar';
|
|
15
15
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
|
16
16
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
17
17
|
import * as i1 from '@angular/router';
|
|
18
18
|
import { NavigationEnd, Router, RouterModule, RouterLink } from '@angular/router';
|
|
19
19
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
20
|
-
import { Auth, authState, signInWithEmailAndPassword } from '@angular/fire/auth';
|
|
21
20
|
import { switchMap } from 'rxjs/operators';
|
|
22
|
-
import * as
|
|
23
|
-
import { isPlatformBrowser, TitleCasePipe, DatePipe, CommonModule, NgClass
|
|
21
|
+
import * as i5$1 from '@angular/common';
|
|
22
|
+
import { isPlatformBrowser, TitleCasePipe, DOCUMENT, DatePipe, CommonModule, NgClass } from '@angular/common';
|
|
24
23
|
import * as i6 from '@angular/material/progress-bar';
|
|
25
24
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
26
25
|
import * as i1$2 from '@angular/material/list';
|
|
27
26
|
import { MatListModule } from '@angular/material/list';
|
|
28
27
|
import { MatTabsModule } from '@angular/material/tabs';
|
|
29
|
-
import * as
|
|
28
|
+
import * as i2$1 from '@angular/forms';
|
|
30
29
|
import { Validators, ReactiveFormsModule, FormBuilder } from '@angular/forms';
|
|
31
|
-
import * as i2$
|
|
30
|
+
import * as i2$2 from '@angular/material/card';
|
|
32
31
|
import { MatCardModule } from '@angular/material/card';
|
|
33
|
-
import * as
|
|
32
|
+
import * as i3$1 from '@angular/material/form-field';
|
|
34
33
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
35
34
|
import * as i4$1 from '@angular/material/input';
|
|
36
35
|
import { MatInputModule } from '@angular/material/input';
|
|
37
36
|
import * as i1$4 from '@angular/material/progress-spinner';
|
|
38
37
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
39
|
-
import
|
|
38
|
+
import * as i2$3 from '@angular/material/chips';
|
|
39
|
+
import { MatChipsModule } from '@angular/material/chips';
|
|
40
|
+
import { MatDividerModule } from '@angular/material/divider';
|
|
40
41
|
import * as i3$2 from '@angular/material/table';
|
|
41
42
|
import { MatTableModule } from '@angular/material/table';
|
|
42
|
-
import * as i2$
|
|
43
|
+
import * as i2$4 from '@angular/material/dialog';
|
|
43
44
|
import { MatDialog, MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
44
|
-
import
|
|
45
|
-
import {
|
|
46
|
-
import * as i5$
|
|
45
|
+
import * as i7 from '@angular/material/select';
|
|
46
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
47
|
+
import * as i5$2 from '@angular/cdk/text-field';
|
|
47
48
|
import { TextFieldModule } from '@angular/cdk/text-field';
|
|
48
|
-
import { MatDividerModule } from '@angular/material/divider';
|
|
49
49
|
import { MarkdownComponent } from 'ngx-markdown';
|
|
50
|
-
import * as i1$
|
|
50
|
+
import * as i1$5 from '@fortawesome/angular-fontawesome';
|
|
51
51
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
|
52
|
-
import {
|
|
52
|
+
import { faXTwitter, faFacebook, faBluesky, faLinkedin, faGithub, faTiktok, faTwitch, faInstagram, faYoutube } from '@fortawesome/free-brands-svg-icons';
|
|
53
53
|
import { faLink } from '@fortawesome/free-solid-svg-icons';
|
|
54
|
-
import * as i5$2 from '@angular/material/select';
|
|
55
|
-
import { MatSelectModule } from '@angular/material/select';
|
|
56
54
|
|
|
57
55
|
var ThemeEnum;
|
|
58
56
|
(function (ThemeEnum) {
|
|
@@ -124,19 +122,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
124
122
|
}]
|
|
125
123
|
}], ctorParameters: () => [{ type: i1.Router }, { type: i1.ActivatedRoute }] });
|
|
126
124
|
|
|
125
|
+
const NG_RHOMBUS_AUTH_ADAPTER = new InjectionToken('NG_RHOMBUS_AUTH_ADAPTER', {
|
|
126
|
+
providedIn: 'root',
|
|
127
|
+
factory: () => ({
|
|
128
|
+
currentUser$: of(null),
|
|
129
|
+
login: async () => {
|
|
130
|
+
throw new Error('NG_RHOMBUS_AUTH_ADAPTER is not provided.');
|
|
131
|
+
},
|
|
132
|
+
logout: async () => { }
|
|
133
|
+
})
|
|
134
|
+
});
|
|
135
|
+
|
|
127
136
|
class NgRhombusAuthenticationService {
|
|
128
137
|
constructor() {
|
|
129
|
-
this.
|
|
130
|
-
this.currentUser$ =
|
|
131
|
-
this.currentUserProfile$ = this.currentUser$.pipe(switchMap((user) =>
|
|
132
|
-
|
|
133
|
-
}));
|
|
134
|
-
this.currentUserProfile = toSignal(this.currentUserProfile$);
|
|
138
|
+
this.authAdapter = inject(NG_RHOMBUS_AUTH_ADAPTER);
|
|
139
|
+
this.currentUser$ = this.authAdapter.currentUser$;
|
|
140
|
+
this.currentUserProfile$ = this.currentUser$.pipe(switchMap((user) => of(user?.uid ? user : null)));
|
|
141
|
+
this.currentUserProfile = toSignal(this.currentUserProfile$, { initialValue: null });
|
|
135
142
|
}
|
|
136
143
|
login(email, password) {
|
|
137
|
-
return
|
|
144
|
+
return this.authAdapter.login(email, password);
|
|
138
145
|
}
|
|
139
|
-
logout() { return this.
|
|
146
|
+
logout() { return this.authAdapter.logout(); }
|
|
140
147
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusAuthenticationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
141
148
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusAuthenticationService, providedIn: 'root' }); }
|
|
142
149
|
}
|
|
@@ -219,11 +226,11 @@ class NgRhombusHeaderComponent {
|
|
|
219
226
|
this.logOut.emit();
|
|
220
227
|
}
|
|
221
228
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
222
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusHeaderComponent, isStandalone: true, selector: "ng-rhombus-header", inputs: { isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { logOut: "logOut", menuToggled: "menuToggled" }, ngImport: i0, template: "<mat-toolbar class=\"header-theme relative\">\r\n @if (isMobile()) {\r\n <button mat-icon-button (click)=\"toggleSidebar()\" color=\"red\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n }\r\n @if (isMobile()) {\r\n <span class=\"flex-1\"></span>\r\n }\r\n <a mat-button class=\"title\" [routerLink]=\"['/
|
|
229
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusHeaderComponent, isStandalone: true, selector: "ng-rhombus-header", inputs: { isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { logOut: "logOut", menuToggled: "menuToggled" }, ngImport: i0, template: "<mat-toolbar class=\"header-theme relative\">\r\n @if (isMobile()) {\r\n <button mat-icon-button (click)=\"toggleSidebar()\" color=\"red\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n }\r\n @if (isMobile()) {\r\n <span class=\"flex-1\"></span>\r\n }\r\n <a mat-button class=\"title\" [routerLink]=\"['/']\">\r\n {{ title() }}\r\n </a>\r\n <span class=\"flex-1\"></span>\r\n <button class=\"mr-2\" mat-icon-button [mat-menu-trigger-for]=\"themeMenu\">\r\n <mat-icon>{{ themeService.selectedTheme()?.icon }}</mat-icon>\r\n </button>\r\n <mat-menu #themeMenu=\"matMenu\">\r\n @for (theme of themeService.getThemes(); track theme.name) {\r\n <button [class.selected-theme]=\"themeService.selectedTheme()?.name === theme.name\" mat-menu-item\r\n (click)=\"themeService.setTheme(theme.name)\">\r\n <mat-icon>{{ theme.icon }}</mat-icon>\r\n <span>{{ theme.name | titlecase }}</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n\r\n @if (user()) {\r\n <button mat-raised-button color=\"primary\" class=\"logout-button\" (click)=\"logout()\">\r\n Logout\r\n </button>\r\n }\r\n</mat-toolbar>\r\n@if (isLoading()) {\r\n<mat-progress-bar class=\"!absolute top-[64px] z-10\" mode=\"indeterminate\" />\r\n}", styles: [".header-theme{--mat-toolbar-container-background-color: var(--mat-sys-primary);--mat-toolbar-container-text-color: var(--mat-sys-on-primary);--mat-icon-button-icon-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.title{font-size:22px}.selected-theme{--mat-menu-item-icon-color: var(--mat-sys-primary);--mat-menu-item-label-text-color: var(--mat-sys-primary)}mat-progress-bar{--mat-progress-bar-active-indicator-color: var(--mat-sys-primary);--mat-progress-bar-track-color: var(--mat-sys-surface-variant)}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i5.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i6.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "pipe", type: TitleCasePipe, name: "titlecase" }] }); }
|
|
223
230
|
}
|
|
224
231
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHeaderComponent, decorators: [{
|
|
225
232
|
type: Component,
|
|
226
|
-
args: [{ selector: 'ng-rhombus-header', imports: [RouterModule, MatButtonModule, MatIconModule, MatMenuModule, MatToolbarModule, TitleCasePipe, MatProgressBarModule], template: "<mat-toolbar class=\"header-theme relative\">\r\n @if (isMobile()) {\r\n <button mat-icon-button (click)=\"toggleSidebar()\" color=\"red\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n }\r\n @if (isMobile()) {\r\n <span class=\"flex-1\"></span>\r\n }\r\n <a mat-button class=\"title\" [routerLink]=\"['/
|
|
233
|
+
args: [{ selector: 'ng-rhombus-header', imports: [RouterModule, MatButtonModule, MatIconModule, MatMenuModule, MatToolbarModule, TitleCasePipe, MatProgressBarModule], template: "<mat-toolbar class=\"header-theme relative\">\r\n @if (isMobile()) {\r\n <button mat-icon-button (click)=\"toggleSidebar()\" color=\"red\">\r\n <mat-icon>menu</mat-icon>\r\n </button>\r\n }\r\n @if (isMobile()) {\r\n <span class=\"flex-1\"></span>\r\n }\r\n <a mat-button class=\"title\" [routerLink]=\"['/']\">\r\n {{ title() }}\r\n </a>\r\n <span class=\"flex-1\"></span>\r\n <button class=\"mr-2\" mat-icon-button [mat-menu-trigger-for]=\"themeMenu\">\r\n <mat-icon>{{ themeService.selectedTheme()?.icon }}</mat-icon>\r\n </button>\r\n <mat-menu #themeMenu=\"matMenu\">\r\n @for (theme of themeService.getThemes(); track theme.name) {\r\n <button [class.selected-theme]=\"themeService.selectedTheme()?.name === theme.name\" mat-menu-item\r\n (click)=\"themeService.setTheme(theme.name)\">\r\n <mat-icon>{{ theme.icon }}</mat-icon>\r\n <span>{{ theme.name | titlecase }}</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n\r\n @if (user()) {\r\n <button mat-raised-button color=\"primary\" class=\"logout-button\" (click)=\"logout()\">\r\n Logout\r\n </button>\r\n }\r\n</mat-toolbar>\r\n@if (isLoading()) {\r\n<mat-progress-bar class=\"!absolute top-[64px] z-10\" mode=\"indeterminate\" />\r\n}", styles: [".header-theme{--mat-toolbar-container-background-color: var(--mat-sys-primary);--mat-toolbar-container-text-color: var(--mat-sys-on-primary);--mat-icon-button-icon-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.title{font-size:22px}.selected-theme{--mat-menu-item-icon-color: var(--mat-sys-primary);--mat-menu-item-label-text-color: var(--mat-sys-primary)}mat-progress-bar{--mat-progress-bar-active-indicator-color: var(--mat-sys-primary);--mat-progress-bar-track-color: var(--mat-sys-surface-variant)}\n"] }]
|
|
227
234
|
}], propDecorators: { isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], isMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "isMobile", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], logOut: [{ type: i0.Output, args: ["logOut"] }], menuToggled: [{
|
|
228
235
|
type: Output
|
|
229
236
|
}] } });
|
|
@@ -269,6 +276,7 @@ class NgRhombusWrapperComponent {
|
|
|
269
276
|
.pipe(takeUntil(this.destroyed))
|
|
270
277
|
.subscribe(result => {
|
|
271
278
|
this.isMobile = result.matches;
|
|
279
|
+
console.log('is mobile: ', this.isMobile);
|
|
272
280
|
});
|
|
273
281
|
}
|
|
274
282
|
ngOnInit() {
|
|
@@ -284,11 +292,11 @@ class NgRhombusWrapperComponent {
|
|
|
284
292
|
this.logout.emit();
|
|
285
293
|
}
|
|
286
294
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusWrapperComponent, deps: [{ token: i1$3.BreakpointObserver }, { token: WrapperService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
287
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusWrapperComponent, isStandalone: true, selector: "ng-rhombus-wrapper", inputs: { isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null }, routeCollection: { classPropertyName: "routeCollection", publicName: "routeCollection", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { clickAddNewEvent: "clickAddNewEvent", logout: "logout" }, providers: [WrapperService], ngImport: i0, template: "<div class=\"h-full flex flex-col\">\r\n <ng-rhombus-header class=\"header-wrapper\" [isMobile]=\"isMobile\" [title]=\"title\" (menuToggled)=\"sidenav.toggle()\"\r\n (logOut)=\"logOut()\" [isLoading]=\"isLoading()\"></ng-rhombus-header>\r\n <mat-sidenav-container class=\"grow\">\r\n <mat-sidenav #sidenav [opened]=\"!isMobile\" [mode]=\"isMobile ? 'over' : 'side'\">\r\n <div class=\"nav-list-wrapper\">\r\n <ng-rhombus-nav-list [routeCollection]=\"routeCollection\"
|
|
295
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusWrapperComponent, isStandalone: true, selector: "ng-rhombus-wrapper", inputs: { isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: false, isRequired: false, transformFunction: null }, routeCollection: { classPropertyName: "routeCollection", publicName: "routeCollection", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { clickAddNewEvent: "clickAddNewEvent", logout: "logout" }, providers: [WrapperService], ngImport: i0, template: "<div class=\"h-full flex flex-col\">\r\n <ng-rhombus-header class=\"header-wrapper\" [isMobile]=\"isMobile\" [title]=\"title\" (menuToggled)=\"sidenav.toggle()\"\r\n (logOut)=\"logOut()\" [isLoading]=\"isLoading()\"></ng-rhombus-header>\r\n <mat-sidenav-container class=\"grow\">\r\n <mat-sidenav #sidenav [opened]=\"!isMobile\" [mode]=\"isMobile ? 'over' : 'side'\">\r\n <div class=\"nav-list-wrapper\">\r\n <ng-rhombus-nav-list [routeCollection]=\"routeCollection\"\r\n (navItemSelected)=\"isMobile && sidenav.close()\"></ng-rhombus-nav-list>\r\n </div>\r\n </mat-sidenav>\r\n <mat-sidenav-content class=\"main-sidenav-content\">\r\n <router-outlet #o=\"outlet\"></router-outlet>\r\n </mat-sidenav-content>\r\n </mat-sidenav-container>\r\n</div>", styles: [".header-wrapper{z-index:5}mat-sidenav{width:250px}\n"], dependencies: [{ kind: "ngmodule", type: MatSidenavModule }, { kind: "component", type: i3.MatSidenav, selector: "mat-sidenav", inputs: ["fixedInViewport", "fixedTopGap", "fixedBottomGap"], exportAs: ["matSidenav"] }, { kind: "component", type: i3.MatSidenavContainer, selector: "mat-sidenav-container", exportAs: ["matSidenavContainer"] }, { kind: "component", type: i3.MatSidenavContent, selector: "mat-sidenav-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "component", type: NgRhombusHeaderComponent, selector: "ng-rhombus-header", inputs: ["isLoading", "isMobile", "title"], outputs: ["logOut", "menuToggled"] }, { kind: "component", type: NgRhombusNavListComponent, selector: "ng-rhombus-nav-list", inputs: ["routeCollection"], outputs: ["navItemSelected"] }] }); }
|
|
288
296
|
}
|
|
289
297
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusWrapperComponent, decorators: [{
|
|
290
298
|
type: Component,
|
|
291
|
-
args: [{ selector: 'ng-rhombus-wrapper', imports: [MatSidenavModule, MatButtonModule, MatIconModule, MatListModule, MatProgressBarModule,
|
|
299
|
+
args: [{ selector: 'ng-rhombus-wrapper', imports: [MatSidenavModule, MatButtonModule, MatIconModule, MatListModule, MatProgressBarModule, RouterModule, NgRhombusHeaderComponent, NgRhombusNavListComponent], providers: [WrapperService], template: "<div class=\"h-full flex flex-col\">\r\n <ng-rhombus-header class=\"header-wrapper\" [isMobile]=\"isMobile\" [title]=\"title\" (menuToggled)=\"sidenav.toggle()\"\r\n (logOut)=\"logOut()\" [isLoading]=\"isLoading()\"></ng-rhombus-header>\r\n <mat-sidenav-container class=\"grow\">\r\n <mat-sidenav #sidenav [opened]=\"!isMobile\" [mode]=\"isMobile ? 'over' : 'side'\">\r\n <div class=\"nav-list-wrapper\">\r\n <ng-rhombus-nav-list [routeCollection]=\"routeCollection\"\r\n (navItemSelected)=\"isMobile && sidenav.close()\"></ng-rhombus-nav-list>\r\n </div>\r\n </mat-sidenav>\r\n <mat-sidenav-content class=\"main-sidenav-content\">\r\n <router-outlet #o=\"outlet\"></router-outlet>\r\n </mat-sidenav-content>\r\n </mat-sidenav-container>\r\n</div>", styles: [".header-wrapper{z-index:5}mat-sidenav{width:250px}\n"] }]
|
|
292
300
|
}], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: WrapperService }], propDecorators: { clickAddNewEvent: [{
|
|
293
301
|
type: Output
|
|
294
302
|
}], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], logout: [{ type: i0.Output, args: ["logout"] }], title: [{
|
|
@@ -331,13 +339,13 @@ class NgRhombusLoginComponent {
|
|
|
331
339
|
const rawForm = this.loginForm.getRawValue();
|
|
332
340
|
this.onSubmit.emit(rawForm);
|
|
333
341
|
}
|
|
334
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusLoginComponent, deps: [{ token:
|
|
335
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusLoginComponent, isStandalone: true, selector: "ng-rhombus-login", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSubmit: "onSubmit" }, ngImport: i0, template: "<div class=\"flex min-h-screen items-center justify-center bg-background\">\r\n <mat-card class=\"w-full max-w-[400px] p-10\">\r\n <mat-card-header>\r\n <mat-card-title-group>\r\n <mat-card-title>{{ title() }}</mat-card-title>\r\n <mat-card-subtitle>Administration</mat-card-subtitle>\r\n </mat-card-title-group>\r\n\r\n </mat-card-header>\r\n <form class=\"flex flex-col\" novalidate [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmitForm()\">\r\n <mat-card-content>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Email Address</mat-label>\r\n <input matInput placeholder=\"Placeholder\" formControlName=\"email\" aria-label=\"Email Address\" />\r\n <mat-icon matSuffix>email</mat-icon>\r\n <mat-error align=\"start\">Email Address Required</mat-error>\r\n </mat-form-field>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Password</mat-label>\r\n <input matInput [type]=\"hidePassword() ? 'password' : 'text'\" placeholder=\"Placeholder\"\r\n formControlName=\"password\" aria-label=\"Password\" />\r\n <button mat-icon-button matSuffix (click)=\"hidePassword.set(!hidePassword())\" type=\"button\"\r\n style=\"margin-right: 0.25rem;\">\r\n <mat-icon>{{ hidePassword() ? 'visibility_off' : 'visibility' }}</mat-icon>\r\n </button>\r\n <mat-error align=\"start\">Password Required</mat-error>\r\n </mat-form-field>\r\n </mat-card-content>\r\n <button mat-flat-button class=\"login-button\" type=\"submit\" [disabled]=\"loading() || loginForm.invalid\">\r\n <ng-rhombus-loading-container [loading]=\"loading()\" [size]=\"20\">\r\n Login\r\n </ng-rhombus-loading-container>\r\n </button>\r\n </form>\r\n </mat-card>\r\n</div>", styles: ["mat-card{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.login-button{margin:8px 16px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type:
|
|
342
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusLoginComponent, deps: [{ token: i2$1.FormBuilder }, { token: i1.Router }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
343
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusLoginComponent, isStandalone: true, selector: "ng-rhombus-login", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSubmit: "onSubmit" }, ngImport: i0, template: "<div class=\"flex min-h-screen items-center justify-center bg-background\">\r\n <mat-card class=\"w-full max-w-[400px] p-10\">\r\n <mat-card-header>\r\n <mat-card-title-group>\r\n <mat-card-title>{{ title() }}</mat-card-title>\r\n <mat-card-subtitle>Administration</mat-card-subtitle>\r\n </mat-card-title-group>\r\n\r\n </mat-card-header>\r\n <form class=\"flex flex-col\" novalidate [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmitForm()\">\r\n <mat-card-content>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Email Address</mat-label>\r\n <input matInput placeholder=\"Placeholder\" formControlName=\"email\" aria-label=\"Email Address\" />\r\n <mat-icon matSuffix>email</mat-icon>\r\n <mat-error align=\"start\">Email Address Required</mat-error>\r\n </mat-form-field>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Password</mat-label>\r\n <input matInput [type]=\"hidePassword() ? 'password' : 'text'\" placeholder=\"Placeholder\"\r\n formControlName=\"password\" aria-label=\"Password\" />\r\n <button mat-icon-button matSuffix (click)=\"hidePassword.set(!hidePassword())\" type=\"button\"\r\n style=\"margin-right: 0.25rem;\">\r\n <mat-icon>{{ hidePassword() ? 'visibility_off' : 'visibility' }}</mat-icon>\r\n </button>\r\n <mat-error align=\"start\">Password Required</mat-error>\r\n </mat-form-field>\r\n </mat-card-content>\r\n <button mat-flat-button class=\"login-button\" type=\"submit\" [disabled]=\"loading() || loginForm.invalid\">\r\n <ng-rhombus-loading-container [loading]=\"loading()\" [size]=\"20\">\r\n Login\r\n </ng-rhombus-loading-container>\r\n </button>\r\n </form>\r\n </mat-card>\r\n</div>", styles: ["mat-card{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.login-button{margin:8px 16px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2$2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$2.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i2$2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "component", type: i2$2.MatCardTitleGroup, selector: "mat-card-title-group" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: NgRhombusLoadingContainerComponent, selector: "ng-rhombus-loading-container", inputs: ["loading", "size"] }] }); }
|
|
336
344
|
}
|
|
337
345
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusLoginComponent, decorators: [{
|
|
338
346
|
type: Component,
|
|
339
347
|
args: [{ selector: 'ng-rhombus-login', imports: [ReactiveFormsModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatIconModule, MatInputModule, NgRhombusLoadingContainerComponent], template: "<div class=\"flex min-h-screen items-center justify-center bg-background\">\r\n <mat-card class=\"w-full max-w-[400px] p-10\">\r\n <mat-card-header>\r\n <mat-card-title-group>\r\n <mat-card-title>{{ title() }}</mat-card-title>\r\n <mat-card-subtitle>Administration</mat-card-subtitle>\r\n </mat-card-title-group>\r\n\r\n </mat-card-header>\r\n <form class=\"flex flex-col\" novalidate [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmitForm()\">\r\n <mat-card-content>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Email Address</mat-label>\r\n <input matInput placeholder=\"Placeholder\" formControlName=\"email\" aria-label=\"Email Address\" />\r\n <mat-icon matSuffix>email</mat-icon>\r\n <mat-error align=\"start\">Email Address Required</mat-error>\r\n </mat-form-field>\r\n <mat-form-field class=\"w-full flex\">\r\n <mat-label>Password</mat-label>\r\n <input matInput [type]=\"hidePassword() ? 'password' : 'text'\" placeholder=\"Placeholder\"\r\n formControlName=\"password\" aria-label=\"Password\" />\r\n <button mat-icon-button matSuffix (click)=\"hidePassword.set(!hidePassword())\" type=\"button\"\r\n style=\"margin-right: 0.25rem;\">\r\n <mat-icon>{{ hidePassword() ? 'visibility_off' : 'visibility' }}</mat-icon>\r\n </button>\r\n <mat-error align=\"start\">Password Required</mat-error>\r\n </mat-form-field>\r\n </mat-card-content>\r\n <button mat-flat-button class=\"login-button\" type=\"submit\" [disabled]=\"loading() || loginForm.invalid\">\r\n <ng-rhombus-loading-container [loading]=\"loading()\" [size]=\"20\">\r\n Login\r\n </ng-rhombus-loading-container>\r\n </button>\r\n </form>\r\n </mat-card>\r\n</div>", styles: ["mat-card{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.login-button{margin:8px 16px}\n"] }]
|
|
340
|
-
}], ctorParameters: () => [{ type:
|
|
348
|
+
}], ctorParameters: () => [{ type: i2$1.FormBuilder }, { type: i1.Router }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], onSubmit: [{ type: i0.Output, args: ["onSubmit"] }] } });
|
|
341
349
|
|
|
342
350
|
class NgRhombusSpinnerComponent {
|
|
343
351
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
@@ -355,52 +363,108 @@ class NgRhombusPageComponent {
|
|
|
355
363
|
this.currentYear = new Date().getFullYear();
|
|
356
364
|
}
|
|
357
365
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
358
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusPageComponent, isStandalone: true, selector: "ng-rhombus-page", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, backLink: { classPropertyName: "backLink", publicName: "backLink", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"page flex flex-col h-full\">\r\n <
|
|
366
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusPageComponent, isStandalone: true, selector: "ng-rhombus-page", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, backLink: { classPropertyName: "backLink", publicName: "backLink", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"page flex flex-col h-full\">\r\n <div class=\"page-header\">\r\n @if (backLink()) {\r\n <button matIconButton [routerLink]=\"backLink()\" aria-label=\"Back\" class=\"back-button\">\r\n <mat-icon>arrow_back</mat-icon>\r\n </button>\r\n }\r\n <div class=\"page-header__title mat-title-large\">{{ title() }}</div>\r\n </div>\r\n\r\n <div class=\"content\">\r\n <ng-content></ng-content>\r\n </div>\r\n <div class=\"page-footer mt-6\">\r\n <div class=\"page-footer__text\">© {{ currentYear }} - Powered By Rhombus</div>\r\n </div>\r\n</div>", styles: [":host{display:flex;flex-direction:column}.page{display:flex;flex-direction:column;margin:12px auto;width:min(100% - 24px,600px)}.content{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}:host(.no-elevation) .content{box-shadow:none}.page-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:12px 16px;border:1px solid var(--mat-sys-outline-variant);border-left:0;border-right:0;background:var(--mat-sys-surface);color:var(--mat-sys-on-surface)}.page-header__title{margin:0}.back-button{margin-right:8px}.page-footer{display:flex;justify-content:center;padding:12px 16px;border:1px solid var(--mat-sys-outline-variant);border-left:0;border-right:0;background:var(--mat-sys-surface);color:var(--mat-sys-on-surface-variant)}.page-footer__text{font-size:14px}.content{margin-bottom:12px}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
|
|
359
367
|
}
|
|
360
368
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusPageComponent, decorators: [{
|
|
361
369
|
type: Component,
|
|
362
|
-
args: [{ selector: 'ng-rhombus-page', imports: [MatButtonModule,
|
|
370
|
+
args: [{ selector: 'ng-rhombus-page', imports: [MatButtonModule, MatIconModule, RouterModule], template: "<div class=\"page flex flex-col h-full\">\r\n <div class=\"page-header\">\r\n @if (backLink()) {\r\n <button matIconButton [routerLink]=\"backLink()\" aria-label=\"Back\" class=\"back-button\">\r\n <mat-icon>arrow_back</mat-icon>\r\n </button>\r\n }\r\n <div class=\"page-header__title mat-title-large\">{{ title() }}</div>\r\n </div>\r\n\r\n <div class=\"content\">\r\n <ng-content></ng-content>\r\n </div>\r\n <div class=\"page-footer mt-6\">\r\n <div class=\"page-footer__text\">© {{ currentYear }} - Powered By Rhombus</div>\r\n </div>\r\n</div>", styles: [":host{display:flex;flex-direction:column}.page{display:flex;flex-direction:column;margin:12px auto;width:min(100% - 24px,600px)}.content{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}:host(.no-elevation) .content{box-shadow:none}.page-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:12px 16px;border:1px solid var(--mat-sys-outline-variant);border-left:0;border-right:0;background:var(--mat-sys-surface);color:var(--mat-sys-on-surface)}.page-header__title{margin:0}.back-button{margin-right:8px}.page-footer{display:flex;justify-content:center;padding:12px 16px;border:1px solid var(--mat-sys-outline-variant);border-left:0;border-right:0;background:var(--mat-sys-surface);color:var(--mat-sys-on-surface-variant)}.page-footer__text{font-size:14px}.content{margin-bottom:12px}\n"] }]
|
|
363
371
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], backLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLink", required: false }] }] } });
|
|
364
372
|
|
|
373
|
+
const NG_RHOMBUS_SEO_ADAPTER = new InjectionToken('NG_RHOMBUS_SEO_ADAPTER', {
|
|
374
|
+
providedIn: 'root',
|
|
375
|
+
factory: () => ({
|
|
376
|
+
setTitle: () => { },
|
|
377
|
+
updateTag: () => { },
|
|
378
|
+
}),
|
|
379
|
+
});
|
|
380
|
+
|
|
365
381
|
/*
|
|
366
382
|
* Public API Surface of @doug-williamson/ng-rhombus/shell
|
|
367
383
|
*/
|
|
368
384
|
|
|
385
|
+
function ngRhombusTimestampToMillis(value) {
|
|
386
|
+
if (value == null)
|
|
387
|
+
return null;
|
|
388
|
+
if (typeof value === 'number')
|
|
389
|
+
return value;
|
|
390
|
+
if (value instanceof Date)
|
|
391
|
+
return value.getTime();
|
|
392
|
+
if (typeof value === 'string') {
|
|
393
|
+
const asNumber = Number(value);
|
|
394
|
+
if (Number.isFinite(asNumber))
|
|
395
|
+
return asNumber;
|
|
396
|
+
const asDate = new Date(value);
|
|
397
|
+
return Number.isFinite(asDate.getTime()) ? asDate.getTime() : null;
|
|
398
|
+
}
|
|
399
|
+
const maybeToMillis = value;
|
|
400
|
+
if (typeof maybeToMillis.toMillis === 'function') {
|
|
401
|
+
return maybeToMillis.toMillis();
|
|
402
|
+
}
|
|
403
|
+
const maybeSeconds = value;
|
|
404
|
+
if (typeof maybeSeconds.seconds === 'number') {
|
|
405
|
+
return maybeSeconds.seconds * 1000;
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
var ContentPillar;
|
|
410
|
+
(function (ContentPillar) {
|
|
411
|
+
ContentPillar["DeveloperLife"] = "Developer Life";
|
|
412
|
+
ContentPillar["AngularWebEngineering"] = "Angular & Web Engineering";
|
|
413
|
+
ContentPillar["GamingAsAnAdult"] = "Gaming as an Adult";
|
|
414
|
+
ContentPillar["FatherhoodFamily"] = "Fatherhood & Family";
|
|
415
|
+
ContentPillar["CareerBalanceIdentity"] = "Career, Balance & Identity";
|
|
416
|
+
})(ContentPillar || (ContentPillar = {}));
|
|
417
|
+
var BlogSeries;
|
|
418
|
+
(function (BlogSeries) {
|
|
419
|
+
BlogSeries["ThisWeekILearned"] = "This Week I Learned";
|
|
420
|
+
BlogSeries["DevDiary"] = "Dev Diary";
|
|
421
|
+
BlogSeries["AdultGamerNotes"] = "Adult Gamer Notes";
|
|
422
|
+
BlogSeries["LessonsFromFatherhood"] = "Lessons from Fatherhood";
|
|
423
|
+
BlogSeries["TradeoffsTruths"] = "Tradeoffs & Truths";
|
|
424
|
+
BlogSeries["FromTheEditor"] = "From The Editor";
|
|
425
|
+
})(BlogSeries || (BlogSeries = {}));
|
|
426
|
+
class IBlog {
|
|
427
|
+
}
|
|
428
|
+
|
|
369
429
|
class NgRhombusBlogListComponent {
|
|
370
430
|
constructor() {
|
|
371
431
|
this.goToRoute = new EventEmitter();
|
|
372
|
-
this.#
|
|
373
|
-
this.#
|
|
432
|
+
this.#seo = inject(NG_RHOMBUS_SEO_ADAPTER);
|
|
433
|
+
this.#platformId = inject(PLATFORM_ID);
|
|
434
|
+
this.#document = inject(DOCUMENT);
|
|
374
435
|
this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
|
|
436
|
+
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
375
437
|
}
|
|
376
|
-
#
|
|
377
|
-
#
|
|
438
|
+
#seo;
|
|
439
|
+
#platformId;
|
|
440
|
+
#document;
|
|
378
441
|
ngOnInit() {
|
|
379
|
-
const
|
|
442
|
+
const origin = isPlatformBrowser(this.#platformId) ? this.#document.location?.origin : undefined;
|
|
443
|
+
const imageUrl = origin ? new URL('/fav.ico', origin).toString() : '/fav.ico';
|
|
380
444
|
// Document title
|
|
381
|
-
this.#
|
|
445
|
+
this.#seo.setTitle('Blog - Doug Williamson');
|
|
382
446
|
// Basic meta
|
|
383
|
-
this.#
|
|
447
|
+
this.#seo.updateTag({ name: 'description', content: 'This is my personal collection of blog posts.' });
|
|
384
448
|
// Open Graph
|
|
385
|
-
this.#
|
|
386
|
-
this.#
|
|
387
|
-
this.#
|
|
388
|
-
this.#
|
|
449
|
+
this.#seo.updateTag({ property: 'og:type', content: 'article' });
|
|
450
|
+
this.#seo.updateTag({ property: 'og:title', content: 'Blog - Doug Williamson' });
|
|
451
|
+
this.#seo.updateTag({ property: 'og:description', content: 'This is my personal collection of blog posts.' });
|
|
452
|
+
this.#seo.updateTag({ property: 'og:image', content: imageUrl });
|
|
389
453
|
// Twitter Card
|
|
390
|
-
this.#
|
|
391
|
-
this.#
|
|
392
|
-
this.#
|
|
393
|
-
this.#
|
|
454
|
+
this.#seo.updateTag({ name: 'twitter:card', content: 'summary' });
|
|
455
|
+
this.#seo.updateTag({ name: 'twitter:title', content: 'Blog - Doug Williamson' });
|
|
456
|
+
this.#seo.updateTag({ name: 'twitter:description', content: 'This is my personal collection of blog posts.' });
|
|
457
|
+
this.#seo.updateTag({ name: 'twitter:image', content: imageUrl });
|
|
394
458
|
}
|
|
395
459
|
goToBlogPost(id) {
|
|
396
460
|
this.goToRoute.emit(id);
|
|
397
461
|
}
|
|
398
462
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
399
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusBlogListComponent, isStandalone: true, selector: "ng-rhombus-blog-list", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { goToRoute: "goToRoute" }, ngImport: i0, template: "@if ((dataSource()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No blog posts found.</p>\r\n} @else {\r\n<mat-nav-list>\r\n\t@for (blogPost of dataSource(); track blogPost.id) {\r\n\t<mat-list-item (click)=\"goToRoute.emit(blogPost.id)\">\r\n\t\t<h3 matListItemTitle>{{ blogPost.title }}</h3>\r\n\t\t<p
|
|
463
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusBlogListComponent, isStandalone: true, selector: "ng-rhombus-blog-list", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { goToRoute: "goToRoute" }, ngImport: i0, template: "@if ((dataSource()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No blog posts found.</p>\r\n} @else {\r\n<mat-nav-list>\r\n\t@for (blogPost of dataSource(); track blogPost.id; let last = $last) {\r\n\t<mat-list-item (click)=\"goToRoute.emit(blogPost.id)\" lines=\"2\">\r\n\t\t<h3 matListItemTitle>{{ blogPost.title }}</h3>\r\n\t\t<p matListItemMeta>{{ timestampToMillis(blogPost.timestamp) | date: 'MMMM d, y' }}</p>\r\n\r\n\t\t@if (blogPost.pillar || blogPost.series || (blogPost.tags?.length ?? 0) > 0) {\r\n\t\t<div matListItemLine class=\"taxonomy\">\r\n\t\t\t<mat-chip-set aria-label=\"Post taxonomy\">\r\n\t\t\t\t@if (blogPost.pillar) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"primary-chips-theme\">Pillar: {{ blogPost.pillar }}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t\t@if (blogPost.series) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"secondary-chips-theme\">Series: {{ blogPost.series }}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t\t<!-- @for (t of blogPost.tags ?? []; track t) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"neutral-chips-theme\">#{{ t }}</mat-chip>\r\n\t\t\t\t} -->\r\n\t\t\t</mat-chip-set>\r\n\t\t</div>\r\n\t\t}\r\n\t</mat-list-item>\r\n\t@if (!last) {\r\n\t<mat-divider></mat-divider>\r\n\t}\r\n\t}\r\n</mat-nav-list>\r\n}", styles: [".neutral-chips-theme{--mat-chip-outline-color: var(--mat-sys-surface-variant);--mat-chip-elevated-selected-container-color: var(--mat-sys-surface-variant);--mat-chip-selected-label-text-color: var(--mat-sys-on-surface-variant);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.primary-chips-theme{--mat-chip-outline-color: var(--mat-sys-primary);--mat-chip-elevated-selected-container-color: var(--mat-sys-primary);--mat-chip-selected-label-text-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.secondary-chips-theme{--mat-chip-outline-color: var(--mat-sys-secondary);--mat-chip-elevated-selected-container-color: var(--mat-sys-secondary);--mat-chip-selected-label-text-color: var(--mat-sys-on-secondary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.mdc-list{padding:0}.mdc-list-item{border-radius:0!important;height:auto;padding:12px 16px}.taxonomy mat-chip-set{max-width:100%}\n"], dependencies: [{ kind: "ngmodule", type: MatListModule }, { kind: "component", type: i1$2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i1$2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "component", type: i1$2.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i1$2.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i1$2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i1$2.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i2$3.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i2$3.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
|
|
400
464
|
}
|
|
401
465
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, decorators: [{
|
|
402
466
|
type: Component,
|
|
403
|
-
args: [{ selector: 'ng-rhombus-blog-list', imports: [DatePipe, MatListModule], template: "@if ((dataSource()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No blog posts found.</p>\r\n} @else {\r\n<mat-nav-list>\r\n\t@for (blogPost of dataSource(); track blogPost.id) {\r\n\t<mat-list-item (click)=\"goToRoute.emit(blogPost.id)\">\r\n\t\t<h3 matListItemTitle>{{ blogPost.title }}</h3>\r\n\t\t<p
|
|
467
|
+
args: [{ selector: 'ng-rhombus-blog-list', imports: [DatePipe, MatListModule, MatDividerModule, MatChipsModule], template: "@if ((dataSource()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No blog posts found.</p>\r\n} @else {\r\n<mat-nav-list>\r\n\t@for (blogPost of dataSource(); track blogPost.id; let last = $last) {\r\n\t<mat-list-item (click)=\"goToRoute.emit(blogPost.id)\" lines=\"2\">\r\n\t\t<h3 matListItemTitle>{{ blogPost.title }}</h3>\r\n\t\t<p matListItemMeta>{{ timestampToMillis(blogPost.timestamp) | date: 'MMMM d, y' }}</p>\r\n\r\n\t\t@if (blogPost.pillar || blogPost.series || (blogPost.tags?.length ?? 0) > 0) {\r\n\t\t<div matListItemLine class=\"taxonomy\">\r\n\t\t\t<mat-chip-set aria-label=\"Post taxonomy\">\r\n\t\t\t\t@if (blogPost.pillar) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"primary-chips-theme\">Pillar: {{ blogPost.pillar }}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t\t@if (blogPost.series) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"secondary-chips-theme\">Series: {{ blogPost.series }}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t\t<!-- @for (t of blogPost.tags ?? []; track t) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"neutral-chips-theme\">#{{ t }}</mat-chip>\r\n\t\t\t\t} -->\r\n\t\t\t</mat-chip-set>\r\n\t\t</div>\r\n\t\t}\r\n\t</mat-list-item>\r\n\t@if (!last) {\r\n\t<mat-divider></mat-divider>\r\n\t}\r\n\t}\r\n</mat-nav-list>\r\n}", styles: [".neutral-chips-theme{--mat-chip-outline-color: var(--mat-sys-surface-variant);--mat-chip-elevated-selected-container-color: var(--mat-sys-surface-variant);--mat-chip-selected-label-text-color: var(--mat-sys-on-surface-variant);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.primary-chips-theme{--mat-chip-outline-color: var(--mat-sys-primary);--mat-chip-elevated-selected-container-color: var(--mat-sys-primary);--mat-chip-selected-label-text-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.secondary-chips-theme{--mat-chip-outline-color: var(--mat-sys-secondary);--mat-chip-elevated-selected-container-color: var(--mat-sys-secondary);--mat-chip-selected-label-text-color: var(--mat-sys-on-secondary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.mdc-list{padding:0}.mdc-list-item{border-radius:0!important;height:auto;padding:12px 16px}.taxonomy mat-chip-set{max-width:100%}\n"] }]
|
|
404
468
|
}], propDecorators: { goToRoute: [{
|
|
405
469
|
type: Output
|
|
406
470
|
}], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
@@ -408,13 +472,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
408
472
|
class NgRhombusBlogPostLatestComponent {
|
|
409
473
|
constructor() {
|
|
410
474
|
this.blogPost = input(...(ngDevMode ? [undefined, { debugName: "blogPost" }] : []));
|
|
475
|
+
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
411
476
|
}
|
|
412
477
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
413
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusBlogPostLatestComponent, isStandalone: true, selector: "ng-rhombus-blog-post-latest", inputs: { blogPost: { classPropertyName: "blogPost", publicName: "blogPost", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<mat-nav-list>\r\n <mat-list-item [routerLink]=\"['/blog', blogPost()?.id]\">\r\n <span matListItemTitle>{{ blogPost()?.title }}</span>\r\n <span matListItemLine>{{ blogPost()?.description }}</span>\r\n <span matListItemLine>{{ blogPost()?.timestamp
|
|
478
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusBlogPostLatestComponent, isStandalone: true, selector: "ng-rhombus-blog-post-latest", inputs: { blogPost: { classPropertyName: "blogPost", publicName: "blogPost", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<mat-nav-list>\r\n <mat-list-item [routerLink]=\"['/blog', blogPost()?.id]\">\r\n <span matListItemTitle>{{ blogPost()?.title }}</span>\r\n <span matListItemLine>{{ blogPost()?.description }}</span>\r\n <span matListItemLine>{{ timestampToMillis(blogPost()?.timestamp) | date: 'MMMM d, y' }}</span>\r\n </mat-list-item>\r\n</mat-nav-list>", styles: [".mdc-list-item{border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatListModule }, { kind: "component", type: i1$2.MatNavList, selector: "mat-nav-list", exportAs: ["matNavList"] }, { kind: "component", type: i1$2.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i1$2.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i1$2.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
|
|
414
479
|
}
|
|
415
480
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, decorators: [{
|
|
416
481
|
type: Component,
|
|
417
|
-
args: [{ selector: 'ng-rhombus-blog-post-latest', imports: [DatePipe, MatListModule, RouterLink], template: "<mat-nav-list>\r\n <mat-list-item [routerLink]=\"['/blog', blogPost()?.id]\">\r\n <span matListItemTitle>{{ blogPost()?.title }}</span>\r\n <span matListItemLine>{{ blogPost()?.description }}</span>\r\n <span matListItemLine>{{ blogPost()?.timestamp
|
|
482
|
+
args: [{ selector: 'ng-rhombus-blog-post-latest', imports: [DatePipe, MatListModule, RouterLink], template: "<mat-nav-list>\r\n <mat-list-item [routerLink]=\"['/blog', blogPost()?.id]\">\r\n <span matListItemTitle>{{ blogPost()?.title }}</span>\r\n <span matListItemLine>{{ blogPost()?.description }}</span>\r\n <span matListItemLine>{{ timestampToMillis(blogPost()?.timestamp) | date: 'MMMM d, y' }}</span>\r\n </mat-list-item>\r\n</mat-nav-list>", styles: [".mdc-list-item{border-radius:0!important}\n"] }]
|
|
418
483
|
}], propDecorators: { blogPost: [{ type: i0.Input, args: [{ isSignal: true, alias: "blogPost", required: false }] }] } });
|
|
419
484
|
|
|
420
485
|
class NgRhombusBlogPostHelper {
|
|
@@ -432,72 +497,125 @@ class NgRhombusBlogPostHelper {
|
|
|
432
497
|
}
|
|
433
498
|
}
|
|
434
499
|
|
|
500
|
+
const NG_RHOMBUS_BLOG_ADAPTER = new InjectionToken('NG_RHOMBUS_BLOG_ADAPTER', {
|
|
501
|
+
providedIn: 'root',
|
|
502
|
+
factory: () => ({
|
|
503
|
+
fetchBlogPosts: async () => {
|
|
504
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
505
|
+
},
|
|
506
|
+
fetchBlogPost: async () => {
|
|
507
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
508
|
+
},
|
|
509
|
+
fetchLatestBlogPost: async () => {
|
|
510
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
511
|
+
},
|
|
512
|
+
fetchLatestBlogPosts: async () => {
|
|
513
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
514
|
+
},
|
|
515
|
+
fetchLatestBlogPostsBySeries: async () => {
|
|
516
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
517
|
+
},
|
|
518
|
+
fetchAdjacentBlogPostsInSeries: async () => {
|
|
519
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
520
|
+
},
|
|
521
|
+
fetchAdjacentBlogPosts: async () => {
|
|
522
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
523
|
+
},
|
|
524
|
+
createBlogPost: async () => {
|
|
525
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
526
|
+
},
|
|
527
|
+
updateBlogPost: async () => {
|
|
528
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
529
|
+
},
|
|
530
|
+
deleteBlogPost: async () => {
|
|
531
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
532
|
+
},
|
|
533
|
+
}),
|
|
534
|
+
});
|
|
535
|
+
|
|
435
536
|
class NgRhombusBlogService {
|
|
436
537
|
constructor() {
|
|
437
538
|
this.WORDS_PER_MINUTE = 200;
|
|
438
|
-
this.
|
|
439
|
-
this.injector = inject(Injector);
|
|
440
|
-
this.blogCollectionRef = collection(this.firestore, 'blog');
|
|
539
|
+
this.blogAdapter = inject(NG_RHOMBUS_BLOG_ADAPTER);
|
|
441
540
|
this.blogPosts = signal([], ...(ngDevMode ? [{ debugName: "blogPosts" }] : []));
|
|
442
541
|
this.selectedBlogPost = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBlogPost" }] : []));
|
|
443
542
|
}
|
|
543
|
+
normalizeBlogPost(post) {
|
|
544
|
+
const timestampMillis = ngRhombusTimestampToMillis(post.timestamp);
|
|
545
|
+
return {
|
|
546
|
+
...post,
|
|
547
|
+
timestamp: timestampMillis ?? post.timestamp,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
444
550
|
async fetchBlogPosts() {
|
|
445
|
-
const
|
|
446
|
-
const snapshot = await runInInjectionContext(this.injector, () => getDocs(q));
|
|
447
|
-
const posts = snapshot.docs.map(d => ({ ...d.data(), id: d.id }));
|
|
551
|
+
const posts = (await this.blogAdapter.fetchBlogPosts()).map(p => this.normalizeBlogPost(p));
|
|
448
552
|
this.blogPosts.set(posts);
|
|
449
553
|
return posts;
|
|
450
554
|
}
|
|
451
555
|
async fetchBlogPost(id) {
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const docData = { ...docSnap.data(), id: id };
|
|
459
|
-
this.selectedBlogPost.set(docData);
|
|
460
|
-
return docData;
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
556
|
+
const post = await this.blogAdapter.fetchBlogPost(id);
|
|
557
|
+
if (!post)
|
|
558
|
+
return undefined;
|
|
559
|
+
const normalized = this.normalizeBlogPost(post);
|
|
560
|
+
this.selectedBlogPost.set(normalized);
|
|
561
|
+
return normalized;
|
|
465
562
|
}
|
|
466
563
|
async fetchLatestBlogPost() {
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return latestPost;
|
|
474
|
-
}
|
|
475
|
-
return undefined;
|
|
564
|
+
const post = await this.blogAdapter.fetchLatestBlogPost();
|
|
565
|
+
if (!post)
|
|
566
|
+
return undefined;
|
|
567
|
+
const normalized = this.normalizeBlogPost(post);
|
|
568
|
+
this.selectedBlogPost.set(normalized);
|
|
569
|
+
return normalized;
|
|
476
570
|
}
|
|
477
571
|
async createBlogPost(blogPost) {
|
|
478
|
-
const
|
|
479
|
-
await
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
thumbnail: blogPost.thumbnail,
|
|
483
|
-
content: blogPost.content,
|
|
572
|
+
const id = NgRhombusBlogPostHelper.createSlug(blogPost.title);
|
|
573
|
+
await this.blogAdapter.createBlogPost({
|
|
574
|
+
...blogPost,
|
|
575
|
+
id,
|
|
484
576
|
timestamp: new Date(),
|
|
485
|
-
|
|
486
|
-
}));
|
|
577
|
+
});
|
|
487
578
|
}
|
|
488
579
|
async updateBlogPost(blogPost) {
|
|
489
|
-
|
|
490
|
-
await runInInjectionContext(this.injector, () => updateDoc(blogPostDocumentRef, {
|
|
491
|
-
title: blogPost.title,
|
|
492
|
-
description: blogPost.description,
|
|
493
|
-
thumbnail: blogPost.thumbnail,
|
|
494
|
-
content: blogPost.content,
|
|
495
|
-
readTimeMinutes: blogPost.readTimeMinutes
|
|
496
|
-
}));
|
|
580
|
+
await this.blogAdapter.updateBlogPost(blogPost);
|
|
497
581
|
}
|
|
498
582
|
deleteBlogPost(id) {
|
|
499
|
-
|
|
500
|
-
|
|
583
|
+
return this.blogAdapter.deleteBlogPost(id);
|
|
584
|
+
}
|
|
585
|
+
/** Latest N posts */
|
|
586
|
+
async fetchLatestBlogPosts(count = 5) {
|
|
587
|
+
const posts = await this.blogAdapter.fetchLatestBlogPosts(count);
|
|
588
|
+
return posts.map(p => this.normalizeBlogPost(p));
|
|
589
|
+
}
|
|
590
|
+
/** Latest N posts for a given series */
|
|
591
|
+
async fetchLatestBlogPostsBySeries(series, count = 5) {
|
|
592
|
+
const posts = await this.blogAdapter.fetchLatestBlogPostsBySeries(series, count);
|
|
593
|
+
return posts.map(p => this.normalizeBlogPost(p));
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Adjacent posts within the same series as the current post.
|
|
597
|
+
* - nextNewer: newer than current within the series (if any)
|
|
598
|
+
* - nextOlder: older than current within the series (if any)
|
|
599
|
+
*/
|
|
600
|
+
async fetchAdjacentBlogPostsInSeries(id) {
|
|
601
|
+
const result = await this.blogAdapter.fetchAdjacentBlogPostsInSeries(id);
|
|
602
|
+
return {
|
|
603
|
+
series: result.series,
|
|
604
|
+
nextNewer: result.nextNewer ? this.normalizeBlogPost(result.nextNewer) : undefined,
|
|
605
|
+
nextOlder: result.nextOlder ? this.normalizeBlogPost(result.nextOlder) : undefined,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Adjacent posts in the global "latest first" ordering.
|
|
610
|
+
* - nextNewer: newer than current (if any)
|
|
611
|
+
* - nextOlder: older than current (if any)
|
|
612
|
+
*/
|
|
613
|
+
async fetchAdjacentBlogPosts(id) {
|
|
614
|
+
const result = await this.blogAdapter.fetchAdjacentBlogPosts(id);
|
|
615
|
+
return {
|
|
616
|
+
nextNewer: result.nextNewer ? this.normalizeBlogPost(result.nextNewer) : undefined,
|
|
617
|
+
nextOlder: result.nextOlder ? this.normalizeBlogPost(result.nextOlder) : undefined,
|
|
618
|
+
};
|
|
501
619
|
}
|
|
502
620
|
calculateReadTimeMinutes(text) {
|
|
503
621
|
if (!text)
|
|
@@ -510,44 +628,30 @@ class NgRhombusBlogService {
|
|
|
510
628
|
}
|
|
511
629
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogService, decorators: [{
|
|
512
630
|
type: Injectable,
|
|
513
|
-
args: [{
|
|
514
|
-
providedIn: 'root',
|
|
515
|
-
}]
|
|
516
|
-
}] });
|
|
517
|
-
|
|
518
|
-
class NgRhombusBlogPostThumbnailService {
|
|
519
|
-
constructor() {
|
|
520
|
-
this.firebaseStorage = inject(Storage);
|
|
521
|
-
}
|
|
522
|
-
uploadImage(imageName, image) {
|
|
523
|
-
const storageRef = ref(this.firebaseStorage, `thumbnails/${imageName}`);
|
|
524
|
-
return uploadBytesResumable(storageRef, image);
|
|
525
|
-
}
|
|
526
|
-
deleteImage(filePath) {
|
|
527
|
-
const storageDocRef = ref(this.firebaseStorage, filePath);
|
|
528
|
-
return deleteObject(storageDocRef).then(() => {
|
|
529
|
-
}).catch((error) => {
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
533
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, providedIn: 'root' }); }
|
|
534
|
-
}
|
|
535
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, decorators: [{
|
|
536
|
-
type: Injectable,
|
|
537
|
-
args: [{
|
|
538
|
-
providedIn: 'root'
|
|
539
|
-
}]
|
|
631
|
+
args: [{ providedIn: 'root' }]
|
|
540
632
|
}] });
|
|
541
633
|
|
|
542
634
|
class NgRhombusBlogTableComponent {
|
|
543
635
|
constructor() {
|
|
544
636
|
this.editEvent = new EventEmitter();
|
|
545
637
|
this.deleteEvent = new EventEmitter();
|
|
546
|
-
this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
|
|
547
638
|
this.blogService = inject(NgRhombusBlogService);
|
|
548
639
|
this.dialog = inject(MatDialog);
|
|
549
640
|
this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
|
|
550
|
-
this.
|
|
641
|
+
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
642
|
+
this.displayedColumns = ['title', 'pillar', 'series', 'timestamp'];
|
|
643
|
+
this.detailColumns = ['detail'];
|
|
644
|
+
this.isMainRow = (_index, row) => row.kind === 'main';
|
|
645
|
+
this.isDetailRow = (_index, row) => row.kind === 'detail';
|
|
646
|
+
}
|
|
647
|
+
tableData() {
|
|
648
|
+
const posts = this.dataSource();
|
|
649
|
+
if (!posts?.length)
|
|
650
|
+
return [];
|
|
651
|
+
return posts.flatMap(post => [
|
|
652
|
+
{ kind: 'main', post },
|
|
653
|
+
{ kind: 'detail', post },
|
|
654
|
+
]);
|
|
551
655
|
}
|
|
552
656
|
goToBlogPost(id) {
|
|
553
657
|
this.editEvent.emit(id);
|
|
@@ -556,17 +660,49 @@ class NgRhombusBlogTableComponent {
|
|
|
556
660
|
this.deleteEvent.emit(blogPost);
|
|
557
661
|
}
|
|
558
662
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
559
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusBlogTableComponent, isStandalone: true, selector: "ng-rhombus-blog-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editEvent: "editEvent", deleteEvent: "deleteEvent" }, ngImport: i0, template: "@if (dataSource().length > 0) {\r\n<table mat-table [dataSource]=\"
|
|
663
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusBlogTableComponent, isStandalone: true, selector: "ng-rhombus-blog-table", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editEvent: "editEvent", deleteEvent: "deleteEvent" }, ngImport: i0, template: "@if (dataSource().length > 0) {\r\n<table mat-table [dataSource]=\"tableData()\" class=\"mat-elevation-z1\" multiTemplateDataRows>\r\n\r\n\t<!-- Title Column -->\r\n\t<ng-container matColumnDef=\"title\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Title</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">{{ row.post.title }}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Pillar Column -->\r\n\t<ng-container matColumnDef=\"pillar\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Pillar</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t@if (row.post.pillar) {\r\n\t\t\t<mat-chip-set aria-label=\"Pillar\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"primary-chips-theme\">{{ row.post.pillar\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t</mat-chip-set>\r\n\t\t\t} @else {\r\n\t\t\t\u2014\r\n\t\t\t}\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Series Column -->\r\n\t<ng-container matColumnDef=\"series\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Series</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t@if (row.post.series) {\r\n\t\t\t<mat-chip-set aria-label=\"Series\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"secondary-chips-theme\">{{ row.post.series\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t</mat-chip-set>\r\n\t\t\t} @else {\r\n\t\t\t\u2014\r\n\t\t\t}\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Timestamp Column -->\r\n\t<ng-container matColumnDef=\"timestamp\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Date</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">{{ timestampToMillis(row.post.timestamp) | date: 'MMMM d, y' }}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Detail Row (full width) -->\r\n\t<ng-container matColumnDef=\"detail\">\r\n\t\t<td mat-cell *matCellDef=\"let row\" [attr.colspan]=\"displayedColumns.length\">\r\n\t\t\t<div class=\"detail-content\">\r\n\t\t\t\t@if (row.post.description) {\r\n\t\t\t\t<div class=\"detail-description\">{{ row.post.description }}</div>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t<div class=\"detail-actions\">\r\n\t\t\t\t\t<button mat-icon-button (click)=\"goToBlogPost(row.post.id)\" aria-label=\"Edit blog post\">\r\n\t\t\t\t\t\t<mat-icon>edit</mat-icon>\r\n\t\t\t\t\t</button>\r\n\t\t\t\t\t<button mat-icon-button (click)=\"onDeleteBlogPost(row.post)\" aria-label=\"Delete blog post\">\r\n\t\t\t\t\t\t<mat-icon>delete</mat-icon>\r\n\t\t\t\t\t</button>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n\t<tr mat-row *matRowDef=\"let row; columns: displayedColumns; when: isMainRow\"></tr>\r\n\t<tr mat-row *matRowDef=\"let row; columns: detailColumns; when: isDetailRow\" class=\"detail-row\"></tr>\r\n</table>\r\n} @else {\r\n<div class=\"empty-set\">\r\n\t<span>No blog posts created yet.</span>\r\n</div>\r\n\r\n}", styles: [".empty-set{margin:12px}.neutral-chips-theme{--mat-chip-outline-color: var(--mat-sys-surface-variant);--mat-chip-elevated-selected-container-color: var(--mat-sys-surface-variant);--mat-chip-selected-label-text-color: var(--mat-sys-on-surface-variant);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.primary-chips-theme{--mat-chip-outline-color: var(--mat-sys-primary);--mat-chip-elevated-selected-container-color: var(--mat-sys-primary);--mat-chip-selected-label-text-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.secondary-chips-theme{--mat-chip-outline-color: var(--mat-sys-secondary);--mat-chip-elevated-selected-container-color: var(--mat-sys-secondary);--mat-chip-selected-label-text-color: var(--mat-sys-on-secondary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.rhombus{overflow-y:auto}.mat-column-source,.mat-column-actions{text-align:center}.mat-column-source fa-icon,.mat-column-actions fa-icon{display:inline-block}.detail-row td{padding-top:0;padding-bottom:0}.detail-content{display:flex;flex-wrap:nowrap;gap:8px 12px;align-items:center;padding:8px 0 12px;font:var(--mat-sys-body-small);color:var(--mat-sys-on-surface-variant)}.detail-actions{margin-left:auto;display:inline-flex;gap:4px;align-items:center}.detail-description{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i3$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i3$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i3$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i3$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i3$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i3$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i3$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i3$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i3$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i3$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i2$3.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i2$3.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "pipe", type: i5$1.DatePipe, name: "date" }] }); }
|
|
560
664
|
}
|
|
561
665
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, decorators: [{
|
|
562
666
|
type: Component,
|
|
563
|
-
args: [{ selector: 'ng-rhombus-blog-table', imports: [CommonModule, MatButtonModule, MatIconModule, MatTableModule], template: "@if (dataSource().length > 0) {\r\n<table mat-table [dataSource]=\"
|
|
667
|
+
args: [{ selector: 'ng-rhombus-blog-table', imports: [CommonModule, MatButtonModule, MatIconModule, MatTableModule, MatChipsModule], template: "@if (dataSource().length > 0) {\r\n<table mat-table [dataSource]=\"tableData()\" class=\"mat-elevation-z1\" multiTemplateDataRows>\r\n\r\n\t<!-- Title Column -->\r\n\t<ng-container matColumnDef=\"title\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Title</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">{{ row.post.title }}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Pillar Column -->\r\n\t<ng-container matColumnDef=\"pillar\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Pillar</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t@if (row.post.pillar) {\r\n\t\t\t<mat-chip-set aria-label=\"Pillar\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"primary-chips-theme\">{{ row.post.pillar\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t</mat-chip-set>\r\n\t\t\t} @else {\r\n\t\t\t\u2014\r\n\t\t\t}\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Series Column -->\r\n\t<ng-container matColumnDef=\"series\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Series</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t@if (row.post.series) {\r\n\t\t\t<mat-chip-set aria-label=\"Series\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"secondary-chips-theme\">{{ row.post.series\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t</mat-chip-set>\r\n\t\t\t} @else {\r\n\t\t\t\u2014\r\n\t\t\t}\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Timestamp Column -->\r\n\t<ng-container matColumnDef=\"timestamp\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Date</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">{{ timestampToMillis(row.post.timestamp) | date: 'MMMM d, y' }}</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Detail Row (full width) -->\r\n\t<ng-container matColumnDef=\"detail\">\r\n\t\t<td mat-cell *matCellDef=\"let row\" [attr.colspan]=\"displayedColumns.length\">\r\n\t\t\t<div class=\"detail-content\">\r\n\t\t\t\t@if (row.post.description) {\r\n\t\t\t\t<div class=\"detail-description\">{{ row.post.description }}</div>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t<div class=\"detail-actions\">\r\n\t\t\t\t\t<button mat-icon-button (click)=\"goToBlogPost(row.post.id)\" aria-label=\"Edit blog post\">\r\n\t\t\t\t\t\t<mat-icon>edit</mat-icon>\r\n\t\t\t\t\t</button>\r\n\t\t\t\t\t<button mat-icon-button (click)=\"onDeleteBlogPost(row.post)\" aria-label=\"Delete blog post\">\r\n\t\t\t\t\t\t<mat-icon>delete</mat-icon>\r\n\t\t\t\t\t</button>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</td>\r\n\t</ng-container>\r\n\r\n\t<tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n\t<tr mat-row *matRowDef=\"let row; columns: displayedColumns; when: isMainRow\"></tr>\r\n\t<tr mat-row *matRowDef=\"let row; columns: detailColumns; when: isDetailRow\" class=\"detail-row\"></tr>\r\n</table>\r\n} @else {\r\n<div class=\"empty-set\">\r\n\t<span>No blog posts created yet.</span>\r\n</div>\r\n\r\n}", styles: [".empty-set{margin:12px}.neutral-chips-theme{--mat-chip-outline-color: var(--mat-sys-surface-variant);--mat-chip-elevated-selected-container-color: var(--mat-sys-surface-variant);--mat-chip-selected-label-text-color: var(--mat-sys-on-surface-variant);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.primary-chips-theme{--mat-chip-outline-color: var(--mat-sys-primary);--mat-chip-elevated-selected-container-color: var(--mat-sys-primary);--mat-chip-selected-label-text-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.secondary-chips-theme{--mat-chip-outline-color: var(--mat-sys-secondary);--mat-chip-elevated-selected-container-color: var(--mat-sys-secondary);--mat-chip-selected-label-text-color: var(--mat-sys-on-secondary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.rhombus{overflow-y:auto}.mat-column-source,.mat-column-actions{text-align:center}.mat-column-source fa-icon,.mat-column-actions fa-icon{display:inline-block}.detail-row td{padding-top:0;padding-bottom:0}.detail-content{display:flex;flex-wrap:nowrap;gap:8px 12px;align-items:center;padding:8px 0 12px;font:var(--mat-sys-body-small);color:var(--mat-sys-on-surface-variant)}.detail-actions{margin-left:auto;display:inline-flex;gap:4px;align-items:center}.detail-description{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n"] }]
|
|
564
668
|
}], propDecorators: { editEvent: [{
|
|
565
669
|
type: Output
|
|
566
670
|
}], deleteEvent: [{
|
|
567
671
|
type: Output
|
|
568
672
|
}], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
569
673
|
|
|
674
|
+
const NG_RHOMBUS_BLOG_THUMBNAIL_ADAPTER = new InjectionToken('NG_RHOMBUS_BLOG_THUMBNAIL_ADAPTER', {
|
|
675
|
+
providedIn: 'root',
|
|
676
|
+
factory: () => ({
|
|
677
|
+
uploadImageAndGetUrl: async () => {
|
|
678
|
+
throw new Error('NG_RHOMBUS_BLOG_THUMBNAIL_ADAPTER is not provided.');
|
|
679
|
+
},
|
|
680
|
+
deleteImage: async () => {
|
|
681
|
+
throw new Error('NG_RHOMBUS_BLOG_THUMBNAIL_ADAPTER is not provided.');
|
|
682
|
+
},
|
|
683
|
+
}),
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
class NgRhombusBlogPostThumbnailService {
|
|
687
|
+
constructor() {
|
|
688
|
+
this.adapter = inject(NG_RHOMBUS_BLOG_THUMBNAIL_ADAPTER);
|
|
689
|
+
}
|
|
690
|
+
uploadImage(imageName, image) {
|
|
691
|
+
return this.adapter.uploadImageAndGetUrl(imageName, image);
|
|
692
|
+
}
|
|
693
|
+
deleteImage(filePath) {
|
|
694
|
+
return this.adapter.deleteImage(filePath);
|
|
695
|
+
}
|
|
696
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
697
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, providedIn: 'root' }); }
|
|
698
|
+
}
|
|
699
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, decorators: [{
|
|
700
|
+
type: Injectable,
|
|
701
|
+
args: [{
|
|
702
|
+
providedIn: 'root'
|
|
703
|
+
}]
|
|
704
|
+
}] });
|
|
705
|
+
|
|
570
706
|
class NgRhombusBlogThumbnailComponent {
|
|
571
707
|
constructor() {
|
|
572
708
|
this.width = input(0, ...(ngDevMode ? [{ debugName: "width" }] : []));
|
|
@@ -589,7 +725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
589
725
|
|
|
590
726
|
class NgRhombusBlogDeleteThumbnailComponent {
|
|
591
727
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeleteThumbnailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
592
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusBlogDeleteThumbnailComponent, isStandalone: true, selector: "ng-rhombus-Blog-delete-thumbnail", ngImport: i0, template: "<h2 mat-dialog-title>Blog Post Thumbnail - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this thumbnail?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$
|
|
728
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusBlogDeleteThumbnailComponent, isStandalone: true, selector: "ng-rhombus-Blog-delete-thumbnail", ngImport: i0, template: "<h2 mat-dialog-title>Blog Post Thumbnail - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this thumbnail?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$4.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i2$4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }] }); }
|
|
593
729
|
}
|
|
594
730
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeleteThumbnailComponent, decorators: [{
|
|
595
731
|
type: Component,
|
|
@@ -602,24 +738,38 @@ class ThumbnailControlComponent {
|
|
|
602
738
|
this.height = input(0, ...(ngDevMode ? [{ debugName: "height" }] : []));
|
|
603
739
|
this.thumbnailSrc = input('', ...(ngDevMode ? [{ debugName: "thumbnailSrc" }] : []));
|
|
604
740
|
this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
741
|
+
this.displayMode = input('preview', ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
|
|
605
742
|
this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
|
|
606
743
|
this.dialog = inject(MatDialog);
|
|
607
744
|
this.onFileUploaded = output();
|
|
608
745
|
this.onFileDeleted = output();
|
|
609
746
|
this.uploadedFile = signal(undefined, ...(ngDevMode ? [{ debugName: "uploadedFile" }] : []));
|
|
610
747
|
this.placeholder = computed(() => `https://placehold.co/${this.width()}x${this.height()}`, ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
611
|
-
this.
|
|
748
|
+
this.hasThumbnail = computed(() => {
|
|
749
|
+
const fromUpload = this.uploadedFile();
|
|
750
|
+
if (fromUpload)
|
|
751
|
+
return true;
|
|
752
|
+
const fromInput = this.thumbnailSrc();
|
|
753
|
+
return !!fromInput;
|
|
754
|
+
}, ...(ngDevMode ? [{ debugName: "hasThumbnail" }] : []));
|
|
755
|
+
this.imageSource = computed(() => {
|
|
756
|
+
const fromUpload = this.uploadedFile();
|
|
757
|
+
if (fromUpload)
|
|
758
|
+
return fromUpload;
|
|
759
|
+
const fromInput = this.thumbnailSrc();
|
|
760
|
+
if (fromInput)
|
|
761
|
+
return fromInput;
|
|
762
|
+
return this.placeholder();
|
|
763
|
+
}, ...(ngDevMode ? [{ debugName: "imageSource" }] : []));
|
|
612
764
|
}
|
|
613
765
|
onThumbnailSelected(input) {
|
|
614
766
|
if (!input.files || input.files.length <= 0) {
|
|
615
767
|
return;
|
|
616
768
|
}
|
|
617
769
|
const file = input.files[0];
|
|
618
|
-
this.thumbnailService.uploadImage(file.name, file).then((
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
this.onFileUploaded.emit(downloadUrl);
|
|
622
|
-
});
|
|
770
|
+
this.thumbnailService.uploadImage(file.name, file).then((downloadUrl) => {
|
|
771
|
+
this.uploadedFile.set(downloadUrl);
|
|
772
|
+
this.onFileUploaded.emit(downloadUrl);
|
|
623
773
|
});
|
|
624
774
|
}
|
|
625
775
|
onThumbnailDeleted() {
|
|
@@ -635,25 +785,22 @@ class ThumbnailControlComponent {
|
|
|
635
785
|
});
|
|
636
786
|
}
|
|
637
787
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: ThumbnailControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
638
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
788
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: ThumbnailControlComponent, isStandalone: true, selector: "ng-rhombus-thumbnail-control", inputs: { width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, thumbnailSrc: { classPropertyName: "thumbnailSrc", publicName: "thumbnailSrc", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, displayMode: { classPropertyName: "displayMode", publicName: "displayMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onFileUploaded: "onFileUploaded", onFileDeleted: "onFileDeleted" }, viewQueries: [{ propertyName: "thumbnailInput", first: true, predicate: ["thumbnailInput"], descendants: true }], ngImport: i0, template: "<div class=\"thumbnail-control-wrapper\">\r\n <input #thumbnailInput hidden type=\"file\" (change)=\"onThumbnailSelected(thumbnailInput)\" />\r\n <button mat-icon-button [disabled]=\"uploadedFile() || disabled()\" (click)=\"thumbnailInput.click()\" type=\"button\">\r\n <mat-icon>upload_file</mat-icon>\r\n </button>\r\n <button class=\"delete-button\" mat-icon-button [disabled]=\"!uploadedFile()\" (click)=\"onThumbnailDeleted()\"\r\n type=\"button\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n <div class=\"flex\"></div>\r\n\r\n @if (displayMode() === 'preview') {\r\n <ng-rhombus-blog-thumbnail [imageSource]=\"imageSource()\" [width]=\"width()\" [height]=\"height()\" />\r\n } @else {\r\n <div class=\"thumbnail-indicator\" [class.is-set]=\"hasThumbnail()\" aria-label=\"Thumbnail status\">\r\n <mat-icon>{{ hasThumbnail() ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n <span class=\"thumbnail-indicator-text\">Thumbnail</span>\r\n </div>\r\n }\r\n</div>", styles: [".thumbnail-control-wrapper{display:flex;flex:1;align-items:center;padding-bottom:20px}.flex{flex:1 1 auto}.image-placeholder{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.delete-button{margin-left:12px}.thumbnail-indicator{display:inline-flex;align-items:center;gap:6px;opacity:.72}.thumbnail-indicator.is-set{opacity:1}.thumbnail-indicator-text{font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: NgRhombusBlogThumbnailComponent, selector: "ng-rhombus-blog-thumbnail", inputs: ["width", "height", "imageSource"] }] }); }
|
|
639
789
|
}
|
|
640
790
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: ThumbnailControlComponent, decorators: [{
|
|
641
791
|
type: Component,
|
|
642
|
-
args: [{ selector: 'ng-rhombus-thumbnail-control', imports: [MatButtonModule, MatIconModule, NgRhombusBlogThumbnailComponent], template: "<div class=\"thumbnail-control-wrapper\">\r\n <input #thumbnailInput hidden type=\"file\" (change)=\"onThumbnailSelected(thumbnailInput)\" />\r\n <button mat-icon-button [disabled]=\"uploadedFile() || disabled()\" (click)=\"thumbnailInput.click()\" type=\"button\">\r\n <mat-icon>upload_file</mat-icon>\r\n </button>\r\n <button class=\"delete-button\" mat-icon-button [disabled]=\"!uploadedFile()\" (click)=\"onThumbnailDeleted()\"\r\n type=\"button\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n <div class=\"flex\"></div>\r\n
|
|
643
|
-
}], propDecorators: { width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], thumbnailSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbnailSrc", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], thumbnailInput: [{
|
|
792
|
+
args: [{ selector: 'ng-rhombus-thumbnail-control', imports: [MatButtonModule, MatIconModule, NgRhombusBlogThumbnailComponent], template: "<div class=\"thumbnail-control-wrapper\">\r\n <input #thumbnailInput hidden type=\"file\" (change)=\"onThumbnailSelected(thumbnailInput)\" />\r\n <button mat-icon-button [disabled]=\"uploadedFile() || disabled()\" (click)=\"thumbnailInput.click()\" type=\"button\">\r\n <mat-icon>upload_file</mat-icon>\r\n </button>\r\n <button class=\"delete-button\" mat-icon-button [disabled]=\"!uploadedFile()\" (click)=\"onThumbnailDeleted()\"\r\n type=\"button\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n <div class=\"flex\"></div>\r\n\r\n @if (displayMode() === 'preview') {\r\n <ng-rhombus-blog-thumbnail [imageSource]=\"imageSource()\" [width]=\"width()\" [height]=\"height()\" />\r\n } @else {\r\n <div class=\"thumbnail-indicator\" [class.is-set]=\"hasThumbnail()\" aria-label=\"Thumbnail status\">\r\n <mat-icon>{{ hasThumbnail() ? 'check_circle' : 'radio_button_unchecked' }}</mat-icon>\r\n <span class=\"thumbnail-indicator-text\">Thumbnail</span>\r\n </div>\r\n }\r\n</div>", styles: [".thumbnail-control-wrapper{display:flex;flex:1;align-items:center;padding-bottom:20px}.flex{flex:1 1 auto}.image-placeholder{box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.delete-button{margin-left:12px}.thumbnail-indicator{display:inline-flex;align-items:center;gap:6px;opacity:.72}.thumbnail-indicator.is-set{opacity:1}.thumbnail-indicator-text{font-size:12px}\n"] }]
|
|
793
|
+
}], propDecorators: { width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], thumbnailSrc: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbnailSrc", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], displayMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayMode", required: false }] }], thumbnailInput: [{
|
|
644
794
|
type: ViewChild,
|
|
645
795
|
args: ['thumbnailInput']
|
|
646
796
|
}], onFileUploaded: [{ type: i0.Output, args: ["onFileUploaded"] }], onFileDeleted: [{ type: i0.Output, args: ["onFileDeleted"] }] } });
|
|
647
797
|
|
|
648
|
-
class IBlog {
|
|
649
|
-
}
|
|
650
|
-
|
|
651
798
|
class NgRhombusBlogConfirmationComponent {
|
|
652
799
|
constructor() {
|
|
653
800
|
this.data = inject(MAT_DIALOG_DATA);
|
|
654
801
|
}
|
|
655
802
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogConfirmationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
656
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusBlogConfirmationComponent, isStandalone: true, selector: "ng-rhombus-blog-confirmation", ngImport: i0, template: "<h2 mat-dialog-title>{{ data.header }}</h2>\r\n<mat-dialog-content>\r\n <p>{{ data.query }}</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$
|
|
803
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusBlogConfirmationComponent, isStandalone: true, selector: "ng-rhombus-blog-confirmation", ngImport: i0, template: "<h2 mat-dialog-title>{{ data.header }}</h2>\r\n<mat-dialog-content>\r\n <p>{{ data.query }}</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$4.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i2$4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }] }); }
|
|
657
804
|
}
|
|
658
805
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogConfirmationComponent, decorators: [{
|
|
659
806
|
type: Component,
|
|
@@ -667,6 +814,8 @@ class NgRhombusBlogAddEditComponent {
|
|
|
667
814
|
this.cancelEvent = output();
|
|
668
815
|
this.submitEvent = output();
|
|
669
816
|
this.contentData = signal('', ...(ngDevMode ? [{ debugName: "contentData" }] : []));
|
|
817
|
+
this.pillars = Object.values(ContentPillar);
|
|
818
|
+
this.seriesOptions = Object.values(BlogSeries);
|
|
670
819
|
this.dialog = inject(MatDialog);
|
|
671
820
|
this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
|
|
672
821
|
this.formBuilder = inject(FormBuilder);
|
|
@@ -704,8 +853,29 @@ class NgRhombusBlogAddEditComponent {
|
|
|
704
853
|
title: [this.blogPost()?.title, Validators.required],
|
|
705
854
|
description: [this.blogPost()?.description, Validators.required],
|
|
706
855
|
thumbnail: [this.blogPost()?.thumbnail, Validators.required],
|
|
707
|
-
content: [this.blogPost()?.content, Validators.required]
|
|
856
|
+
content: [this.blogPost()?.content, Validators.required],
|
|
857
|
+
// creator system
|
|
858
|
+
pillar: [this.blogPost()?.pillar ?? null, Validators.required],
|
|
859
|
+
series: [this.blogPost()?.series ?? null, Validators.required],
|
|
860
|
+
tagsCsv: [(this.blogPost()?.tags ?? []).join(', '), Validators.required],
|
|
708
861
|
});
|
|
862
|
+
this.applyCreatorSystemRules();
|
|
863
|
+
this.blogPostForm.get('series')?.valueChanges.subscribe(() => this.applyCreatorSystemRules());
|
|
864
|
+
}
|
|
865
|
+
applyCreatorSystemRules() {
|
|
866
|
+
const pillarControl = this.blogPostForm.get('pillar');
|
|
867
|
+
const seriesValue = this.blogPostForm.get('series')?.value;
|
|
868
|
+
if (!pillarControl)
|
|
869
|
+
return;
|
|
870
|
+
if (seriesValue === BlogSeries.FromTheEditor) {
|
|
871
|
+
pillarControl.clearValidators();
|
|
872
|
+
if (pillarControl.value !== null)
|
|
873
|
+
pillarControl.setValue(null);
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
pillarControl.setValidators([Validators.required]);
|
|
877
|
+
}
|
|
878
|
+
pillarControl.updateValueAndValidity({ emitEvent: false });
|
|
709
879
|
}
|
|
710
880
|
onContentChange() {
|
|
711
881
|
this.contentData.set(this.blogPostForm.getRawValue().content);
|
|
@@ -771,28 +941,41 @@ class NgRhombusBlogAddEditComponent {
|
|
|
771
941
|
}
|
|
772
942
|
}
|
|
773
943
|
onSubmit() {
|
|
774
|
-
if (this.blogPostForm.invalid)
|
|
944
|
+
if (this.blogPostForm.invalid)
|
|
945
|
+
return;
|
|
946
|
+
const raw = this.blogPostForm.getRawValue();
|
|
947
|
+
const tags = this.parseTags(raw.tagsCsv);
|
|
948
|
+
if (tags.length < 3 || tags.length > 6) {
|
|
949
|
+
// keep it simple; if you prefer, set a form error and show mat-error
|
|
775
950
|
return;
|
|
776
951
|
}
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
if (this.blogPost()) {
|
|
952
|
+
const submittedBlogPost = new IBlog();
|
|
953
|
+
if (this.blogPost())
|
|
780
954
|
submittedBlogPost.id = this.blogPost().id;
|
|
781
|
-
|
|
782
|
-
submittedBlogPost.
|
|
783
|
-
submittedBlogPost.
|
|
784
|
-
submittedBlogPost.
|
|
785
|
-
submittedBlogPost.
|
|
786
|
-
|
|
787
|
-
submittedBlogPost.
|
|
955
|
+
submittedBlogPost.title = raw.title;
|
|
956
|
+
submittedBlogPost.description = raw.description;
|
|
957
|
+
submittedBlogPost.thumbnail = raw.thumbnail;
|
|
958
|
+
submittedBlogPost.content = raw.content;
|
|
959
|
+
submittedBlogPost.pillar = raw.pillar;
|
|
960
|
+
submittedBlogPost.series = raw.series;
|
|
961
|
+
submittedBlogPost.tags = tags;
|
|
962
|
+
submittedBlogPost.readTimeMinutes = this.blogService.calculateReadTimeMinutes(raw.content);
|
|
788
963
|
this.submitEvent.emit(submittedBlogPost);
|
|
789
964
|
}
|
|
965
|
+
parseTags(csv) {
|
|
966
|
+
const tags = (csv ?? '')
|
|
967
|
+
.split(',')
|
|
968
|
+
.map(t => t.trim())
|
|
969
|
+
.filter(Boolean);
|
|
970
|
+
// unique, preserve order
|
|
971
|
+
return Array.from(new Set(tags));
|
|
972
|
+
}
|
|
790
973
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
791
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
974
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusBlogAddEditComponent, isStandalone: true, selector: "ng-rhombus-blog-form", inputs: { blogPost: { classPropertyName: "blogPost", publicName: "blogPost", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancelEvent: "cancelEvent", submitEvent: "submitEvent" }, viewQueries: [{ propertyName: "autosize", first: true, predicate: ["autosize"], descendants: true }, { propertyName: "blogPostPreview", first: true, predicate: ["blogPost"], descendants: true }], ngImport: i0, template: "<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"blogPostForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<ng-rhombus-thumbnail-control [thumbnailSrc]=\"thumbnailSource\" [width]=\"200\" [height]=\"112\"\r\n\t\t\t\t[displayMode]=\"'indicator'\" [disabled]=\"blogPost() !== undefined\"\r\n\t\t\t\t(onFileUploaded)=\"onFileUploaded($event)\"\r\n\t\t\t\t(onFileDeleted)=\"onFileDeleted()\"></ng-rhombus-thumbnail-control>\r\n\t\t\t<mat-form-field class=\"h-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required cdkTextareaAutosize #autosize=\"cdkTextareaAutosize\" cdkAutosizeMinRows=\"1\"\r\n\t\t\t\t\tcdkAutosizeMaxRows=\"20\" formControlName=\"content\" (keyup)=\"onContentChange()\"></textarea>\r\n\t\t\t</mat-form-field>\r\n\t\t\t<div class=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\r\n\t\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t\t<mat-label>Content Pillar (optional for From The Editor)</mat-label>\r\n\t\t\t\t\t<mat-select formControlName=\"pillar\" required>\r\n\t\t\t\t\t\t@for (p of pillars; track p) {\r\n\t\t\t\t\t\t<mat-option [value]=\"p\">{{ p }}</mat-option>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</mat-select>\r\n\t\t\t\t</mat-form-field>\r\n\r\n\t\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t\t<mat-label>Series</mat-label>\r\n\t\t\t\t\t<mat-select formControlName=\"series\" required>\r\n\t\t\t\t\t\t@for (s of seriesOptions; track s) {\r\n\t\t\t\t\t\t<mat-option [value]=\"s\">{{ s }}</mat-option>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</mat-select>\r\n\t\t\t\t</mat-form-field>\r\n\t\t\t</div>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Tags</mat-label>\r\n\t\t\t\t<input matInput formControlName=\"tagsCsv\" placeholder=\"3\u20136 tags, comma-separated\">\r\n\t\t\t\t<mat-hint>Example: angular, firestore, testing</mat-hint>\r\n\t\t\t</mat-form-field>\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"blogPostForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto\">\r\n\t\t<mat-card>\r\n\t\t\t<mat-card-header>\r\n\t\t\t\t<mat-card-title>{{ blogPostForm.getRawValue().title }}</mat-card-title>\r\n\t\t\t\t<mat-card-subtitle>{{ blogPostForm.getRawValue().description }}</mat-card-subtitle>\r\n\t\t\t</mat-card-header>\r\n\t\t\t<img mat-card-image [src]=\"blogPostForm.getRawValue().thumbnail\" />\r\n\t\t\t<mat-card-content>\r\n\t\t\t\t<markdown [ngClass]=\"{ 'prose': true, 'dark:prose-invert': themeService.isDarkMode() }\"\r\n\t\t\t\t\t[data]=\"contentData()\" />\r\n\t\t\t</mat-card-content>\r\n\t\t\t<mat-card-footer>\r\n\t\t\t\t<span class=\"flex flex-row-reverse\">Doug Williamson</span>\r\n\t\t\t</mat-card-footer>\r\n\t\t</mat-card>\r\n\t</div>\r\n</div>", styles: [".mat-mdc-card-header,.mat-mdc-card-content,.mat-mdc-card-footer{padding:32px 16px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatListModule }, { kind: "ngmodule", type: MatDividerModule }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2$2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$2.MatCardContent, selector: "mat-card-content" }, { kind: "directive", type: i2$2.MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: i2$2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$2.MatCardImage, selector: "[mat-card-image], [matCardImage]" }, { kind: "directive", type: i2$2.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i2$2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: i5$2.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "ngmodule", type: MatSidenavModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: TextFieldModule }, { kind: "component", type: ThumbnailControlComponent, selector: "ng-rhombus-thumbnail-control", inputs: ["width", "height", "thumbnailSrc", "disabled", "displayMode"], outputs: ["onFileUploaded", "onFileDeleted"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: 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"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] }); }
|
|
792
975
|
}
|
|
793
976
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, decorators: [{
|
|
794
977
|
type: Component,
|
|
795
|
-
args: [{ selector: 'ng-rhombus-blog-form', imports: [NgClass, MatListModule, MatDividerModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatInputModule, MatProgressSpinnerModule, MatSidenavModule, ReactiveFormsModule, TextFieldModule, ThumbnailControlComponent,
|
|
978
|
+
args: [{ selector: 'ng-rhombus-blog-form', imports: [NgClass, MatListModule, MatDividerModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatInputModule, MatProgressSpinnerModule, MatSidenavModule, ReactiveFormsModule, TextFieldModule, ThumbnailControlComponent, MatIconModule, MarkdownComponent, MatSelectModule], template: "<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"blogPostForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<ng-rhombus-thumbnail-control [thumbnailSrc]=\"thumbnailSource\" [width]=\"200\" [height]=\"112\"\r\n\t\t\t\t[displayMode]=\"'indicator'\" [disabled]=\"blogPost() !== undefined\"\r\n\t\t\t\t(onFileUploaded)=\"onFileUploaded($event)\"\r\n\t\t\t\t(onFileDeleted)=\"onFileDeleted()\"></ng-rhombus-thumbnail-control>\r\n\t\t\t<mat-form-field class=\"h-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required cdkTextareaAutosize #autosize=\"cdkTextareaAutosize\" cdkAutosizeMinRows=\"1\"\r\n\t\t\t\t\tcdkAutosizeMaxRows=\"20\" formControlName=\"content\" (keyup)=\"onContentChange()\"></textarea>\r\n\t\t\t</mat-form-field>\r\n\t\t\t<div class=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\r\n\t\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t\t<mat-label>Content Pillar (optional for From The Editor)</mat-label>\r\n\t\t\t\t\t<mat-select formControlName=\"pillar\" required>\r\n\t\t\t\t\t\t@for (p of pillars; track p) {\r\n\t\t\t\t\t\t<mat-option [value]=\"p\">{{ p }}</mat-option>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</mat-select>\r\n\t\t\t\t</mat-form-field>\r\n\r\n\t\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t\t<mat-label>Series</mat-label>\r\n\t\t\t\t\t<mat-select formControlName=\"series\" required>\r\n\t\t\t\t\t\t@for (s of seriesOptions; track s) {\r\n\t\t\t\t\t\t<mat-option [value]=\"s\">{{ s }}</mat-option>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</mat-select>\r\n\t\t\t\t</mat-form-field>\r\n\t\t\t</div>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Tags</mat-label>\r\n\t\t\t\t<input matInput formControlName=\"tagsCsv\" placeholder=\"3\u20136 tags, comma-separated\">\r\n\t\t\t\t<mat-hint>Example: angular, firestore, testing</mat-hint>\r\n\t\t\t</mat-form-field>\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"blogPostForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto\">\r\n\t\t<mat-card>\r\n\t\t\t<mat-card-header>\r\n\t\t\t\t<mat-card-title>{{ blogPostForm.getRawValue().title }}</mat-card-title>\r\n\t\t\t\t<mat-card-subtitle>{{ blogPostForm.getRawValue().description }}</mat-card-subtitle>\r\n\t\t\t</mat-card-header>\r\n\t\t\t<img mat-card-image [src]=\"blogPostForm.getRawValue().thumbnail\" />\r\n\t\t\t<mat-card-content>\r\n\t\t\t\t<markdown [ngClass]=\"{ 'prose': true, 'dark:prose-invert': themeService.isDarkMode() }\"\r\n\t\t\t\t\t[data]=\"contentData()\" />\r\n\t\t\t</mat-card-content>\r\n\t\t\t<mat-card-footer>\r\n\t\t\t\t<span class=\"flex flex-row-reverse\">Doug Williamson</span>\r\n\t\t\t</mat-card-footer>\r\n\t\t</mat-card>\r\n\t</div>\r\n</div>", styles: [".mat-mdc-card-header,.mat-mdc-card-content,.mat-mdc-card-footer{padding:32px 16px}\n"] }]
|
|
796
979
|
}], ctorParameters: () => [], propDecorators: { blogPost: [{ type: i0.Input, args: [{ isSignal: true, alias: "blogPost", required: false }] }], cancelEvent: [{ type: i0.Output, args: ["cancelEvent"] }], submitEvent: [{ type: i0.Output, args: ["submitEvent"] }], autosize: [{
|
|
797
980
|
type: ViewChild,
|
|
798
981
|
args: ['autosize']
|
|
@@ -806,6 +989,11 @@ class NgRhombusBlogPostComponent {
|
|
|
806
989
|
this.dataSource = input(...(ngDevMode ? [undefined, { debugName: "dataSource" }] : []));
|
|
807
990
|
this.themeService = inject(ThemeService);
|
|
808
991
|
this.#doc = inject(DOCUMENT);
|
|
992
|
+
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
993
|
+
this.faXTwitter = faXTwitter;
|
|
994
|
+
this.faFacebook = faFacebook;
|
|
995
|
+
this.faBluesky = faBluesky;
|
|
996
|
+
this.faLinkedin = faLinkedin;
|
|
809
997
|
}
|
|
810
998
|
#doc;
|
|
811
999
|
ngOnInit() {
|
|
@@ -833,17 +1021,40 @@ class NgRhombusBlogPostComponent {
|
|
|
833
1021
|
navigator.clipboard.writeText(u).catch(() => { });
|
|
834
1022
|
}
|
|
835
1023
|
}
|
|
1024
|
+
normalizeTags(tags) {
|
|
1025
|
+
if (!tags)
|
|
1026
|
+
return [];
|
|
1027
|
+
if (Array.isArray(tags))
|
|
1028
|
+
return tags.map(t => `${t}`.trim()).filter(Boolean);
|
|
1029
|
+
// handle "tag1, tag2"
|
|
1030
|
+
return `${tags}`
|
|
1031
|
+
.split(',')
|
|
1032
|
+
.map(t => t.trim())
|
|
1033
|
+
.filter(Boolean);
|
|
1034
|
+
}
|
|
1035
|
+
get contentPillar() {
|
|
1036
|
+
return (this.dataSource()?.pillar ?? null) || null;
|
|
1037
|
+
}
|
|
1038
|
+
get series() {
|
|
1039
|
+
return (this.dataSource()?.series ?? null) || null;
|
|
1040
|
+
}
|
|
1041
|
+
get tags() {
|
|
1042
|
+
return this.normalizeTags(this.dataSource()?.tags ?? null);
|
|
1043
|
+
}
|
|
1044
|
+
get hasTaxonomy() {
|
|
1045
|
+
return !!this.contentPillar || !!this.series || this.tags.length > 0;
|
|
1046
|
+
}
|
|
836
1047
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
837
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
1048
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusBlogPostComponent, isStandalone: true, selector: "ng-rhombus-blog-post", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<mat-card>\r\n\t<mat-card-header>\r\n\t\t<mat-card-title>{{ dataSource()?.title }}</mat-card-title>\r\n\t\t<mat-card-subtitle>\r\n\t\t\t@if (contentPillar || series) {\r\n\t\t\t<mat-chip-set aria-label=\"Post taxonomy\" role=\"list\">\r\n\t\t\t\t@if (contentPillar) {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"primary-chips-theme\" [highlighted]=\"true\">Pillar: {{ contentPillar\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t\t@if (series) {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"secondary-chips-theme\" [highlighted]=\"true\">Series: {{ series\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t</mat-chip-set>\r\n\t\t\t}\r\n\t\t</mat-card-subtitle>\r\n\t</mat-card-header>\r\n\t<mat-card-content>\r\n\t\t<div class=\"flex items-center justify-between\">\r\n\t\t\t<p class=\"timestamp\">\r\n\t\t\t\t{{ dataSource()?.readTimeMinutes }} {{ dataSource()?.readTimeMinutes === 1 ? 'min' : 'mins' }}\r\n\t\t\t\t·\r\n\t\t\t\t{{ timestampToMillis(dataSource()?.timestamp) | date: 'MMM d, y' }}\r\n\t\t\t</p>\r\n\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t<button matIconButton [matMenuTriggerFor]=\"shareMenu\" aria-label=\"Share post\">\r\n\t\t\t\t<mat-icon>share</mat-icon>\r\n\t\t\t</button>\r\n\t\t</div>\r\n\r\n\t\t<mat-menu #shareMenu=\"matMenu\">\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.twitter\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faXTwitter\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on X</span>\r\n\t\t\t</a>\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.facebook\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faFacebook\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on Facebook</span>\r\n\t\t\t</a>\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.bluesky\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faBluesky\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on Bluesky</span>\r\n\t\t\t</a>\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.linkedin\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faLinkedin\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on LinkedIn</span>\r\n\t\t\t</a>\r\n\t\t\t<button mat-menu-item (click)=\"copyLink()\">\r\n\t\t\t\t<mat-icon>content_copy</mat-icon>\r\n\t\t\t\t<span>Copy link</span>\r\n\t\t\t</button>\r\n\t\t</mat-menu>\r\n\t</mat-card-content>\r\n\t<img mat-card-image [src]=\"dataSource()?.thumbnail\" />\r\n\t<mat-card-content class=\"content\">\r\n\t\t<markdown [ngClass]=\"{ 'prose': true, 'dark:prose-invert': themeService.isDarkMode() }\"\r\n\t\t\t[data]=\"dataSource()?.content\" />\r\n\t</mat-card-content>\r\n\t@if (tags.length) {\r\n\t<mat-card-content>\r\n\t\t<mat-chip-set aria-label=\"Post tags\" role=\"list\">\r\n\t\t\t@for (t of tags; track t) {\r\n\t\t\t<mat-chip role=\"listitem\" highlighted=\"true\" class=\"neutral-chips-theme\">#{{ t }}</mat-chip>\r\n\t\t\t}\r\n\t\t</mat-chip-set>\r\n\t</mat-card-content>\r\n\t}\r\n</mat-card>", styles: [".neutral-chips-theme{--mat-chip-outline-color: var(--mat-sys-surface-variant);--mat-chip-elevated-selected-container-color: var(--mat-sys-surface-variant);--mat-chip-selected-label-text-color: var(--mat-sys-on-surface-variant);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.primary-chips-theme{--mat-chip-outline-color: var(--mat-sys-primary);--mat-chip-elevated-selected-container-color: var(--mat-sys-primary);--mat-chip-selected-label-text-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.secondary-chips-theme{--mat-chip-outline-color: var(--mat-sys-secondary);--mat-chip-elevated-selected-container-color: var(--mat-sys-secondary);--mat-chip-selected-label-text-color: var(--mat-sys-on-secondary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.card-image{--mat-card-elevated-container-shape: var(--mat-sys-shape-medium)}.mat-mdc-card-header{padding:32px 32px 16px}.mat-mdc-card-footer,.mat-mdc-card-content{padding:0 32px 32px}.content{padding:32px}.mat-mdc-card-title{margin-bottom:12px}.mat-mdc-card{border-radius:0}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: 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"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2$2.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$2.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$2.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$2.MatCardImage, selector: "[mat-card-image], [matCardImage]" }, { kind: "directive", type: i2$2.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i2$2.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i4.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i4.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i4.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "component", type: i2$3.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i2$3.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "ngmodule", type: FontAwesomeModule }, { kind: "component", type: i1$5.FaIconComponent, selector: "fa-icon", inputs: ["icon", "title", "animation", "mask", "flip", "size", "pull", "border", "inverse", "symbol", "rotate", "fixedWidth", "transform", "a11yRole"], outputs: ["iconChange", "titleChange", "animationChange", "maskChange", "flipChange", "sizeChange", "pullChange", "borderChange", "inverseChange", "symbolChange", "rotateChange", "fixedWidthChange", "transformChange", "a11yRoleChange"] }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
|
|
838
1049
|
}
|
|
839
1050
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, decorators: [{
|
|
840
1051
|
type: Component,
|
|
841
|
-
args: [{ selector: 'ng-rhombus-blog-post', imports: [DatePipe, NgClass, MarkdownComponent, MatCardModule, MatMenuModule, MatIconModule, MatButtonModule], template: "<mat-card>\r\n\t<mat-card-header>\r\n\t\t<mat-card-title>{{ dataSource()?.title }}</mat-card-title>\r\n\t\t<mat-card-subtitle>{{
|
|
1052
|
+
args: [{ selector: 'ng-rhombus-blog-post', imports: [DatePipe, NgClass, MarkdownComponent, MatCardModule, MatMenuModule, MatIconModule, MatButtonModule, MatChipsModule, FontAwesomeModule], template: "<mat-card>\r\n\t<mat-card-header>\r\n\t\t<mat-card-title>{{ dataSource()?.title }}</mat-card-title>\r\n\t\t<mat-card-subtitle>\r\n\t\t\t@if (contentPillar || series) {\r\n\t\t\t<mat-chip-set aria-label=\"Post taxonomy\" role=\"list\">\r\n\t\t\t\t@if (contentPillar) {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"primary-chips-theme\" [highlighted]=\"true\">Pillar: {{ contentPillar\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t\t@if (series) {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"secondary-chips-theme\" [highlighted]=\"true\">Series: {{ series\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t\t}\r\n\t\t\t</mat-chip-set>\r\n\t\t\t}\r\n\t\t</mat-card-subtitle>\r\n\t</mat-card-header>\r\n\t<mat-card-content>\r\n\t\t<div class=\"flex items-center justify-between\">\r\n\t\t\t<p class=\"timestamp\">\r\n\t\t\t\t{{ dataSource()?.readTimeMinutes }} {{ dataSource()?.readTimeMinutes === 1 ? 'min' : 'mins' }}\r\n\t\t\t\t·\r\n\t\t\t\t{{ timestampToMillis(dataSource()?.timestamp) | date: 'MMM d, y' }}\r\n\t\t\t</p>\r\n\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t<button matIconButton [matMenuTriggerFor]=\"shareMenu\" aria-label=\"Share post\">\r\n\t\t\t\t<mat-icon>share</mat-icon>\r\n\t\t\t</button>\r\n\t\t</div>\r\n\r\n\t\t<mat-menu #shareMenu=\"matMenu\">\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.twitter\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faXTwitter\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on X</span>\r\n\t\t\t</a>\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.facebook\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faFacebook\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on Facebook</span>\r\n\t\t\t</a>\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.bluesky\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faBluesky\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on Bluesky</span>\r\n\t\t\t</a>\r\n\t\t\t<a mat-menu-item [href]=\"shareLinks.linkedin\" target=\"_blank\" rel=\"noopener\">\r\n\t\t\t\t<fa-icon [icon]=\"faLinkedin\" class=\"mr-2\"></fa-icon>\r\n\t\t\t\t<span>Share on LinkedIn</span>\r\n\t\t\t</a>\r\n\t\t\t<button mat-menu-item (click)=\"copyLink()\">\r\n\t\t\t\t<mat-icon>content_copy</mat-icon>\r\n\t\t\t\t<span>Copy link</span>\r\n\t\t\t</button>\r\n\t\t</mat-menu>\r\n\t</mat-card-content>\r\n\t<img mat-card-image [src]=\"dataSource()?.thumbnail\" />\r\n\t<mat-card-content class=\"content\">\r\n\t\t<markdown [ngClass]=\"{ 'prose': true, 'dark:prose-invert': themeService.isDarkMode() }\"\r\n\t\t\t[data]=\"dataSource()?.content\" />\r\n\t</mat-card-content>\r\n\t@if (tags.length) {\r\n\t<mat-card-content>\r\n\t\t<mat-chip-set aria-label=\"Post tags\" role=\"list\">\r\n\t\t\t@for (t of tags; track t) {\r\n\t\t\t<mat-chip role=\"listitem\" highlighted=\"true\" class=\"neutral-chips-theme\">#{{ t }}</mat-chip>\r\n\t\t\t}\r\n\t\t</mat-chip-set>\r\n\t</mat-card-content>\r\n\t}\r\n</mat-card>", styles: [".neutral-chips-theme{--mat-chip-outline-color: var(--mat-sys-surface-variant);--mat-chip-elevated-selected-container-color: var(--mat-sys-surface-variant);--mat-chip-selected-label-text-color: var(--mat-sys-on-surface-variant);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.primary-chips-theme{--mat-chip-outline-color: var(--mat-sys-primary);--mat-chip-elevated-selected-container-color: var(--mat-sys-primary);--mat-chip-selected-label-text-color: var(--mat-sys-on-primary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.secondary-chips-theme{--mat-chip-outline-color: var(--mat-sys-secondary);--mat-chip-elevated-selected-container-color: var(--mat-sys-secondary);--mat-chip-selected-label-text-color: var(--mat-sys-on-secondary);box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.card-image{--mat-card-elevated-container-shape: var(--mat-sys-shape-medium)}.mat-mdc-card-header{padding:32px 32px 16px}.mat-mdc-card-footer,.mat-mdc-card-content{padding:0 32px 32px}.content{padding:32px}.mat-mdc-card-title{margin-bottom:12px}.mat-mdc-card{border-radius:0}\n"] }]
|
|
842
1053
|
}], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
843
1054
|
|
|
844
1055
|
class NgRhombusBlogDeletePostComponent {
|
|
845
1056
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeletePostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
846
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusBlogDeletePostComponent, isStandalone: true, selector: "ng-rhombus-Blog-delete-post", ngImport: i0, template: "<h2 mat-dialog-title>Blog Post - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this blog post?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$
|
|
1057
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusBlogDeletePostComponent, isStandalone: true, selector: "ng-rhombus-Blog-delete-post", ngImport: i0, template: "<h2 mat-dialog-title>Blog Post - Delete</h2>\r\n<mat-dialog-content>\r\n <p>Would you like to delete this blog post?</p>\r\n</mat-dialog-content>\r\n<mat-dialog-actions>\r\n <button mat-button [mat-dialog-close]=\"false\" cdkFocusInitial>Cancel</button>\r\n <button mat-button [mat-dialog-close]=\"true\">Confirm</button>\r\n</mat-dialog-actions>", styles: [""], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$4.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i2$4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }] }); }
|
|
847
1058
|
}
|
|
848
1059
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeletePostComponent, decorators: [{
|
|
849
1060
|
type: Component,
|
|
@@ -896,14 +1107,48 @@ class NgRhombusHomeAdminComponent {
|
|
|
896
1107
|
console.log('cancel');
|
|
897
1108
|
}
|
|
898
1109
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHomeAdminComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
899
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusHomeAdminComponent, isStandalone: true, selector: "ng-rhombus-home-admin", inputs: { formAdminData: { classPropertyName: "formAdminData", publicName: "formAdminData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancelEvent: "cancelEvent", submitEvent: "submitEvent" }, ngImport: i0, template: "<
|
|
1110
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.4", type: NgRhombusHomeAdminComponent, isStandalone: true, selector: "ng-rhombus-home-admin", inputs: { formAdminData: { classPropertyName: "formAdminData", publicName: "formAdminData", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cancelEvent: "cancelEvent", submitEvent: "submitEvent" }, ngImport: i0, template: "<div class=\"page-header\">\r\n\t<div class=\"page-header__title mat-headline-small\">Admin Component</div>\r\n</div>\r\n<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"homeAdminForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"homeAdminForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n</div>", styles: [":host{height:100%;width:100%;box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.page-header{display:flex;align-items:center;padding:16px;border-bottom:1px solid var(--mat-sys-outline-variant);background:var(--mat-sys-surface-container);color:var(--mat-sys-on-surface)}.page-header__title{margin:0}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] }); }
|
|
900
1111
|
}
|
|
901
1112
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHomeAdminComponent, decorators: [{
|
|
902
1113
|
type: Component,
|
|
903
|
-
args: [{ selector: 'ng-rhombus-home-admin', imports: [MatButtonModule,
|
|
1114
|
+
args: [{ selector: 'ng-rhombus-home-admin', imports: [MatButtonModule, ReactiveFormsModule, MatFormFieldModule, MatInputModule], template: "<div class=\"page-header\">\r\n\t<div class=\"page-header__title mat-headline-small\">Admin Component</div>\r\n</div>\r\n<div class=\"h-full flex flex-row\">\r\n\t<div class=\"flex flex-col w-1/2 p-4\">\r\n\t\t<form novalidate class=\"h-full flex flex-col\" [formGroup]=\"homeAdminForm\">\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Title</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"title\">\r\n\t\t\t</mat-form-field>\r\n\t\t\t<mat-form-field>\r\n\t\t\t\t<mat-label>Description</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Placeholder\" formControlName=\"description\">\r\n\t\t\t</mat-form-field>\r\n\r\n\t\t\t<div class=\"flex\">\r\n\t\t\t\t<button mat-raised-button class=\"\" (click)=\"onCancelClick()\" type=\"button\">Cancel</button>\r\n\t\t\t\t<div class=\"flex-1\"></div>\r\n\t\t\t\t<button mat-flat-button [disabled]=\"homeAdminForm.invalid\" type=\"button\"\r\n\t\t\t\t\t(click)=\"onSubmit()\">Submit</button>\r\n\t\t\t</div>\r\n\t\t</form>\r\n\t</div>\r\n</div>", styles: [":host{height:100%;width:100%;box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}.page-header{display:flex;align-items:center;padding:16px;border-bottom:1px solid var(--mat-sys-outline-variant);background:var(--mat-sys-surface-container);color:var(--mat-sys-on-surface)}.page-header__title{margin:0}\n"] }]
|
|
904
1115
|
}], propDecorators: { formAdminData: [{ type: i0.Input, args: [{ isSignal: true, alias: "formAdminData", required: false }] }], cancelEvent: [{ type: i0.Output, args: ["cancelEvent"] }], submitEvent: [{ type: i0.Output, args: ["submitEvent"] }] } });
|
|
905
1116
|
|
|
906
|
-
|
|
1117
|
+
const NG_RHOMBUS_HOME_ADAPTER = new InjectionToken('NG_RHOMBUS_HOME_ADAPTER', {
|
|
1118
|
+
providedIn: 'root',
|
|
1119
|
+
factory: () => ({
|
|
1120
|
+
fetchTopHomeDocument: async () => undefined,
|
|
1121
|
+
saveOrUpdateHomeDocument: async () => {
|
|
1122
|
+
throw new Error('NG_RHOMBUS_HOME_ADAPTER is not provided.');
|
|
1123
|
+
}
|
|
1124
|
+
})
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
class NgRhombusHomeService {
|
|
1128
|
+
constructor() {
|
|
1129
|
+
this.adapter = inject(NG_RHOMBUS_HOME_ADAPTER);
|
|
1130
|
+
this.homeAdminData = signal(undefined, ...(ngDevMode ? [{ debugName: "homeAdminData" }] : []));
|
|
1131
|
+
}
|
|
1132
|
+
async fetchTopHomeDocument() {
|
|
1133
|
+
const topHome = await this.adapter.fetchTopHomeDocument();
|
|
1134
|
+
this.homeAdminData.set(topHome);
|
|
1135
|
+
return topHome;
|
|
1136
|
+
}
|
|
1137
|
+
async saveOrUpdateHomeDocument(homeData) {
|
|
1138
|
+
const result = await this.adapter.saveOrUpdateHomeDocument(homeData);
|
|
1139
|
+
this.homeAdminData.set({ ...homeData, id: result.id });
|
|
1140
|
+
return result;
|
|
1141
|
+
}
|
|
1142
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHomeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1143
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHomeService, providedIn: 'root' }); }
|
|
1144
|
+
}
|
|
1145
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHomeService, decorators: [{
|
|
1146
|
+
type: Injectable,
|
|
1147
|
+
args: [{
|
|
1148
|
+
providedIn: 'root',
|
|
1149
|
+
}]
|
|
1150
|
+
}] });
|
|
1151
|
+
|
|
907
1152
|
var SocialsSource;
|
|
908
1153
|
(function (SocialsSource) {
|
|
909
1154
|
SocialsSource["Twitch"] = "Twitch";
|
|
@@ -914,45 +1159,82 @@ var SocialsSource;
|
|
|
914
1159
|
SocialsSource["GitHub"] = "GitHub";
|
|
915
1160
|
SocialsSource["LinkedIn"] = "LinkedIn";
|
|
916
1161
|
})(SocialsSource || (SocialsSource = {}));
|
|
1162
|
+
|
|
1163
|
+
class NgRhombusSocialsListComponent {
|
|
1164
|
+
constructor() {
|
|
1165
|
+
this.wrapperService = inject(WrapperService);
|
|
1166
|
+
this.socials = input([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
|
|
1167
|
+
}
|
|
1168
|
+
iconFor(source) {
|
|
1169
|
+
switch (source) {
|
|
1170
|
+
case SocialsSource.YouTube: return faYoutube;
|
|
1171
|
+
case SocialsSource.Instagram: return faInstagram;
|
|
1172
|
+
case SocialsSource.Twitch: return faTwitch;
|
|
1173
|
+
case SocialsSource.TikTok: return faTiktok;
|
|
1174
|
+
case SocialsSource.Bluesky: return faBluesky; // no official brand, fallback
|
|
1175
|
+
case SocialsSource.GitHub: return faGithub;
|
|
1176
|
+
case SocialsSource.LinkedIn: return faLinkedin;
|
|
1177
|
+
default: return faLink;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1181
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusSocialsListComponent, isStandalone: true, selector: "ng-rhombus-socials-list", inputs: { socials: { classPropertyName: "socials", publicName: "socials", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if ((socials()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No socials found.</p>\r\n} @else {\r\n<div class=\"toolbar-list\">\r\n @for (s of socials(); track s.id) {\r\n <a [attr.href]=\"s.url || null\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"block\"\r\n [attr.aria-label]=\"'Open ' + s.source + ' link'\">\r\n <div class=\"social-row actionable\">\r\n <fa-icon [icon]=\"iconFor(s.source)\" class=\"fa-icon left\"></fa-icon>\r\n <span class=\"center\">{{ s.source }}</span>\r\n @if (s.url) {\r\n <button mat-icon-button class=\"right\" aria-label=\"Copy link\">\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n </a>\r\n }\r\n</div>\r\n}", styles: [".social-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;min-height:48px;width:100%;padding:0 16px;border:1px solid var(--mat-sys-outline-variant);border-radius:12px;background:var(--mat-sys-surface-container);color:var(--mat-sys-on-surface);box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f}@media(prefers-color-scheme:light){:host{--mat-list-list-item-leading-icon-color: var(--mat-sys-primary);--mat-list-list-item-label-text-color: var(--mat-sys-primary)}}.toolbar-list{display:grid;gap:12px}.actionable{cursor:pointer;transition:background-color .14s ease,box-shadow .14s ease}.actionable:hover{background:var(--mat-sys-surface-container-high);box-shadow:0 3px 3px -2px #0003,0 3px 4px #00000024,0 1px 8px #0000001f}.actionable:focus{outline:2px solid var(--mat-sys-primary);outline-offset:2px}.toolbar-list a:focus-visible .social-row{outline:2px solid var(--mat-sys-primary);outline-offset:2px}.left{margin-left:64px}.center{text-align:center}.right{margin-right:64px}\n"], dependencies: [{ kind: "ngmodule", type: FontAwesomeModule }, { kind: "component", type: i1$5.FaIconComponent, selector: "fa-icon", inputs: ["icon", "title", "animation", "mask", "flip", "size", "pull", "border", "inverse", "symbol", "rotate", "fixedWidth", "transform", "a11yRole"], outputs: ["iconChange", "titleChange", "animationChange", "maskChange", "flipChange", "sizeChange", "pullChange", "borderChange", "inverseChange", "symbolChange", "rotateChange", "fixedWidthChange", "transformChange", "a11yRoleChange"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }] }); }
|
|
1182
|
+
}
|
|
1183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsListComponent, decorators: [{
|
|
1184
|
+
type: Component,
|
|
1185
|
+
args: [{ selector: 'ng-rhombus-socials-list', standalone: true, imports: [FontAwesomeModule, MatIconModule, MatButtonModule], template: "@if ((socials()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No socials found.</p>\r\n} @else {\r\n<div class=\"toolbar-list\">\r\n @for (s of socials(); track s.id) {\r\n <a [attr.href]=\"s.url || null\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"block\"\r\n [attr.aria-label]=\"'Open ' + s.source + ' link'\">\r\n <div class=\"social-row actionable\">\r\n <fa-icon [icon]=\"iconFor(s.source)\" class=\"fa-icon left\"></fa-icon>\r\n <span class=\"center\">{{ s.source }}</span>\r\n @if (s.url) {\r\n <button mat-icon-button class=\"right\" aria-label=\"Copy link\">\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n </a>\r\n }\r\n</div>\r\n}", styles: [".social-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;min-height:48px;width:100%;padding:0 16px;border:1px solid var(--mat-sys-outline-variant);border-radius:12px;background:var(--mat-sys-surface-container);color:var(--mat-sys-on-surface);box-shadow:0 2px 1px -1px #0003,0 1px 1px #00000024,0 1px 3px #0000001f}@media(prefers-color-scheme:light){:host{--mat-list-list-item-leading-icon-color: var(--mat-sys-primary);--mat-list-list-item-label-text-color: var(--mat-sys-primary)}}.toolbar-list{display:grid;gap:12px}.actionable{cursor:pointer;transition:background-color .14s ease,box-shadow .14s ease}.actionable:hover{background:var(--mat-sys-surface-container-high);box-shadow:0 3px 3px -2px #0003,0 3px 4px #00000024,0 1px 8px #0000001f}.actionable:focus{outline:2px solid var(--mat-sys-primary);outline-offset:2px}.toolbar-list a:focus-visible .social-row{outline:2px solid var(--mat-sys-primary);outline-offset:2px}.left{margin-left:64px}.center{text-align:center}.right{margin-right:64px}\n"] }]
|
|
1186
|
+
}], propDecorators: { socials: [{ type: i0.Input, args: [{ isSignal: true, alias: "socials", required: false }] }] } });
|
|
1187
|
+
|
|
1188
|
+
const NG_RHOMBUS_SOCIALS_ADAPTER = new InjectionToken('NG_RHOMBUS_SOCIALS_ADAPTER', {
|
|
1189
|
+
providedIn: 'root',
|
|
1190
|
+
factory: () => ({
|
|
1191
|
+
fetchAll: async () => [],
|
|
1192
|
+
fetchById: async () => undefined,
|
|
1193
|
+
create: async () => {
|
|
1194
|
+
throw new Error('NG_RHOMBUS_SOCIALS_ADAPTER is not provided.');
|
|
1195
|
+
},
|
|
1196
|
+
update: async () => {
|
|
1197
|
+
throw new Error('NG_RHOMBUS_SOCIALS_ADAPTER is not provided.');
|
|
1198
|
+
},
|
|
1199
|
+
remove: async () => {
|
|
1200
|
+
throw new Error('NG_RHOMBUS_SOCIALS_ADAPTER is not provided.');
|
|
1201
|
+
}
|
|
1202
|
+
})
|
|
1203
|
+
});
|
|
1204
|
+
|
|
917
1205
|
class NgRhombusSocialsService {
|
|
918
1206
|
constructor() {
|
|
919
|
-
this.
|
|
920
|
-
this.injector = inject(Injector);
|
|
921
|
-
this.socialsRef = collection(this.firestore, 'socials');
|
|
1207
|
+
this.adapter = inject(NG_RHOMBUS_SOCIALS_ADAPTER);
|
|
922
1208
|
this.socials = signal([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
|
|
923
1209
|
}
|
|
1210
|
+
sort(items) {
|
|
1211
|
+
return [...items].sort((a, b) => `${a.source}`.localeCompare(`${b.source}`));
|
|
1212
|
+
}
|
|
924
1213
|
// List
|
|
925
1214
|
async fetchAll() {
|
|
926
|
-
const
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
return items;
|
|
1215
|
+
const items = await this.adapter.fetchAll();
|
|
1216
|
+
const sorted = this.sort(items);
|
|
1217
|
+
this.socials.set(sorted);
|
|
1218
|
+
return sorted;
|
|
931
1219
|
}
|
|
932
1220
|
// Read by id
|
|
933
1221
|
async fetchById(id) {
|
|
934
|
-
|
|
935
|
-
const d = await runInInjectionContext(this.injector, () => getDoc(ref));
|
|
936
|
-
return d.exists() ? ({ ...d.data(), id: d.id, }) : undefined;
|
|
1222
|
+
return this.adapter.fetchById(id);
|
|
937
1223
|
}
|
|
938
1224
|
// Create (auto-id)
|
|
939
1225
|
async create(social) {
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
this.socials.set([...this.socials(), { ...social, id: newDoc.id, }]);
|
|
944
|
-
return newDoc.id;
|
|
1226
|
+
const id = await this.adapter.create(social);
|
|
1227
|
+
this.socials.set(this.sort([...this.socials(), { ...social, id }]));
|
|
1228
|
+
return id;
|
|
945
1229
|
}
|
|
946
1230
|
// Update (partial)
|
|
947
1231
|
async update(id, patch) {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
this.socials.set(this.socials().map(s => (s.id === id ? { ...s, ...patch } : s)));
|
|
1232
|
+
await this.adapter.update(id, patch);
|
|
1233
|
+
this.socials.set(this.sort(this.socials().map(s => (s.id === id ? { ...s, ...patch } : s))));
|
|
951
1234
|
}
|
|
952
1235
|
// Delete
|
|
953
1236
|
async remove(id) {
|
|
954
|
-
|
|
955
|
-
await runInInjectionContext(this.injector, () => deleteDoc(ref));
|
|
1237
|
+
await this.adapter.remove(id);
|
|
956
1238
|
this.socials.set(this.socials().filter(s => s.id !== id));
|
|
957
1239
|
}
|
|
958
1240
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -963,31 +1245,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
963
1245
|
args: [{ providedIn: 'root' }]
|
|
964
1246
|
}] });
|
|
965
1247
|
|
|
966
|
-
class NgRhombusSocialsListComponent {
|
|
967
|
-
constructor() {
|
|
968
|
-
this.wrapperService = inject(WrapperService);
|
|
969
|
-
this.socials = input([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
|
|
970
|
-
}
|
|
971
|
-
iconFor(source) {
|
|
972
|
-
switch (source) {
|
|
973
|
-
case SocialsSource.YouTube: return faYoutube;
|
|
974
|
-
case SocialsSource.Instagram: return faInstagram;
|
|
975
|
-
case SocialsSource.Twitch: return faTwitch;
|
|
976
|
-
case SocialsSource.TikTok: return faTiktok;
|
|
977
|
-
case SocialsSource.Bluesky: return faBluesky; // no official brand, fallback
|
|
978
|
-
case SocialsSource.GitHub: return faGithub;
|
|
979
|
-
case SocialsSource.LinkedIn: return faLinkedin;
|
|
980
|
-
default: return faLink;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
984
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusSocialsListComponent, isStandalone: true, selector: "ng-rhombus-socials-list", inputs: { socials: { classPropertyName: "socials", publicName: "socials", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if ((socials()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No socials found.</p>\r\n} @else {\r\n<div class=\"toolbar-list\">\r\n @for (s of socials(); track s.id) {\r\n <a [attr.href]=\"s.url || null\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"block\"\r\n [attr.aria-label]=\"'Open ' + s.source + ' link'\">\r\n <mat-toolbar class=\"social-toolbar actionable\" color=\"primary\">\r\n <fa-icon [icon]=\"iconFor(s.source)\" class=\"fa-icon left\"></fa-icon>\r\n <span class=\"center\">{{ s.source }}</span>\r\n @if (s.url) {\r\n <button mat-icon-button class=\"right\" aria-label=\"Copy link\">\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n }\r\n </mat-toolbar>\r\n </a>\r\n }\r\n</div>\r\n}", styles: [".social-toolbar{--mat-toolbar-container-background-color: var(--mat-sys-surface);--mat-toolbar-container-text-color: var(--mat-sys-on-surface);--mat-icon-button-icon-color: var(--mat-sys-on-surface);display:grid;grid-template-columns:auto 1fr auto;align-items:center;min-height:48px;width:100%;box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}@media(prefers-color-scheme:light){:host{--mat-list-list-item-leading-icon-color: var(--mat-sys-primary);--mat-list-list-item-label-text-color: var(--mat-sys-primary)}}.toolbar-list{display:grid;gap:12px}.actionable{cursor:pointer;transition:background-color .14s ease,box-shadow .14s ease}.actionable:hover{filter:brightness(1.08);box-shadow:0 2px 6px #00000026}.actionable:focus{outline:2px solid rgba(255,255,255,.8);outline-offset:2px}.left{margin-left:64px}.center{text-align:center}.right{margin-right:64px}\n"], dependencies: [{ kind: "ngmodule", type: FontAwesomeModule }, { kind: "component", type: i1$6.FaIconComponent, selector: "fa-icon", inputs: ["icon", "title", "animation", "mask", "flip", "size", "pull", "border", "inverse", "symbol", "rotate", "fixedWidth", "transform", "a11yRole"], outputs: ["iconChange", "titleChange", "animationChange", "maskChange", "flipChange", "sizeChange", "pullChange", "borderChange", "inverseChange", "symbolChange", "rotateChange", "fixedWidthChange", "transformChange", "a11yRoleChange"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] }); }
|
|
985
|
-
}
|
|
986
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsListComponent, decorators: [{
|
|
987
|
-
type: Component,
|
|
988
|
-
args: [{ selector: 'ng-rhombus-socials-list', standalone: true, imports: [FontAwesomeModule, MatToolbarModule, MatIconModule], template: "@if ((socials()).length === 0) {\r\n<p class=\"m-6 justify-self-center\">No socials found.</p>\r\n} @else {\r\n<div class=\"toolbar-list\">\r\n @for (s of socials(); track s.id) {\r\n <a [attr.href]=\"s.url || null\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"block\"\r\n [attr.aria-label]=\"'Open ' + s.source + ' link'\">\r\n <mat-toolbar class=\"social-toolbar actionable\" color=\"primary\">\r\n <fa-icon [icon]=\"iconFor(s.source)\" class=\"fa-icon left\"></fa-icon>\r\n <span class=\"center\">{{ s.source }}</span>\r\n @if (s.url) {\r\n <button mat-icon-button class=\"right\" aria-label=\"Copy link\">\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n }\r\n </mat-toolbar>\r\n </a>\r\n }\r\n</div>\r\n}", styles: [".social-toolbar{--mat-toolbar-container-background-color: var(--mat-sys-surface);--mat-toolbar-container-text-color: var(--mat-sys-on-surface);--mat-icon-button-icon-color: var(--mat-sys-on-surface);display:grid;grid-template-columns:auto 1fr auto;align-items:center;min-height:48px;width:100%;box-shadow:0 3px 5px -1px #0003,0 6px 10px #00000024,0 1px 18px #0000001f}@media(prefers-color-scheme:light){:host{--mat-list-list-item-leading-icon-color: var(--mat-sys-primary);--mat-list-list-item-label-text-color: var(--mat-sys-primary)}}.toolbar-list{display:grid;gap:12px}.actionable{cursor:pointer;transition:background-color .14s ease,box-shadow .14s ease}.actionable:hover{filter:brightness(1.08);box-shadow:0 2px 6px #00000026}.actionable:focus{outline:2px solid rgba(255,255,255,.8);outline-offset:2px}.left{margin-left:64px}.center{text-align:center}.right{margin-right:64px}\n"] }]
|
|
989
|
-
}], propDecorators: { socials: [{ type: i0.Input, args: [{ isSignal: true, alias: "socials", required: false }] }] } });
|
|
990
|
-
|
|
991
1248
|
class NgRhombusSocialEditDialogComponent {
|
|
992
1249
|
constructor() {
|
|
993
1250
|
this.fb = inject(FormBuilder);
|
|
@@ -1017,7 +1274,7 @@ class NgRhombusSocialEditDialogComponent {
|
|
|
1017
1274
|
this.dialogRef.close();
|
|
1018
1275
|
}
|
|
1019
1276
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialEditDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1020
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusSocialEditDialogComponent, isStandalone: true, selector: "ng-rhombus-social-edit-dialog", ngImport: i0, template: "<h2 mat-dialog-title>\r\n {{ data.social ? 'Edit Social' : 'Add Social' }}\r\n</h2>\r\n\r\n<form [formGroup]=\"form\" (ngSubmit)=\"save()\">\r\n <mat-dialog-content>\r\n <mat-form-field appearance=\"fill\" style=\"width: 100%\">\r\n <mat-label>Source</mat-label>\r\n <mat-select formControlName=\"source\">\r\n @for (opt of socialTypeOptions; track opt.value) {\r\n <mat-option [value]=\"opt.value\">\r\n {{ opt.label }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"fill\" style=\"width: 100%\">\r\n <mat-label>URL</mat-label>\r\n <input matInput formControlName=\"url\" />\r\n </mat-form-field>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\">\r\n <button mat-button type=\"button\" (click)=\"cancel()\">Cancel</button>\r\n <button mat-raised-button color=\"primary\" type=\"submit\" [disabled]=\"form.invalid\">\r\n Save\r\n </button>\r\n </mat-dialog-actions>\r\n</form>", dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$
|
|
1277
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusSocialEditDialogComponent, isStandalone: true, selector: "ng-rhombus-social-edit-dialog", ngImport: i0, template: "<h2 mat-dialog-title>\r\n {{ data.social ? 'Edit Social' : 'Add Social' }}\r\n</h2>\r\n\r\n<form [formGroup]=\"form\" (ngSubmit)=\"save()\">\r\n <mat-dialog-content>\r\n <mat-form-field appearance=\"fill\" style=\"width: 100%\">\r\n <mat-label>Source</mat-label>\r\n <mat-select formControlName=\"source\">\r\n @for (opt of socialTypeOptions; track opt.value) {\r\n <mat-option [value]=\"opt.value\">\r\n {{ opt.label }}\r\n </mat-option>\r\n }\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"fill\" style=\"width: 100%\">\r\n <mat-label>URL</mat-label>\r\n <input matInput formControlName=\"url\" />\r\n </mat-form-field>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\">\r\n <button mat-button type=\"button\" (click)=\"cancel()\">Cancel</button>\r\n <button mat-raised-button color=\"primary\" type=\"submit\" [disabled]=\"form.invalid\">\r\n Save\r\n </button>\r\n </mat-dialog-actions>\r\n</form>", dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i7.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i7.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] }); }
|
|
1021
1278
|
}
|
|
1022
1279
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialEditDialogComponent, decorators: [{
|
|
1023
1280
|
type: Component,
|
|
@@ -1036,7 +1293,7 @@ class NgRhombusSocialDeleteDialogComponent {
|
|
|
1036
1293
|
this.dialogRef.close(true);
|
|
1037
1294
|
}
|
|
1038
1295
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialDeleteDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1039
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusSocialDeleteDialogComponent, isStandalone: true, selector: "ng-rhombus-social-delete-dialog", ngImport: i0, template: "<h2 mat-dialog-title>Delete Social</h2>\r\n\r\n<mat-dialog-content>\r\n Are you sure you want to delete this social?\r\n <br />\r\n <strong>{{ data.social.source }} - {{ data.social.url }}</strong>\r\n</mat-dialog-content>\r\n\r\n<mat-dialog-actions align=\"end\">\r\n <button mat-button (click)=\"cancel()\">Cancel</button>\r\n <button mat-raised-button color=\"warn\" (click)=\"confirm()\">Delete</button>\r\n</mat-dialog-actions>\r\n", dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$
|
|
1296
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.4", type: NgRhombusSocialDeleteDialogComponent, isStandalone: true, selector: "ng-rhombus-social-delete-dialog", ngImport: i0, template: "<h2 mat-dialog-title>Delete Social</h2>\r\n\r\n<mat-dialog-content>\r\n Are you sure you want to delete this social?\r\n <br />\r\n <strong>{{ data.social.source }} - {{ data.social.url }}</strong>\r\n</mat-dialog-content>\r\n\r\n<mat-dialog-actions align=\"end\">\r\n <button mat-button (click)=\"cancel()\">Cancel</button>\r\n <button mat-raised-button color=\"warn\" (click)=\"confirm()\">Delete</button>\r\n</mat-dialog-actions>\r\n", dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2$4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2$4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2$4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] }); }
|
|
1040
1297
|
}
|
|
1041
1298
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialDeleteDialogComponent, decorators: [{
|
|
1042
1299
|
type: Component,
|
|
@@ -1100,7 +1357,7 @@ class NgRhombusSocialsTableComponent {
|
|
|
1100
1357
|
await this.svc.remove(row.id);
|
|
1101
1358
|
}
|
|
1102
1359
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1103
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusSocialsTableComponent, isStandalone: true, selector: "ng-rhombus-socials-table", inputs: { socials: { classPropertyName: "socials", publicName: "socials", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<table mat-table [dataSource]=\"socials()\" class=\"mat-elevation-z1\">\r\n\r\n <ng-container matColumnDef=\"source\">\r\n <th mat-header-cell *matHeaderCellDef>Source</th>\r\n <td mat-cell *matCellDef=\"let row\">\r\n <!-- replace text with icon -->\r\n <fa-icon [icon]=\"iconFor(row.source)\" class=\"fa-icon left\"></fa-icon>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"url\">\r\n <th mat-header-cell *matHeaderCellDef>URL</th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <a [href]=\"s.url\" target=\"_blank\" rel=\"noopener\">{{ s.url }}</a>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"actions\">\r\n <th mat-header-cell *matHeaderCellDef></th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <button mat-icon-button color=\"primary\" (click)=\"startEdit(s)\"><mat-icon>edit</mat-icon></button>\r\n <button mat-icon-button color=\"warn\" (click)=\"delete(s)\"><mat-icon>delete</mat-icon></button>\r\n </td>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n</table>\r\n\r\n\r\n@if (socials() && socials().length === 0) {\r\n<p>No socials found.</p>\r\n}", styles: [".full-width{width:100%}.mat-column-source,.mat-column-actions{text-align:center}.mat-column-source fa-icon,.mat-column-actions fa-icon{display:inline-block}\n"], dependencies: [{ kind: "ngmodule", type: FontAwesomeModule }, { kind: "component", type: i1$
|
|
1360
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.4", type: NgRhombusSocialsTableComponent, isStandalone: true, selector: "ng-rhombus-socials-table", inputs: { socials: { classPropertyName: "socials", publicName: "socials", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<table mat-table [dataSource]=\"socials()\" class=\"mat-elevation-z1\">\r\n\r\n <ng-container matColumnDef=\"source\">\r\n <th mat-header-cell *matHeaderCellDef>Source</th>\r\n <td mat-cell *matCellDef=\"let row\">\r\n <!-- replace text with icon -->\r\n <fa-icon [icon]=\"iconFor(row.source)\" class=\"fa-icon left\"></fa-icon>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"url\">\r\n <th mat-header-cell *matHeaderCellDef>URL</th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <a [href]=\"s.url\" target=\"_blank\" rel=\"noopener\">{{ s.url }}</a>\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"actions\">\r\n <th mat-header-cell *matHeaderCellDef></th>\r\n <td mat-cell *matCellDef=\"let s\">\r\n <button mat-icon-button color=\"primary\" (click)=\"startEdit(s)\"><mat-icon>edit</mat-icon></button>\r\n <button mat-icon-button color=\"warn\" (click)=\"delete(s)\"><mat-icon>delete</mat-icon></button>\r\n </td>\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\r\n</table>\r\n\r\n\r\n@if (socials() && socials().length === 0) {\r\n<p>No socials found.</p>\r\n}", styles: [".full-width{width:100%}.mat-column-source,.mat-column-actions{text-align:center}.mat-column-source fa-icon,.mat-column-actions fa-icon{display:inline-block}\n"], dependencies: [{ kind: "ngmodule", type: FontAwesomeModule }, { kind: "component", type: i1$5.FaIconComponent, selector: "fa-icon", inputs: ["icon", "title", "animation", "mask", "flip", "size", "pull", "border", "inverse", "symbol", "rotate", "fixedWidth", "transform", "a11yRole"], outputs: ["iconChange", "titleChange", "animationChange", "maskChange", "flipChange", "sizeChange", "pullChange", "borderChange", "inverseChange", "symbolChange", "rotateChange", "fixedWidthChange", "transformChange", "a11yRoleChange"] }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i3$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i3$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i3$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i3$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i3$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i3$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i3$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i3$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i3$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i3$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: MatInputModule }] }); }
|
|
1104
1361
|
}
|
|
1105
1362
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsTableComponent, decorators: [{
|
|
1106
1363
|
type: Component,
|
|
@@ -1115,5 +1372,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
1115
1372
|
* Generated bundle index. Do not edit.
|
|
1116
1373
|
*/
|
|
1117
1374
|
|
|
1118
|
-
export { IBlog, NgRhombusBlogAddEditComponent, NgRhombusBlogDeletePostComponent, NgRhombusBlogListComponent, NgRhombusBlogPostComponent, NgRhombusBlogPostLatestComponent, NgRhombusBlogPostThumbnailService, NgRhombusBlogService, NgRhombusBlogTableComponent, NgRhombusHomeAdminComponent, NgRhombusLoginComponent, NgRhombusPageComponent, NgRhombusSocialsListComponent, NgRhombusSocialsService, NgRhombusSocialsTableComponent, NgRhombusSpinnerComponent, NgRhombusWrapperComponent, SocialsSource, ThemeEnum, WrapperService };
|
|
1375
|
+
export { BlogSeries, ContentPillar, IBlog, IHome, NG_RHOMBUS_AUTH_ADAPTER, NG_RHOMBUS_BLOG_ADAPTER, NG_RHOMBUS_BLOG_THUMBNAIL_ADAPTER, NG_RHOMBUS_HOME_ADAPTER, NG_RHOMBUS_SEO_ADAPTER, NG_RHOMBUS_SOCIALS_ADAPTER, NgRhombusBlogAddEditComponent, NgRhombusBlogDeletePostComponent, NgRhombusBlogListComponent, NgRhombusBlogPostComponent, NgRhombusBlogPostLatestComponent, NgRhombusBlogPostThumbnailService, NgRhombusBlogService, NgRhombusBlogTableComponent, NgRhombusHomeAdminComponent, NgRhombusHomeService, NgRhombusLoginComponent, NgRhombusPageComponent, NgRhombusSocialsListComponent, NgRhombusSocialsService, NgRhombusSocialsTableComponent, NgRhombusSpinnerComponent, NgRhombusWrapperComponent, SocialsSource, ThemeEnum, WrapperService, ngRhombusTimestampToMillis };
|
|
1119
1376
|
//# sourceMappingURL=doug-williamson-ng-rhombus.mjs.map
|