@propbinder/mobile-design 0.2.47 → 0.2.50

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.
Files changed (221) hide show
  1. package/ng-package.json +24 -0
  2. package/package.json +3 -39
  3. package/src/animations/page-transitions.ts +165 -0
  4. package/src/assets/fonts/brockmann-mediumitalic-webfont.woff2 +0 -0
  5. package/src/assets/fonts/brockmann-regularitalic-webfont.woff2 +0 -0
  6. package/src/assets/fonts/brockmann-semibolditalic-webfont.woff2 +0 -0
  7. package/src/components/action-list-item/ds-mobile-action-list-item.ts +102 -0
  8. package/src/components/action-list-item/index.ts +2 -0
  9. package/src/components/app-icon/ds-app-icon.ts +133 -0
  10. package/src/components/app-icon/index.ts +2 -0
  11. package/src/components/attachment-preview/ds-mobile-attachment-preview.css +139 -0
  12. package/src/components/attachment-preview/ds-mobile-attachment-preview.ts +164 -0
  13. package/src/components/attachment-preview/index.ts +1 -0
  14. package/src/components/avatar-with-badge/ds-avatar-with-badge.ts +142 -0
  15. package/src/components/avatar-with-badge/index.ts +2 -0
  16. package/src/components/booking-modal/ds-mobile-booking-confirmation-wrapper.ts +71 -0
  17. package/src/components/booking-modal/ds-mobile-booking-modal.service.ts +121 -0
  18. package/src/components/booking-modal/ds-mobile-booking-modal.ts +598 -0
  19. package/src/components/booking-modal/ds-mobile-booking-summary.ts +161 -0
  20. package/src/components/booking-modal/index.ts +4 -0
  21. package/src/components/bottom-sheet/ds-mobile-actions-bottom-sheet.ts +266 -0
  22. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-header.ts +146 -0
  23. package/src/components/bottom-sheet/ds-mobile-bottom-sheet-wrapper.ts +156 -0
  24. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.css +101 -0
  25. package/src/components/bottom-sheet/ds-mobile-bottom-sheet.service.ts +169 -0
  26. package/src/components/bottom-sheet/ds-mobile-confirmation-sheet.ts +211 -0
  27. package/src/components/bottom-sheet/ds-mobile-post-create-bottom-sheet.ts +578 -0
  28. package/src/components/bottom-sheet/ds-mobile-profile-actions-sheet.ts +614 -0
  29. package/src/components/bottom-sheet/index.ts +8 -0
  30. package/src/components/bottom-sheet/modal-shadow-fix.ts +42 -0
  31. package/src/components/card-inline/ds-mobile-card-inline.ts +301 -0
  32. package/src/components/card-inline/index.ts +2 -0
  33. package/src/components/card-inline-banner/ds-mobile-card-inline-banner.ts +118 -0
  34. package/src/components/card-inline-banner/index.ts +1 -0
  35. package/src/components/card-inline-contact/ds-mobile-card-inline-contact.ts +120 -0
  36. package/src/components/card-inline-contact/index.ts +1 -0
  37. package/src/components/card-inline-file/ds-mobile-card-inline-file.ts +141 -0
  38. package/src/components/card-inline-file/index.ts +1 -0
  39. package/src/components/chat-modal/ds-mobile-chat-modal.css +159 -0
  40. package/src/components/chat-modal/ds-mobile-chat-modal.service.ts +105 -0
  41. package/src/components/chat-modal/ds-mobile-chat-modal.ts +918 -0
  42. package/src/components/chat-modal/index.ts +8 -0
  43. package/src/components/comment/ds-mobile-comment.ts +568 -0
  44. package/src/components/comment/index.ts +2 -0
  45. package/src/components/contact-list-item/ds-mobile-contact-list-item.ts +182 -0
  46. package/src/components/contact-list-item/index.ts +2 -0
  47. package/src/components/content/ds-mobile-content.ts +139 -0
  48. package/src/components/content/index.ts +2 -0
  49. package/src/components/dropdown/ds-mobile-dropdown.css +199 -0
  50. package/src/components/dropdown/ds-mobile-dropdown.ts +340 -0
  51. package/src/components/dropdown/index.ts +2 -0
  52. package/src/components/ds-mobile-tabs.css +407 -0
  53. package/src/components/ds-mobile-tabs.ts +216 -0
  54. package/src/components/empty-state/ds-mobile-empty-state.ts +120 -0
  55. package/src/components/empty-state/index.ts +2 -0
  56. package/src/components/fab/ds-mobile-fab.ts +315 -0
  57. package/src/components/fab/index.ts +1 -0
  58. package/src/components/facility-creation-modal/ds-mobile-facility-creation-confirmation-wrapper.ts +121 -0
  59. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.css +189 -0
  60. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.service.ts +135 -0
  61. package/src/components/facility-creation-modal/ds-mobile-facility-creation-modal.ts +656 -0
  62. package/src/components/facility-creation-modal/index.ts +9 -0
  63. package/src/components/facility-creation-modal/sheets/ds-mobile-access-sheet.ts +105 -0
  64. package/src/components/facility-creation-modal/sheets/ds-mobile-price-sheet.ts +188 -0
  65. package/src/components/facility-creation-modal/sheets/ds-mobile-when-can-book-sheet.ts +460 -0
  66. package/src/components/facility-creation-modal/sheets/ds-mobile-who-can-book-sheet.ts +134 -0
  67. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.service.ts +69 -0
  68. package/src/components/facility-detail-modal/ds-mobile-facility-detail-modal.ts +379 -0
  69. package/src/components/facility-detail-modal/index.ts +2 -0
  70. package/src/components/file-attachment/ds-mobile-file-attachment.ts +164 -0
  71. package/src/components/file-attachment/index.ts +2 -0
  72. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.css +214 -0
  73. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.service.ts +84 -0
  74. package/src/components/handbook-detail-modal/ds-mobile-handbook-detail-modal.ts +424 -0
  75. package/src/components/handbook-detail-modal/index.ts +3 -0
  76. package/src/components/handbook-folder/ds-mobile-handbook-folder-mini.ts +175 -0
  77. package/src/components/handbook-folder/ds-mobile-handbook-folder.ts +533 -0
  78. package/src/components/handbook-folder/index.ts +4 -0
  79. package/src/components/header-content/ds-mobile-header-content.ts +222 -0
  80. package/src/components/header-content/index.ts +2 -0
  81. package/src/components/illustration/ds-mobile-illustration.ts +124 -0
  82. package/src/components/illustration/index.ts +2 -0
  83. package/src/components/index.ts +124 -0
  84. package/src/components/inline-photo/ds-mobile-inline-photo.ts +361 -0
  85. package/src/components/inline-photo/index.ts +1 -0
  86. package/src/components/inline-tabs/ds-mobile-inline-tabs.ts +132 -0
  87. package/src/components/inline-tabs/index.ts +2 -0
  88. package/src/components/interactive-list-item-booking/ds-mobile-interactive-list-item-booking.ts +350 -0
  89. package/src/components/interactive-list-item-booking/index.ts +1 -0
  90. package/src/components/interactive-list-item-inquiry/ds-mobile-interactive-list-item-inquiry.ts +321 -0
  91. package/src/components/interactive-list-item-inquiry/index.ts +2 -0
  92. package/src/components/interactive-list-item-message/ds-mobile-interactive-list-item-message.ts +237 -0
  93. package/src/components/interactive-list-item-message/index.ts +2 -0
  94. package/src/components/interactive-list-item-post/ds-mobile-interactive-list-item-post.ts +549 -0
  95. package/src/components/interactive-list-item-post/ds-mobile-post-pdf-attachment.ts +124 -0
  96. package/src/components/interactive-list-item-post/index.ts +13 -0
  97. package/src/components/lightbox/ds-mobile-lightbox-footer.ts +315 -0
  98. package/src/components/lightbox/ds-mobile-lightbox-header.ts +202 -0
  99. package/src/components/lightbox/ds-mobile-lightbox-image.ts +484 -0
  100. package/src/components/lightbox/ds-mobile-lightbox-pdf.css +377 -0
  101. package/src/components/lightbox/ds-mobile-lightbox-pdf.ts +374 -0
  102. package/src/components/lightbox/ds-mobile-lightbox.css +587 -0
  103. package/src/components/lightbox/ds-mobile-lightbox.service.ts +296 -0
  104. package/src/components/lightbox/ds-mobile-lightbox.ts +529 -0
  105. package/src/components/lightbox/index.ts +22 -0
  106. package/src/components/list-item/ds-mobile-list-item.ts +603 -0
  107. package/src/components/list-item/index.ts +2 -0
  108. package/src/components/list-item-static/ds-mobile-list-item-static.ts +133 -0
  109. package/src/components/list-item-static/index.ts +2 -0
  110. package/src/components/loader-overlay/ds-mobile-loader-overlay.css +49 -0
  111. package/src/components/loader-overlay/ds-mobile-loader-overlay.ts +77 -0
  112. package/src/components/loader-overlay/index.ts +1 -0
  113. package/src/components/logo/ds-logo.ts +95 -0
  114. package/src/components/logo/index.ts +2 -0
  115. package/src/components/message-bubble/ds-mobile-message-bubble.ts +633 -0
  116. package/src/components/message-bubble/index.ts +7 -0
  117. package/src/components/message-composer/ds-mobile-message-composer.ts +1146 -0
  118. package/src/components/message-composer/index.ts +7 -0
  119. package/src/components/modal/ds-mobile-modal.css +163 -0
  120. package/src/components/modal/ds-mobile-modal.service.ts +329 -0
  121. package/src/components/modal/index.ts +8 -0
  122. package/src/components/modal-base/ds-mobile-modal-base.css +378 -0
  123. package/src/components/modal-base/ds-mobile-modal-base.ts +261 -0
  124. package/src/components/modal-base/index.ts +2 -0
  125. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.css +112 -0
  126. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.service.ts +93 -0
  127. package/src/components/new-inquiry-modal/ds-mobile-new-inquiry-modal.ts +442 -0
  128. package/src/components/new-inquiry-modal/index.ts +4 -0
  129. package/src/components/offline-banner/ds-mobile-offline-banner.ts +135 -0
  130. package/src/components/offline-banner/index.ts +1 -0
  131. package/src/components/page-details/ds-mobile-page-details.css +83 -0
  132. package/src/components/page-details/ds-mobile-page-details.ts +282 -0
  133. package/src/components/page-details/index.ts +2 -0
  134. package/src/components/page-main/ds-mobile-page-main.css +68 -0
  135. package/src/components/page-main/ds-mobile-page-main.ts +421 -0
  136. package/src/components/page-main/index.ts +2 -0
  137. package/src/components/post-composer/ds-mobile-post-composer.ts +140 -0
  138. package/src/components/post-composer/index.ts +2 -0
  139. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.css +390 -0
  140. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.service.ts +108 -0
  141. package/src/components/post-detail-modal/ds-mobile-post-detail-modal.ts +722 -0
  142. package/src/components/post-detail-modal/index.ts +9 -0
  143. package/src/components/property-banner/ds-mobile-property-banner.ts +95 -0
  144. package/src/components/property-banner/index.ts +2 -0
  145. package/src/components/section/ds-mobile-section.ts +263 -0
  146. package/src/components/section/index.ts +2 -0
  147. package/src/components/shared/directives/index.ts +2 -0
  148. package/src/components/shared/directives/long-press.directive.ts +212 -0
  149. package/src/components/shared/index.ts +3 -0
  150. package/src/components/shared/mobile-modal-base.ts +457 -0
  151. package/src/components/shared/mobile-page-base.ts +204 -0
  152. package/src/components/swiper/ds-mobile-swiper-with-nav.ts +160 -0
  153. package/src/components/swiper/ds-mobile-swiper.ts +327 -0
  154. package/src/components/swiper/index.ts +3 -0
  155. package/src/components/system-message-banner/ds-mobile-system-message-banner.ts +129 -0
  156. package/src/components/system-message-banner/index.ts +2 -0
  157. package/src/components/tab-bar/ds-mobile-tab-bar.css +533 -0
  158. package/src/components/tab-bar/ds-mobile-tab-bar.ts +735 -0
  159. package/src/components/tab-bar/index.ts +2 -0
  160. package/src/components/tabs/ds-mobile-tabs.css +25 -0
  161. package/src/components/tabs/ds-mobile-tabs.ts +89 -0
  162. package/src/components/tabs/index.ts +2 -0
  163. package/src/components/text-input/ds-text-input.ts +287 -0
  164. package/src/components/text-input/index.ts +2 -0
  165. package/src/examples/booking.page.ts +434 -0
  166. package/src/examples/community.page.ts +776 -0
  167. package/src/examples/handbook.page.ts +324 -0
  168. package/src/examples/home.page.ts +347 -0
  169. package/src/examples/index.ts +12 -0
  170. package/src/examples/inquiries.example.ts +273 -0
  171. package/src/examples/inquiry-detail.example.css +189 -0
  172. package/src/examples/inquiry-detail.example.ts +415 -0
  173. package/src/examples/mobile-tabs-example.component.ts +208 -0
  174. package/src/examples/post-create.page.ts +311 -0
  175. package/src/examples/post-detail.page.ts +296 -0
  176. package/src/examples/sign-in.page.ts +291 -0
  177. package/src/examples/whitelabel-demo-modal.component.ts +1094 -0
  178. package/src/examples/whitelabel-demo-modal.service.ts +77 -0
  179. package/src/models/index.ts +7 -0
  180. package/src/models/post.model.ts +41 -0
  181. package/src/pages/community.page.ts +769 -0
  182. package/src/pages/handbook.page.ts +388 -0
  183. package/src/pages/home.page.ts +303 -0
  184. package/src/pages/index.ts +11 -0
  185. package/src/pages/inquiries.example.ts +273 -0
  186. package/src/pages/inquiry-detail.example.css +189 -0
  187. package/src/pages/inquiry-detail.example.ts +415 -0
  188. package/src/pages/mobile-tabs-example.component.ts +179 -0
  189. package/src/pages/post-create.page.ts +311 -0
  190. package/src/pages/post-detail.page.ts +296 -0
  191. package/src/pages/sign-in.page.ts +291 -0
  192. package/src/pages/whitelabel-demo-modal.component.ts +1094 -0
  193. package/src/pages/whitelabel-demo-modal.service.ts +77 -0
  194. package/src/public-api.ts +6 -0
  195. package/src/services/base-modal.service.ts +101 -0
  196. package/src/services/index.ts +11 -0
  197. package/src/services/posts.service.ts +542 -0
  198. package/src/services/tracking-permission.service.ts +88 -0
  199. package/src/services/user.service.ts +60 -0
  200. package/src/services/whitelabel.service.ts +675 -0
  201. package/{styles → src/styles}/ionic.css +25 -0
  202. package/tsconfig.lib.json +17 -0
  203. package/tsconfig.lib.prod.json +9 -0
  204. package/tsconfig.spec.json +13 -0
  205. package/fesm2022/propbinder-mobile-design.mjs +0 -26136
  206. package/fesm2022/propbinder-mobile-design.mjs.map +0 -1
  207. package/index.d.ts +0 -8154
  208. /package/{assets → src/assets}/fonts/Brockmann-Bold.otf +0 -0
  209. /package/{assets → src/assets}/fonts/Brockmann-BoldItalic.otf +0 -0
  210. /package/{assets → src/assets}/fonts/Brockmann-Medium.otf +0 -0
  211. /package/{assets → src/assets}/fonts/Brockmann-MediumItalic.otf +0 -0
  212. /package/{assets → src/assets}/fonts/Brockmann-Regular.otf +0 -0
  213. /package/{assets → src/assets}/fonts/Brockmann-RegularItalic.otf +0 -0
  214. /package/{assets → src/assets}/fonts/Brockmann-SemiBold.otf +0 -0
  215. /package/{assets → src/assets}/fonts/Brockmann-SemiBoldItalic.otf +0 -0
  216. /package/{assets → src/assets}/fonts/Brockmann_desktop_license.pdf +0 -0
  217. /package/{assets → src/assets}/fonts/brockmann-medium-webfont.woff2 +0 -0
  218. /package/{assets → src/assets}/fonts/brockmann-regular-webfont.woff2 +0 -0
  219. /package/{assets → src/assets}/fonts/brockmann-semibold-webfont.woff2 +0 -0
  220. /package/{styles → src/components/shared}/mobile-common.css +0 -0
  221. /package/{styles → src/components/shared}/mobile-page-base.css +0 -0
