@doug-williamson/ng-rhombus 2.0.0-beta.3 → 2.0.0-beta.4

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.
@@ -434,14 +434,14 @@ function ngRhombusTimestampToMillis(value) {
434
434
  }
435
435
  return null;
436
436
  }
437
- var ContentPillar;
438
- (function (ContentPillar) {
439
- ContentPillar["DeveloperLife"] = "Developer Life";
440
- ContentPillar["AngularWebEngineering"] = "Angular & Web Engineering";
441
- ContentPillar["GamingAsAnAdult"] = "Gaming as an Adult";
442
- ContentPillar["FatherhoodFamily"] = "Fatherhood & Family";
443
- ContentPillar["CareerBalanceIdentity"] = "Career, Balance & Identity";
444
- })(ContentPillar || (ContentPillar = {}));
437
+ var ContentDimension;
438
+ (function (ContentDimension) {
439
+ ContentDimension["DeveloperLife"] = "Developer Life";
440
+ ContentDimension["AngularWebEngineering"] = "Angular & Web Engineering";
441
+ ContentDimension["GamingAsAnAdult"] = "Gaming as an Adult";
442
+ ContentDimension["FatherhoodFamily"] = "Fatherhood & Family";
443
+ ContentDimension["CareerBalanceIdentity"] = "Career, Balance & Identity";
444
+ })(ContentDimension || (ContentDimension = {}));
445
445
  var BlogSeries;
446
446
  (function (BlogSeries) {
447
447
  BlogSeries["ThisWeekILearned"] = "This Week I Learned";
@@ -451,8 +451,6 @@ var BlogSeries;
451
451
  BlogSeries["TradeoffsTruths"] = "Tradeoffs & Truths";
452
452
  BlogSeries["FromTheEditor"] = "From The Editor";
453
453
  })(BlogSeries || (BlogSeries = {}));
454
- class IBlog {
455
- }
456
454
 
457
455
  class NgRhombusBlogListComponent {
458
456
  constructor() {
@@ -466,6 +464,19 @@ class NgRhombusBlogListComponent {
466
464
  #seo;
467
465
  #document;
468
466
  #site;
467
+ hasDimensions(post) {
468
+ return Boolean(post.dimensions?.length);
469
+ }
470
+ firstDimension(post) {
471
+ const dims = post.dimensions;
472
+ return dims && dims.length ? dims[0] : undefined;
473
+ }
474
+ hasTaxonomy(post) {
475
+ return this.hasDimensions(post) || Boolean(post.series) || Boolean(post.tags?.length);
476
+ }
477
+ getDisplayDate(post) {
478
+ return post.publishedAt ?? post.publishAt ?? post.createdAt ?? post.updatedAt;
479
+ }
469
480
  ngOnInit() {
470
481
  const origin = this.#site.origin ?? this.#document.location?.origin;
471
482
  const path = this.#document.location?.pathname ?? '/latest';
@@ -497,11 +508,11 @@ class NgRhombusBlogListComponent {
497
508
  this.goToRoute.emit(id);
498
509
  }
499
510
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
500
- 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" }] }); }
511
+ 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(getDisplayDate(blogPost)) | date: 'MMMM d, y' }}</p>\r\n\r\n\t\t@if (hasTaxonomy(blogPost)) {\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 (hasDimensions(blogPost)) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"primary-chips-theme\">Dimension: {{ firstDimension(blogPost)\r\n\t\t\t\t\t}}</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" }] }); }
501
512
  }
502
513
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, decorators: [{
503
514
  type: Component,
504
- 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"] }]
515
+ 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(getDisplayDate(blogPost)) | date: 'MMMM d, y' }}</p>\r\n\r\n\t\t@if (hasTaxonomy(blogPost)) {\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 (hasDimensions(blogPost)) {\r\n\t\t\t\t<mat-chip [highlighted]=\"true\" class=\"primary-chips-theme\">Dimension: {{ firstDimension(blogPost)\r\n\t\t\t\t\t}}</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"] }]
505
516
  }], propDecorators: { goToRoute: [{
506
517
  type: Output
507
518
  }], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
@@ -511,12 +522,17 @@ class NgRhombusBlogPostLatestComponent {
511
522
  this.blogPost = input(...(ngDevMode ? [undefined, { debugName: "blogPost" }] : []));
512
523
  this.timestampToMillis = ngRhombusTimestampToMillis;
513
524
  }
525
+ getDisplayDate(post) {
526
+ if (!post)
527
+ return null;
528
+ return post.publishedAt ?? post.publishAt ?? post.createdAt ?? post.updatedAt;
529
+ }
514
530
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
515
- 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" }] }); }
531
+ 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()?.excerpt ?? blogPost()?.description }}</span>\r\n <span matListItemLine>{{ timestampToMillis(getDisplayDate(blogPost())) | 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" }] }); }
516
532
  }
517
533
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, decorators: [{
518
534
  type: Component,
519
- 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"] }]
535
+ 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()?.excerpt ?? blogPost()?.description }}</span>\r\n <span matListItemLine>{{ timestampToMillis(getDisplayDate(blogPost())) | date: 'MMMM d, y' }}</span>\r\n </mat-list-item>\r\n</mat-nav-list>", styles: [".mdc-list-item{border-radius:0!important}\n"] }]
520
536
  }], propDecorators: { blogPost: [{ type: i0.Input, args: [{ isSignal: true, alias: "blogPost", required: false }] }] } });
