decap-cms-backend-github 3.6.0 → 3.8.0

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/esm/API.js CHANGED
@@ -7,9 +7,12 @@ import result from 'lodash/result';
7
7
  import trimStart from 'lodash/trimStart';
8
8
  import trim from 'lodash/trim';
9
9
  import { oneLine } from 'common-tags';
10
- import { getAllResponses, APIError, EditorialWorkflowError, localForage, basename, readFileMetadata, CMS_BRANCH_PREFIX, generateContentKey, DEFAULT_PR_BODY, MERGE_COMMIT_MESSAGE, PreviewState, parseContentKey, branchFromContentKey, isCMSLabel, labelToStatus, statusToLabel, contentKeyFromBranch, requestWithBackoff, unsentRequest, throwOnConflictingBranches } from 'decap-cms-lib-util';
11
10
  import { dirname } from 'path';
11
+ import { getAllResponses, APIError, EditorialWorkflowError, localForage, basename, readFileMetadata, CMS_BRANCH_PREFIX, generateContentKey, DEFAULT_PR_BODY, MERGE_COMMIT_MESSAGE, PreviewState, parseContentKey, branchFromContentKey, isCMSLabel, labelToStatus, statusToLabel, contentKeyFromBranch, requestWithBackoff, unsentRequest, throwOnConflictingBranches } from 'decap-cms-lib-util';
12
12
  export const API_NAME = 'GitHub';
13
+ const {
14
+ fetchWithTimeout: fetch
15
+ } = unsentRequest;
13
16
  export const MOCK_PULL_REQUEST = -1;
