@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
|
|
438
|
-
(function (
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
})(
|
|
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
|
|
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
|
|
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()
|
|
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()
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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', '
|
|
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<!--
|
|
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<!--
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1086
|
+
dimensions: [this.blogService.dimensionsOf(this.blogPost()) ?? [], this.dimensionRequiredValidator],
|
|
896
1087
|
series: [this.blogPost()?.series ?? null, Validators.required],
|
|
897
|
-
tagsCsv: [(this.blogPost()?.tags ?? []).join(', ')
|
|
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
|
|
1094
|
+
const dimensionControl = this.blogPostForm.get('dimensions');
|
|
904
1095
|
const seriesValue = this.blogPostForm.get('series')?.value;
|
|
905
|
-
if (!
|
|
1096
|
+
if (!dimensionControl)
|
|
906
1097
|
return;
|
|
907
1098
|
if (seriesValue === BlogSeries.FromTheEditor) {
|
|
908
|
-
|
|
909
|
-
if (pillarControl.value !== null)
|
|
910
|
-
pillarControl.setValue(null);
|
|
1099
|
+
dimensionControl.setValidators([this.dimensionRequiredValidator]);
|
|
911
1100
|
}
|
|
912
1101
|
else {
|
|
913
|
-
|
|
1102
|
+
dimensionControl.clearValidators();
|
|
914
1103
|
}
|
|
915
|
-
|
|
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.
|
|
925
|
-
|
|
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.
|
|
931
|
-
|
|
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
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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>
|
|
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>
|
|
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
|
|
1070
|
-
return (this.dataSource()
|
|
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 ??
|
|
1278
|
+
return this.normalizeTags(this.dataSource()?.tags ?? undefined);
|
|
1077
1279
|
}
|
|
1078
1280
|
get hasTaxonomy() {
|
|
1079
|
-
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));
|
|
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 (
|
|
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" }] }); }
|
|
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 (
|
|
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"] }]
|
|
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,
|
|
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
|