@doug-williamson/ng-rhombus 2.0.0-beta.2 → 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.
|
@@ -401,6 +401,11 @@ const NG_RHOMBUS_SEO_ADAPTER = new InjectionToken('NG_RHOMBUS_SEO_ADAPTER', {
|
|
|
401
401
|
}),
|
|
402
402
|
});
|
|
403
403
|
|
|
404
|
+
const NG_RHOMBUS_SITE_METADATA = new InjectionToken('NG_RHOMBUS_SITE_METADATA', {
|
|
405
|
+
providedIn: 'root',
|
|
406
|
+
factory: () => ({}),
|
|
407
|
+
});
|
|
408
|
+
|
|
404
409
|
/*
|
|
405
410
|
* Public API Surface of @doug-williamson/ng-rhombus/shell
|
|
406
411
|
*/
|
|
@@ -429,14 +434,14 @@ function ngRhombusTimestampToMillis(value) {
|
|
|
429
434
|
}
|
|
430
435
|
return null;
|
|
431
436
|
}
|
|
432
|
-
var
|
|
433
|
-
(function (
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
})(
|
|
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 = {}));
|
|
440
445
|
var BlogSeries;
|
|
441
446
|
(function (BlogSeries) {
|
|
442
447
|
BlogSeries["ThisWeekILearned"] = "This Week I Learned";
|
|
@@ -446,48 +451,68 @@ var BlogSeries;
|
|
|
446
451
|
BlogSeries["TradeoffsTruths"] = "Tradeoffs & Truths";
|
|
447
452
|
BlogSeries["FromTheEditor"] = "From The Editor";
|
|
448
453
|
})(BlogSeries || (BlogSeries = {}));
|
|
449
|
-
class IBlog {
|
|
450
|
-
}
|
|
451
454
|
|
|
452
455
|
class NgRhombusBlogListComponent {
|
|
453
456
|
constructor() {
|
|
454
457
|
this.goToRoute = new EventEmitter();
|
|
455
458
|
this.#seo = inject(NG_RHOMBUS_SEO_ADAPTER);
|
|
456
|
-
this.#platformId = inject(PLATFORM_ID);
|
|
457
459
|
this.#document = inject(DOCUMENT);
|
|
460
|
+
this.#site = inject(NG_RHOMBUS_SITE_METADATA);
|
|
458
461
|
this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
|
|
459
462
|
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
460
463
|
}
|
|
461
464
|
#seo;
|
|
462
|
-
#platformId;
|
|
463
465
|
#document;
|
|
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
|
+
}
|
|
464
480
|
ngOnInit() {
|
|
465
|
-
const origin =
|
|
466
|
-
const
|
|
481
|
+
const origin = this.#site.origin ?? this.#document.location?.origin;
|
|
482
|
+
const path = this.#document.location?.pathname ?? '/latest';
|
|
483
|
+
const pageUrl = origin ? new URL(path, origin).toString() : path;
|
|
484
|
+
const imagePath = '/favicon.ico';
|
|
485
|
+
const imageUrl = origin ? new URL(imagePath, origin).toString() : imagePath;
|
|
486
|
+
const siteName = this.#site.siteName ?? 'Rhombus Software';
|
|
467
487
|
// Document title
|
|
468
488
|
this.#seo.setTitle('Blog - Doug Williamson');
|
|
469
489
|
// Basic meta
|
|
470
490
|
this.#seo.updateTag({ name: 'description', content: 'This is my personal collection of blog posts.' });
|
|
471
491
|
// Open Graph
|
|
472
|
-
this.#seo.updateTag({ property: 'og:type', content: '
|
|
492
|
+
this.#seo.updateTag({ property: 'og:type', content: 'website' });
|
|
493
|
+
this.#seo.updateTag({ property: 'og:site_name', content: siteName });
|
|
473
494
|
this.#seo.updateTag({ property: 'og:title', content: 'Blog - Doug Williamson' });
|
|
474
495
|
this.#seo.updateTag({ property: 'og:description', content: 'This is my personal collection of blog posts.' });
|
|
496
|
+
this.#seo.updateTag({ property: 'og:url', content: pageUrl });
|
|
475
497
|
this.#seo.updateTag({ property: 'og:image', content: imageUrl });
|
|
476
498
|
// Twitter Card
|
|
477
499
|
this.#seo.updateTag({ name: 'twitter:card', content: 'summary' });
|
|
500
|
+
if (this.#site.twitterSite)
|
|
501
|
+
this.#seo.updateTag({ name: 'twitter:site', content: this.#site.twitterSite });
|
|
478
502
|
this.#seo.updateTag({ name: 'twitter:title', content: 'Blog - Doug Williamson' });
|
|
479
503
|
this.#seo.updateTag({ name: 'twitter:description', content: 'This is my personal collection of blog posts.' });
|
|
504
|
+
this.#seo.updateTag({ name: 'twitter:url', content: pageUrl });
|
|
480
505
|
this.#seo.updateTag({ name: 'twitter:image', content: imageUrl });
|
|
481
506
|
}
|
|
482
507
|
goToBlogPost(id) {
|
|
483
508
|
this.goToRoute.emit(id);
|
|
484
509
|
}
|
|
485
510
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
486
|
-
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
|
|
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" }] }); }
|
|
487
512
|
}
|
|
488
513
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogListComponent, decorators: [{
|
|
489
514
|
type: Component,
|
|
490
|
-
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
|
|
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"] }]
|
|
491
516
|
}], propDecorators: { goToRoute: [{
|
|
492
517
|
type: Output
|
|
493
518
|
}], dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
@@ -497,12 +522,17 @@ class NgRhombusBlogPostLatestComponent {
|
|
|
497
522
|
this.blogPost = input(...(ngDevMode ? [undefined, { debugName: "blogPost" }] : []));
|
|
498
523
|
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
499
524
|
}
|
|
525
|
+
getDisplayDate(post) {
|
|
526
|
+
if (!post)
|
|
527
|
+
return null;
|
|
528
|
+
return post.publishedAt ?? post.publishAt ?? post.createdAt ?? post.updatedAt;
|
|
529
|
+
}
|
|
500
530
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
501
|
-
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()
|
|
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" }] }); }
|
|
502
532
|
}
|
|
503
533
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostLatestComponent, decorators: [{
|
|
504
534
|
type: Component,
|
|
505
|
-
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()
|
|
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"] }]
|
|
506
536
|
}], propDecorators: { blogPost: [{ type: i0.Input, args: [{ isSignal: true, alias: "blogPost", required: false }] }] } });
|
|
507
537
|
|
|
508
538
|
class NgRhombusBlogPostHelper {
|
|
@@ -518,6 +548,21 @@ class NgRhombusBlogPostHelper {
|
|
|
518
548
|
const randomThreeDigitNumber = Math.floor(Math.random() * 1000);
|
|
519
549
|
return `${slug}-${randomThreeDigitNumber}`;
|
|
520
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
|
+
}
|
|
521
566
|
}
|
|
522
567
|
|
|
523
568
|
const NG_RHOMBUS_BLOG_ADAPTER = new InjectionToken('NG_RHOMBUS_BLOG_ADAPTER', {
|
|
@@ -550,6 +595,9 @@ const NG_RHOMBUS_BLOG_ADAPTER = new InjectionToken('NG_RHOMBUS_BLOG_ADAPTER', {
|
|
|
550
595
|
updateBlogPost: async () => {
|
|
551
596
|
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
552
597
|
},
|
|
598
|
+
updateQueueOrder: async () => {
|
|
599
|
+
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
600
|
+
},
|
|
553
601
|
deleteBlogPost: async () => {
|
|
554
602
|
throw new Error('NG_RHOMBUS_BLOG_ADAPTER is not provided.');
|
|
555
603
|
},
|
|
@@ -563,15 +611,71 @@ class NgRhombusBlogService {
|
|
|
563
611
|
this.blogPosts = signal([], ...(ngDevMode ? [{ debugName: "blogPosts" }] : []));
|
|
564
612
|
this.selectedBlogPost = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBlogPost" }] : []));
|
|
565
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
|
+
}
|
|
566
628
|
normalizeBlogPost(post) {
|
|
567
|
-
const
|
|
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');
|
|
568
661
|
return {
|
|
569
662
|
...post,
|
|
570
|
-
|
|
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,
|
|
571
674
|
};
|
|
572
675
|
}
|
|
573
676
|
async fetchBlogPosts() {
|
|
574
677
|
const posts = (await this.blogAdapter.fetchBlogPosts()).map(p => this.normalizeBlogPost(p));
|
|
678
|
+
console.log('Fetched blog posts:', posts);
|
|
575
679
|
this.blogPosts.set(posts);
|
|
576
680
|
return posts;
|
|
577
681
|
}
|
|
@@ -592,15 +696,46 @@ class NgRhombusBlogService {
|
|
|
592
696
|
return normalized;
|
|
593
697
|
}
|
|
594
698
|
async createBlogPost(blogPost) {
|
|
595
|
-
const
|
|
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);
|
|
596
706
|
await this.blogAdapter.createBlogPost({
|
|
597
707
|
...blogPost,
|
|
598
|
-
id,
|
|
599
|
-
|
|
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,
|
|
600
721
|
});
|
|
601
722
|
}
|
|
602
723
|
async updateBlogPost(blogPost) {
|
|
603
|
-
|
|
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
|
+
});
|
|
604
739
|
}
|
|
605
740
|
deleteBlogPost(id) {
|
|
606
741
|
return this.blogAdapter.deleteBlogPost(id);
|
|
@@ -640,12 +775,68 @@ class NgRhombusBlogService {
|
|
|
640
775
|
nextOlder: result.nextOlder ? this.normalizeBlogPost(result.nextOlder) : undefined,
|
|
641
776
|
};
|
|
642
777
|
}
|
|
643
|
-
|
|
778
|
+
calculateReadingTime(text) {
|
|
644
779
|
if (!text)
|
|
645
780
|
return 1;
|
|
646
781
|
const words = text.trim().split(/\s+/).length;
|
|
647
782
|
return Math.max(1, Math.ceil(words / this.WORDS_PER_MINUTE));
|
|
648
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
|
+
}
|
|
649
840
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
650
841
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogService, providedIn: 'root' }); }
|
|
651
842
|
}
|
|
@@ -662,11 +853,21 @@ class NgRhombusBlogTableComponent {
|
|
|
662
853
|
this.dialog = inject(MatDialog);
|
|
663
854
|
this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
|
|
664
855
|
this.timestampToMillis = ngRhombusTimestampToMillis;
|
|
665
|
-
this.displayedColumns = ['title', '
|
|
856
|
+
this.displayedColumns = ['title', 'status', 'dimension', 'series', 'publishedAt'];
|
|
666
857
|
this.detailColumns = ['detail'];
|
|
667
858
|
this.isMainRow = (_index, row) => row.kind === 'main';
|
|
668
859
|
this.isDetailRow = (_index, row) => row.kind === 'detail';
|
|
669
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
|
+
}
|
|
670
871
|
tableData() {
|
|
671
872
|
const posts = this.dataSource();
|
|
672
873
|
if (!posts?.length)
|
|
@@ -683,11 +884,11 @@ class NgRhombusBlogTableComponent {
|
|
|
683
884
|
this.deleteEvent.emit(blogPost);
|
|
684
885
|
}
|
|
685
886
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
686
|
-
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<!--
|
|
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" }] }); }
|
|
687
888
|
}
|
|
688
889
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogTableComponent, decorators: [{
|
|
689
890
|
type: Component,
|
|
690
|
-
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<!--
|
|
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"] }]
|
|
691
892
|
}], propDecorators: { editEvent: [{
|
|
692
893
|
type: Output
|
|
693
894
|
}], deleteEvent: [{
|
|
@@ -837,7 +1038,11 @@ class NgRhombusBlogAddEditComponent {
|
|
|
837
1038
|
this.cancelEvent = output();
|
|
838
1039
|
this.submitEvent = output();
|
|
839
1040
|
this.contentData = signal('', ...(ngDevMode ? [{ debugName: "contentData" }] : []));
|
|
840
|
-
this.
|
|
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);
|
|
841
1046
|
this.seriesOptions = Object.values(BlogSeries);
|
|
842
1047
|
this.dialog = inject(MatDialog);
|
|
843
1048
|
this.thumbnailService = inject(NgRhombusBlogPostThumbnailService);
|
|
@@ -863,7 +1068,7 @@ class NgRhombusBlogAddEditComponent {
|
|
|
863
1068
|
if (this.blogPost()) {
|
|
864
1069
|
this.blogPostForm.patchValue({
|
|
865
1070
|
title: this.blogPost()?.title,
|
|
866
|
-
|
|
1071
|
+
excerpt: this.blogPost()?.excerpt ?? this.blogPost()?.description,
|
|
867
1072
|
thumbnail: this.blogPost()?.thumbnail,
|
|
868
1073
|
content: this.blogPost()?.content,
|
|
869
1074
|
});
|
|
@@ -874,31 +1079,29 @@ class NgRhombusBlogAddEditComponent {
|
|
|
874
1079
|
ngOnInit() {
|
|
875
1080
|
this.blogPostForm = this.formBuilder.group({
|
|
876
1081
|
title: [this.blogPost()?.title, Validators.required],
|
|
877
|
-
|
|
1082
|
+
excerpt: [this.blogPost()?.excerpt ?? this.blogPost()?.description, Validators.required],
|
|
878
1083
|
thumbnail: [this.blogPost()?.thumbnail, Validators.required],
|
|
879
1084
|
content: [this.blogPost()?.content, Validators.required],
|
|
880
1085
|
// creator system
|
|
881
|
-
|
|
1086
|
+
dimensions: [this.blogService.dimensionsOf(this.blogPost()) ?? [], this.dimensionRequiredValidator],
|
|
882
1087
|
series: [this.blogPost()?.series ?? null, Validators.required],
|
|
883
|
-
tagsCsv: [(this.blogPost()?.tags ?? []).join(', ')
|
|
1088
|
+
tagsCsv: [(this.blogPost()?.tags ?? []).join(', ')],
|
|
884
1089
|
});
|
|
885
1090
|
this.applyCreatorSystemRules();
|
|
886
1091
|
this.blogPostForm.get('series')?.valueChanges.subscribe(() => this.applyCreatorSystemRules());
|
|
887
1092
|
}
|
|
888
1093
|
applyCreatorSystemRules() {
|
|
889
|
-
const
|
|
1094
|
+
const dimensionControl = this.blogPostForm.get('dimensions');
|
|
890
1095
|
const seriesValue = this.blogPostForm.get('series')?.value;
|
|
891
|
-
if (!
|
|
1096
|
+
if (!dimensionControl)
|
|
892
1097
|
return;
|
|
893
1098
|
if (seriesValue === BlogSeries.FromTheEditor) {
|
|
894
|
-
|
|
895
|
-
if (pillarControl.value !== null)
|
|
896
|
-
pillarControl.setValue(null);
|
|
1099
|
+
dimensionControl.setValidators([this.dimensionRequiredValidator]);
|
|
897
1100
|
}
|
|
898
1101
|
else {
|
|
899
|
-
|
|
1102
|
+
dimensionControl.clearValidators();
|
|
900
1103
|
}
|
|
901
|
-
|
|
1104
|
+
dimensionControl.updateValueAndValidity({ emitEvent: false });
|
|
902
1105
|
}
|
|
903
1106
|
onContentChange() {
|
|
904
1107
|
this.contentData.set(this.blogPostForm.getRawValue().content);
|
|
@@ -906,16 +1109,21 @@ class NgRhombusBlogAddEditComponent {
|
|
|
906
1109
|
get thumbnailSource() {
|
|
907
1110
|
return this.blogPostForm.getRawValue().thumbnail;
|
|
908
1111
|
}
|
|
909
|
-
onFileUploaded(downloadUrl) {
|
|
910
|
-
this.blogPostForm.
|
|
911
|
-
|
|
912
|
-
|
|
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 });
|
|
913
1118
|
this.blogPostForm.markAsDirty();
|
|
914
1119
|
}
|
|
915
|
-
onFileDeleted() {
|
|
916
|
-
this.blogPostForm.
|
|
917
|
-
|
|
918
|
-
|
|
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();
|
|
919
1127
|
}
|
|
920
1128
|
onCancelClick() {
|
|
921
1129
|
if (this.blogPost()) {
|
|
@@ -968,21 +1176,24 @@ class NgRhombusBlogAddEditComponent {
|
|
|
968
1176
|
return;
|
|
969
1177
|
const raw = this.blogPostForm.getRawValue();
|
|
970
1178
|
const tags = this.parseTags(raw.tagsCsv);
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
+
};
|
|
986
1197
|
this.submitEvent.emit(submittedBlogPost);
|
|
987
1198
|
}
|
|
988
1199
|
parseTags(csv) {
|
|
@@ -994,11 +1205,11 @@ class NgRhombusBlogAddEditComponent {
|
|
|
994
1205
|
return Array.from(new Set(tags));
|
|
995
1206
|
}
|
|
996
1207
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
997
|
-
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>
|
|
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"] }] }); }
|
|
998
1209
|
}
|
|
999
1210
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogAddEditComponent, decorators: [{
|
|
1000
1211
|
type: Component,
|
|
1001
|
-
args: [{ selector: 'ng-rhombus-blog-form', imports: [
|
|
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"] }]
|
|
1002
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: [{
|
|
1003
1214
|
type: ViewChild,
|
|
1004
1215
|
args: ['blogPost']
|
|
@@ -1016,11 +1227,16 @@ class NgRhombusBlogPostComponent {
|
|
|
1016
1227
|
this.faLinkedin = faLinkedin;
|
|
1017
1228
|
}
|
|
1018
1229
|
#doc;
|
|
1230
|
+
getDisplayDate(post) {
|
|
1231
|
+
if (!post)
|
|
1232
|
+
return null;
|
|
1233
|
+
return post.publishedAt ?? post.publishAt ?? post.createdAt ?? post.updatedAt;
|
|
1234
|
+
}
|
|
1019
1235
|
ngOnInit() {
|
|
1020
1236
|
}
|
|
1021
1237
|
// Build absolute URL for sharing
|
|
1022
1238
|
getShareUrl() {
|
|
1023
|
-
const id = this.dataSource()?.id;
|
|
1239
|
+
const id = this.dataSource()?.slug ?? this.dataSource()?.id;
|
|
1024
1240
|
const origin = this.#doc?.location?.origin ?? '';
|
|
1025
1241
|
const path = id ? `/blog/${id}` : this.#doc?.location?.pathname ?? '/blog';
|
|
1026
1242
|
return `${origin}${path}`;
|
|
@@ -1052,24 +1268,39 @@ class NgRhombusBlogPostComponent {
|
|
|
1052
1268
|
.map(t => t.trim())
|
|
1053
1269
|
.filter(Boolean);
|
|
1054
1270
|
}
|
|
1055
|
-
get
|
|
1056
|
-
return (this.dataSource()
|
|
1271
|
+
get contentDimensions() {
|
|
1272
|
+
return this.normalizeDimensions(this.dataSource());
|
|
1057
1273
|
}
|
|
1058
1274
|
get series() {
|
|
1059
1275
|
return (this.dataSource()?.series ?? null) || null;
|
|
1060
1276
|
}
|
|
1061
1277
|
get tags() {
|
|
1062
|
-
return this.normalizeTags(this.dataSource()?.tags ??
|
|
1278
|
+
return this.normalizeTags(this.dataSource()?.tags ?? undefined);
|
|
1063
1279
|
}
|
|
1064
1280
|
get hasTaxonomy() {
|
|
1065
|
-
return
|
|
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));
|
|
1066
1297
|
}
|
|
1067
1298
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1068
|
-
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 (
|
|
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·\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" }] }); }
|
|
1069
1300
|
}
|
|
1070
1301
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImport: i0, type: NgRhombusBlogPostComponent, decorators: [{
|
|
1071
1302
|
type: Component,
|
|
1072
|
-
args: [{ selector: 'ng-rhombus-blog-post', imports: [DatePipe,
|
|
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·\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"] }]
|
|
1073
1304
|
}], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: false }] }] } });
|
|
1074
1305
|
|
|
1075
1306
|
class NgRhombusBlogDeletePostComponent {
|
|
@@ -1392,5 +1623,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.4", ngImpor
|
|
|
1392
1623
|
* Generated bundle index. Do not edit.
|
|
1393
1624
|
*/
|
|
1394
1625
|
|
|
1395
|
-
export { BlogSeries,
|
|
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 };
|
|
1396
1627
|
//# sourceMappingURL=doug-williamson-ng-rhombus.mjs.map
|