collavre_github 0.5.0 → 0.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8f3ffe59f1a7e85a1a42fd49cd5fa3ee1ad40667996a69e405fef0bb8415318
4
- data.tar.gz: 5a9c05bf70a341ca56253bfa2d6a2849acc335038e42c1b9919747be2d564f7f
3
+ metadata.gz: 6fb4ed5d918d784abb2dbfcaf6618391b4e1f888ccd166844db4bd823dce2e08
4
+ data.tar.gz: 9d569c3485a15ae9a282c2857bf8e30a3928e30f50fe9efa6e1ab13f12d2fe7d
5
5
  SHA512:
6
- metadata.gz: ee53fe7588353302c64f84decc488e62b1d8f9fb9d65f8332439d0bd8d2f353fc8777ff59724c9c9d82390fd5094ab513f796ab5b1666fd456e382fd0d13099b
7
- data.tar.gz: aa439ab76e282b9841abcaea1a84ad0e3ccfaf06214cb8380af7e24661ea93848de8197fd8f69b4108b5a4b609ed2cc8caaae69cc5c59e48f833328a6829b2e6
6
+ metadata.gz: ed515fb1cd7ea7481ec30269b871b792a74c8a30d780abe4fb5473bf478111937f8748a60e4a432928aa7dbe75f8bef8fc8f1f91bacbd809b8b6367012b91f4a
7
+ data.tar.gz: 4ffb6a588103ddf1865539e93bff437d8ead4187354e0c4807707248d6f773bd49245d96cde837b6c02330f508ff4b7108da687f298a73d43e546f0f27419180
@@ -0,0 +1,706 @@
1
+ import { csrfToken, showError, clearError, updateStepVisibility, openOAuthPopup, fetchWithCsrf, setupModalClose } from 'collavre/modules/integration_wizard';
2
+
3
+ let githubIntegrationInitialized = false;
4
+
5
+ if (!githubIntegrationInitialized) {
6
+ githubIntegrationInitialized = true;
7
+
8
+ document.addEventListener('turbo:load', function () {
9
+ const openBtn = document.getElementById('github-integration-btn');
10
+ const modal = document.getElementById('github-integration-modal');
11
+ if (!openBtn || !modal) return;
12
+
13
+ const statusEl = document.getElementById('github-integration-status');
14
+ const loginBtn = document.getElementById('github-login-btn');
15
+ const loginForm = document.getElementById('github-login-form');
16
+ const closeBtn = document.getElementById('close-github-modal');
17
+ const prevBtn = document.getElementById('github-prev-btn');
18
+ const nextBtn = document.getElementById('github-next-btn');
19
+ const finishBtn = document.getElementById('github-finish-btn');
20
+ const orgList = document.getElementById('github-organization-list');
21
+ const repoList = document.getElementById('github-repository-list');
22
+ const summaryList = document.getElementById('github-selected-repos');
23
+ const summaryEmpty = document.getElementById('github-summary-empty');
24
+ const summaryInstructions = document.getElementById('github-webhook-instructions');
25
+
26
+ const errorEl = document.getElementById('github-wizard-error');
27
+ const webhookUrlLabel = modal.dataset.webhookUrlLabel || 'Webhook URL';
28
+ const webhookSecretLabel = modal.dataset.webhookSecretLabel || 'Webhook secret';
29
+ const existingContainer = document.getElementById('github-existing-connections');
30
+ const existingList = document.getElementById('github-existing-repo-list');
31
+ const deleteBtn = document.getElementById('github-delete-btn');
32
+ const connectMessage = document.getElementById('github-connect-message');
33
+ const existingMessage = modal.dataset.existingMessage || '이미 연동된 Repository가 있습니다.';
34
+ const deleteConfirm = modal.dataset.deleteConfirm || 'Github 연동을 삭제하시겠습니까?';
35
+ const deleteSuccess = modal.dataset.deleteSuccess || 'Github 연동이 삭제되었습니다.';
36
+ const deleteError = modal.dataset.deleteError || 'Github 연동을 삭제하지 못했습니다.';
37
+ const deleteSelectWarning = modal.dataset.deleteSelectWarning || '삭제할 Repository를 선택하세요.';
38
+ const resyncBtn = document.getElementById('github-resync-btn');
39
+ const resyncConfirm = modal.dataset.resyncConfirm || '선택한 저장소의 마크다운을 전체 다시 가져오시겠습니까?';
40
+ const resyncSuccess = modal.dataset.resyncSuccess || '재동기화가 시작되었습니다.';
41
+ const resyncError = modal.dataset.resyncError || '재동기화를 시작하지 못했습니다.';
42
+ const resyncSelectWarning = modal.dataset.resyncSelectWarning || '재동기화할 저장소를 선택하세요.';
43
+
44
+ let creativeId = null;
45
+ let currentStep = 'connect';
46
+ let organizations = [];
47
+ let selectedOrg = null;
48
+ let selectedRepos = new Set();
49
+ let markdownSyncRepos = new Set();
50
+ let webhookDetails = {};
51
+
52
+ let hasExistingIntegration = false;
53
+ let selectedReposForDeletion = new Set();
54
+
55
+ const markdownSyncList = document.getElementById('github-markdown-sync-list');
56
+
57
+ function resetWizard() {
58
+ currentStep = 'connect';
59
+ organizations = [];
60
+ selectedOrg = null;
61
+ selectedRepos = new Set();
62
+ markdownSyncRepos = new Set();
63
+ webhookDetails = {};
64
+
65
+ hasExistingIntegration = false;
66
+ selectedReposForDeletion = new Set();
67
+ statusEl.textContent = '';
68
+ errorEl.style.display = 'none';
69
+ errorEl.textContent = '';
70
+ if (summaryInstructions) summaryInstructions.style.display = 'none';
71
+
72
+ if (existingContainer) {
73
+ existingContainer.style.display = 'none';
74
+ }
75
+ if (existingList) {
76
+ existingList.innerHTML = '';
77
+ }
78
+ if (connectMessage) {
79
+ connectMessage.style.display = '';
80
+ }
81
+ if (deleteBtn) deleteBtn.style.display = 'none';
82
+ if (resyncBtn) resyncBtn.style.display = 'none';
83
+ updateDeleteButtonState();
84
+ if (loginBtn) loginBtn.style.display = 'inline-block';
85
+ updateStep();
86
+ }
87
+
88
+ function updateDeleteButtonState() {
89
+ if (deleteBtn) deleteBtn.disabled = selectedReposForDeletion.size === 0;
90
+ if (resyncBtn) resyncBtn.disabled = selectedReposForDeletion.size === 0;
91
+ }
92
+
93
+ function updateStep() {
94
+ ['connect', 'organization', 'repositories', 'markdown-sync', 'summary']
95
+ .forEach(function (step) {
96
+ const el = document.getElementById(`github-step-${step}`);
97
+ if (!el) return;
98
+ el.style.display = (step === currentStep) ? 'block' : 'none';
99
+ });
100
+
101
+ if (currentStep === 'connect') {
102
+ prevBtn.style.display = 'none';
103
+ if (hasExistingIntegration) {
104
+ nextBtn.style.display = 'block';
105
+ nextBtn.disabled = false;
106
+ } else {
107
+ nextBtn.style.display = 'none';
108
+ }
109
+ finishBtn.style.display = 'none';
110
+ } else if (currentStep === 'organization') {
111
+ prevBtn.style.display = 'block';
112
+ nextBtn.style.display = 'block';
113
+ nextBtn.disabled = !selectedOrg;
114
+ finishBtn.style.display = 'none';
115
+ } else if (currentStep === 'repositories') {
116
+ prevBtn.style.display = 'block';
117
+ nextBtn.style.display = 'block';
118
+ nextBtn.disabled = false;
119
+ finishBtn.style.display = 'none';
120
+ } else if (currentStep === 'markdown-sync') {
121
+ prevBtn.style.display = 'block';
122
+ nextBtn.style.display = 'block';
123
+ nextBtn.disabled = false;
124
+ finishBtn.style.display = 'none';
125
+ } else if (currentStep === 'summary') {
126
+ prevBtn.style.display = 'block';
127
+ nextBtn.style.display = 'none';
128
+ finishBtn.style.display = 'block';
129
+ updateSummary();
130
+ }
131
+ }
132
+
133
+ function populateMarkdownSync() {
134
+ if (!markdownSyncList) return;
135
+ markdownSyncList.innerHTML = '';
136
+ const repos = Array.from(selectedRepos);
137
+ if (!repos.length) {
138
+ markdownSyncList.innerHTML = '<p style="padding:0.5em;color:#999;">No repositories selected.</p>';
139
+ return;
140
+ }
141
+ repos.forEach(function (fullName) {
142
+ const label = document.createElement('label');
143
+ label.style.display = 'flex';
144
+ label.style.alignItems = 'center';
145
+ label.style.gap = '0.5em';
146
+ label.style.marginBottom = '0.5em';
147
+ label.style.cursor = 'pointer';
148
+
149
+ const input = document.createElement('input');
150
+ input.type = 'checkbox';
151
+ input.value = fullName;
152
+ input.checked = markdownSyncRepos.has(fullName);
153
+ input.addEventListener('change', function () {
154
+ if (input.checked) {
155
+ markdownSyncRepos.add(fullName);
156
+ } else {
157
+ markdownSyncRepos.delete(fullName);
158
+ }
159
+ });
160
+
161
+ const span = document.createElement('span');
162
+ span.textContent = fullName + ' — Markdown Sync';
163
+
164
+ label.appendChild(input);
165
+ label.appendChild(span);
166
+ markdownSyncList.appendChild(label);
167
+ });
168
+ }
169
+
170
+ function showModal() {
171
+ modal.style.display = 'flex';
172
+ document.body.classList.add('no-scroll');
173
+ }
174
+
175
+ function closeModal() {
176
+ modal.style.display = 'none';
177
+ document.body.classList.remove('no-scroll');
178
+ }
179
+
180
+ function showError(message) {
181
+ if (!message) return;
182
+ errorEl.textContent = message;
183
+ errorEl.style.display = 'block';
184
+ }
185
+
186
+ function clearError() {
187
+ errorEl.textContent = '';
188
+ errorEl.style.display = 'none';
189
+ }
190
+
191
+ function fetchStatus() {
192
+ if (!creativeId) {
193
+ showError(modal.dataset.noCreative);
194
+ return;
195
+ }
196
+ clearError();
197
+ fetch(`/github/creatives/${creativeId}/integration`, { headers: { Accept: 'application/json' } })
198
+ .then(function (response) { return response.json(); })
199
+ .then(function (data) {
200
+ if (!data.connected) {
201
+ var allRepos = data.all_repositories || [];
202
+ if (allRepos.length > 0) {
203
+ // Other users have linked repositories to this creative
204
+ statusEl.textContent = existingMessage;
205
+ hasExistingIntegration = true;
206
+ renderExistingConnections(allRepos, true);
207
+ if (connectMessage) connectMessage.style.display = 'none';
208
+ if (loginBtn) loginBtn.style.display = 'inline-block';
209
+ } else {
210
+ statusEl.textContent = '';
211
+ hasExistingIntegration = false;
212
+ renderExistingConnections([]);
213
+ if (connectMessage) connectMessage.style.display = '';
214
+ if (loginBtn) loginBtn.style.display = 'inline-block';
215
+ }
216
+ currentStep = 'connect';
217
+ updateStep();
218
+ return;
219
+ }
220
+ selectedRepos = new Set(data.selected_repositories || []);
221
+ webhookDetails = data.webhooks || {};
222
+
223
+ // Rehydrate markdownSyncRepos from existing server state
224
+ markdownSyncRepos = new Set();
225
+ var syncData = data.markdown_sync || {};
226
+ Object.keys(syncData).forEach(function (repo) {
227
+ if (syncData[repo] && syncData[repo].enabled) {
228
+ markdownSyncRepos.add(repo);
229
+ }
230
+ });
231
+
232
+ hasExistingIntegration = selectedRepos.size > 0;
233
+
234
+ if (loginBtn) loginBtn.style.display = 'none';
235
+ renderExistingConnections(Array.from(selectedRepos));
236
+
237
+ if (hasExistingIntegration) {
238
+ statusEl.textContent = existingMessage;
239
+ if (connectMessage) connectMessage.style.display = 'none';
240
+ currentStep = 'connect';
241
+ updateStep();
242
+ } else {
243
+ statusEl.textContent = data.account && data.account.login ?
244
+ `${data.account.login} 님의 Github 계정과 연동됩니다.` : '';
245
+ if (connectMessage) connectMessage.style.display = 'none';
246
+ selectedOrg = null;
247
+ organizations = [];
248
+ currentStep = 'organization';
249
+ updateStep();
250
+ loadOrganizations();
251
+ }
252
+ })
253
+ .catch(function () {
254
+ showError('Github 연동 정보를 불러오지 못했습니다.');
255
+ });
256
+ }
257
+
258
+ function renderExistingConnections(repos, readOnly) {
259
+ if (!existingContainer || !existingList) return;
260
+ existingList.innerHTML = '';
261
+ selectedReposForDeletion = new Set();
262
+ if (!repos || !repos.length) {
263
+ existingContainer.style.display = 'none';
264
+ if (deleteBtn) {
265
+ deleteBtn.style.display = 'none';
266
+ updateDeleteButtonState();
267
+ }
268
+ return;
269
+ }
270
+
271
+ repos.forEach(function (fullName) {
272
+ const li = document.createElement('li');
273
+ li.style.display = 'flex';
274
+ li.style.alignItems = 'center';
275
+ li.style.gap = '0.5em';
276
+ li.style.marginBottom = '0.4em';
277
+ li.style.listStyle = 'none';
278
+
279
+ const label = document.createElement('label');
280
+ label.style.display = 'flex';
281
+ label.style.alignItems = 'center';
282
+ label.style.gap = '0.5em';
283
+ label.style.cursor = readOnly ? 'default' : 'pointer';
284
+ label.style.flex = '1';
285
+
286
+ if (!readOnly) {
287
+ const checkbox = document.createElement('input');
288
+ checkbox.type = 'checkbox';
289
+ checkbox.value = fullName;
290
+ checkbox.className = 'github-existing-repo-checkbox';
291
+ checkbox.addEventListener('change', function () {
292
+ if (checkbox.checked) {
293
+ selectedReposForDeletion.add(fullName);
294
+ } else {
295
+ selectedReposForDeletion.delete(fullName);
296
+ }
297
+ updateDeleteButtonState();
298
+ });
299
+ label.appendChild(checkbox);
300
+ }
301
+
302
+ const nameSpan = document.createElement('span');
303
+ nameSpan.textContent = fullName;
304
+ nameSpan.style.flex = '1';
305
+
306
+ label.appendChild(nameSpan);
307
+ li.appendChild(label);
308
+ existingList.appendChild(li);
309
+ });
310
+
311
+ existingContainer.style.display = 'block';
312
+ if (deleteBtn) {
313
+ deleteBtn.style.display = readOnly ? 'none' : 'inline-flex';
314
+ updateDeleteButtonState();
315
+ }
316
+ if (resyncBtn) {
317
+ resyncBtn.style.display = readOnly ? 'none' : 'inline-flex';
318
+ resyncBtn.disabled = true;
319
+ }
320
+ if (loginBtn) loginBtn.style.display = 'none';
321
+ }
322
+
323
+ function loadOrganizations() {
324
+ fetch('/github/account/organizations', { headers: { Accept: 'application/json' } })
325
+ .then(function (response) { return response.json(); })
326
+ .then(function (data) {
327
+ organizations = data.organizations || [];
328
+ renderOrganizations();
329
+ })
330
+ .catch(function () {
331
+ showError('Organization 목록을 불러오지 못했습니다.');
332
+ });
333
+ }
334
+
335
+ function renderOrganizations() {
336
+ if (!orgList) return;
337
+ orgList.innerHTML = '';
338
+ if (!organizations.length) {
339
+ const p = document.createElement('p');
340
+ p.textContent = '조회할 수 있는 Organization이 없습니다.';
341
+ orgList.appendChild(p);
342
+ return;
343
+ }
344
+ organizations.forEach(function (org) {
345
+ const label = document.createElement('label');
346
+ label.style.display = 'flex';
347
+ label.style.alignItems = 'center';
348
+ label.style.gap = '0.5em';
349
+ label.style.marginBottom = '0.5em';
350
+ label.style.cursor = 'pointer';
351
+
352
+ const input = document.createElement('input');
353
+ input.type = 'radio';
354
+ input.name = 'github-organization';
355
+ input.value = org.login;
356
+ input.checked = selectedOrg === org.login;
357
+ input.style.flexShrink = '0';
358
+ input.style.margin = '0';
359
+ input.addEventListener('change', function () {
360
+ selectedOrg = org.login;
361
+ nextBtn.disabled = false;
362
+ });
363
+
364
+ const span = document.createElement('span');
365
+ span.textContent = org.name || org.login;
366
+
367
+ label.appendChild(input);
368
+ label.appendChild(span);
369
+ orgList.appendChild(label);
370
+ });
371
+ nextBtn.disabled = !selectedOrg;
372
+ }
373
+
374
+ function loadRepositories() {
375
+ if (!selectedOrg) return;
376
+ clearError();
377
+ if (repoList) {
378
+ repoList.textContent = '...';
379
+ }
380
+ const params = new URLSearchParams({ organization: selectedOrg });
381
+ if (creativeId) params.append('creative_id', creativeId);
382
+ fetch(`/github/account/repositories?${params.toString()}`, { headers: { Accept: 'application/json' } })
383
+ .then(function (response) { return response.json(); })
384
+ .then(function (data) {
385
+ renderRepositories(data.repositories || []);
386
+ })
387
+ .catch(function () {
388
+ if (repoList) repoList.textContent = '';
389
+ showError('Repository 목록을 불러오지 못했습니다.');
390
+ });
391
+ }
392
+
393
+ function renderRepositories(repositories) {
394
+ if (!repoList) return;
395
+ repoList.innerHTML = '';
396
+ if (!repositories.length) {
397
+ const p = document.createElement('p');
398
+ p.textContent = '선택 가능한 Repository가 없습니다.';
399
+ repoList.appendChild(p);
400
+ return;
401
+ }
402
+ repositories.forEach(function (repo) {
403
+ const label = document.createElement('label');
404
+ label.style.display = 'flex';
405
+ label.style.alignItems = 'center';
406
+ label.style.gap = '0.5em';
407
+ label.style.marginBottom = '0.5em';
408
+ label.style.cursor = 'pointer';
409
+
410
+ const input = document.createElement('input');
411
+ input.type = 'checkbox';
412
+ input.value = repo.full_name;
413
+ input.checked = selectedRepos.has(repo.full_name) || repo.selected;
414
+ input.addEventListener('change', function () {
415
+ if (input.checked) {
416
+ selectedRepos.add(repo.full_name);
417
+ } else {
418
+ selectedRepos.delete(repo.full_name);
419
+ }
420
+ });
421
+
422
+ const span = document.createElement('span');
423
+ span.textContent = repo.full_name;
424
+
425
+ label.appendChild(input);
426
+ label.appendChild(span);
427
+ repoList.appendChild(label);
428
+ });
429
+ }
430
+
431
+ function updateSummary() {
432
+ if (!summaryList) return;
433
+ summaryList.innerHTML = '';
434
+ const repos = Array.from(selectedRepos);
435
+ if (!repos.length) {
436
+ summaryEmpty.style.display = 'block';
437
+ if (summaryInstructions) summaryInstructions.style.display = 'none';
438
+ return;
439
+ }
440
+ summaryEmpty.style.display = 'none';
441
+ if (summaryInstructions) summaryInstructions.style.display = 'block';
442
+ repos.forEach(function (fullName) {
443
+ const li = document.createElement('li');
444
+ const title = document.createElement('strong');
445
+ title.textContent = fullName;
446
+ li.appendChild(title);
447
+
448
+ const details = webhookDetails[fullName] || {};
449
+ const urlValue = details.url;
450
+ const secretValue = details.secret;
451
+
452
+ if (urlValue || secretValue) {
453
+ const detailsContainer = document.createElement('div');
454
+ detailsContainer.className = 'github-webhook-details';
455
+ detailsContainer.style.marginTop = '0.3em';
456
+
457
+ if (urlValue) {
458
+ detailsContainer.appendChild(createWebhookDetail(webhookUrlLabel, urlValue));
459
+ }
460
+
461
+ if (secretValue) {
462
+ detailsContainer.appendChild(createWebhookDetail(webhookSecretLabel, secretValue));
463
+ }
464
+
465
+ li.appendChild(detailsContainer);
466
+ }
467
+
468
+ summaryList.appendChild(li);
469
+ });
470
+ }
471
+
472
+ function createWebhookDetail(label, value) {
473
+ const row = document.createElement('div');
474
+ row.className = 'github-webhook-detail-row';
475
+
476
+ const labelEl = document.createElement('span');
477
+ labelEl.textContent = `${label}: `;
478
+ labelEl.style.fontWeight = '600';
479
+
480
+ const codeEl = document.createElement('code');
481
+ codeEl.textContent = value;
482
+
483
+ row.appendChild(labelEl);
484
+ row.appendChild(codeEl);
485
+
486
+ return row;
487
+ }
488
+
489
+ function saveSelection() {
490
+ clearError();
491
+ const markdownSync = {};
492
+ Array.from(selectedRepos).forEach(function (repo) {
493
+ markdownSync[repo] = markdownSyncRepos.has(repo);
494
+ });
495
+ const payload = { repositories: Array.from(selectedRepos), markdown_sync: markdownSync };
496
+
497
+ fetch(`/github/creatives/${creativeId}/integration`, {
498
+ method: 'PATCH',
499
+ headers: {
500
+ 'Content-Type': 'application/json',
501
+ 'X-CSRF-Token': csrfToken()
502
+ },
503
+ body: JSON.stringify(payload)
504
+ })
505
+ .then(function (response) { return response.json().then(function (body) { return { ok: response.ok, body: body }; }); })
506
+ .then(function (result) {
507
+ if (!result.ok) {
508
+ showError(result.body.error || '연동 저장 중 오류가 발생했습니다.');
509
+ return;
510
+ }
511
+ selectedRepos = new Set(result.body.selected_repositories || []);
512
+ webhookDetails = result.body.webhooks || {};
513
+
514
+
515
+ updateSummary();
516
+ alert(modal.dataset.successMessage);
517
+ })
518
+ .catch(function () {
519
+ showError('연동 정보를 저장하지 못했습니다.');
520
+ });
521
+ }
522
+
523
+ openBtn.addEventListener('click', function () {
524
+ creativeId = openBtn.dataset.creativeId;
525
+ if (!creativeId) {
526
+ alert(modal.dataset.noCreative);
527
+ return;
528
+ }
529
+ resetWizard();
530
+ showModal();
531
+ fetchStatus();
532
+ });
533
+
534
+ closeBtn?.addEventListener('click', closeModal);
535
+ modal.addEventListener('click', function (event) {
536
+ if (event.target === modal) closeModal();
537
+ });
538
+
539
+ prevBtn.addEventListener('click', function () {
540
+ clearError();
541
+ if (currentStep === 'organization') {
542
+ currentStep = 'connect';
543
+ } else if (currentStep === 'repositories') {
544
+ currentStep = 'organization';
545
+ } else if (currentStep === 'markdown-sync') {
546
+ currentStep = 'repositories';
547
+ } else if (currentStep === 'summary') {
548
+ currentStep = 'markdown-sync';
549
+ }
550
+ updateStep();
551
+ if (currentStep === 'organization' && organizations.length === 0) loadOrganizations();
552
+ if (currentStep === 'repositories') loadRepositories();
553
+ });
554
+
555
+ nextBtn.addEventListener('click', function () {
556
+ clearError();
557
+ if (currentStep === 'connect') {
558
+ currentStep = 'organization';
559
+ updateStep();
560
+ loadOrganizations();
561
+ } else if (currentStep === 'organization') {
562
+ currentStep = 'repositories';
563
+ updateStep();
564
+ loadRepositories();
565
+ } else if (currentStep === 'repositories') {
566
+ currentStep = 'markdown-sync';
567
+ updateStep();
568
+ populateMarkdownSync();
569
+ } else if (currentStep === 'markdown-sync') {
570
+ currentStep = 'summary';
571
+ updateStep();
572
+ }
573
+ });
574
+
575
+ finishBtn.addEventListener('click', function () {
576
+ saveSelection();
577
+ });
578
+
579
+ loginBtn.addEventListener('click', function () {
580
+ const width = Number(loginBtn.dataset.windowWidth) || 600;
581
+ const height = Number(loginBtn.dataset.windowHeight) || 700;
582
+ const left = window.screenX + Math.max(0, (window.outerWidth - width) / 2);
583
+ const top = window.screenY + Math.max(0, (window.outerHeight - height) / 2);
584
+
585
+ // Open popup synchronously to avoid browser popup blocking
586
+ window.open('', 'github-auth-window', `width=${width},height=${height},left=${left},top=${top}`);
587
+
588
+ // Store creative_id in session, then submit OAuth form into the already-open popup
589
+ fetch('/github/auth/store_creative', {
590
+ method: 'POST',
591
+ headers: {
592
+ 'Content-Type': 'application/json',
593
+ 'X-CSRF-Token': csrfToken()
594
+ },
595
+ body: JSON.stringify({ creative_id: creativeId })
596
+ }).finally(function () {
597
+ loginForm.submit();
598
+ });
599
+ });
600
+
601
+ deleteBtn?.addEventListener('click', function () {
602
+ if (!creativeId) {
603
+ alert(modal.dataset.noCreative);
604
+ return;
605
+ }
606
+ clearError();
607
+ const selectedToDelete = Array.from(selectedReposForDeletion);
608
+ if (!selectedToDelete.length) {
609
+ showError(deleteSelectWarning);
610
+ return;
611
+ }
612
+ if (!window.confirm(deleteConfirm)) return;
613
+
614
+ fetch(`/github/creatives/${creativeId}/integration`, {
615
+ method: 'DELETE',
616
+ headers: {
617
+ 'Content-Type': 'application/json',
618
+ 'X-CSRF-Token': csrfToken()
619
+ },
620
+ body: JSON.stringify({ repositories: selectedToDelete })
621
+ })
622
+ .then(function (response) { return response.json().then(function (body) { return { ok: response.ok, body: body }; }); })
623
+ .then(function (result) {
624
+ if (!result.ok) {
625
+ showError(result.body.error || deleteError);
626
+ return;
627
+ }
628
+
629
+ selectedRepos = new Set(result.body.selected_repositories || []);
630
+ webhookDetails = result.body.webhooks || {};
631
+
632
+ hasExistingIntegration = selectedRepos.size > 0;
633
+
634
+ renderExistingConnections(Array.from(selectedRepos));
635
+ updateSummary();
636
+ statusEl.textContent = deleteSuccess;
637
+
638
+ if (!hasExistingIntegration) {
639
+ selectedOrg = null;
640
+ organizations = [];
641
+ currentStep = 'organization';
642
+ updateStep();
643
+ loadOrganizations();
644
+ }
645
+ })
646
+ .catch(function () {
647
+ showError(deleteError);
648
+ });
649
+ });
650
+
651
+ resyncBtn?.addEventListener('click', function () {
652
+ if (!creativeId) return;
653
+ clearError();
654
+ const checkboxes = existingList ? existingList.querySelectorAll('.github-existing-repo-checkbox:checked') : [];
655
+ const selectedToResync = Array.from(checkboxes).map(cb => cb.value);
656
+ if (!selectedToResync.length) {
657
+ showError(resyncSelectWarning);
658
+ return;
659
+ }
660
+ if (!window.confirm(resyncConfirm)) return;
661
+
662
+ resyncBtn.disabled = true;
663
+ let completed = 0;
664
+ let failed = 0;
665
+
666
+ selectedToResync.forEach(function (repo) {
667
+ fetch(`/github/creatives/${creativeId}/integration/resync`, {
668
+ method: 'POST',
669
+ headers: {
670
+ 'Content-Type': 'application/json',
671
+ 'X-CSRF-Token': csrfToken()
672
+ },
673
+ body: JSON.stringify({ repository: repo })
674
+ })
675
+ .then(function (response) { return response.json().then(function (body) { return { ok: response.ok, body: body }; }); })
676
+ .then(function (result) {
677
+ completed++;
678
+ if (!result.ok) failed++;
679
+ if (completed === selectedToResync.length) {
680
+ if (failed > 0) {
681
+ showError(resyncError);
682
+ } else {
683
+ statusEl.textContent = resyncSuccess;
684
+ }
685
+ resyncBtn.disabled = false;
686
+ }
687
+ })
688
+ .catch(function () {
689
+ completed++;
690
+ failed++;
691
+ if (completed === selectedToResync.length) {
692
+ showError(resyncError);
693
+ resyncBtn.disabled = false;
694
+ }
695
+ });
696
+ });
697
+ });
698
+
699
+ window.addEventListener('message', function (event) {
700
+ if (event.origin !== window.location.origin) return;
701
+ if (event.data && event.data.type === 'githubConnected') {
702
+ fetchStatus();
703
+ }
704
+ });
705
+ });
706
+ }
@@ -1,3 +1,3 @@
1
1
  module CollavreGithub
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: collavre_github
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Collavre
@@ -66,6 +66,7 @@ files:
66
66
  - app/controllers/collavre_github/auth_controller.rb
67
67
  - app/controllers/collavre_github/creatives/integrations_controller.rb
68
68
  - app/controllers/collavre_github/webhooks_controller.rb
69
+ - app/javascript/collavre_github.js
69
70
  - app/jobs/collavre_github/initial_markdown_sync_job.rb
70
71
  - app/jobs/collavre_github/markdown_sync_job.rb
71
72
  - app/models/collavre_github/account.rb