@doug-williamson/ng-rhombus 1.0.8 → 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 +467 -215
- 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 +198 -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
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,7 +226,7 @@ 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]=\"['/']\">\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:
|
|
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,
|
|
@@ -285,11 +292,11 @@ class NgRhombusWrapperComponent {
|
|
|
285
292
|
this.logout.emit();
|
|
286
293
|
}
|
|
287
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 }); }
|
|
288
|
-
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"] }] }); }
|
|
289
296
|
}
|
|
290
297
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusWrapperComponent, decorators: [{
|
|
291
298
|
type: Component,
|
|
292
|
-
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"] }]
|
|
293
300
|
}], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: WrapperService }], propDecorators: { clickAddNewEvent: [{
|
|
294
301
|
type: Output
|
|
295
302
|
}], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], logout: [{ type: i0.Output, args: ["logout"] }], title: [{
|
|
@@ -332,13 +339,13 @@ class NgRhombusLoginComponent {
|
|
|
332
339
|
const rawForm = this.loginForm.getRawValue();
|
|
333
340
|
this.onSubmit.emit(rawForm);
|
|
334
341
|
}
|
|
335
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusLoginComponent, deps: [{ token:
|
|
336
|
-
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"] }] }); }
|
|
337
344
|
}
|
|
338
345
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusLoginComponent, decorators: [{
|
|
339
346
|
type: Component,
|
|
340
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"] }]
|
|
341
|
-
}], 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"] }] } });
|
|
342
349
|
|
|
343
350
|
class NgRhombusSpinnerComponent {
|
|
344
351
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
@@ -356,52 +363,108 @@ class NgRhombusPageComponent {
|
|
|
356
363
|
this.currentYear = new Date().getFullYear();
|
|
357
364
|
}
|
|
358
365
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
359
|
-
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"] }] }); }
|
|
360
367
|
}
|
|
361
368
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusPageComponent, decorators: [{
|
|
362
369
|
type: Component,
|
|
363
|
-
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"] }]
|
|
364
371
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], backLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLink", required: false }] }] } });
|
|
365
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
|
+
|
|
366
381
|
/*
|
|
367
382
|
* Public API Surface of @doug-williamson/ng-rhombus/shell
|
|
368
383
|
*/
|
|
369
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
|
+
|
|
370
429
|
class NgRhombusBlogListComponent {
|
|
371
430
|
constructor() {
|
|
372
431
|
this.goToRoute = new EventEmitter();
|
|
373
|
-
this.#
|
|
374
|
-
this.#
|
|
432
|
+
this.#seo = inject(NG_RHOMBUS_SEO_ADAPTER);
|
|
433
|
+
this.#platformId = inject(PLATFORM_ID);
|
|
434
|
+
this.#document = inject(DOCUMENT);
|
|
375
435
|
this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
|
|
436
|
+
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
376
437
|
}
|
|
377
|
-
#
|
|
378
|
-
#
|
|
438
|
+
#seo;
|
|
439
|
+
#platformId;
|
|
440
|
+
#document;
|
|
379
441
|
ngOnInit() {
|
|
380
|
-
const
|
|
442
|
+
const origin = isPlatformBrowser(this.#platformId) ? this.#document.location?.origin : undefined;
|
|
443
|
+
const imageUrl = origin ? new URL('/fav.ico', origin).toString() : '/fav.ico';
|
|
381
444
|
// Document title
|
|
382
|
-
this.#
|
|
445
|
+
this.#seo.setTitle('Blog - Doug Williamson');
|
|
383
446
|
// Basic meta
|
|
384
|
-
this.#
|
|
447
|
+
this.#seo.updateTag({ name: 'description', content: 'This is my personal collection of blog posts.' });
|
|
385
448
|
// Open Graph
|
|
386
|
-
this.#
|
|
387
|
-
this.#
|
|
388
|
-
this.#
|
|
389
|
-
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 });
|
|
390
453
|
// Twitter Card
|
|
391
|
-
this.#
|
|
392
|
-
this.#
|
|
393
|
-
this.#
|
|
394
|
-
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 });
|
|
395
458
|
}
|
|
396
459
|
goToBlogPost(id) {
|
|
397
460
|
this.goToRoute.emit(id);
|
|
398
461
|
}
|
|
399
462
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
400
|
-
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" }] }); }
|
|
401
464
|
}
|
|
402
465
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, decorators: [{
|
|
403
466
|
type: Component,
|
|
404
|
-
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"] }]
|
|
405
468
|
}], propDecorators: { goToRoute: [{
|
|
406
469
|
type: Output
|
|
407
470
|
}], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
@@ -409,13 +472,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
409
472
|
class NgRhombusBlogPostLatestComponent {
|
|
410
473
|
constructor() {
|
|
411
474
|
this.blogPost = input(...(ngDevMode ? [undefined, { debugName: "blogPost" }] : []));
|
|
475
|
+
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
412
476
|
}
|
|
413
477
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
414
|
-
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" }] }); }
|
|
415
479
|
}
|
|
416
480
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, decorators: [{
|
|
417
481
|
type: Component,
|
|
418
|
-
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"] }]
|
|
419
483
|
}], propDecorators: { blogPost: [{ type: i0.Input, args: [{ isSignal: true, alias: "blogPost", required: false }] }] } });
|
|
420
484
|
|
|
421
485
|
class NgRhombusBlogPostHelper {
|
|
@@ -433,72 +497,125 @@ class NgRhombusBlogPostHelper {
|
|
|
433
497
|
}
|
|
434
498
|
}
|
|
435
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
|
+
|
|
436
536
|
class NgRhombusBlogService {
|
|
437
537
|
constructor() {
|
|
438
538
|
this.WORDS_PER_MINUTE = 200;
|
|
439
|
-
this.
|
|
440
|
-
this.injector = inject(Injector);
|
|
441
|
-
this.blogCollectionRef = collection(this.firestore, 'blog');
|
|
539
|
+
this.blogAdapter = inject(NG_RHOMBUS_BLOG_ADAPTER);
|
|
442
540
|
this.blogPosts = signal([], ...(ngDevMode ? [{ debugName: "blogPosts" }] : []));
|
|
443
541
|
this.selectedBlogPost = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBlogPost" }] : []));
|
|
444
542
|
}
|
|
543
|
+
normalizeBlogPost(post) {
|
|
544
|
+
const timestampMillis = ngRhombusTimestampToMillis(post.timestamp);
|
|
545
|
+
return {
|
|
546
|
+
...post,
|
|
547
|
+
timestamp: timestampMillis ?? post.timestamp,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
445
550
|
async fetchBlogPosts() {
|
|
446
|
-
const
|
|
447
|
-
const snapshot = await runInInjectionContext(this.injector, () => getDocs(q));
|
|
448
|
-
const posts = snapshot.docs.map(d => ({ ...d.data(), id: d.id }));
|
|
551
|
+
const posts = (await this.blogAdapter.fetchBlogPosts()).map(p => this.normalizeBlogPost(p));
|
|
449
552
|
this.blogPosts.set(posts);
|
|
450
553
|
return posts;
|
|
451
554
|
}
|
|
452
555
|
async fetchBlogPost(id) {
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const docData = { ...docSnap.data(), id: id };
|
|
460
|
-
this.selectedBlogPost.set(docData);
|
|
461
|
-
return docData;
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
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;
|
|
466
562
|
}
|
|
467
563
|
async fetchLatestBlogPost() {
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
return latestPost;
|
|
475
|
-
}
|
|
476
|
-
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;
|
|
477
570
|
}
|
|
478
571
|
async createBlogPost(blogPost) {
|
|
479
|
-
const
|
|
480
|
-
await
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
thumbnail: blogPost.thumbnail,
|
|
484
|
-
content: blogPost.content,
|
|
572
|
+
const id = NgRhombusBlogPostHelper.createSlug(blogPost.title);
|
|
573
|
+
await this.blogAdapter.createBlogPost({
|
|
574
|
+
...blogPost,
|
|
575
|
+
id,
|
|
485
576
|
timestamp: new Date(),
|
|
486
|
-
|
|
487
|
-
}));
|
|
577
|
+
});
|
|
488
578
|
}
|
|
489
579
|
async updateBlogPost(blogPost) {
|
|
490
|
-
|
|
491
|
-
await runInInjectionContext(this.injector, () => updateDoc(blogPostDocumentRef, {
|
|
492
|
-
title: blogPost.title,
|
|
493
|
-
description: blogPost.description,
|
|
494
|
-
thumbnail: blogPost.thumbnail,
|
|
495
|
-
content: blogPost.content,
|
|
496
|
-
readTimeMinutes: blogPost.readTimeMinutes
|
|
497
|
-
}));
|
|
580
|
+
await this.blogAdapter.updateBlogPost(blogPost);
|
|
498
581
|
}
|
|
499
582
|
deleteBlogPost(id) {
|
|
500
|
-
|
|
501
|
-
|
|
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
|
+
};
|
|
502
619
|
}
|
|
503
620
|
calculateReadTimeMinutes(text) {
|
|
504
621
|
if (!text)
|
|
@@ -511,44 +628,30 @@ class NgRhombusBlogService {
|
|
|
511
628
|
}
|
|
512
629
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogService, decorators: [{
|
|
513
630
|
type: Injectable,
|
|
514
|
-
args: [{
|
|
515
|
-
providedIn: 'root',
|
|
516
|
-
}]
|
|
517
|
-
}] });
|
|
518
|
-
|
|
519
|
-
class NgRhombusBlogPostThumbnailService {
|
|
520
|
-
constructor() {
|
|
521
|
-
this.firebaseStorage = inject(Storage);
|
|
522
|
-
}
|
|
523
|
-
uploadImage(imageName, image) {
|
|
524
|
-
const storageRef = ref(this.firebaseStorage, `thumbnails/${imageName}`);
|
|
525
|
-
return uploadBytesResumable(storageRef, image);
|
|
526
|
-
}
|
|
527
|
-
deleteImage(filePath) {
|
|
528
|
-
const storageDocRef = ref(this.firebaseStorage, filePath);
|
|
529
|
-
return deleteObject(storageDocRef).then(() => {
|
|
530
|
-
}).catch((error) => {
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
534
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, providedIn: 'root' }); }
|
|
535
|
-
}
|
|
536
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostThumbnailService, decorators: [{
|
|
537
|
-
type: Injectable,
|
|
538
|
-
args: [{
|
|
539
|
-
providedIn: 'root'
|
|
540
|
-
}]
|
|
631
|
+
args: [{ providedIn: 'root' }]
|
|
541
632
|
}] });
|
|
542
633
|
|
|
543
634
|
class NgRhombusBlogTableComponent {
|
|
544
635
|
constructor() {
|
|
545
636
|
this.editEvent = new EventEmitter();
|
|
546
637
|
this.deleteEvent = new EventEmitter();
|
|
547
|
-
this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
|
|
548
638
|
this.blogService = inject(NgRhombusBlogService);
|
|
549
639
|
this.dialog = inject(MatDialog);
|
|
550
640
|
this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
|
|
551
|
-
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
|
+
]);
|
|
552
655
|
}
|
|
553
656
|
goToBlogPost(id) {
|
|
554
657
|
this.editEvent.emit(id);
|
|
@@ -557,17 +660,49 @@ class NgRhombusBlogTableComponent {
|
|
|
557
660
|
this.deleteEvent.emit(blogPost);
|
|
558
661
|
}
|
|
559
662
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
560
|
-
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" }] }); }
|
|
561
664
|
}
|
|
562
665
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, decorators: [{
|
|
563
666
|
type: Component,
|
|
564
|
-
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"] }]
|
|
565
668
|
}], propDecorators: { editEvent: [{
|
|
566
669
|
type: Output
|
|
567
670
|
}], deleteEvent: [{
|
|
568
671
|
type: Output
|
|
569
672
|
}], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
570
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
|
+
|
|
571
706
|
class NgRhombusBlogThumbnailComponent {
|
|
572
707
|
constructor() {
|
|
573
708
|
this.width = input(0, ...(ngDevMode ? [{ debugName: "width" }] : []));
|
|
@@ -590,7 +725,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
590
725
|
|
|
591
726
|
class NgRhombusBlogDeleteThumbnailComponent {
|
|
592
727
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeleteThumbnailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
593
|
-
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]" }] }); }
|
|
594
729
|
}
|
|
595
730
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeleteThumbnailComponent, decorators: [{
|
|
596
731
|
type: Component,
|
|
@@ -603,24 +738,38 @@ class ThumbnailControlComponent {
|
|
|
603
738
|
this.height = input(0, ...(ngDevMode ? [{ debugName: "height" }] : []));
|
|
604
739
|
this.thumbnailSrc = input('', ...(ngDevMode ? [{ debugName: "thumbnailSrc" }] : []));
|
|
605
740
|
this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
741
|
+
this.displayMode = input('preview', ...(ngDevMode ? [{ debugName: "displayMode" }] : []));
|
|
606
742
|
this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
|
|
607
743
|
this.dialog = inject(MatDialog);
|
|
608
744
|
this.onFileUploaded = output();
|
|
609
745
|
this.onFileDeleted = output();
|
|
610
746
|
this.uploadedFile = signal(undefined, ...(ngDevMode ? [{ debugName: "uploadedFile" }] : []));
|
|
611
747
|
this.placeholder = computed(() => `https://placehold.co/${this.width()}x${this.height()}`, ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
612
|
-
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" }] : []));
|
|
613
764
|
}
|
|
614
765
|
onThumbnailSelected(input) {
|
|
615
766
|
if (!input.files || input.files.length <= 0) {
|
|
616
767
|
return;
|
|
617
768
|
}
|
|
618
769
|
const file = input.files[0];
|
|
619
|
-
this.thumbnailService.uploadImage(file.name, file).then((
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
this.onFileUploaded.emit(downloadUrl);
|
|
623
|
-
});
|
|
770
|
+
this.thumbnailService.uploadImage(file.name, file).then((downloadUrl) => {
|
|
771
|
+
this.uploadedFile.set(downloadUrl);
|
|
772
|
+
this.onFileUploaded.emit(downloadUrl);
|
|
624
773
|
});
|
|
625
774
|
}
|
|
626
775
|
onThumbnailDeleted() {
|
|
@@ -636,25 +785,22 @@ class ThumbnailControlComponent {
|
|
|
636
785
|
});
|
|
637
786
|
}
|
|
638
787
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: ThumbnailControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
639
|
-
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"] }] }); }
|
|
640
789
|
}
|
|
641
790
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: ThumbnailControlComponent, decorators: [{
|
|
642
791
|
type: Component,
|
|
643
|
-
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
|
|
644
|
-
}], 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: [{
|
|
645
794
|
type: ViewChild,
|
|
646
795
|
args: ['thumbnailInput']
|
|
647
796
|
}], onFileUploaded: [{ type: i0.Output, args: ["onFileUploaded"] }], onFileDeleted: [{ type: i0.Output, args: ["onFileDeleted"] }] } });
|
|
648
797
|
|
|
649
|
-
class IBlog {
|
|
650
|
-
}
|
|
651
|
-
|
|
652
798
|
class NgRhombusBlogConfirmationComponent {
|
|
653
799
|
constructor() {
|
|
654
800
|
this.data = inject(MAT_DIALOG_DATA);
|
|
655
801
|
}
|
|
656
802
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogConfirmationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
657
|
-
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]" }] }); }
|
|
658
804
|
}
|
|
659
805
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogConfirmationComponent, decorators: [{
|
|
660
806
|
type: Component,
|
|
@@ -668,6 +814,8 @@ class NgRhombusBlogAddEditComponent {
|
|
|
668
814
|
this.cancelEvent = output();
|
|
669
815
|
this.submitEvent = output();
|
|
670
816
|
this.contentData = signal('', ...(ngDevMode ? [{ debugName: "contentData" }] : []));
|
|
817
|
+
this.pillars = Object.values(ContentPillar);
|
|
818
|
+
this.seriesOptions = Object.values(BlogSeries);
|
|
671
819
|
this.dialog = inject(MatDialog);
|
|
672
820
|
this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
|
|
673
821
|
this.formBuilder = inject(FormBuilder);
|
|
@@ -705,8 +853,29 @@ class NgRhombusBlogAddEditComponent {
|
|
|
705
853
|
title: [this.blogPost()?.title, Validators.required],
|
|
706
854
|
description: [this.blogPost()?.description, Validators.required],
|
|
707
855
|
thumbnail: [this.blogPost()?.thumbnail, Validators.required],
|
|
708
|
-
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],
|
|
709
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 });
|
|
710
879
|
}
|
|
711
880
|
onContentChange() {
|
|
712
881
|
this.contentData.set(this.blogPostForm.getRawValue().content);
|
|
@@ -772,28 +941,41 @@ class NgRhombusBlogAddEditComponent {
|
|
|
772
941
|
}
|
|
773
942
|
}
|
|
774
943
|
onSubmit() {
|
|
775
|
-
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
|
|
776
950
|
return;
|
|
777
951
|
}
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
if (this.blogPost()) {
|
|
952
|
+
const submittedBlogPost = new IBlog();
|
|
953
|
+
if (this.blogPost())
|
|
781
954
|
submittedBlogPost.id = this.blogPost().id;
|
|
782
|
-
|
|
783
|
-
submittedBlogPost.
|
|
784
|
-
submittedBlogPost.
|
|
785
|
-
submittedBlogPost.
|
|
786
|
-
submittedBlogPost.
|
|
787
|
-
|
|
788
|
-
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);
|
|
789
963
|
this.submitEvent.emit(submittedBlogPost);
|
|
790
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
|
+
}
|
|
791
973
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
792
|
-
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"] }] }); }
|
|
793
975
|
}
|
|
794
976
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, decorators: [{
|
|
795
977
|
type: Component,
|
|
796
|
-
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"] }]
|
|
797
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: [{
|
|
798
980
|
type: ViewChild,
|
|
799
981
|
args: ['autosize']
|
|
@@ -807,6 +989,7 @@ class NgRhombusBlogPostComponent {
|
|
|
807
989
|
this.dataSource = input(...(ngDevMode ? [undefined, { debugName: "dataSource" }] : []));
|
|
808
990
|
this.themeService = inject(ThemeService);
|
|
809
991
|
this.#doc = inject(DOCUMENT);
|
|
992
|
+
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
810
993
|
this.faXTwitter = faXTwitter;
|
|
811
994
|
this.faFacebook = faFacebook;
|
|
812
995
|
this.faBluesky = faBluesky;
|
|
@@ -838,17 +1021,40 @@ class NgRhombusBlogPostComponent {
|
|
|
838
1021
|
navigator.clipboard.writeText(u).catch(() => { });
|
|
839
1022
|
}
|
|
840
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
|
+
}
|
|
841
1047
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
842
|
-
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" }] }); }
|
|
843
1049
|
}
|
|
844
1050
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, decorators: [{
|
|
845
1051
|
type: Component,
|
|
846
|
-
args: [{ selector: 'ng-rhombus-blog-post', imports: [DatePipe, NgClass, MarkdownComponent, MatCardModule, MatMenuModule, MatIconModule, MatButtonModule, 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>{{
|
|
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"] }]
|
|
847
1053
|
}], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
848
1054
|
|
|
849
1055
|
class NgRhombusBlogDeletePostComponent {
|
|
850
1056
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeletePostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
851
|
-
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]" }] }); }
|
|
852
1058
|
}
|
|
853
1059
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogDeletePostComponent, decorators: [{
|
|
854
1060
|
type: Component,
|
|
@@ -901,14 +1107,48 @@ class NgRhombusHomeAdminComponent {
|
|
|
901
1107
|
console.log('cancel');
|
|
902
1108
|
}
|
|
903
1109
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHomeAdminComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
904
|
-
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"] }] }); }
|
|
905
1111
|
}
|
|
906
1112
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusHomeAdminComponent, decorators: [{
|
|
907
1113
|
type: Component,
|
|
908
|
-
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"] }]
|
|
909
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"] }] } });
|
|
910
1116
|
|
|
911
|
-
|
|
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
|
+
|
|
912
1152
|
var SocialsSource;
|
|
913
1153
|
(function (SocialsSource) {
|
|
914
1154
|
SocialsSource["Twitch"] = "Twitch";
|
|
@@ -919,45 +1159,82 @@ var SocialsSource;
|
|
|
919
1159
|
SocialsSource["GitHub"] = "GitHub";
|
|
920
1160
|
SocialsSource["LinkedIn"] = "LinkedIn";
|
|
921
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
|
+
|
|
922
1205
|
class NgRhombusSocialsService {
|
|
923
1206
|
constructor() {
|
|
924
|
-
this.
|
|
925
|
-
this.injector = inject(Injector);
|
|
926
|
-
this.socialsRef = collection(this.firestore, 'socials');
|
|
1207
|
+
this.adapter = inject(NG_RHOMBUS_SOCIALS_ADAPTER);
|
|
927
1208
|
this.socials = signal([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
|
|
928
1209
|
}
|
|
1210
|
+
sort(items) {
|
|
1211
|
+
return [...items].sort((a, b) => `${a.source}`.localeCompare(`${b.source}`));
|
|
1212
|
+
}
|
|
929
1213
|
// List
|
|
930
1214
|
async fetchAll() {
|
|
931
|
-
const
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
return items;
|
|
1215
|
+
const items = await this.adapter.fetchAll();
|
|
1216
|
+
const sorted = this.sort(items);
|
|
1217
|
+
this.socials.set(sorted);
|
|
1218
|
+
return sorted;
|
|
936
1219
|
}
|
|
937
1220
|
// Read by id
|
|
938
1221
|
async fetchById(id) {
|
|
939
|
-
|
|
940
|
-
const d = await runInInjectionContext(this.injector, () => getDoc(ref));
|
|
941
|
-
return d.exists() ? ({ ...d.data(), id: d.id, }) : undefined;
|
|
1222
|
+
return this.adapter.fetchById(id);
|
|
942
1223
|
}
|
|
943
1224
|
// Create (auto-id)
|
|
944
1225
|
async create(social) {
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
this.socials.set([...this.socials(), { ...social, id: newDoc.id, }]);
|
|
949
|
-
return newDoc.id;
|
|
1226
|
+
const id = await this.adapter.create(social);
|
|
1227
|
+
this.socials.set(this.sort([...this.socials(), { ...social, id }]));
|
|
1228
|
+
return id;
|
|
950
1229
|
}
|
|
951
1230
|
// Update (partial)
|
|
952
1231
|
async update(id, patch) {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
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))));
|
|
956
1234
|
}
|
|
957
1235
|
// Delete
|
|
958
1236
|
async remove(id) {
|
|
959
|
-
|
|
960
|
-
await runInInjectionContext(this.injector, () => deleteDoc(ref));
|
|
1237
|
+
await this.adapter.remove(id);
|
|
961
1238
|
this.socials.set(this.socials().filter(s => s.id !== id));
|
|
962
1239
|
}
|
|
963
1240
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -968,31 +1245,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
968
1245
|
args: [{ providedIn: 'root' }]
|
|
969
1246
|
}] });
|
|
970
1247
|
|
|
971
|
-
class NgRhombusSocialsListComponent {
|
|
972
|
-
constructor() {
|
|
973
|
-
this.wrapperService = inject(WrapperService);
|
|
974
|
-
this.socials = input([], ...(ngDevMode ? [{ debugName: "socials" }] : []));
|
|
975
|
-
}
|
|
976
|
-
iconFor(source) {
|
|
977
|
-
switch (source) {
|
|
978
|
-
case SocialsSource.YouTube: return faYoutube;
|
|
979
|
-
case SocialsSource.Instagram: return faInstagram;
|
|
980
|
-
case SocialsSource.Twitch: return faTwitch;
|
|
981
|
-
case SocialsSource.TikTok: return faTiktok;
|
|
982
|
-
case SocialsSource.Bluesky: return faBluesky; // no official brand, fallback
|
|
983
|
-
case SocialsSource.GitHub: return faGithub;
|
|
984
|
-
case SocialsSource.LinkedIn: return faLinkedin;
|
|
985
|
-
default: return faLink;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
989
|
-
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"] }] }); }
|
|
990
|
-
}
|
|
991
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsListComponent, decorators: [{
|
|
992
|
-
type: Component,
|
|
993
|
-
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"] }]
|
|
994
|
-
}], propDecorators: { socials: [{ type: i0.Input, args: [{ isSignal: true, alias: "socials", required: false }] }] } });
|
|
995
|
-
|
|
996
1248
|
class NgRhombusSocialEditDialogComponent {
|
|
997
1249
|
constructor() {
|
|
998
1250
|
this.fb = inject(FormBuilder);
|
|
@@ -1022,7 +1274,7 @@ class NgRhombusSocialEditDialogComponent {
|
|
|
1022
1274
|
this.dialogRef.close();
|
|
1023
1275
|
}
|
|
1024
1276
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialEditDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1025
|
-
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"] }] }); }
|
|
1026
1278
|
}
|
|
1027
1279
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialEditDialogComponent, decorators: [{
|
|
1028
1280
|
type: Component,
|
|
@@ -1041,7 +1293,7 @@ class NgRhombusSocialDeleteDialogComponent {
|
|
|
1041
1293
|
this.dialogRef.close(true);
|
|
1042
1294
|
}
|
|
1043
1295
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialDeleteDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1044
|
-
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"] }] }); }
|
|
1045
1297
|
}
|
|
1046
1298
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialDeleteDialogComponent, decorators: [{
|
|
1047
1299
|
type: Component,
|
|
@@ -1105,7 +1357,7 @@ class NgRhombusSocialsTableComponent {
|
|
|
1105
1357
|
await this.svc.remove(row.id);
|
|
1106
1358
|
}
|
|
1107
1359
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1108
|
-
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 }] }); }
|
|
1109
1361
|
}
|
|
1110
1362
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusSocialsTableComponent, decorators: [{
|
|
1111
1363
|
type: Component,
|
|
@@ -1120,5 +1372,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
1120
1372
|
* Generated bundle index. Do not edit.
|
|
1121
1373
|
*/
|
|
1122
1374
|
|
|
1123
|
-
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 };
|
|
1124
1376
|
//# sourceMappingURL=doug-williamson-ng-rhombus.mjs.map
|