@feedlog-ai/webcomponents 0.0.9 → 0.0.11
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.
- package/dist/cjs/feedlog-badge.cjs.entry.js +21 -0
- package/dist/cjs/feedlog-button_2.cjs.entry.js +101 -0
- package/dist/cjs/feedlog-github-issues-client.cjs.entry.js +55 -5
- package/dist/cjs/feedlog-github-issues.cjs.entry.js +5 -2
- package/dist/cjs/feedlog-issues-list.cjs.entry.js +66 -0
- package/dist/cjs/feedlog-toolkit.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/feedlog-github-issues/feedlog-github-issues.css +36 -1
- package/dist/collection/components/feedlog-github-issues/feedlog-github-issues.js +7 -4
- package/dist/collection/components/feedlog-github-issues-client/feedlog-github-issues-client.js +55 -5
- package/dist/collection/components/feedlog-issue/feedlog-issue.css +205 -0
- package/dist/collection/components/feedlog-issue/feedlog-issue.js +137 -0
- package/dist/collection/components/feedlog-issue/feedlog-issue.stories.js +113 -0
- package/dist/collection/components/feedlog-issues-list/feedlog-issues-list.css +64 -55
- package/dist/collection/components/feedlog-issues-list/feedlog-issues-list.js +6 -6
- package/dist/collection/components/index.js +1 -0
- package/dist/components/feedlog-github-issues-client.js +1 -1
- package/dist/components/feedlog-github-issues.js +1 -1
- package/dist/components/feedlog-issue.d.ts +11 -0
- package/dist/components/feedlog-issue.js +1 -0
- package/dist/components/feedlog-issues-list.js +1 -1
- package/dist/components/p-5qPAHrMz.js +1 -0
- package/dist/components/p-DaNa3wCt.js +1 -0
- package/dist/esm/feedlog-badge.entry.js +19 -0
- package/dist/esm/feedlog-button_2.entry.js +98 -0
- package/dist/esm/feedlog-github-issues-client.entry.js +55 -5
- package/dist/esm/feedlog-github-issues.entry.js +5 -2
- package/dist/esm/feedlog-issues-list.entry.js +64 -0
- package/dist/esm/feedlog-toolkit.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/feedlog-toolkit/feedlog-toolkit.esm.js +1 -1
- package/dist/feedlog-toolkit/p-386ab9fb.entry.js +1 -0
- package/dist/feedlog-toolkit/p-5df44120.entry.js +1 -0
- package/dist/feedlog-toolkit/{p-964cfcd8.entry.js → p-767ecb94.entry.js} +1 -1
- package/dist/feedlog-toolkit/p-95fea2f4.entry.js +1 -0
- package/dist/feedlog-toolkit/p-f172074f.entry.js +1 -0
- package/dist/types/components/feedlog-github-issues/feedlog-github-issues.d.ts +4 -3
- package/dist/types/components/feedlog-github-issues-client/feedlog-github-issues-client.d.ts +9 -2
- package/dist/types/components/feedlog-issue/feedlog-issue.d.ts +31 -0
- package/dist/types/components/feedlog-issue/feedlog-issue.stories.d.ts +12 -0
- package/dist/types/components/feedlog-issues-list/feedlog-issues-list.d.ts +4 -4
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/components.d.ts +85 -11
- package/package.json +2 -2
- package/dist/cjs/feedlog-badge_3.cjs.entry.js +0 -119
- package/dist/components/p-C7AZiNqt.js +0 -1
- package/dist/components/p-rh0Uv7Ks.js +0 -1
- package/dist/esm/feedlog-badge_3.entry.js +0 -115
- package/dist/feedlog-toolkit/p-4874f7e8.entry.js +0 -1
- package/dist/feedlog-toolkit/p-f16f2491.entry.js +0 -1
package/dist/collection/components/feedlog-github-issues-client/feedlog-github-issues-client.js
CHANGED
|
@@ -23,16 +23,29 @@ export class FeedlogGithubIssuesClient {
|
|
|
23
23
|
this.hasMore = false;
|
|
24
24
|
this.isLoadingMore = false;
|
|
25
25
|
this.sdk = null;
|
|
26
|
+
/** Counter to track fetch operations and prevent stale updates */
|
|
27
|
+
this.fetchRequestId = 0;
|
|
28
|
+
/** Flag to prevent state updates after component disconnect */
|
|
29
|
+
this.isDisconnected = false;
|
|
30
|
+
/** Map to track the latest upvote request ID for each issue to handle race conditions */
|
|
31
|
+
this.upvoteRequestIds = new Map();
|
|
26
32
|
this.handleUpvote = async (event) => {
|
|
27
|
-
if (!this.sdk) {
|
|
33
|
+
if (!this.sdk || this.isDisconnected) {
|
|
28
34
|
return;
|
|
29
35
|
}
|
|
30
36
|
const { issueId, currentUpvoted, currentCount } = event.detail;
|
|
37
|
+
// Track request to handle race conditions
|
|
38
|
+
const requestId = (this.upvoteRequestIds.get(issueId) || 0) + 1;
|
|
39
|
+
this.upvoteRequestIds.set(issueId, requestId);
|
|
31
40
|
// Optimistic update
|
|
32
41
|
this.issues = this.issues.map(issue => issue.id === issueId
|
|
33
42
|
? Object.assign(Object.assign({}, issue), { hasUpvoted: !currentUpvoted, upvoteCount: currentUpvoted ? currentCount - 1 : currentCount + 1 }) : issue);
|
|
34
43
|
try {
|
|
35
44
|
const result = await this.sdk.toggleUpvote(issueId);
|
|
45
|
+
// Ignore if component disconnected or request is stale
|
|
46
|
+
if (this.isDisconnected || this.upvoteRequestIds.get(issueId) !== requestId) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
36
49
|
// Update with server response
|
|
37
50
|
this.issues = this.issues.map(issue => issue.id === issueId
|
|
38
51
|
? Object.assign(Object.assign({}, issue), { hasUpvoted: result.upvoted, upvoteCount: result.upvoteCount }) : issue);
|
|
@@ -43,6 +56,10 @@ export class FeedlogGithubIssuesClient {
|
|
|
43
56
|
});
|
|
44
57
|
}
|
|
45
58
|
catch (err) {
|
|
59
|
+
// Ignore if component disconnected or request is stale
|
|
60
|
+
if (this.isDisconnected || this.upvoteRequestIds.get(issueId) !== requestId) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
46
63
|
// Revert optimistic update on error
|
|
47
64
|
this.issues = this.issues.map(issue => issue.id === issueId
|
|
48
65
|
? Object.assign(Object.assign({}, issue), { hasUpvoted: currentUpvoted, upvoteCount: currentCount }) : issue);
|
|
@@ -57,11 +74,18 @@ export class FeedlogGithubIssuesClient {
|
|
|
57
74
|
this.initializeSDK();
|
|
58
75
|
this.fetchIssues();
|
|
59
76
|
}
|
|
77
|
+
disconnectedCallback() {
|
|
78
|
+
// Prevent any pending async operations from updating state
|
|
79
|
+
this.isDisconnected = true;
|
|
80
|
+
this.fetchRequestId++;
|
|
81
|
+
}
|
|
60
82
|
componentDidUpdate() {
|
|
61
83
|
// Re-fetch if any props changed
|
|
62
84
|
const typeChanged = this.previousType !== this.type;
|
|
63
85
|
const limitChanged = this.previousLimit !== this.limit;
|
|
64
86
|
if (typeChanged || limitChanged) {
|
|
87
|
+
// Invalidate any in-flight requests
|
|
88
|
+
this.fetchRequestId++;
|
|
65
89
|
// Reset pagination when filters change
|
|
66
90
|
this.cursor = null;
|
|
67
91
|
this.hasMore = false;
|
|
@@ -89,6 +113,8 @@ export class FeedlogGithubIssuesClient {
|
|
|
89
113
|
if (!this.sdk) {
|
|
90
114
|
return;
|
|
91
115
|
}
|
|
116
|
+
// Capture current request ID to detect stale responses
|
|
117
|
+
const currentRequestId = this.fetchRequestId;
|
|
92
118
|
try {
|
|
93
119
|
this.loading = true;
|
|
94
120
|
this.error = null;
|
|
@@ -103,11 +129,19 @@ export class FeedlogGithubIssuesClient {
|
|
|
103
129
|
params.cursor = this.cursor;
|
|
104
130
|
}
|
|
105
131
|
const response = await this.sdk.fetchIssues(params);
|
|
132
|
+
// Ignore response if component disconnected or a newer request was made
|
|
133
|
+
if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
106
136
|
this.issues = response.issues;
|
|
107
137
|
this.cursor = response.pagination.cursor;
|
|
108
138
|
this.hasMore = response.pagination.hasMore;
|
|
109
139
|
}
|
|
110
140
|
catch (err) {
|
|
141
|
+
// Ignore errors from stale requests
|
|
142
|
+
if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
111
145
|
const errorMsg = err instanceof Error ? err.message : 'Failed to fetch issues';
|
|
112
146
|
this.error = errorMsg;
|
|
113
147
|
this.issues = [];
|
|
@@ -117,14 +151,19 @@ export class FeedlogGithubIssuesClient {
|
|
|
117
151
|
});
|
|
118
152
|
}
|
|
119
153
|
finally {
|
|
120
|
-
|
|
121
|
-
this.
|
|
154
|
+
// Only update loading state if this is still the current request
|
|
155
|
+
if (!this.isDisconnected && currentRequestId === this.fetchRequestId) {
|
|
156
|
+
this.loading = false;
|
|
157
|
+
this.isLoadingMore = false;
|
|
158
|
+
}
|
|
122
159
|
}
|
|
123
160
|
}
|
|
124
161
|
async loadMore() {
|
|
125
162
|
if (!this.sdk || !this.hasMore || this.isLoadingMore || this.loading) {
|
|
126
163
|
return;
|
|
127
164
|
}
|
|
165
|
+
// Capture current request ID to detect stale responses
|
|
166
|
+
const currentRequestId = this.fetchRequestId;
|
|
128
167
|
this.isLoadingMore = true;
|
|
129
168
|
try {
|
|
130
169
|
const params = {};
|
|
@@ -138,11 +177,19 @@ export class FeedlogGithubIssuesClient {
|
|
|
138
177
|
params.cursor = this.cursor;
|
|
139
178
|
}
|
|
140
179
|
const response = await this.sdk.fetchIssues(params);
|
|
180
|
+
// Ignore response if component disconnected or a newer request was made
|
|
181
|
+
if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
141
184
|
this.issues = [...this.issues, ...response.issues];
|
|
142
185
|
this.cursor = response.pagination.cursor;
|
|
143
186
|
this.hasMore = response.pagination.hasMore;
|
|
144
187
|
}
|
|
145
188
|
catch (err) {
|
|
189
|
+
// Ignore errors from stale requests
|
|
190
|
+
if (this.isDisconnected || currentRequestId !== this.fetchRequestId) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
146
193
|
const errorMsg = err instanceof Error ? err.message : 'Failed to load more issues';
|
|
147
194
|
this.feedlogError.emit({
|
|
148
195
|
error: errorMsg,
|
|
@@ -150,11 +197,14 @@ export class FeedlogGithubIssuesClient {
|
|
|
150
197
|
});
|
|
151
198
|
}
|
|
152
199
|
finally {
|
|
153
|
-
this
|
|
200
|
+
// Only update loading state if this is still the current request
|
|
201
|
+
if (!this.isDisconnected && currentRequestId === this.fetchRequestId) {
|
|
202
|
+
this.isLoadingMore = false;
|
|
203
|
+
}
|
|
154
204
|
}
|
|
155
205
|
}
|
|
156
206
|
render() {
|
|
157
|
-
return (h("feedlog-github-issues", { key: '
|
|
207
|
+
return (h("feedlog-github-issues", { key: 'bb63dd29e99dfd9418b6cb12a1f45dff086378ab', issues: this.issues, maxWidth: this.maxWidth, theme: this.theme, heading: this.heading, subtitle: this.subtitle, loading: this.loading, error: this.error, hasMore: this.hasMore, isLoadingMore: this.isLoadingMore, onFeedlogUpvote: this.handleUpvote, onFeedlogLoadMore: async () => this.loadMore() }));
|
|
158
208
|
}
|
|
159
209
|
static get is() { return "feedlog-github-issues-client"; }
|
|
160
210
|
static get encapsulation() { return "shadow"; }
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
4
|
+
|
|
5
|
+
/* Light theme defaults */
|
|
6
|
+
--feedlog-background: #ffffff;
|
|
7
|
+
--feedlog-foreground: oklch(0.145 0 0);
|
|
8
|
+
--feedlog-card: #ffffff;
|
|
9
|
+
--feedlog-card-foreground: oklch(0.145 0 0);
|
|
10
|
+
--feedlog-muted: #ececf0;
|
|
11
|
+
--feedlog-muted-foreground: #717182;
|
|
12
|
+
--feedlog-border: rgba(0, 0, 0, 0.1);
|
|
13
|
+
--feedlog-accent-color: #2563eb;
|
|
14
|
+
--feedlog-destructive: #d4183d;
|
|
15
|
+
--feedlog-blue-400: oklch(0.707 0.165 254.624);
|
|
16
|
+
--feedlog-blue-600: oklch(0.546 0.245 262.881);
|
|
17
|
+
--feedlog-blue-100: oklch(0.932 0.032 255.585);
|
|
18
|
+
--feedlog-red-100: #fce7f3;
|
|
19
|
+
--feedlog-red-400: #f472b6;
|
|
20
|
+
--feedlog-red-600: #db2777;
|
|
21
|
+
--feedlog-radius: 0.625rem;
|
|
22
|
+
--feedlog-gap: 0.5rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
:host(.dark) {
|
|
26
|
+
/* Dark theme values */
|
|
27
|
+
--feedlog-background: oklch(0.145 0 0);
|
|
28
|
+
--feedlog-foreground: oklch(0.985 0 0);
|
|
29
|
+
--feedlog-card: oklch(0.145 0 0);
|
|
30
|
+
--feedlog-card-foreground: oklch(0.985 0 0);
|
|
31
|
+
--feedlog-muted: oklch(0.269 0 0);
|
|
32
|
+
--feedlog-muted-foreground: oklch(0.708 0 0);
|
|
33
|
+
--feedlog-border: oklch(0.269 0 0);
|
|
34
|
+
--feedlog-accent-color: #3b82f6;
|
|
35
|
+
--feedlog-destructive: oklch(0.396 0.141 25.723);
|
|
36
|
+
--feedlog-blue-400: oklch(0.707 0.165 254.624);
|
|
37
|
+
--feedlog-blue-600: oklch(0.546 0.245 262.881);
|
|
38
|
+
--feedlog-blue-900-30: color-mix(in oklab, oklch(0.379 0.146 265.522) 30%, transparent);
|
|
39
|
+
--feedlog-red-900-30: color-mix(in oklab, oklch(0.396 0.141 25.723) 30%, transparent);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.issue-card {
|
|
43
|
+
background-color: var(--feedlog-card);
|
|
44
|
+
border: 1px solid var(--feedlog-border);
|
|
45
|
+
border-radius: var(--feedlog-radius);
|
|
46
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
|
47
|
+
transition: box-shadow 0.15s ease;
|
|
48
|
+
position: relative;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.issue-card:hover {
|
|
52
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.issue-content {
|
|
56
|
+
padding: 1.5rem;
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: 1rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.issue-header {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: 0.5rem;
|
|
66
|
+
justify-content: space-between;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.issue-type-badge {
|
|
70
|
+
width: fit-content;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.pinned-indicator {
|
|
74
|
+
font-size: 1rem;
|
|
75
|
+
opacity: 0.7;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.issue-main {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: flex-start;
|
|
81
|
+
justify-content: space-between;
|
|
82
|
+
gap: 1rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.issue-details {
|
|
86
|
+
flex: 1;
|
|
87
|
+
min-width: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.issue-title {
|
|
91
|
+
color: var(--feedlog-card-foreground);
|
|
92
|
+
font-size: 0.875rem;
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
margin: 0 0 0.375rem 0;
|
|
95
|
+
line-height: 1.4;
|
|
96
|
+
word-break: break-word;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.issue-body {
|
|
100
|
+
color: var(--feedlog-muted-foreground);
|
|
101
|
+
font-size: 0.75rem;
|
|
102
|
+
line-height: 1.625;
|
|
103
|
+
margin: 0 0 0.75rem 0;
|
|
104
|
+
word-break: break-word;
|
|
105
|
+
white-space: pre-wrap;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.issue-repository {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: 0.5rem;
|
|
112
|
+
font-size: 0.75rem;
|
|
113
|
+
color: var(--feedlog-muted-foreground);
|
|
114
|
+
margin-bottom: 0.5rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.repo-name {
|
|
118
|
+
font-weight: 500;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.github-number {
|
|
122
|
+
color: var(--feedlog-blue-600);
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
:host(.dark) .github-number {
|
|
127
|
+
color: var(--feedlog-blue-400);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.upvote-button {
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 0.125rem;
|
|
135
|
+
padding: 0.5rem 0.75rem;
|
|
136
|
+
border-radius: 0.5rem;
|
|
137
|
+
background-color: var(--feedlog-muted);
|
|
138
|
+
border: 1px solid transparent;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
transition: all 0.15s ease;
|
|
141
|
+
flex-shrink: 0;
|
|
142
|
+
font-size: 0.75rem;
|
|
143
|
+
font-weight: 600;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.upvote-button:hover {
|
|
147
|
+
background-color: var(--feedlog-blue-100);
|
|
148
|
+
border-color: var(--feedlog-blue-400);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.upvote-button.upvoted {
|
|
152
|
+
background-color: var(--feedlog-red-100);
|
|
153
|
+
border-color: var(--feedlog-red-400);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.upvote-button.upvoted:hover {
|
|
157
|
+
background-color: var(--feedlog-red-100);
|
|
158
|
+
border-color: var(--feedlog-red-600);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
:host(.dark) .upvote-button:hover {
|
|
162
|
+
background-color: var(--feedlog-blue-900-30);
|
|
163
|
+
border-color: var(--feedlog-blue-600);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
:host(.dark) .upvote-button.upvoted {
|
|
167
|
+
background-color: var(--feedlog-red-900-30);
|
|
168
|
+
border-color: var(--feedlog-red-600);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.upvote-icon {
|
|
172
|
+
width: 1rem;
|
|
173
|
+
height: 1rem;
|
|
174
|
+
stroke-width: 2;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.upvote-icon.filled {
|
|
178
|
+
color: var(--feedlog-red-600);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.upvote-icon.outline {
|
|
182
|
+
color: var(--feedlog-blue-600);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
:host(.dark) .upvote-icon.outline {
|
|
186
|
+
color: var(--feedlog-blue-400);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.upvote-count {
|
|
190
|
+
font-size: 0.75rem;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
color: var(--feedlog-card-foreground);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.issue-footer {
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-direction: column;
|
|
198
|
+
gap: 0.25rem;
|
|
199
|
+
font-size: 0.75rem;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.issue-date {
|
|
203
|
+
color: var(--feedlog-muted-foreground);
|
|
204
|
+
cursor: help;
|
|
205
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { h, Host } from "@stencil/core";
|
|
2
|
+
/**
|
|
3
|
+
* Heart icon SVG component (filled)
|
|
4
|
+
*/
|
|
5
|
+
const HeartFilledIcon = () => (h("svg", { class: "upvote-icon filled", xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none" }, h("path", { d: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" })));
|
|
6
|
+
/**
|
|
7
|
+
* Heart icon SVG component (outline)
|
|
8
|
+
*/
|
|
9
|
+
const HeartOutlineIcon = () => (h("svg", { class: "upvote-icon outline", xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" })));
|
|
10
|
+
/**
|
|
11
|
+
* Feedlog Issue Component
|
|
12
|
+
*
|
|
13
|
+
* A component for displaying a single GitHub issue.
|
|
14
|
+
*/
|
|
15
|
+
export class FeedlogIssueComponent {
|
|
16
|
+
constructor() {
|
|
17
|
+
/**
|
|
18
|
+
* Theme variant: 'light' or 'dark'
|
|
19
|
+
*/
|
|
20
|
+
this.theme = 'light';
|
|
21
|
+
this.handleUpvote = (event) => {
|
|
22
|
+
event.stopPropagation();
|
|
23
|
+
this.feedlogUpvote.emit({
|
|
24
|
+
issueId: this.issue.id,
|
|
25
|
+
currentUpvoted: this.issue.hasUpvoted,
|
|
26
|
+
currentCount: this.issue.upvoteCount,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Format an ISO date string to a relative time string
|
|
32
|
+
*/
|
|
33
|
+
formatDate(dateString) {
|
|
34
|
+
try {
|
|
35
|
+
const date = new Date(dateString);
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
|
38
|
+
if (seconds < 60)
|
|
39
|
+
return 'just now';
|
|
40
|
+
if (seconds < 3600)
|
|
41
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
42
|
+
if (seconds < 86400)
|
|
43
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
44
|
+
if (seconds < 604800)
|
|
45
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
46
|
+
if (seconds < 2592000)
|
|
47
|
+
return `${Math.floor(seconds / 604800)}w ago`;
|
|
48
|
+
return date.toLocaleDateString();
|
|
49
|
+
}
|
|
50
|
+
catch (_a) {
|
|
51
|
+
return 'unknown date';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
render() {
|
|
55
|
+
const { issue } = this;
|
|
56
|
+
if (!issue)
|
|
57
|
+
return null;
|
|
58
|
+
return (h(Host, { class: this.theme === 'dark' ? 'dark' : '' }, h("div", { class: "issue-card" }, h("div", { class: "issue-content" }, h("div", { class: "issue-header" }, h("div", { class: "issue-type-badge" }, issue.type === 'bug' ? (h("feedlog-badge", { variant: "destructive" }, "Bug")) : (h("feedlog-badge", { variant: "enhancement" }, "Enhancement"))), issue.pinnedAt && (h("div", { class: "pinned-indicator", title: "Pinned issue" }, "\uD83D\uDCCC"))), h("div", { class: "issue-main" }, h("div", { class: "issue-details" }, h("h3", { class: "issue-title" }, issue.title), h("p", { class: "issue-body" }, issue.body), h("div", { class: "issue-repository" }, h("span", { class: "repo-name" }, issue.repository.owner, "/", issue.repository.name))), issue.type !== 'bug' && (h("button", { class: `upvote-button ${issue.hasUpvoted ? 'upvoted' : ''}`, onClick: (e) => this.handleUpvote(e), title: issue.hasUpvoted ? 'Remove upvote' : 'Upvote this issue' }, issue.hasUpvoted ? h(HeartFilledIcon, null) : h(HeartOutlineIcon, null), h("span", { class: "upvote-count" }, issue.upvoteCount)))), h("div", { class: "issue-footer" }, h("span", { class: "issue-date", title: `Updated: ${issue.updatedAt}` }, "Updated ", this.formatDate(issue.updatedAt)), h("span", { class: "issue-date", title: `Created: ${issue.createdAt}` }, "Created ", this.formatDate(issue.createdAt)))))));
|
|
59
|
+
}
|
|
60
|
+
static get is() { return "feedlog-issue"; }
|
|
61
|
+
static get encapsulation() { return "shadow"; }
|
|
62
|
+
static get originalStyleUrls() {
|
|
63
|
+
return {
|
|
64
|
+
"$": ["feedlog-issue.css"]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
static get styleUrls() {
|
|
68
|
+
return {
|
|
69
|
+
"$": ["feedlog-issue.css"]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
static get properties() {
|
|
73
|
+
return {
|
|
74
|
+
"issue": {
|
|
75
|
+
"type": "unknown",
|
|
76
|
+
"mutable": false,
|
|
77
|
+
"complexType": {
|
|
78
|
+
"original": "FeedlogIssueType",
|
|
79
|
+
"resolved": "FeedlogIssue",
|
|
80
|
+
"references": {
|
|
81
|
+
"FeedlogIssueType": {
|
|
82
|
+
"location": "import",
|
|
83
|
+
"path": "@feedlog-ai/core",
|
|
84
|
+
"id": "../core/dist/index.d.ts::FeedlogIssue"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"required": true,
|
|
89
|
+
"optional": false,
|
|
90
|
+
"docs": {
|
|
91
|
+
"tags": [],
|
|
92
|
+
"text": "The issue to display"
|
|
93
|
+
},
|
|
94
|
+
"getter": false,
|
|
95
|
+
"setter": false
|
|
96
|
+
},
|
|
97
|
+
"theme": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"mutable": false,
|
|
100
|
+
"complexType": {
|
|
101
|
+
"original": "'light' | 'dark'",
|
|
102
|
+
"resolved": "\"dark\" | \"light\"",
|
|
103
|
+
"references": {}
|
|
104
|
+
},
|
|
105
|
+
"required": false,
|
|
106
|
+
"optional": false,
|
|
107
|
+
"docs": {
|
|
108
|
+
"tags": [],
|
|
109
|
+
"text": "Theme variant: 'light' or 'dark'"
|
|
110
|
+
},
|
|
111
|
+
"getter": false,
|
|
112
|
+
"setter": false,
|
|
113
|
+
"reflect": false,
|
|
114
|
+
"attribute": "theme",
|
|
115
|
+
"defaultValue": "'light'"
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
static get events() {
|
|
120
|
+
return [{
|
|
121
|
+
"method": "feedlogUpvote",
|
|
122
|
+
"name": "feedlogUpvote",
|
|
123
|
+
"bubbles": true,
|
|
124
|
+
"cancelable": true,
|
|
125
|
+
"composed": true,
|
|
126
|
+
"docs": {
|
|
127
|
+
"tags": [],
|
|
128
|
+
"text": "Event emitted when the issue is upvoted"
|
|
129
|
+
},
|
|
130
|
+
"complexType": {
|
|
131
|
+
"original": "{\n issueId: string;\n currentUpvoted: boolean;\n currentCount: number;\n }",
|
|
132
|
+
"resolved": "{ issueId: string; currentUpvoted: boolean; currentCount: number; }",
|
|
133
|
+
"references": {}
|
|
134
|
+
}
|
|
135
|
+
}];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { h } from "@stencil/core";
|
|
2
|
+
const sampleBugIssue = {
|
|
3
|
+
id: 'issue-bug-1',
|
|
4
|
+
title: 'Charts not rendering on mobile',
|
|
5
|
+
body: 'The chart components are not properly responsive on smaller screens. They overflow the container and break the layout.',
|
|
6
|
+
type: 'bug',
|
|
7
|
+
status: 'open',
|
|
8
|
+
pinnedAt: null,
|
|
9
|
+
revision: 1,
|
|
10
|
+
repository: {
|
|
11
|
+
id: 'repo-1',
|
|
12
|
+
name: 'feedlog-toolkit',
|
|
13
|
+
owner: 'feedlog',
|
|
14
|
+
},
|
|
15
|
+
updatedAt: new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString(), // 5 hours ago
|
|
16
|
+
createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), // 1 week ago
|
|
17
|
+
upvoteCount: 3,
|
|
18
|
+
hasUpvoted: false,
|
|
19
|
+
};
|
|
20
|
+
const sampleEnhancementIssue = {
|
|
21
|
+
id: 'issue-enhancement-1',
|
|
22
|
+
title: 'Add dark mode support',
|
|
23
|
+
body: 'It would be great to have a dark mode option for the dashboard. This would reduce eye strain for users working late at night.',
|
|
24
|
+
type: 'enhancement',
|
|
25
|
+
status: 'open',
|
|
26
|
+
pinnedAt: null,
|
|
27
|
+
revision: 1,
|
|
28
|
+
repository: {
|
|
29
|
+
id: 'repo-1',
|
|
30
|
+
name: 'feedlog-toolkit',
|
|
31
|
+
owner: 'feedlog',
|
|
32
|
+
},
|
|
33
|
+
updatedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), // 2 hours ago
|
|
34
|
+
createdAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(), // 10 days ago
|
|
35
|
+
upvoteCount: 24,
|
|
36
|
+
hasUpvoted: false,
|
|
37
|
+
};
|
|
38
|
+
const sampleUpvotedEnhancementIssue = Object.assign(Object.assign({}, sampleEnhancementIssue), { id: 'issue-enhancement-upvoted', hasUpvoted: true });
|
|
39
|
+
const samplePinnedIssue = Object.assign(Object.assign({}, sampleEnhancementIssue), { id: 'issue-enhancement-pinned', pinnedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString() });
|
|
40
|
+
const meta = {
|
|
41
|
+
title: 'Components/Issue',
|
|
42
|
+
component: 'feedlog-issue',
|
|
43
|
+
parameters: {
|
|
44
|
+
layout: 'centered',
|
|
45
|
+
},
|
|
46
|
+
argTypes: {
|
|
47
|
+
issue: {
|
|
48
|
+
control: 'object',
|
|
49
|
+
description: 'The issue to display',
|
|
50
|
+
},
|
|
51
|
+
theme: {
|
|
52
|
+
control: 'select',
|
|
53
|
+
options: ['light', 'dark'],
|
|
54
|
+
description: 'Theme variant',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
args: {
|
|
58
|
+
issue: sampleEnhancementIssue,
|
|
59
|
+
theme: 'light',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
export default meta;
|
|
63
|
+
export const Default = {
|
|
64
|
+
args: {
|
|
65
|
+
issue: sampleEnhancementIssue,
|
|
66
|
+
},
|
|
67
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
68
|
+
};
|
|
69
|
+
export const Bug = {
|
|
70
|
+
args: {
|
|
71
|
+
issue: sampleBugIssue,
|
|
72
|
+
},
|
|
73
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
74
|
+
};
|
|
75
|
+
export const Upvoted = {
|
|
76
|
+
args: {
|
|
77
|
+
issue: sampleUpvotedEnhancementIssue,
|
|
78
|
+
},
|
|
79
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
80
|
+
};
|
|
81
|
+
export const Pinned = {
|
|
82
|
+
args: {
|
|
83
|
+
issue: samplePinnedIssue,
|
|
84
|
+
},
|
|
85
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
86
|
+
};
|
|
87
|
+
export const DarkTheme = {
|
|
88
|
+
args: {
|
|
89
|
+
issue: sampleEnhancementIssue,
|
|
90
|
+
theme: 'dark',
|
|
91
|
+
},
|
|
92
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
93
|
+
};
|
|
94
|
+
export const DarkThemeBug = {
|
|
95
|
+
args: {
|
|
96
|
+
issue: sampleBugIssue,
|
|
97
|
+
theme: 'dark',
|
|
98
|
+
},
|
|
99
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
100
|
+
};
|
|
101
|
+
export const DarkThemePinned = {
|
|
102
|
+
args: {
|
|
103
|
+
issue: samplePinnedIssue,
|
|
104
|
+
theme: 'dark',
|
|
105
|
+
},
|
|
106
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
107
|
+
};
|
|
108
|
+
export const HighUpvoteCount = {
|
|
109
|
+
args: {
|
|
110
|
+
issue: Object.assign(Object.assign({}, sampleEnhancementIssue), { id: 'issue-high-upvote', upvoteCount: 156 }),
|
|
111
|
+
},
|
|
112
|
+
render: (props) => h("feedlog-issue", Object.assign({}, props)),
|
|
113
|
+
};
|