@@ -0,0 +1,722 @@
1
+ import { Component, signal, computed, CUSTOM_ELEMENTS_SCHEMA, Input, ViewChild, ElementRef, OnInit, AfterViewInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { Keyboard } from '@capacitor/keyboard';
5
+ import { DsIconComponent, DsIconButtonComponent } from '@propbinder/design-system';
6
+ import { DsAvatarComponent } from '@propbinder/design-system';
7
+ import { PostContentComponent, PostTextComponent, PostMediaComponent, PostActionsComponent, ActionLikeComponent, ActionCommentComponent } from '../interactive-list-item-post';
8
+ import { DsMobileCommentComponent } from '../comment/ds-mobile-comment';
9
+ import { DsMobileLightboxService, LightboxAuthor } from '../lightbox';
10
+ import { DsMobileBottomSheetService, DsMobileCommentActionsBottomSheetComponent, CommentActionResult } from '../bottom-sheet';
11
+ import { DsMobileModalBaseComponent } from '../modal-base/ds-mobile-modal-base';
12
+ import { DsMobileEmptyStateComponent } from '../empty-state';
13
+ import { DsMobileSectionComponent } from '../section';
14
+
15
+ /**
16
+ * Post data interface for the modal
17
+ *
18
+ * Represents a post with its content, author info, and comments.
19
+ * Use this interface to map your API response data to the component.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const postData: PostDetailData = {
24
+ * postId: '123',
25
+ * authorName: 'John Doe',
26
+ * authorRole: 'Tenant',
27
+ * timestamp: '2h ago',
28
+ * avatarInitials: 'JD',
29
+ * content: 'Post content here...',
30
+ * likeCount: 42,
31
+ * commentCount: 12,
32
+ * comments: [...]
33
+ * };
34
+ * ```
35
+ */
36
+ export interface PostDetailData {
37
+ /** Unique post identifier */
38
+ postId: string;
39
+ /** Post author name */
40
+ authorName: string;
41
+ /** Author role (e.g., 'Tenant', 'Manager') */
42
+ authorRole: string;
43
+ /** Post timestamp (e.g., '2h ago', 'Yesterday') */
44
+ timestamp: string;
45
+ /** Author avatar initials (1-2 letters) */
46
+ avatarInitials?: string;
47
+ /** Avatar display type */
48
+ avatarType?: 'photo' | 'initials';
49
+ /** Author avatar image URL */
50
+ avatarSrc?: string;
51
+ /** Post text content */
52
+ content: string;
53
+ /** Optional post image URL */
54
+ imageSrc?: string;
55
+ /** Image alt text */
56
+ imageAlt?: string;
57
+ /** Whether the current user has liked this post */
58
+ isLiked?: boolean;
59
+ /** Number of likes */
60
+ likeCount?: number;
61
+ /** Number of comments */
62
+ commentCount?: number;
63
+ /** Array of comments */
64
+ comments?: CommentData[];
65
+ /** Auto-focus comment input when modal opens */
66
+ focusComment?: boolean;
67
+ }
68
+
69
+ /**
70
+ * Comment data interface
71
+ *
72
+ * Represents a single comment on a post.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const comment: CommentData = {
77
+ * authorName: 'Jane Smith',
78
+ * authorRole: 'Tenant',
79
+ * timestamp: '1h ago',
80
+ * avatarInitials: 'JS',
81
+ * content: 'Great post!',
82
+ * isLiked: false,
83
+ * likeCount: 5,
84
+ * isOwnComment: false
85
+ * };
86
+ * ```
87
+ */
88
+ export interface CommentData {
89
+ /** Unique comment identifier */
90
+ id?: string;
91
+ /** Comment author name */
92
+ authorName: string;
93
+ /** Author role */
94
+ authorRole: string;
95
+ /** Comment timestamp */
96
+ timestamp: string;
97
+ /** Author avatar initials */
98
+ avatarInitials: string;
99
+ /** Comment text content */
100
+ content: string;
101
+ /** Whether the current user has liked this comment */
102
+ isLiked?: boolean;
103
+ /** Number of likes on this comment */
104
+ likeCount?: number;
105
+ /** Whether this comment belongs to the current user */
106
+ isOwnComment?: boolean;
107
+ }
108
+
109
+ /**
110
+ * DsMobilePostDetailModalComponent
111
+ *
112
+ * Modal wrapper for displaying post details with comments.
113
+ * Follows the same pattern as the lightbox modal for consistent behavior.
114
+ *
115
+ * Features:
116
+ * - Full post content display
117
+ * - Comments section
118
+ * - Image lightbox integration
119
+ * - Native modal controls (close, swipe down)
120
+ * - Safe area support
121
+ *
122
+ * This component is typically not used directly - use DsMobilePostDetailModalService instead.
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * // Don't instantiate directly - use the service:
127
+ * constructor(private postModal: DsMobilePostDetailModalService) {}
128
+ *
129
+ * openPost() {
130
+ * this.postModal.open({
131
+ * postId: '123',
132
+ * authorName: 'John Doe',
133
+ * content: 'Post content...'
134
+ * });
135
+ * }
136
+ * ```
137
+ */
138
+ @Component({
139
+ selector: 'ds-mobile-post-detail-modal',
140
+ standalone: true,
141
+ imports: [
142
+ CommonModule,
143
+ FormsModule,
144
+ DsIconComponent,
145
+ DsIconButtonComponent,
146
+ DsAvatarComponent,
147
+ PostTextComponent,
148
+ PostMediaComponent,
149
+ ActionLikeComponent,
150
+ ActionCommentComponent,
151
+ DsMobileCommentComponent,
152
+ DsMobileModalBaseComponent,
153
+ DsMobileEmptyStateComponent,
154
+ DsMobileSectionComponent
155
+ ],
156
+ styleUrls: ['../shared/mobile-common.css', './ds-mobile-post-detail-modal.css'],
157
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
158
+ template: `
159
+ <ds-mobile-modal-base
160
+ [loading]="loading"
161
+ [error]="error"
162
+ [headerTitle]="post().authorName"
163
+ [headerMeta]="post().authorRole + ' · ' + post().timestamp"
164
+ [hasFixedBottom]="true"
165
+ [enableKeyboardHandling]="true"
166
+ closeButtonLabel="Luk opslag"
167
+ >
168
+ <!-- Header Avatar -->
169
+ <ds-avatar header-leading [initials]="post().avatarInitials || ''" [type]="post().avatarType || 'initials'" [src]="post().avatarSrc || ''" size="md" />
170
+
171
+ <!-- Post Section -->
172
+ <ds-mobile-section>
173
+ <div class="post-content-only">
174
+ <post-text>
175
+ <div [innerHTML]="post().content"></div>
176
+ </post-text>
177
+ @if (post().imageSrc) {
178
+ <post-media>
179
+ <img [src]="post().imageSrc" [alt]="post().imageAlt || 'Post image'" class="clickable-image" (click)="openImageLightbox()" />
180
+ </post-media>
181
+ }
182
+ </div>
183
+
184
+ <!-- Post actions -->
185
+ <div class="post-actions">
186
+ <action-like
187
+ [active]="post().isLiked || false"
188
+ [count]="post().likeCount || 0"
189
+ (likeClick)="handlePostLikeToggle($event)"
190
+ />
191
+ <action-comment [count]="post().commentCount || 0" (commentClick)="focusCommentInput()" />
192
+ </div>
193
+ </ds-mobile-section>
194
+
195
+ <!-- Comments Section -->
196
+ <ds-mobile-section
197
+ [headline]="post().comments && post().comments!.length > 0 ? (post().comments!.length + ' ' + (post().comments!.length === 1 ? 'reply' : 'replies')) : ''">
198
+ @if (post().comments && post().comments!.length > 0) {
199
+ <div class="comments-list">
200
+ @for (comment of post().comments!; track comment.id) {
201
+ <ds-mobile-comment
202
+ [authorName]="comment.authorName"
203
+ [authorRole]="comment.authorRole"
204
+ [timestamp]="comment.timestamp"
205
+ [avatarInitials]="comment.avatarInitials"
206
+ [content]="comment.content"
207
+ [isLiked]="comment.isLiked || false"
208
+ [likeCount]="comment.likeCount || 0"
209
+ [clickable]="true"
210
+ [isOwnComment]="comment.isOwnComment || false"
211
+ (likeToggled)="handleCommentLikeToggle(comment, $event)"
212
+ (replyClick)="handleReply(comment.authorName, comment.content)"
213
+ (editClick)="handleEditComment(comment)"
214
+ (longPress)="handleCommentLongPress(comment.authorName, comment.content, comment.isOwnComment || false)" />
215
+ }
216
+ </div>
217
+ } @else {
218
+ <!-- Empty State -->
219
+ <ds-mobile-empty-state
220
+ [imageSrc]="'/Assets/empty-state-inquiry.svg'"
221
+ [imageAlt]="'Ingen kommentarer endnu'"
222
+ [title]="'Ingen svar endnu'"
223
+ [description]="'Vær den første til at svare på dette opslag'">
224
+ </ds-mobile-empty-state>
225
+ }
226
+ </ds-mobile-section>
227
+
228
+ <!-- Fixed comment composer -->
229
+ <div fixed-bottom>
230
+ <div class="comment-composer">
231
+ <!-- Edit indicator -->
232
+ @if (editingComment()) {
233
+ <div class="edit-indicator">
234
+ <div class="edit-indicator-content">
235
+ <ds-icon name="remixEditLine" size="16px" />
236
+ <span class="edit-text">Redigerer kommentar</span>
237
+ </div>
238
+ <button class="cancel-edit" (click)="cancelEdit()">
239
+ <ds-icon name="remixCloseLine" size="16px" />
240
+ </button>
241
+ </div>
242
+ } @else if (replyingTo()) {
243
+ <!-- Reply indicator -->
244
+ <div class="reply-indicator">
245
+ <div class="reply-indicator-content">
246
+ <ds-icon name="remixReplyLine" size="16px" />
247
+ <span class="reply-to-text">
248
+ Svarer til <span class="reply-author">{{ replyingTo()!.authorName }}</span>
249
+ </span>
250
+ </div>
251
+ <button class="cancel-reply" (click)="cancelReply()">
252
+ <ds-icon name="remixCloseLine" size="16px" />
253
+ </button>
254
+ </div>
255
+ }
256
+
257
+ <div class="composer-content">
258
+ <ds-avatar [initials]="currentUserInitials()" [type]="'initials'" size="md" />
259
+ <div class="composer-input-wrapper">
260
+ <textarea
261
+ #commentInput
262
+ class="composer-input"
263
+ [placeholder]="editingComment() ? 'Rediger din kommentar...' : replyingTo() ? 'Tilføj et svar...' : 'Tilføj et svar...'"
264
+ [ngModel]="commentText()"
265
+ (ngModelChange)="commentText.set($event)"
266
+ (input)="handleInput($event)"
267
+ (focus)="showKeyboard()"
268
+ (click)="showKeyboard()"
269
+ rows="1"
270
+ ></textarea>
271
+ </div>
272
+ <ds-icon-button
273
+ icon="remixCheckLine"
274
+ variant="primary"
275
+ size="sm"
276
+ (clicked)="submitComment()"
277
+ aria-label="Send kommentar"
278
+ [class.send-button-fixed]="true"
279
+ [class.show]="commentText().trim().length > 0"
280
+ >
281
+ </ds-icon-button>
282
+ </div>
283
+ </div>
284
+ </div>
285
+ </ds-mobile-modal-base>
286
+ `,
287
+ })
288
+ export class DsMobilePostDetailModalComponent implements OnInit, AfterViewInit {
289
+ // Post data passed from service
290
+ @Input() postData!: PostDetailData;
291
+
292
+ // Current user info for comment composer
293
+ @Input() currentUserName: string = '';
294
+ @Input() currentUserInitialsInput: string = '';
295
+
296
+ // State management inputs (passed to modal base)
297
+ @Input() loading: boolean = false;
298
+ @Input() error: string | undefined;
299
+
300
+ // Callback for toggling like on the main post
301
+ @Input() onTogglePostLike?: (payload: { postId: string; active: boolean }) => void;
302
+
303
+ // Callback for submitting a new comment
304
+ @Input() onSubmitComment?: (payload: { postId: string; text: string }) => void;
305
+
306
+ // Callback for liking/unliking a comment
307
+ @Input() onToggleCommentLike?: (payload: { commentId: string; active: boolean }) => void;
308
+
309
+ // Callback for editing a comment
310
+ @Input() onEditComment?: (payload: { commentId: string; newText: string }) => void;
311
+
312
+ // Callback for deleting a comment
313
+ @Input() onDeleteComment?: (payload: { commentId: string }) => void;
314
+
315
+ // ViewChild for comment input
316
+ @ViewChild('commentInput') commentInput?: ElementRef<HTMLTextAreaElement>;
317
+
318
+ // Signal for reactive post data
319
+ post = signal<PostDetailData>({
320
+ postId: '',
321
+ authorName: '',
322
+ authorRole: '',
323
+ timestamp: '',
324
+ content: '',
325
+ comments: [],
326
+ });
327
+
328
+ // Comment composer state
329
+ commentText = signal('');
330
+ currentUserInitials = signal('');
331
+ replyingTo = signal<{ authorName: string; content: string } | null>(null);
332
+ editingComment = signal<{ id?: string; authorName: string; originalContent: string; timestamp: string } | null>(null);
333
+
334
+ constructor(private lightbox: DsMobileLightboxService, private bottomSheet: DsMobileBottomSheetService) {}
335
+
336
+ ngOnInit(): void {
337
+ // Initialize post data from input
338
+ if (this.postData) {
339
+ this.post.set(this.postData);
340
+ }
341
+
342
+ // Set current user initials
343
+ if (this.currentUserInitialsInput) {
344
+ this.currentUserInitials.set(this.currentUserInitialsInput);
345
+ } else if (this.currentUserName) {
346
+ // fallback: derive from name
347
+ const initials = this.currentUserName
348
+ .trim()
349
+ .split(/\s+/)
350
+ .map((p) => p[0])
351
+ .join('')
352
+ .substring(0, 2)
353
+ .toUpperCase();
354
+ this.currentUserInitials.set(initials);
355
+ }
356
+ }
357
+
358
+ ngAfterViewInit(): void {
359
+ // Auto-focus comment input if requested
360
+ if (this.postData?.focusComment) {
361
+ // Small delay to ensure modal animation is complete
362
+ setTimeout(() => {
363
+ this.commentInput?.nativeElement.focus();
364
+ // Show keyboard on mobile
365
+ this.showKeyboard();
366
+ }, 300);
367
+ }
368
+ }
369
+
370
+ /**
371
+ * Show the keyboard when user interacts with input
372
+ */
373
+ showKeyboard(): void {
374
+ Keyboard.show().catch((e) => {
375
+ // console.log('Keyboard.show() not available')
376
+ });
377
+ }
378
+
379
+ /**
380
+ * Focus the comment input when comment icon is tapped
381
+ */
382
+ focusCommentInput(): void {
383
+ // Focus the input
384
+ this.commentInput?.nativeElement.focus();
385
+ // Show keyboard on mobile
386
+ this.showKeyboard();
387
+ }
388
+
389
+ /**
390
+ * Handle input changes for auto-resize
391
+ */
392
+ handleInput(event: Event): void {
393
+ const textarea = event.target as HTMLTextAreaElement;
394
+
395
+ // Auto-resize textarea
396
+ textarea.style.height = 'auto';
397
+ textarea.style.height = textarea.scrollHeight + 'px';
398
+ }
399
+
400
+ /**
401
+ * Handle reply to a comment
402
+ */
403
+ handleReply(authorName: string, content: string): void {
404
+ this.replyingTo.set({ authorName, content });
405
+ // Focus the input and show keyboard
406
+ setTimeout(() => {
407
+ this.commentInput?.nativeElement.focus();
408
+ this.showKeyboard();
409
+ }, 100);
410
+ }
411
+
412
+ /**
413
+ * Cancel reply
414
+ */
415
+ cancelReply(): void {
416
+ this.replyingTo.set(null);
417
+ }
418
+
419
+ /**
420
+ * Cancel edit
421
+ */
422
+ cancelEdit(): void {
423
+ this.editingComment.set(null);
424
+ this.commentText.set('');
425
+ }
426
+
427
+ /**
428
+ * Handle edit comment
429
+ */
430
+ handleEditComment(comment: CommentData): void {
431
+ // Clear reply state if active
432
+ this.replyingTo.set(null);
433
+
434
+ // Set edit state
435
+ this.editingComment.set({
436
+ id: comment.id,
437
+ authorName: comment.authorName,
438
+ originalContent: comment.content,
439
+ timestamp: comment.timestamp,
440
+ });
441
+
442
+ // Populate the input with existing content
443
+ this.commentText.set(comment.content);
444
+
445
+ // Focus the input, show keyboard, and auto-resize
446
+ setTimeout(() => {
447
+ if (this.commentInput?.nativeElement) {
448
+ const textarea = this.commentInput.nativeElement;
449
+ textarea.focus();
450
+
451
+ // Auto-resize textarea to fit content
452
+ textarea.style.height = 'auto';
453
+ textarea.style.height = textarea.scrollHeight + 'px';
454
+
455
+ this.showKeyboard();
456
+ }
457
+ }, 100);
458
+ }
459
+
460
+ /**
461
+ * Handle post like/unlike toggle
462
+ */
463
+ handlePostLikeToggle(ev: { active: boolean; count: number }): void {
464
+ const currentPost = this.post();
465
+
466
+ // Update local state immediately for responsiveness
467
+ this.post.set({
468
+ ...currentPost,
469
+ isLiked: ev.active,
470
+ likeCount: ev.count
471
+ });
472
+
473
+ // Call the callback if provided
474
+ if (this.onTogglePostLike) {
475
+ this.onTogglePostLike({
476
+ postId: currentPost.postId,
477
+ active: ev.active
478
+ });
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Handle comment like/unlike toggle
484
+ * @param comment The comment being liked/unliked
485
+ * @param ev Event data with active state and new count
486
+ */
487
+ handleCommentLikeToggle(comment: CommentData, ev: { active: boolean; count: number }): void {
488
+ // Update local state immediately for responsiveness
489
+ const currentPost = this.post();
490
+ const updatedComments = currentPost.comments?.map((c) => (c.id === comment.id ? { ...c, isLiked: ev.active, likeCount: ev.count } : c));
491
+
492
+ this.post.set({
493
+ ...currentPost,
494
+ comments: updatedComments,
495
+ });
496
+
497
+ // Call the callback if provided
498
+ if (this.onToggleCommentLike) {
499
+ this.onToggleCommentLike({
500
+ commentId: comment.id!,
501
+ active: ev.active,
502
+ });
503
+ }
504
+ }
505
+
506
+ // Note: close() method is inherited from MobileModalBase
507
+
508
+ /**
509
+ * Submit a comment
510
+ */
511
+ submitComment(): void {
512
+ const text = this.commentText().trim();
513
+ if (!text) return;
514
+
515
+ const currentPost = this.post();
516
+ const postId = currentPost.postId;
517
+
518
+ // Create new comment
519
+ const finalText = this.replyingTo() ? `@${this.replyingTo()!.authorName} ${text}` : text;
520
+
521
+ // Check if we're editing an existing comment
522
+ if (this.editingComment()) {
523
+ // console.log('[PostDetailModal] Updating comment:', text);
524
+
525
+ const editing = this.editingComment()!;
526
+
527
+ const updatedComments = currentPost.comments?.map((comment) => {
528
+ if (comment.id && editing.id && comment.id === editing.id) {
529
+ return {
530
+ ...comment,
531
+ content: text,
532
+ timestamp: 'Just now (edited)',
533
+ };
534
+ }
535
+ return comment;
536
+ });
537
+
538
+ this.post.set({
539
+ ...currentPost,
540
+ comments: updatedComments,
541
+ });
542
+
543
+ // Call the edit callback
544
+ if (this.onEditComment && editing.id) {
545
+ this.onEditComment({
546
+ commentId: editing.id,
547
+ newText: finalText, // or finalText if you want to include @mention
548
+ });
549
+ }
550
+
551
+ this.editingComment.set(null);
552
+ } else {
553
+ // console.log('[PostDetailModal] Submitting comment:', finalText);
554
+ // console.log('[PostDetailModal] onSubmitComment =', this.onSubmitComment);
555
+
556
+ const newComment: CommentData = {
557
+ authorName: this.currentUserName || 'Dig',
558
+ authorRole: 'Dig',
559
+ timestamp: 'Just now',
560
+ avatarInitials: this.currentUserInitials(),
561
+ content: finalText,
562
+ isLiked: false,
563
+ likeCount: 0,
564
+ isOwnComment: true,
565
+ };
566
+
567
+ // Add comment to the list
568
+ const updatedComments = [...(currentPost.comments || []), newComment];
569
+
570
+ this.post.set({
571
+ ...currentPost,
572
+ comments: updatedComments,
573
+ commentCount: updatedComments.length,
574
+ });
575
+
576
+ // Clear reply state
577
+ this.replyingTo.set(null);
578
+ }
579
+
580
+ // Clear the input
581
+ this.commentText.set('');
582
+
583
+ if (this.commentInput?.nativeElement) {
584
+ // Reset textarea height to initial state
585
+ this.commentInput.nativeElement.style.height = 'auto';
586
+ // Blur the input to hide the keyboard
587
+ this.commentInput?.nativeElement.blur();
588
+ }
589
+
590
+ // Hide keyboard explicitly
591
+ Keyboard.hide().catch(() => {});
592
+
593
+ // In a real app, you would also send this to your backend
594
+ // this.commentService.addComment(currentPost.postId, text);
595
+ if (this.onSubmitComment) {
596
+ this.onSubmitComment({ postId, text: finalText });
597
+ }
598
+ }
599
+
600
+ /**
601
+ * Open image in lightbox
602
+ */
603
+ openImageLightbox(): void {
604
+ const postData = this.post();
605
+
606
+ if (!postData.imageSrc) return;
607
+
608
+ const authorMeta: LightboxAuthor = {
609
+ name: postData.authorName,
610
+ role: postData.authorRole,
611
+ avatarInitials: postData.avatarInitials || '',
612
+ avatarType: postData.avatarType || 'initials',
613
+ avatarSrc: postData.avatarSrc || '',
614
+ timestamp: postData.timestamp,
615
+ };
616
+
617
+ this.lightbox.open({
618
+ images: [
619
+ {
620
+ type: 'image',
621
+ src: postData.imageSrc,
622
+ alt: postData.imageAlt || 'Post image',
623
+ title: postData.imageAlt || '',
624
+ description: postData.content,
625
+ isLiked: postData.isLiked || false,
626
+ likeCount: postData.likeCount || 0,
627
+ commentCount: postData.commentCount || 0,
628
+ },
629
+ ],
630
+ author: authorMeta,
631
+ enableZoom: true,
632
+ showControls: false,
633
+ showInfo: true,
634
+ });
635
+ }
636
+
637
+ /**
638
+ * Handle long press on a comment to show action sheet
639
+ */
640
+ async handleCommentLongPress(authorName: string, content: string, isOwnComment: boolean): Promise<void> {
641
+ const sheet = await this.bottomSheet.create({
642
+ component: DsMobileCommentActionsBottomSheetComponent,
643
+ componentProps: {
644
+ isOwnContent: isOwnComment,
645
+ },
646
+ breakpoints: [0, 1],
647
+ initialBreakpoint: 1,
648
+ handle: true,
649
+ backdropDismiss: true,
650
+ cssClass: 'auto-height',
651
+ });
652
+
653
+ const result = await sheet.onWillDismiss();
654
+
655
+ if (result.role === 'select' && result.data) {
656
+ const action = (result.data as CommentActionResult).action;
657
+ const currentPost = this.post();
658
+
659
+ switch (action) {
660
+ case 'like':
661
+ // console.log('Like comment by', authorName);
662
+ let toggledCommentId: string | null = null;
663
+ let newActive = false;
664
+ // Update the comment like state locally
665
+ const updatedComments = currentPost.comments?.map((comment) => {
666
+ if (comment.authorName === authorName && comment.content === content) {
667
+ newActive = !comment.isLiked;
668
+ toggledCommentId = comment.id!;
669
+
670
+ return {
671
+ ...comment,
672
+ isLiked: newActive,
673
+ likeCount: newActive ? (comment.likeCount || 0) + 1 : Math.max(0, (comment.likeCount || 0) - 1),
674
+ };
675
+ }
676
+ return comment;
677
+ });
678
+
679
+ this.post.set({ ...currentPost, comments: updatedComments });
680
+
681
+ // Call the like callback
682
+ if (toggledCommentId && this.onToggleCommentLike) {
683
+ this.onToggleCommentLike({
684
+ commentId: toggledCommentId,
685
+ active: newActive,
686
+ });
687
+ }
688
+ break;
689
+ case 'reply':
690
+ // console.log('Reply to comment by', authorName);
691
+ this.handleReply(authorName, content);
692
+ break;
693
+ case 'edit':
694
+ // console.log('Edit comment by', authorName);
695
+ // Find the full comment data to get timestamp
696
+ const commentToEdit = currentPost.comments?.find((comment) => comment.authorName === authorName && comment.content === content);
697
+ if (commentToEdit) {
698
+ this.handleEditComment(commentToEdit);
699
+ }
700
+ break;
701
+ case 'delete':
702
+ // console.log('Delete comment by', authorName);
703
+ // Show confirmation before deleting
704
+ if (confirm('Are you sure you want to delete this comment?')) {
705
+ const commentToDelete = currentPost.comments?.find((comment) => comment.authorName === authorName && comment.content === content);
706
+ const updatedCommentsAfterDelete = currentPost.comments?.filter((comment) => !(comment.authorName === authorName && comment.content === content));
707
+ this.post.set({
708
+ ...currentPost,
709
+ comments: updatedCommentsAfterDelete,
710
+ commentCount: updatedCommentsAfterDelete?.length || 0,
711
+ });
712
+
713
+ // Call the delete callback
714
+ if (commentToDelete?.id && this.onDeleteComment) {
715
+ this.onDeleteComment({ commentId: commentToDelete.id });
716
+ }
717
+ }
718
+ break;
719
+ }
720
+ }
721
+ }
722
+ }