@product7/feedback-sdk 1.5.4 → 1.5.7

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.
@@ -4,6 +4,17 @@ export class HelpView {
4
4
  this.options = options;
5
5
  this.element = null;
6
6
  this._unsubscribe = null;
7
+
8
+ this._avatarColors = [
9
+ { bg: '#EF4444', text: '#FFFFFF' },
10
+ { bg: '#F97316', text: '#FFFFFF' },
11
+ { bg: '#F59E0B', text: '#FFFFFF' },
12
+ { bg: '#10B981', text: '#FFFFFF' },
13
+ { bg: '#06B6D4', text: '#FFFFFF' },
14
+ { bg: '#3B82F6', text: '#FFFFFF' },
15
+ { bg: '#8B5CF6', text: '#FFFFFF' },
16
+ { bg: '#EC4899', text: '#FFFFFF' },
17
+ ];
7
18
  }
8
19
 
9
20
  render() {
@@ -29,8 +40,10 @@ export class HelpView {
29
40
  <div class="messenger-help-header-top">
30
41
  <h2>Help</h2>
31
42
  <button class="sdk-close-btn" aria-label="Close">
32
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 256 256">
33
- <path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
43
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="18" height="18">
44
+ <rect width="256" height="256" fill="none"/>
45
+ <line x1="200" y1="56" x2="56" y2="200" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
46
+ <line x1="200" y1="200" x2="56" y2="56" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
34
47
  </svg>
35
48
  </button>
36
49
  </div>
@@ -58,7 +71,7 @@ export class HelpView {
58
71
  const collections = this.state.helpArticles || [];
59
72
  const searchQuery = (this.state.helpSearchQuery || '').toLowerCase();
60
73
 
61
- const filteredCollections = searchQuery
74
+ const filtered = searchQuery
62
75
  ? collections.filter(
63
76
  (c) =>
64
77
  c.title.toLowerCase().includes(searchQuery) ||
@@ -66,41 +79,119 @@ export class HelpView {
66
79
  )
67
80
  : collections;
68
81
 
69
- const headerEl = this.element.querySelector(
70
- '.messenger-help-collections-header'
71
- );
72
- if (headerEl) {
73
- headerEl.textContent = `${filteredCollections.length} collections`;
74
- }
75
-
76
- if (filteredCollections.length === 0) {
82
+ if (filtered.length === 0) {
77
83
  collectionsContainer.innerHTML = this._renderEmptyState();
78
84
  return;
79
85
  }
80
86
 
81
- collectionsContainer.innerHTML = filteredCollections
82
- .map((collection) => this._renderCollectionItem(collection))
87
+ collectionsContainer.innerHTML = filtered
88
+ .map((c) => this._renderCollectionItem(c))
83
89
  .join('');
84
90
 
91
+ // Resolve Phosphor icons async after render
92
+ filtered.forEach((c) => {
93
+ if (c.icon && !c.icon.startsWith('<svg')) {
94
+ const item = collectionsContainer.querySelector(
95
+ `[data-collection-id="${c.id}"] .messenger-help-collection-icon`
96
+ );
97
+ if (item) this._resolvePhosphorIcon(item, c.icon);
98
+ }
99
+ });
100
+
85
101
  this._attachCollectionEvents();
86
102
  }
87
103
 
88
104
  _renderCollectionItem(collection) {
89
- const articleCount = collection.articleCount || 0;
105
+ const count = collection.article_count || collection.articleCount || 0;
106
+ const icon = this._renderIcon(collection.icon);
107
+ const avatar = this._renderAuthorAvatar(collection);
108
+
90
109
  return `
91
110
  <div class="messenger-help-collection" data-collection-id="${collection.id}">
92
- <div class="messenger-help-collection-content">
111
+ <div class="messenger-help-collection-header">
112
+ <div class="messenger-help-collection-icon">${icon}</div>
93
113
  <h3 class="messenger-help-collection-title">${collection.title}</h3>
94
- <p class="messenger-help-collection-desc">${collection.description || ''}</p>
95
- <span class="messenger-help-collection-count">${articleCount} articles</span>
96
114
  </div>
97
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256" class="messenger-help-collection-arrow">
98
- <path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"></path>
99
- </svg>
115
+ ${
116
+ collection.description
117
+ ? `<p class="messenger-help-collection-desc">${collection.description}</p>`
118
+ : ''
119
+ }
120
+ <div class="messenger-help-collection-footer">
121
+ ${avatar}
122
+ <span>${count} ${count === 1 ? 'article' : 'articles'}</span>
123
+ </div>
124
+ </div>
125
+ `;
126
+ }
127
+
128
+ _renderIcon(icon) {
129
+ if (!icon) return this._getFolderIcon();
130
+ if (icon.startsWith('<svg')) return icon;
131
+ // Iconify string — render placeholder, then fetch async
132
+ return this._getFolderIcon();
133
+ }
134
+
135
+ async _resolvePhosphorIcon(el, iconStr) {
136
+ // iconStr e.g. "ph:house-line-duotone"
137
+ const [set, name] = iconStr.split(':');
138
+ if (!set || !name) return;
139
+ try {
140
+ const res = await fetch(`https://api.iconify.design/${set}/${name}.svg`);
141
+ if (!res.ok) return;
142
+ const svg = await res.text();
143
+ if (svg && svg.startsWith('<svg')) {
144
+ el.innerHTML = svg;
145
+ const s = el.querySelector('svg');
146
+ if (s) {
147
+ s.setAttribute('width', '16');
148
+ s.setAttribute('height', '16');
149
+ }
150
+ }
151
+ } catch (e) {
152
+ // keep fallback folder icon
153
+ }
154
+ }
155
+
156
+ _getFolderIcon() {
157
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="20" height="20">
158
+ <rect width="256" height="256" fill="none"/>
159
+ <path d="M216,208H40a8,8,0,0,1-8-8V64a8,8,0,0,1,8-8H93.33a8,8,0,0,1,4.8,1.6l27.74,20.8a8,8,0,0,0,4.8,1.6H216a8,8,0,0,1,8,8V200A8,8,0,0,1,216,208Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
160
+ </svg>`;
161
+ }
162
+
163
+ _renderAuthorAvatar(collection) {
164
+ const author = collection.author;
165
+ if (!author) return '';
166
+
167
+ if (author.picture) {
168
+ return `<img class="messenger-help-author-avatar" src="${author.picture}" alt="${author.name}" />`;
169
+ }
170
+
171
+ const color = this._getAvatarColor(collection.id);
172
+ const initials = this._getInitials(author.name);
173
+ return `
174
+ <div class="messenger-help-author-avatar messenger-help-author-initials"
175
+ style="background:${color.bg};color:${color.text}">
176
+ ${initials}
100
177
  </div>
101
178
  `;
102
179
  }
103
180
 
181
+ _getAvatarColor(id) {
182
+ const hash = id.split('').reduce((acc, ch) => acc + ch.charCodeAt(0), 0);
183
+ return this._avatarColors[hash % this._avatarColors.length];
184
+ }
185
+
186
+ _getInitials(name) {
187
+ return name
188
+ .split(' ')
189
+ .map((n) => n[0])
190
+ .join('')
191
+ .toUpperCase()
192
+ .slice(0, 2);
193
+ }
194
+
104
195
  _renderEmptyState() {
105
196
  const isSearching = this.state.helpSearchQuery;
106
197
 
@@ -171,9 +262,7 @@ export class HelpView {
171
262
  }
172
263
 
173
264
  destroy() {
174
- if (this._unsubscribe) {
175
- this._unsubscribe();
176
- }
265
+ if (this._unsubscribe) this._unsubscribe();
177
266
  if (this.element && this.element.parentNode) {
178
267
  this.element.parentNode.removeChild(this.element);
179
268
  }