14
17
  var GitHubCommitStatusState = /*#__PURE__*/function (GitHubCommitStatusState) {
15
18
  GitHubCommitStatusState["Error"] = "error";
@@ -758,9 +761,10 @@ export default class API {
758
761
  const contentKey = this.generateContentKey(options.collectionName, slug);
759
762
  const branch = branchFromContentKey(contentKey);
760
763
  const unpublished = options.unpublished || false;
764
+ const hasSubfolders = options.hasSubfolders !== false; // default to true
761
765
  if (!unpublished) {
762
766
  const branchData = await this.getDefaultBranch();
763
- const changeTree = await this.updateTree(branchData.commit.sha, files);
767
+ const changeTree = await this.updateTree(branchData.commit.sha, files, this.branch, hasSubfolders);
764
768
  const commitResponse = await this.commit(options.commitMessage, changeTree);
765
769
  if (this.useOpenAuthoring) {
766
770
  await this.createBranch(branch, commitResponse.sha);
@@ -788,7 +792,7 @@ export default class API {
788
792
  // rebase the branch before applying new changes
789
793
  const rebasedHead = await this.rebaseBranch(branch);
790
794
  const treeFiles = mediaFilesToRemove.concat(files);
791
- const changeTree = await this.updateTree(rebasedHead.sha, treeFiles, branch);
795
+ const changeTree = await this.updateTree(rebasedHead.sha, treeFiles, branch, hasSubfolders);
792
796
  const commit = await this.commit(options.commitMessage, changeTree);
793
797
  return this.patchBranch(branch, commit.sha, {
794
798
  force: true
@@ -933,6 +937,7 @@ export default class API {
933
937
  const pullRequest = await this.getBranchPullRequest(branch);
934
938
  await this.mergePR(pullRequest);
935
939
  await this.deleteBranch(branch);
940
+ await this.closeIssueOnPublish(collectionName, slug);
936
941
  }
937
942
  async createRef(type, name, sha) {
938
943
  const result = await this.request(`${this.repoURL}/git/refs`, {
@@ -1104,7 +1109,7 @@ export default class API {
1104
1109
  item.sha = response.sha;
1105
1110
  return item;
1106
1111
  }
1107
- async updateTree(baseSha, files, branch = this.branch) {
1112
+ async updateTree(baseSha, files, branch = this.branch, hasSubfolders = true) {
1108
1113
  const toMove = [];
1109
1114
  const tree = files.reduce((acc, file) => {
1110
1115
  const entry = {
@@ -1129,27 +1134,47 @@ export default class API {
1129
1134
  to,
1130
1135
  sha
1131
1136
  } of toMove) {
1132
- const sourceDir = dirname(from);
1133
- const destDir = dirname(to);
1134
- const files = await this.listFiles(sourceDir, {
1135
- branch,
1136
- depth: 100
1137
- });
1138
- for (const file of files) {
1139
- // delete current path
1137
+ if (!hasSubfolders) {
1138
+ // New behavior (subfolders: false): Only move the specific file
1139
+ // Delete the file at the old path
1140
1140
  tree.push({
1141
- path: file.path,
1141
+ path: trimStart(from, '/'),
1142
1142
  mode: '100644',
1143
1143
  type: 'blob',
1144
1144
  sha: null
1145
1145
  });
1146
- // create in new path
1146
+ // Create the file at the new path
1147
1147
  tree.push({
1148
- path: file.path.replace(sourceDir, destDir),
1148
+ path: trimStart(to, '/'),
1149
1149
  mode: '100644',
1150
1150
  type: 'blob',
1151
- sha: file.path === from ? sha : file.id
1151
+ sha
1152
1152
  });
1153
+ } else {
1154
+ // Legacy behavior (subfolders: true, default): Move all files in the directory
1155
+ // This is for collections where all files in a folder represent a single entry
1156
+ const sourceDir = dirname(from);
1157
+ const destDir = dirname(to);
1158
+ const files = await this.listFiles(sourceDir, {
1159
+ branch,
1160
+ depth: 100
1161
+ });
1162
+ for (const file of files) {
1163
+ // delete current path
1164
+ tree.push({
1165
+ path: file.path,
1166
+ mode: '100644',
1167
+ type: 'blob',
1168
+ sha: null
1169
+ });
1170
+ // create in new path
1171
+ tree.push({
1172
+ path: file.path.replace(sourceDir, destDir),
1173
+ mode: '100644',
1174
+ type: 'blob',
1175
+ sha: file.path === from ? sha : file.id
1176
+ });
1177
+ }
1153
1178
  }
1154
1179
  }
1155
1180
  const newTree = await this.createTree(baseSha, tree);
@@ -1191,4 +1216,414 @@ export default class API {
1191
1216
  const pullRequest = await this.getBranchPullRequest(branch);
1192
1217
  return pullRequest.head.sha;
1193
1218
  }
1219
+
1220
+ /**
1221
+ * Constants for note formatting to aid with PR comment to note conversion
1222
+ */
1223
+ static NOTE_STATUS_RESOLVED = 'RESOLVED';
1224
+ static NOTE_STATUS_OPEN = 'OPEN';
1225
+ static NOTES_LABEL = 'decap-cms-notes';
1226
+ static NOTE_ISSUE_PREFIX = 'Notes: ';
1227
+ // In Github we hide Decap Notes metadata in a HTML comment, that way we can track status of whether or not a note has been resolved (similar to GDocs)
1228
+ static NOTE_REGEX = /^<!-- DecapCMS Note - Status: (RESOLVED|OPEN) -->([\s\S]+)$/;
1229
+
1230
+ /**
1231
+ * Format a note for PR comment display
1232
+ */
1233
+ formatNoteForGithub(note) {
1234
+ const status = note.resolved ? API.NOTE_STATUS_RESOLVED : API.NOTE_STATUS_OPEN;
1235
+ return `<!-- DecapCMS Note - Status: ${status} -->
1236
+ ${note.content}`;
1237
+ }
1238
+
1239
+ /**
1240
+ * Parse a GitHub comment into a Note object
1241
+ */
1242
+ parseCommentToNote(comment) {
1243
+ if (!comment || !comment.body || !comment.user) {
1244
+ throw new Error('Invalid comment structure');
1245
+ }
1246
+ const structuredMatch = comment.body.match(API.NOTE_REGEX);
1247
+ const content = structuredMatch ? structuredMatch[2].trim() : comment.body;
1248
+ const resolved = structuredMatch ? structuredMatch[1] === API.NOTE_STATUS_RESOLVED : false;
1249
+ if (!content.trim()) {
1250
+ throw new Error('Empty note content');
1251
+ }
1252
+ return {
1253
+ id: comment.id.toString(),
1254
+ author: comment.user.login,
1255
+ avatarUrl: comment.user.avatar_url,
1256
+ timestamp: comment.created_at,
1257
+ content,
1258
+ resolved,
1259
+ entrySlug: ''
1260
+ };
1261
+ }
1262
+
1263
+ /**
1264
+ * Create a GitHub issue for storing notes for a specific entry
1265
+ */
1266
+ async createEntryIssue(collectionName, slug, entryTitle) {
1267
+ const title = `${API.NOTE_ISSUE_PREFIX}${entryTitle || `${collectionName}/${slug}`}`;
1268
+ const body = `This issue tracks notes for entry: \`${collectionName}/${slug}\`\n\n---\n*This issue was created automatically by Decap CMS for note management.*`;
1269
+ const response = await this.request(`${this.repoURL}/issues`, {
1270
+ method: 'POST',
1271
+ body: JSON.stringify({
1272
+ title,
1273
+ body,
1274
+ labels: [API.NOTES_LABEL, `collection:${collectionName}`]
1275
+ })
1276
+ });
1277
+ return response;
1278
+ }
1279
+
1280
+ /**
1281
+ * Find existing issue for an entry (returns null if not found)
1282
+ */
1283
+ async findEntryIssue(collectionName, slug) {
1284
+ // Search for existing issue
1285
+ const searchQuery = `repo:${this.repo} label:${API.NOTES_LABEL} "${collectionName}/${slug}" in:body`;
1286
+ try {
1287
+ const searchResponse = await this.request('/search/issues', {
1288
+ params: {
1289
+ q: searchQuery
1290
+ }
1291
+ });
1292
+ if (searchResponse.items && searchResponse.items.length > 0) {
1293
+ return searchResponse.items[0];
1294
+ }
1295
+ return null;
1296
+ } catch (error) {
1297
+ console.warn('Failed to search for existing notes issue:', error);
1298
+ return null;
1299
+ }
1300
+ }
1301
+ /**
1302
+ * Get issue with ETag support for conditional requests
1303
+ * Returns { status: 304 } if not modified, or { status: 200, data, etag } if modified
1304
+ */
1305
+ async getIssueWithETag(issueNumber, etag) {
1306
+ try {
1307
+ const headers = {
1308
+ Authorization: `${this.tokenKeyword} ${this.token}`
1309
+ };
1310
+ if (etag) {
1311
+ headers['If-None-Match'] = etag;
1312
+ }
1313
+ const response = await fetch(`${this.apiRoot}${this.repoURL}/issues/${issueNumber}`, {
1314
+ headers
1315
+ });
1316
+ if (response.status === 304) {
1317
+ return {
1318
+ status: 304
1319
+ };
1320
+ }
1321
+ if (response.status === 200) {
1322
+ const issue = await response.json();
1323
+ const newETag = response.headers.get('ETag');
1324
+ const commentsResponse = await fetch(`${this.apiRoot}${this.repoURL}/issues/${issueNumber}/comments`, {
1325
+ headers
1326
+ });
1327
+ const commentsRaw = await commentsResponse.json();
1328
+ const comments = commentsRaw.map(comment => ({
1329
+ id: comment.id,
1330
+ body: comment.body,
1331
+ user: comment.user,
1332
+ created_at: comment.created_at,
1333
+ updated_at: comment.updated_at
1334
+ }));
1335
+ const issueState = {
1336
+ number: issue.number,
1337
+ title: issue.title,
1338
+ body: issue.body,
1339
+ state: issue.state,
1340
+ updated_at: issue.updated_at,
1341
+ comments,
1342
+ labels: issue.labels,
1343
+ html_url: issue.html_url
1344
+ };
1345
+ return {
1346
+ status: 200,
1347
+ data: issueState,
1348
+ etag: newETag
1349
+ };
1350
+ }
1351
+ throw new Error(`Unexpected status: ${response.status}`);
1352
+ } catch (error) {
1353
+ if (error.status === 304) {
1354
+ return {
1355
+ status: 304
1356
+ };
1357
+ }
1358
+ throw error;
1359
+ }
1360
+ }
1361
+
1362
+ /**
1363
+ * Get the current state of an issue (without ETag)
1364
+ */
1365
+ async getIssueState(issueNumber) {
1366
+ const response = await this.getIssueWithETag(issueNumber, null);
1367
+ if (response.status === 200 && response.data) {
1368
+ return response.data;
1369
+ }
1370
+ throw new Error('Failed to get issue state');
1371
+ }
1372
+ /**
1373
+ * Get comments from a GitHub issue
1374
+ */
1375
+ async getIssueComments(issueNumber) {
1376
+ try {
1377
+ const response = await this.request(`${this.repoURL}/issues/${issueNumber}/comments`);
1378
+ return Array.isArray(response) ? response : [];
1379
+ } catch (error) {
1380
+ console.error('Failed to get issue comments:', error);
1381
+ return [];
1382
+ }
1383
+ }
1384
+
1385
+ /**
1386
+ * Create a comment on a GitHub issue
1387
+ */
1388
+ async createIssueComment(issueNumber, note) {
1389
+ try {
1390
+ const response = await this.request(`${this.repoURL}/issues/${issueNumber}/comments`, {
1391
+ method: 'POST',
1392
+ body: JSON.stringify({
1393
+ body: this.formatNoteForGithub(note)
1394
+ })
1395
+ });
1396
+ return response.id.toString();
1397
+ } catch (error) {
1398
+ console.error('Failed to create issue comment:', error);
1399
+ throw new APIError('Failed to create note', error.status || 500, API_NAME);
1400
+ }
1401
+ }
1402
+
1403
+ /**
1404
+ * Update a GitHub issue comment
1405
+ */
1406
+ async updateIssueComment(commentId, note) {
1407
+ try {
1408
+ await this.request(`${this.repoURL}/issues/comments/${commentId}`, {
1409
+ method: 'PATCH',
1410
+ body: JSON.stringify({
1411
+ body: this.formatNoteForGithub(note)
1412
+ })
1413
+ });
1414
+ } catch (error) {
1415
+ console.error('Failed to update issue comment:', error);
1416
+ throw new APIError('Failed to update note', error.status || 500, API_NAME);
1417
+ }
1418
+ }
1419
+
1420
+ /**
1421
+ * Delete a GitHub issue comment
1422
+ */
1423
+ async deleteIssueComment(commentId) {
1424
+ try {
1425
+ await this.request(`${this.repoURL}/issues/comments/${commentId}`, {
1426
+ method: 'DELETE'
1427
+ });
1428
+ } catch (error) {
1429
+ console.error('Failed to delete issue comment:', error);
1430
+ throw new APIError('Failed to delete note', error.status || 500, API_NAME);
1431
+ }
1432
+ }
1433
+
1434
+ /**
1435
+ * Close the notes issue when an entry is published
1436
+ */
1437
+ async closeIssueOnPublish(collectionName, slug) {
1438
+ try {
1439
+ const searchQuery = `repo:${this.repo} label:${API.NOTES_LABEL} "${collectionName}/${slug}" in:body state:open`;
1440
+ const searchResponse = await this.request('/search/issues', {
1441
+ params: {
1442
+ q: searchQuery
1443
+ }
1444
+ });
1445
+ if (searchResponse.items && searchResponse.items.length > 0) {
1446
+ const issue = searchResponse.items[0];
1447
+ await this.request(`${this.repoURL}/issues/${issue.number}`, {
1448
+ method: 'PATCH',
1449
+ body: JSON.stringify({
1450
+ state: 'closed',
1451
+ labels: [...(issue.labels || []).map(l => l.name), 'entry-published']
1452
+ })
1453
+ });
1454
+ }
1455
+ } catch (error) {
1456
+ console.warn('Failed to close notes issue on publish:', error);
1457
+ }
1458
+ }
1459
+
1460
+ /**
1461
+ * Reopen the notes issue when an entry is unpublished
1462
+ */
1463
+ async reopenIssueOnUnpublish(collectionName, slug) {
1464
+ try {
1465
+ const searchQuery = `repo:${this.repo} label:${API.NOTES_LABEL} "${collectionName}/${slug}" in:body`;
1466
+ const searchResponse = await this.request('/search/issues', {
1467
+ params: {
1468
+ q: searchQuery
1469
+ }
1470
+ });
1471
+ if (searchResponse.items && searchResponse.items.length > 0) {
1472
+ const issue = searchResponse.items[0];
1473
+ // Remove 'entry-published' or 'entry-deleted' labels and reopen
1474
+ const updatedLabels = (issue.labels || []).map(l => l.name).filter(name => name !== 'entry-published' && name !== 'entry-deleted');
1475
+ await this.request(`${this.repoURL}/issues/${issue.number}`, {
1476
+ method: 'PATCH',
1477
+ body: JSON.stringify({
1478
+ state: 'open',
1479
+ labels: updatedLabels
1480
+ })
1481
+ });
1482
+ }
1483
+ } catch (error) {
1484
+ console.warn('Failed to reopen notes issue on unpublish:', error);
1485
+ }
1486
+ }
1487
+
1488
+ /**
1489
+ * Get all notes for an entry
1490
+ */
1491
+ async getEntryNotes(collectionName, slug) {
1492
+ try {
1493
+ const issue = await this.findEntryIssue(collectionName, slug);
1494
+ if (!issue) {
1495
+ return []; // No issue means no notes yet
1496
+ }
1497
+ const comments = await this.getIssueComments(issue.number);
1498
+ const issueUrl = issue.html_url; // Get the issue URL once
1499
+
1500
+ // Add issueUrl to each note
1501
+ return comments.map(comment => ({
1502
+ ...this.parseCommentToNote(comment),
1503
+ issueUrl // Add the issue URL to each note (this info is picked up by the UI to direct users to the source of the Notes in Github)
1504
+ }));
1505
+ } catch (error) {
1506
+ console.error('Failed to get entry notes:', error);
1507
+ return [];
1508
+ }
1509
+ }
1510
+
1511
+ /**
1512
+ * Add a note to any entry
1513
+ */
1514
+ async addNoteToEntry(collectionName, slug, note, entryTitle) {
1515
+ try {
1516
+ let issue = await this.findEntryIssue(collectionName, slug);
1517
+ if (!issue) {
1518
+ issue = await this.createEntryIssue(collectionName, slug, entryTitle);
1519
+ }
1520
+ const commentId = await this.createIssueComment(issue.number, note);
1521
+ return {
1522
+ commentId,
1523
+ issueUrl: issue.html_url
1524
+ };
1525
+ } catch (error) {
1526
+ console.error('Failed to add note to entry:', error);
1527
+ throw new APIError('Failed to create note', error.status || 500, API_NAME);
1528
+ }
1529
+ }
1530
+ async updateEntryNote(noteId, note) {
1531
+ try {
1532
+ await this.updateIssueComment(noteId, note);
1533
+ } catch (error) {
1534
+ console.error('Failed to update entry note:', error);
1535
+ throw new APIError('Failed to update note', error.status || 500, API_NAME);
1536
+ }
1537
+ }
1538
+ async deleteEntryNote(noteId) {
1539
+ try {
1540
+ await this.deleteIssueComment(noteId);
1541
+ } catch (error) {
1542
+ console.error('Failed to delete entry note:', error);
1543
+ throw new APIError('Failed to delete note', error.status || 500, API_NAME);
1544
+ }
1545
+ }
1546
+
1547
+ /**
1548
+ * Get all entries that have notes (useful for showing notes indicator in UI)
1549
+ */
1550
+ async getEntriesWithNotes() {
1551
+ try {
1552
+ const searchQuery = `repo:${this.repo} label:${API.NOTES_LABEL} state:open`;
1553
+ const searchResponse = await this.request('/search/issues', {
1554
+ params: {
1555
+ q: searchQuery,
1556
+ per_page: 100
1557
+ }
1558
+ });
1559
+ const entriesWithNotes = [];
1560
+ for (const issue of searchResponse.items || []) {
1561
+ // Extract collection/slug from issue body
1562
+ const match = issue.body.match(/entry: `(.+)\/(.+)`/);
1563
+ if (match) {
1564
+ const [, collection, slug] = match;
1565
+ entriesWithNotes.push({
1566
+ collection,
1567
+ slug,
1568
+ noteCount: issue.comments
1569
+ });
1570
+ }
1571
+ }
1572
+ return entriesWithNotes;
1573
+ } catch (error) {
1574
+ console.error('Failed to get entries with notes:', error);
1575
+ return [];
1576
+ }
1577
+ }
1578
+
1579
+ /**
1580
+ * Close notes issue when entry is deleted
1581
+ */
1582
+ async closeEntryNotesIssue(collectionName, slug) {
1583
+ try {
1584
+ const searchQuery = `repo:${this.repo} label:${API.NOTES_LABEL} "${collectionName}/${slug}" in:body state:open`;
1585
+ const searchResponse = await this.request('/search/issues', {
1586
+ params: {
1587
+ q: searchQuery
1588
+ }
1589
+ });
1590
+ if (searchResponse.items && searchResponse.items.length > 0) {
1591
+ const issue = searchResponse.items[0];
1592
+ await this.request(`${this.repoURL}/issues/${issue.number}`, {
1593
+ method: 'PATCH',
1594
+ body: JSON.stringify({
1595
+ state: 'closed',
1596
+ labels: [...(issue.labels || []).map(l => l.name), 'entry-deleted']
1597
+ })
1598
+ });
1599
+ }
1600
+ } catch (error) {
1601
+ console.warn('Failed to close notes issue:', error);
1602
+ }
1603
+ }
1604
+
1605
+ /**
1606
+ * Get PR metadata from branch name
1607
+ */
1608
+ async getPRMetadataFromBranch(branchName) {
1609
+ try {
1610
+ const response = await this.request(`${this.originRepoURL}/pulls`, {
1611
+ params: {
1612
+ head: await this.getHeadReference(branchName),
1613
+ state: 'open'
1614
+ }
1615
+ });
1616
+ const pr = response[0];
1617
+ if (!pr) return null;
1618
+ return {
1619
+ id: pr.number.toString(),
1620
+ url: pr.html_url,
1621
+ author: pr.user?.login || 'unknown',
1622
+ createdAt: pr.created_at
1623
+ };
1624
+ } catch (error) {
1625
+ console.error('Failed to get PR metadata:', error);
1626
+ return null;
1627
+ }
1628
+ }
1194
1629
  }
@@ -1,11 +1,10 @@
1
1
  import _styled from "@emotion/styled/base";
2
- function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
3
2
  function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
4
3
  import React from 'react';
5
4
  import PropTypes from 'prop-types';
6
5
  import { NetlifyAuthenticator } from 'decap-cms-lib-auth';
7
6
  import { AuthenticationPage, Icon } from 'decap-cms-ui-default';
8
- import { jsx as ___EmotionJSX } from "@emotion/react";
7
+ import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
9
8
  const LoginButtonIcon = /*#__PURE__*/_styled(Icon, {
10
9
  target: "e1ko648l2",
11
10
  label: "LoginButtonIcon"
@@ -126,9 +125,11 @@ export default class GitHubAuthenticationPage extends React.Component {
126
125
  inProgress,
127
126
  t
128
127
  } = this.props;
129
- return inProgress || this.state.findingFork ? t('auth.loggingIn') : ___EmotionJSX(React.Fragment, null, ___EmotionJSX(LoginButtonIcon, {
130
- type: "github"
131
- }), t('auth.loginWithGitHub'));
128
+ return inProgress || this.state.findingFork ? t('auth.loggingIn') : _jsxs(React.Fragment, {
129
+ children: [_jsx(LoginButtonIcon, {
130
+ type: "github"
131
+ }), t('auth.loginWithGitHub')]
132
+ });
132
133
  };
133
134
  getAuthenticationPageRenderArgs() {
134
135
  const {
@@ -144,11 +145,19 @@ export default class GitHubAuthenticationPage extends React.Component {
144
145
  LoginButton,
145
146
  TextButton,
146
147
  showAbortButton
147
- }) => ___EmotionJSX(ForkApprovalContainer, null, ___EmotionJSX("p", null, "Open Authoring is enabled: we need to use a fork on your github account. (If a fork already exists, we'll use that.)"), ___EmotionJSX(ForkButtonsContainer, null, ___EmotionJSX(LoginButton, {
148
- onClick: approveFork
149
- }, "Fork the repo"), showAbortButton && ___EmotionJSX(TextButton, {
150
- onClick: refuseFork
151
- }, "Don't fork the repo")))
148
+ }) => _jsxs(ForkApprovalContainer, {
149
+ children: [_jsx("p", {
150
+ children: "Open Authoring is enabled: we need to use a fork on your github account. (If a fork already exists, we'll use that.)"
151
+ }), _jsxs(ForkButtonsContainer, {
152
+ children: [_jsx(LoginButton, {
153
+ onClick: approveFork,
154
+ children: "Fork the repo"
155
+ }), showAbortButton && _jsx(TextButton, {
156
+ onClick: refuseFork,
157
+ children: "Don't fork the repo"
158
+ })]
159
+ })]
160
+ })
152
161
  };
153
162
  }
154
163
  return {
@@ -166,16 +175,16 @@ export default class GitHubAuthenticationPage extends React.Component {
166
175
  requestingFork,
167
176
  findingFork
168
177
  } = this.state;
169
- return ___EmotionJSX(AuthenticationPage, _extends({
178
+ return _jsx(AuthenticationPage, {
170
179
  onLogin: this.handleLogin,
171
180
  loginDisabled: inProgress || findingFork || requestingFork,
172
181
  loginErrorMessage: loginError,
173
182
  logoUrl: config.logo_url // Deprecated, replaced by `logo.src`
174
183
  ,
175
184
  logo: config.logo,
176
- siteUrl: config.site_url
177
- }, this.getAuthenticationPageRenderArgs(), {
185
+ siteUrl: config.site_url,
186
+ ...this.getAuthenticationPageRenderArgs(),
178
187
  t: t
179
- }));
188
+ });
180
189
  }
181
190
  }