521
537
 
522
538
  class NgRhombusBlogPostHelper {
@@ -532,6 +548,21 @@ class NgRhombusBlogPostHelper {
532
548
  const randomThreeDigitNumber = Math.floor(Math.random() * 1000);
533
549
  return `${slug}-${randomThreeDigitNumber}`;
534
550
  }
551
+ static buildSnippet(content, limit = 280) {
552
+ if (!content)
553
+ return '';
554
+ const firstParagraph = content.split(/\n\s*\n/)[0] ?? content;
555
+ const normalized = firstParagraph
556
+ .replace(/<[^>]+>/g, '')
557
+ .replace(/\s+/g, ' ')
558
+ .trim();
559
+ if (normalized.length <= limit)
560
+ return normalized;
561
+ const trimmed = normalized.slice(0, limit);
562
+ const lastSpace = trimmed.lastIndexOf(' ');
563
+ const safeCut = lastSpace > 0 ? trimmed.slice(0, lastSpace) : trimmed;
564
+ return `${safeCut.trim()}...`;
565
+ }
535
566
  }
536
567
 
537
568
  const NG_RHOMBUS_BLOG_ADAPTER = new InjectionToken('NG_RHOMBUS_BLOG_ADAPTER', {
@@ -564,6 +595,9 @@ const NG_RHOMBUS_BLOG_ADAPTER = new InjectionToken('NG_RHOMBUS_BLOG_ADAPTER', {
564
595
  updateBlogPost: async () => {
565
596
  throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
566
597
  },
598
+ updateQueueOrder: async () => {
599
+ throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
600
+ },
567
601
  deleteBlogPost: async () => {
568
602
  throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
569
603
  },
@@ -577,15 +611,71 @@ class NgRhombusBlogService {
577
611
  this.blogPosts = signal([], ...(ngDevMode ? [{ debugName: "blogPosts" }] : []));
578
612
  this.selectedBlogPost = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBlogPost" }] : []));
579
613
  }
614
+ normalizeDimensions(raw) {
615
+ const asArray = (value) => {
616
+ if (Array.isArray(value))
617
+ return value.map(v => `${v}`.trim()).filter(Boolean);
618
+ if (typeof value === 'string')
619
+ return [`${value}`.trim()].filter(Boolean);
620
+ return undefined;
621
+ };
622
+ const fromDimensions = asArray(raw?.dimensions);
623
+ const fromPillars = asArray(raw?.pillars);
624
+ const fromDimension = asArray(raw?.dimension ?? raw?.pillar);
625
+ const merged = [fromDimensions, fromPillars, fromDimension].filter(Boolean).flat();
626
+ return Array.from(new Set(merged));
627
+ }
580
628
  normalizeBlogPost(post) {
581
- const timestampMillis = ngRhombusTimestampToMillis(post.timestamp);
629
+ const coerceTimestamp = (value) => {
630
+ if (value == null)
631
+ return undefined;
632
+ const millis = ngRhombusTimestampToMillis(value);
633
+ if (millis != null)
634
+ return millis;
635
+ if (value instanceof Date)
636
+ return value;
637
+ if (typeof value === 'number' || typeof value === 'string')
638
+ return value;
639
+ const maybeSeconds = value;
640
+ if (typeof maybeSeconds.seconds === 'number')
641
+ return maybeSeconds;
642
+ const maybeToMillis = value;
643
+ if (typeof maybeToMillis.toMillis === 'function') {
644
+ const result = maybeToMillis.toMillis();
645
+ if (Number.isFinite(result))
646
+ return result;
647
+ }
648
+ return undefined;
649
+ };
650
+ const createdAt = coerceTimestamp(post.createdAt) ?? Date.now();
651
+ const updatedAt = coerceTimestamp(post.updatedAt) ?? createdAt;
652
+ const publishAt = coerceTimestamp(post.publishAt);
653
+ const publishedAt = post.publishedAt
654
+ ? coerceTimestamp(post.publishedAt)
655
+ : coerceTimestamp(post.timestamp);
656
+ const excerpt = post.excerpt ?? post.description ?? '';
657
+ const dimensions = this.normalizeDimensions(post);
658
+ const readingTime = post.readingTime ?? post.readTimeMinutes ?? this.calculateReadingTime(post.content);
659
+ const slug = post.slug ?? post.id;
660
+ const status = post.status ?? (publishedAt ? 'published' : 'draft');
582
661
  return {
583
662
  ...post,
584
- timestamp: timestampMillis ?? post.timestamp,
663
+ slug,
664
+ excerpt,
665
+ description: post.description ?? excerpt,
666
+ createdAt,
667
+ updatedAt,
668
+ publishAt,
669
+ publishedAt,
670
+ dimensions,
671
+ readingTime,
672
+ tags: post.tags ?? [],
673
+ status,
585
674
  };
586
675
  }
587
676
  async fetchBlogPosts() {
588
677
  const posts = (await this.blogAdapter.fetchBlogPosts()).map(p => this.normalizeBlogPost(p));
678
+ console.log('Fetched blog posts:', posts);
589
679
  this.blogPosts.set(posts);
590
680
  return posts;
591
681
  }
@@ -606,15 +696,46 @@ class NgRhombusBlogService {
606
696
  return normalized;
607
697
  }
608
698
  async createBlogPost(blogPost) {
609
- const id = NgRhombusBlogPostHelper.createSlug(blogPost.title);
699
+ const now = new Date();
700
+ const slug = blogPost.slug ?? NgRhombusBlogPostHelper.createSlug(blogPost.title);
701
+ const excerpt = blogPost.excerpt ?? blogPost.description ?? '';
702
+ const status = blogPost.status ?? 'draft';
703
+ const dimensions = this.normalizeDimensions(blogPost);
704
+ const readingTime = blogPost.readingTime ?? this.calculateReadingTime(blogPost.content);
705
+ const social = this.ensureSocialSnippet(blogPost.content, blogPost.social, status);
610
706
  await this.blogAdapter.createBlogPost({
611
707
  ...blogPost,
612
- id,
613
- timestamp: new Date(),
708
+ id: slug,
709
+ slug,
710
+ status,
711
+ excerpt,
712
+ description: blogPost.description ?? excerpt,
713
+ createdAt: blogPost.createdAt ?? now,
714
+ updatedAt: now,
715
+ publishAt: blogPost.publishAt,
716
+ publishedAt: blogPost.publishedAt,
717
+ queueOrder: blogPost.queueOrder,
718
+ dimensions,
719
+ readingTime,
720
+ social,
614
721
  });
615
722
  }
616
723
  async updateBlogPost(blogPost) {
617
- await this.blogAdapter.updateBlogPost(blogPost);
724
+ const now = new Date();
725
+ const status = blogPost.status ?? 'draft';
726
+ const excerpt = blogPost.excerpt ?? blogPost.description ?? '';
727
+ const dimensions = this.normalizeDimensions(blogPost);
728
+ const readingTime = blogPost.readingTime ?? this.calculateReadingTime(blogPost.content);
729
+ const social = this.ensureSocialSnippet(blogPost.content, blogPost.social, status);
730
+ await this.blogAdapter.updateBlogPost({
731
+ ...blogPost,
732
+ excerpt,
733
+ description: blogPost.description ?? excerpt,
734
+ updatedAt: now,
735
+ readingTime,
736
+ social,
737
+ dimensions,
738
+ });
618
739
  }
619
740
  deleteBlogPost(id) {
620
741
  return this.blogAdapter.deleteBlogPost(id);
@@ -654,12 +775,68 @@ class NgRhombusBlogService {
654
775
  nextOlder: result.nextOlder ? this.normalizeBlogPost(result.nextOlder) : undefined,
655
776
  };
656
777
  }
657
- calculateReadTimeMinutes(text) {
778
+ calculateReadingTime(text) {
658
779
  if (!text)
659
780
  return 1;
660
781
  const words = text.trim().split(/\s+/).length;
661
782
  return Math.max(1, Math.ceil(words / this.WORDS_PER_MINUTE));
662
783
  }
784
+ async saveQueueOrder(queue) {
785
+ const ordered = queue.map((post, idx) => ({
786
+ id: post.id,
787
+ queueOrder: idx + 1,
788
+ status: 'queued',
789
+ }));
790
+ if (this.blogAdapter.updateQueueOrder) {
791
+ await this.blogAdapter.updateQueueOrder(ordered);
792
+ return;
793
+ }
794
+ await Promise.all(ordered.map(update => this.blogAdapter.updateBlogPost(update)));
795
+ }
796
+ primaryDimension(post) {
797
+ if (!post)
798
+ return null;
799
+ const dims = this.normalizeDimensions(post);
800
+ return dims.length > 0 ? dims[0] : null;
801
+ }
802
+ dimensionsOf(post) {
803
+ if (!post)
804
+ return [];
805
+ return this.normalizeDimensions(post);
806
+ }
807
+ hasDimension(post, dimension) {
808
+ if (!dimension)
809
+ return false;
810
+ const dims = this.normalizeDimensions(post);
811
+ return dims.includes(dimension);
812
+ }
813
+ overlapDimensions(a, b) {
814
+ const dimsA = this.normalizeDimensions(a);
815
+ const dimsB = this.normalizeDimensions(b);
816
+ return dimsA.some(d => dimsB.includes(d));
817
+ }
818
+ async fetchRelatedPosts(current, opts = {}) {
819
+ const primaryLimit = opts.primaryLimit ?? 3;
820
+ const bridgeLimit = opts.bridgeLimit ?? 1;
821
+ const latest = await this.fetchLatestBlogPosts(Math.max(6, primaryLimit + bridgeLimit + 3));
822
+ const filtered = latest.filter(p => p.id !== current.id);
823
+ const primaryDimension = this.primaryDimension(current);
824
+ const primary = filtered.filter(p => this.overlapDimensions(p, current)).slice(0, primaryLimit);
825
+ let bridge = null;
826
+ if (bridgeLimit > 0) {
827
+ bridge = filtered.find(p => !this.hasDimension(p, primaryDimension)) ?? null;
828
+ }
829
+ return { primary, bridge };
830
+ }
831
+ ensureSocialSnippet(content, social, status) {
832
+ if (status !== 'draft')
833
+ return social;
834
+ const hasSnippet = social?.x && social.x.trim().length > 0;
835
+ if (hasSnippet)
836
+ return social;
837
+ const snippet = NgRhombusBlogPostHelper.buildSnippet(content, 280);
838
+ return { ...social, x: snippet };
839
+ }
663
840
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
664
841
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogService, providedIn: 'root' }); }
665
842
  }
@@ -676,11 +853,21 @@ class NgRhombusBlogTableComponent {
676
853
  this.dialog = inject(MatDialog);
677
854
  this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
678
855
  this.timestampToMillis = ngRhombusTimestampToMillis;
679
- this.displayedColumns = ['title', 'pillar', 'series', 'timestamp'];
856
+ this.displayedColumns = ['title', 'status', 'dimension', 'series', 'publishedAt'];
680
857
  this.detailColumns = ['detail'];
681
858
  this.isMainRow = (_index, row) => row.kind === 'main';
682
859
  this.isDetailRow = (_index, row) => row.kind === 'detail';
683
860
  }
861
+ hasDimensions(post) {
862
+ return Boolean(post.dimensions?.length);
863
+ }
864
+ firstDimension(post) {
865
+ const dims = post.dimensions;
866
+ return dims && dims.length ? dims[0] : undefined;
867
+ }
868
+ getDisplayDate(post) {
869
+ return post.publishedAt ?? post.publishAt ?? post.createdAt ?? post.updatedAt;
870
+ }
684
871
  tableData() {
685
872
  const posts = this.dataSource();
686
873
  if (!posts?.length)
@@ -697,11 +884,11 @@ class NgRhombusBlogTableComponent {
697
884
  this.deleteEvent.emit(blogPost);
698
885
  }
699
886
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
700
- 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" }] }); }
887
+ 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<!-- Status Column -->\r\n\t<ng-container matColumnDef=\"status\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Status</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t<mat-chip-set aria-label=\"Status\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"neutral-chips-theme\">{{ row.post.status\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t\t@if (row.post.queueOrder && row.post.status === 'queued') {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"secondary-chips-theme\" [highlighted]=\"true\">#{{ row.post.queueOrder\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</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Dimension Column -->\r\n\t<ng-container matColumnDef=\"dimension\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Dimension</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t@if (hasDimensions(row.post)) {\r\n\t\t\t<mat-chip-set aria-label=\"Dimension\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"primary-chips-theme\">{{ firstDimension(row.post)\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<!-- PublishedAt Column -->\r\n\t<ng-container matColumnDef=\"publishedAt\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Date</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">{{ timestampToMillis(getDisplayDate(row.post)) | 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" }] }); }
701
888
  }
702
889
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, decorators: [{
703
890
  type: Component,
704
- 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"] }]
891
+ 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<!-- Status Column -->\r\n\t<ng-container matColumnDef=\"status\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Status</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t<mat-chip-set aria-label=\"Status\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"neutral-chips-theme\">{{ row.post.status\r\n\t\t\t\t\t}}</mat-chip>\r\n\t\t\t\t@if (row.post.queueOrder && row.post.status === 'queued') {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"secondary-chips-theme\" [highlighted]=\"true\">#{{ row.post.queueOrder\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</td>\r\n\t</ng-container>\r\n\r\n\t<!-- Dimension Column -->\r\n\t<ng-container matColumnDef=\"dimension\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Dimension</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">\r\n\t\t\t@if (hasDimensions(row.post)) {\r\n\t\t\t<mat-chip-set aria-label=\"Dimension\" role=\"list\">\r\n\t\t\t\t<mat-chip role=\"listitem\" [highlighted]=\"true\" class=\"primary-chips-theme\">{{ firstDimension(row.post)\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<!-- PublishedAt Column -->\r\n\t<ng-container matColumnDef=\"publishedAt\">\r\n\t\t<th mat-header-cell *matHeaderCellDef>Date</th>\r\n\t\t<td mat-cell *matCellDef=\"let row\">{{ timestampToMillis(getDisplayDate(row.post)) | 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"] }]
705
892
  }], propDecorators: { editEvent: [{
706
893
  type: Output
707
894
  }], deleteEvent: [{
@@ -851,7 +1038,11 @@ class NgRhombusBlogAddEditComponent {
851
1038
  this.cancelEvent = output();
852
1039
  this.submitEvent = output();
853
1040
  this.contentData = signal('', ...(ngDevMode ? [{ debugName: "contentData" }] : []));
854
- this.pillars = Object.values(ContentPillar);
1041
+ this.dimensionRequiredValidator = (control) => {
1042
+ const value = control.value;
1043
+ return value && value.length > 0 ? null : { required: true };
1044
+ };
1045
+ this.dimensions = Object.values(ContentDimension);
855
1046
  this.seriesOptions = Object.values(BlogSeries);
856
1047
  this.dialog = inject(MatDialog);
857
1048
  this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
@@ -877,7 +1068,7 @@ class NgRhombusBlogAddEditComponent {
877
1068
  if (this.blogPost()) {
878
1069
  this.blogPostForm.patchValue({
879
1070
  title: this.blogPost()?.title,
880
- description: this.blogPost()?.description,
1071
+ excerpt: this.blogPost()?.excerpt ?? this.blogPost()?.description,
881
1072
  thumbnail: this.blogPost()?.thumbnail,
882
1073
  content: this.blogPost()?.content,
883
1074
  });
@@ -888,31 +1079,29 @@ class NgRhombusBlogAddEditComponent {
888
1079
  ngOnInit() {
889
1080
  this.blogPostForm = this.formBuilder.group({
890
1081
  title: [this.blogPost()?.title, Validators.required],
891
- description: [this.blogPost()?.description, Validators.required],
1082
+ excerpt: [this.blogPost()?.excerpt ?? this.blogPost()?.description, Validators.required],
892
1083
  thumbnail: [this.blogPost()?.thumbnail, Validators.required],
893
1084
  content: [this.blogPost()?.content, Validators.required],
894
1085
  // creator system
895
- pillar: [this.blogPost()?.pillar ?? null, Validators.required],
1086
+ dimensions: [this.blogService.dimensionsOf(this.blogPost()) ?? [], this.dimensionRequiredValidator],
896
1087
  series: [this.blogPost()?.series ?? null, Validators.required],
897
- tagsCsv: [(this.blogPost()?.tags ?? []).join(', '), Validators.required],
1088
+ tagsCsv: [(this.blogPost()?.tags ?? []).join(', ')],
898
1089
  });
899
1090
  this.applyCreatorSystemRules();
900
1091
  this.blogPostForm.get('series')?.valueChanges.subscribe(() => this.applyCreatorSystemRules());
901
1092
  }
902
1093
  applyCreatorSystemRules() {
903
- const pillarControl = this.blogPostForm.get('pillar');
1094
+ const dimensionControl = this.blogPostForm.get('dimensions');
904
1095
  const seriesValue = this.blogPostForm.get('series')?.value;
905
- if (!pillarControl)
1096
+ if (!dimensionControl)
906
1097
  return;
907
1098
  if (seriesValue === BlogSeries.FromTheEditor) {
908
- pillarControl.clearValidators();
909
- if (pillarControl.value !== null)
910
- pillarControl.setValue(null);
1099
+ dimensionControl.setValidators([this.dimensionRequiredValidator]);
911
1100
  }
912
1101
  else {
913
- pillarControl.setValidators([Validators.required]);
1102
+ dimensionControl.clearValidators();
914
1103
  }
915
- pillarControl.updateValueAndValidity({ emitEvent: false });
1104
+ dimensionControl.updateValueAndValidity({ emitEvent: false });
916
1105
  }
917
1106
  onContentChange() {
918
1107
  this.contentData.set(this.blogPostForm.getRawValue().content);
@@ -920,16 +1109,21 @@ class NgRhombusBlogAddEditComponent {
920
1109
  get thumbnailSource() {
921
1110
  return this.blogPostForm.getRawValue().thumbnail;
922
1111
  }
923
- onFileUploaded(downloadUrl) {
924
- this.blogPostForm.patchValue({
925
- thumbnail: downloadUrl
926
- });
1112
+ async onFileUploaded(downloadUrl) {
1113
+ const current = this.blogPostForm.getRawValue().thumbnail;
1114
+ if (current && current !== downloadUrl) {
1115
+ await this.thumbnailService.deleteImage(current);
1116
+ }
1117
+ this.blogPostForm.patchValue({ thumbnail: downloadUrl });
927
1118
  this.blogPostForm.markAsDirty();
928
1119
  }
929
- onFileDeleted() {
930
- this.blogPostForm.patchValue({
931
- thumbnail: ''
932
- });
1120
+ async onFileDeleted() {
1121
+ const current = this.blogPostForm.getRawValue().thumbnail;
1122
+ if (current) {
1123
+ await this.thumbnailService.deleteImage(current);
1124
+ }
1125
+ this.blogPostForm.patchValue({ thumbnail: '' });
1126
+ this.blogPostForm.markAsDirty();
933
1127
  }
934
1128
  onCancelClick() {
935
1129
  if (this.blogPost()) {
@@ -982,21 +1176,24 @@ class NgRhombusBlogAddEditComponent {
982
1176
  return;
983
1177
  const raw = this.blogPostForm.getRawValue();
984
1178
  const tags = this.parseTags(raw.tagsCsv);
985
- if (tags.length < 3 || tags.length > 6) {
986
- // keep it simple; if you prefer, set a form error and show mat-error
987
- return;
988
- }
989
- const submittedBlogPost = new IBlog();
990
- if (this.blogPost())
991
- submittedBlogPost.id = this.blogPost().id;
992
- submittedBlogPost.title = raw.title;
993
- submittedBlogPost.description = raw.description;
994
- submittedBlogPost.thumbnail = raw.thumbnail;
995
- submittedBlogPost.content = raw.content;
996
- submittedBlogPost.pillar = raw.pillar;
997
- submittedBlogPost.series = raw.series;
998
- submittedBlogPost.tags = tags;
999
- submittedBlogPost.readTimeMinutes = this.blogService.calculateReadTimeMinutes(raw.content);
1179
+ const slug = this.blogPost()?.slug ?? NgRhombusBlogPostHelper.createSlug(raw.title);
1180
+ const id = this.blogPost()?.id ?? slug;
1181
+ const submittedBlogPost = {
1182
+ id,
1183
+ slug,
1184
+ title: raw.title,
1185
+ excerpt: raw.excerpt,
1186
+ description: raw.excerpt,
1187
+ thumbnail: raw.thumbnail,
1188
+ content: raw.content,
1189
+ dimensions: raw.dimensions ?? [],
1190
+ series: raw.series,
1191
+ tags,
1192
+ readingTime: this.blogService.calculateReadingTime(raw.content),
1193
+ status: this.blogPost()?.status ?? 'draft',
1194
+ createdAt: this.blogPost()?.createdAt ?? new Date(),
1195
+ updatedAt: new Date(),
1196
+ };
1000
1197
  this.submitEvent.emit(submittedBlogPost);
1001
1198
  }
1002
1199
  parseTags(csv) {
@@ -1008,11 +1205,11 @@ class NgRhombusBlogAddEditComponent {
1008
1205
  return Array.from(new Set(tags));
1009
1206
  }
1010
1207
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1011
- 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: "blogPostPreview", first: true, predicate: ["blogPost"], descendants: true }], ngImport: i0, template: "<div class=\"h-full flex flex-row overflow-hidden min-h-0\">\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto min-h-0\">\r\n\t\t<form novalidate class=\"flex flex-col gap-4 min-h-0\" [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=\"w-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required class=\"content-textarea\" rows=\"12\" formControlName=\"content\"\r\n\t\t\t\t\t(input)=\"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<ng-rhombus-markdown [classes]=\"{ '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}.content-textarea{resize:vertical}\n"], dependencies: [{ 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: "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: "component", type: ThumbnailControlComponent, selector: "ng-rhombus-thumbnail-control", inputs: ["width", "height", "thumbnailSrc", "disabled", "displayMode"], outputs: ["onFileUploaded", "onFileDeleted"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: NgRhombusMarkdownComponent, selector: "ng-rhombus-markdown", inputs: ["data", "classes"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6$1.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: i6$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] }); }
1208
+ 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: "blogPostPreview", first: true, predicate: ["blogPost"], descendants: true }], ngImport: i0, template: "<div class=\"h-full flex flex-row overflow-hidden min-h-0\">\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto min-h-0\">\r\n\t\t<form novalidate class=\"flex flex-col gap-4 min-h-0\" [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>Excerpt</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Short summary\" formControlName=\"excerpt\">\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'\" (onFileUploaded)=\"onFileUploaded($event)\"\r\n\t\t\t\t(onFileDeleted)=\"onFileDeleted()\"></ng-rhombus-thumbnail-control>\r\n\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required class=\"content-textarea\" rows=\"12\" formControlName=\"content\"\r\n\t\t\t\t\t(input)=\"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>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\r\n\t\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t\t<mat-label>Dimensions (pick the modes)</mat-label>\r\n\t\t\t\t\t<mat-select formControlName=\"dimensions\" multiple>\r\n\t\t\t\t\t\t@for (p of dimensions; 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\t<mat-hint>Choose one or more cognitive modes for this piece.</mat-hint>\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 (optional)</mat-label>\r\n\t\t\t\t<input matInput formControlName=\"tagsCsv\" placeholder=\"Comma-separated tags\">\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().excerpt }}</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<ng-rhombus-markdown [classes]=\"{ '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}.content-textarea{resize:vertical}\n"], dependencies: [{ 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: "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: "component", type: ThumbnailControlComponent, selector: "ng-rhombus-thumbnail-control", inputs: ["width", "height", "thumbnailSrc", "disabled", "displayMode"], outputs: ["onFileUploaded", "onFileDeleted"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: NgRhombusMarkdownComponent, selector: "ng-rhombus-markdown", inputs: ["data", "classes"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6$1.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: i6$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] }); }
1012
1209
  }
1013
1210
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, decorators: [{
1014
1211
  type: Component,
1015
- args: [{ selector: 'ng-rhombus-blog-form', imports: [MatListModule, MatDividerModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatInputModule, MatProgressSpinnerModule, MatSidenavModule, ReactiveFormsModule, ThumbnailControlComponent, MatIconModule, NgRhombusMarkdownComponent, MatSelectModule], template: "<div class=\"h-full flex flex-row overflow-hidden min-h-0\">\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto min-h-0\">\r\n\t\t<form novalidate class=\"flex flex-col gap-4 min-h-0\" [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=\"w-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required class=\"content-textarea\" rows=\"12\" formControlName=\"content\"\r\n\t\t\t\t\t(input)=\"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<ng-rhombus-markdown [classes]=\"{ '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}.content-textarea{resize:vertical}\n"] }]
1212
+ args: [{ selector: 'ng-rhombus-blog-form', imports: [MatListModule, MatDividerModule, MatButtonModule, MatCardModule, MatFormFieldModule, MatInputModule, MatProgressSpinnerModule, MatSidenavModule, ReactiveFormsModule, ThumbnailControlComponent, MatIconModule, NgRhombusMarkdownComponent, MatSelectModule], template: "<div class=\"h-full flex flex-row overflow-hidden min-h-0\">\r\n\t<div class=\"flex flex-col w-1/2 p-4 overflow-y-auto min-h-0\">\r\n\t\t<form novalidate class=\"flex flex-col gap-4 min-h-0\" [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>Excerpt</mat-label>\r\n\t\t\t\t<input matInput placeholder=\"Short summary\" formControlName=\"excerpt\">\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'\" (onFileUploaded)=\"onFileUploaded($event)\"\r\n\t\t\t\t(onFileDeleted)=\"onFileDeleted()\"></ng-rhombus-thumbnail-control>\r\n\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t<mat-label>Content</mat-label>\r\n\t\t\t\t<textarea matInput required class=\"content-textarea\" rows=\"12\" formControlName=\"content\"\r\n\t\t\t\t\t(input)=\"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>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\r\n\t\t\t\t<mat-form-field class=\"w-full\">\r\n\t\t\t\t\t<mat-label>Dimensions (pick the modes)</mat-label>\r\n\t\t\t\t\t<mat-select formControlName=\"dimensions\" multiple>\r\n\t\t\t\t\t\t@for (p of dimensions; 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\t<mat-hint>Choose one or more cognitive modes for this piece.</mat-hint>\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 (optional)</mat-label>\r\n\t\t\t\t<input matInput formControlName=\"tagsCsv\" placeholder=\"Comma-separated tags\">\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().excerpt }}</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<ng-rhombus-markdown [classes]=\"{ '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}.content-textarea{resize:vertical}\n"] }]
1016
1213
  }], 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"] }], blogPostPreview: [{
1017
1214
  type: ViewChild,
1018
1215
  args: ['blogPost']
@@ -1030,11 +1227,16 @@ class NgRhombusBlogPostComponent {
1030
1227
  this.faLinkedin = faLinkedin;
1031
1228
  }
1032
1229
  #doc;
1230
+ getDisplayDate(post) {
1231
+ if (!post)
1232
+ return null;
1233
+ return post.publishedAt ?? post.publishAt ?? post.createdAt ?? post.updatedAt;
1234
+ }
1033
1235
  ngOnInit() {
1034
1236
  }
1035
1237
  // Build absolute URL for sharing
1036
1238
  getShareUrl() {
1037
- const id = this.dataSource()?.id;
1239
+ const id = this.dataSource()?.slug ?? this.dataSource()?.id;
1038
1240
  const origin = this.#doc?.location?.origin ?? '';
1039
1241
  const path = id ? `/blog/${id}` : this.#doc?.location?.pathname ?? '/blog';
1040
1242
  return `${origin}${path}`;
@@ -1066,24 +1268,39 @@ class NgRhombusBlogPostComponent {
1066
1268
  .map(t => t.trim())
1067
1269
  .filter(Boolean);
1068
1270
  }
1069
- get contentPillar() {
1070
- return (this.dataSource()?.pillar ?? null) || null;
1271
+ get contentDimensions() {
1272
+ return this.normalizeDimensions(this.dataSource());
1071
1273
  }
1072
1274
  get series() {
1073
1275
  return (this.dataSource()?.series ?? null) || null;
1074
1276
  }
1075
1277
  get tags() {
1076
- return this.normalizeTags(this.dataSource()?.tags ?? null);
1278
+ return this.normalizeTags(this.dataSource()?.tags ?? undefined);
1077
1279
  }
1078
1280
  get hasTaxonomy() {
1079
- return !!this.contentPillar || !!this.series || this.tags.length > 0;
1281
+ return this.contentDimensions.length > 0 || !!this.series || this.tags.length > 0;
1282
+ }
1283
+ normalizeDimensions(post) {
1284
+ if (!post)
1285
+ return [];
1286
+ const asArray = (value) => {
1287
+ if (Array.isArray(value))
1288
+ return value.map(v => `${v}`.trim()).filter(Boolean);
1289
+ if (typeof value === 'string')
1290
+ return [`${value}`.trim()].filter(Boolean);
1291
+ return undefined;
1292
+ };
1293
+ const dims = [asArray(post.dimensions), asArray(post.pillars), asArray(post.dimension ?? post.pillar)]
1294
+ .filter(Boolean)
1295
+ .flat();
1296
+ return Array.from(new Set(dims));
1080
1297
  }
1081
1298
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1082
- 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&middot;\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<ng-rhombus-markdown\r\n\t\t\t[classes]=\"{ 'prose': true, 'dark:prose-invert': themeService.isDarkMode() }\"\r\n\t\t\t[data]=\"dataSource()?.content\"\r\n\t\t/>\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: "component", type: NgRhombusMarkdownComponent, selector: "ng-rhombus-markdown", inputs: ["data", "classes"] }, { 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" }] }); }
1299
+ 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 (contentDimensions.length || series) {\r\n\t\t\t<mat-chip-set aria-label=\"Post taxonomy\" role=\"list\">\r\n\t\t\t\t@for (d of contentDimensions; track d) {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"primary-chips-theme\" [highlighted]=\"true\">Dimension: {{ d }}</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()?.readingTime }}\r\n\t\t\t\t{{ dataSource()?.readingTime === 1 ? 'min' : 'mins' }}\r\n\t\t\t\t&middot;\r\n\t\t\t\t{{ timestampToMillis(getDisplayDate(dataSource())) | 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<ng-rhombus-markdown [classes]=\"{ '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: "component", type: NgRhombusMarkdownComponent, selector: "ng-rhombus-markdown", inputs: ["data", "classes"] }, { 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" }] }); }
1083
1300
  }
1084
1301
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, decorators: [{
1085
1302
  type: Component,
1086
- args: [{ selector: 'ng-rhombus-blog-post', imports: [DatePipe, NgRhombusMarkdownComponent, 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&middot;\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<ng-rhombus-markdown\r\n\t\t\t[classes]=\"{ 'prose': true, 'dark:prose-invert': themeService.isDarkMode() }\"\r\n\t\t\t[data]=\"dataSource()?.content\"\r\n\t\t/>\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"] }]
1303
+ args: [{ selector: 'ng-rhombus-blog-post', imports: [DatePipe, NgRhombusMarkdownComponent, 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 (contentDimensions.length || series) {\r\n\t\t\t<mat-chip-set aria-label=\"Post taxonomy\" role=\"list\">\r\n\t\t\t\t@for (d of contentDimensions; track d) {\r\n\t\t\t\t<mat-chip role=\"listitem\" class=\"primary-chips-theme\" [highlighted]=\"true\">Dimension: {{ d }}</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()?.readingTime }}\r\n\t\t\t\t{{ dataSource()?.readingTime === 1 ? 'min' : 'mins' }}\r\n\t\t\t\t&middot;\r\n\t\t\t\t{{ timestampToMillis(getDisplayDate(dataSource())) | 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<ng-rhombus-markdown [classes]=\"{ '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"] }]
1087
1304
  }], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
1088
1305
 
1089
1306
  class NgRhombusBlogDeletePostComponent {
@@ -1406,5 +1623,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
1406
1623
  * Generated bundle index. Do not edit.
1407
1624
  */
1408
1625
 
1409
- 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_SITE_METADATA, NG_RHOMBUS_SOCIALS_ADAPTER, NgRhombusBlogAddEditComponent, NgRhombusBlogDeletePostComponent, NgRhombusBlogListComponent, NgRhombusBlogPostComponent, NgRhombusBlogPostLatestComponent, NgRhombusBlogPostThumbnailService, NgRhombusBlogService, NgRhombusBlogTableComponent, NgRhombusHomeAdminComponent, NgRhombusHomeService, NgRhombusLoginComponent, NgRhombusMarkdownComponent, NgRhombusPageComponent, NgRhombusSocialsListComponent, NgRhombusSocialsService, NgRhombusSocialsTableComponent, NgRhombusSpinnerComponent, NgRhombusWrapperComponent, SocialsSource, ThemeEnum, WrapperService, ngRhombusTimestampToMillis };
1626
+ export { BlogSeries, ContentDimension, IHome, NG_RHOMBUS_AUTH_ADAPTER, NG_RHOMBUS_BLOG_ADAPTER, NG_RHOMBUS_BLOG_THUMBNAIL_ADAPTER, NG_RHOMBUS_HOME_ADAPTER, NG_RHOMBUS_SEO_ADAPTER, NG_RHOMBUS_SITE_METADATA, NG_RHOMBUS_SOCIALS_ADAPTER, NgRhombusBlogAddEditComponent, NgRhombusBlogDeletePostComponent, NgRhombusBlogListComponent, NgRhombusBlogPostComponent, NgRhombusBlogPostLatestComponent, NgRhombusBlogPostThumbnailService, NgRhombusBlogService, NgRhombusBlogTableComponent, NgRhombusHomeAdminComponent, NgRhombusHomeService, NgRhombusLoginComponent, NgRhombusMarkdownComponent, NgRhombusPageComponent, NgRhombusSocialsListComponent, NgRhombusSocialsService, NgRhombusSocialsTableComponent, NgRhombusSpinnerComponent, NgRhombusWrapperComponent, SocialsSource, ThemeEnum, WrapperService, ngRhombusTimestampToMillis };
1410
1627
  //# sourceMappingURL=doug-williamson-ng-rhombus.mjs.map