@einblick/sdk 0.4.1 → 0.4.6

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.
@@ -5,6 +5,7 @@ const RELATIVE_POSITION_SENTINEL = '__einblick_runtime_original_static__';
5
5
  const EINBLICK_ACCENT_RGB = '250, 105, 62';
6
6
  const EINBLICK_ACCENT_COLOR = `rgb(${EINBLICK_ACCENT_RGB})`;
7
7
  const EINBLICK_ACCENT_BORDER = `rgba(${EINBLICK_ACCENT_RGB}, 0.92)`;
8
+ const CONTROL_BAR_HEIGHT = 44;
8
9
  const INLINE_EDITOR_SHADOW_STYLES = `
9
10
  :host {
10
11
  display: block;
@@ -105,6 +106,28 @@ function getBrandBadgeSvg(size) {
105
106
  </svg>
106
107
  `;
107
108
  }
109
+ function getBrandMarkSvg(size) {
110
+ return `
111
+ <svg viewBox="0 0 22.11 22.11" width="${size}" height="${size}" aria-hidden="true" focusable="false">
112
+ <path
113
+ d="M22.11 9.96h-8.35l5.84-5.84.07-.07-1.49-1.48-.07-.07-5.97 5.97V0h-2.2v8.4L4.11 2.56l-.07-.07-1.48 1.49-.07.07 5.92 5.92H0v2.2h8.45l-5.9 5.9-.07.07 1.49 1.48.07.07 5.92-5.92v8.35h2.2v-8.79c0-.65.53-1.17 1.17-1.17h8.79v-2.2Z"
114
+ fill="currentColor"
115
+ />
116
+ </svg>
117
+ `;
118
+ }
119
+ function getMoreActionsSvg(size) {
120
+ const dotRadius = size * 0.08;
121
+ const centerY = size / 2;
122
+ const centers = [size * 0.26, size * 0.5, size * 0.74];
123
+ return `
124
+ <svg viewBox="0 0 ${size} ${size}" width="${size}" height="${size}" aria-hidden="true" focusable="false">
125
+ ${centers
126
+ .map((centerX) => `<circle cx="${centerX}" cy="${centerY}" r="${dotRadius}" fill="currentColor" />`)
127
+ .join('')}
128
+ </svg>
129
+ `;
130
+ }
108
131
  function createRandomId() {
109
132
  if (typeof crypto !== 'undefined' &&
110
133
  typeof crypto.randomUUID === 'function') {
@@ -419,6 +442,8 @@ class EinblickEditRuntime {
419
442
  siteKey;
420
443
  appOrigin;
421
444
  callbacks;
445
+ chrome;
446
+ reserveBottomSpace;
422
447
  locale;
423
448
  messages;
424
449
  bridgeNonce = createRandomId();
@@ -442,6 +467,15 @@ class EinblickEditRuntime {
442
467
  brandPopoverHint = null;
443
468
  brandPopoverOpenAppButton = null;
444
469
  brandPopoverLogoutButton = null;
470
+ bottomBarHost = null;
471
+ bottomBarSiteName = null;
472
+ bottomBarCmsButton = null;
473
+ bottomBarMoreButton = null;
474
+ bottomBarMoreMenu = null;
475
+ bottomBarOpenAppButton = null;
476
+ bottomBarLogoutButton = null;
477
+ originalBodyPaddingBottom = null;
478
+ hasReservedBodyPadding = false;
445
479
  mutationObserver = null;
446
480
  destroyed = false;
447
481
  started = false;
@@ -452,11 +486,16 @@ class EinblickEditRuntime {
452
486
  editorNonce = null;
453
487
  editorTarget = null;
454
488
  editorBinding = null;
489
+ navigatorPanel = null;
490
+ navigatorNonce = null;
455
491
  pendingInlineSaves = new Map();
456
492
  pendingLogin = null;
457
493
  constructor(options) {
458
494
  this.siteKey = options.siteKey;
459
495
  this.appOrigin = resolveAppOrigin(options.appOrigin, options.appUrl);
496
+ this.chrome = options.chrome ?? 'bar';
497
+ this.reserveBottomSpace =
498
+ options.reserveBottomSpace ?? this.chrome === 'bar';
460
499
  this.locale = resolveEinblickSdkLocale(options.locale);
461
500
  this.messages = getEinblickSdkMessages(this.locale);
462
501
  this.session =
@@ -488,7 +527,12 @@ class EinblickEditRuntime {
488
527
  this.started = true;
489
528
  injectRuntimeStyles();
490
529
  this.ensureHoverButton();
491
- this.ensureBrandBadge();
530
+ if (this.chrome === 'badge') {
531
+ this.ensureBrandBadge();
532
+ }
533
+ else if (this.chrome === 'bar') {
534
+ this.ensureBottomBar();
535
+ }
492
536
  window.addEventListener('message', this.handleMessage);
493
537
  window.addEventListener('pointerdown', this.handleGlobalPointerDown, true);
494
538
  window.addEventListener('pointermove', this.handlePointerMove, true);
@@ -547,7 +591,17 @@ class EinblickEditRuntime {
547
591
  this.brandPopoverHint = null;
548
592
  this.brandPopoverOpenAppButton = null;
549
593
  this.brandPopoverLogoutButton = null;
594
+ this.bottomBarHost?.remove();
595
+ this.bottomBarHost = null;
596
+ this.bottomBarSiteName = null;
597
+ this.bottomBarCmsButton = null;
598
+ this.bottomBarMoreButton = null;
599
+ this.bottomBarMoreMenu = null;
600
+ this.bottomBarOpenAppButton = null;
601
+ this.bottomBarLogoutButton = null;
602
+ this.applyBottomLayout(false);
550
603
  this.closeInlineEditor();
604
+ this.closeNavigator();
551
605
  this.closeDrawer();
552
606
  if (this.pendingLogin) {
553
607
  if (this.pendingLogin.closePollId !== null) {
@@ -593,6 +647,7 @@ class EinblickEditRuntime {
593
647
  this.messages.actions.logout;
594
648
  }
595
649
  this.updateBrandPopover();
650
+ this.updateBottomBar();
596
651
  }
597
652
  async login(options) {
598
653
  if (typeof window === 'undefined') {
@@ -641,6 +696,8 @@ class EinblickEditRuntime {
641
696
  }
642
697
  openEditor(binding, element) {
643
698
  this.setBrandPopoverOpen(false);
699
+ this.setBottomBarMenuOpen(false);
700
+ this.closeNavigator();
644
701
  const target = element ?? this.findElementForBinding(binding);
645
702
  if (shouldUseInlineEditor(binding)) {
646
703
  this.openInlineEditor(target, binding);
@@ -651,7 +708,9 @@ class EinblickEditRuntime {
651
708
  handlePointerMove = (event) => {
652
709
  if (!this.state.isAuthenticated ||
653
710
  this.currentInlineTarget ||
654
- this.editorBackdrop) {
711
+ this.editorBackdrop ||
712
+ this.navigatorPanel ||
713
+ this.isBottomBarMenuOpen()) {
655
714
  this.setActiveElement(null);
656
715
  return;
657
716
  }
@@ -665,6 +724,7 @@ class EinblickEditRuntime {
665
724
  };
666
725
  handleWindowBlur = () => {
667
726
  this.setBrandPopoverOpen(false);
727
+ this.setBottomBarMenuOpen(false);
668
728
  if (!this.currentInlineTarget && !this.editorBackdrop) {
669
729
  this.setActiveElement(null);
670
730
  }
@@ -678,6 +738,16 @@ class EinblickEditRuntime {
678
738
  this.setBrandPopoverOpen(false);
679
739
  return;
680
740
  }
741
+ if (this.isBottomBarMenuOpen()) {
742
+ event.preventDefault();
743
+ this.setBottomBarMenuOpen(false);
744
+ return;
745
+ }
746
+ if (this.navigatorPanel) {
747
+ event.preventDefault();
748
+ this.closeNavigator();
749
+ return;
750
+ }
681
751
  if (this.currentInlineTarget) {
682
752
  event.preventDefault();
683
753
  this.closeInlineEditor();
@@ -715,15 +785,22 @@ class EinblickEditRuntime {
715
785
  this.openDrawer(this.activeElement, binding, 'create');
716
786
  };
717
787
  handleGlobalPointerDown = (event) => {
718
- if (!this.isBrandPopoverOpen()) {
719
- return;
720
- }
721
788
  const target = event.target;
722
- if (target instanceof Node &&
723
- (this.brandBadge?.contains(target) || this.brandPopover?.contains(target))) {
789
+ if (!(target instanceof Node)) {
790
+ this.setBrandPopoverOpen(false);
791
+ this.setBottomBarMenuOpen(false);
724
792
  return;
725
793
  }
726
- this.setBrandPopoverOpen(false);
794
+ if (this.isBrandPopoverOpen() &&
795
+ !this.brandBadge?.contains(target) &&
796
+ !this.brandPopover?.contains(target)) {
797
+ this.setBrandPopoverOpen(false);
798
+ }
799
+ if (this.isBottomBarMenuOpen() &&
800
+ !this.bottomBarHost?.contains(target) &&
801
+ !this.bottomBarMoreMenu?.contains(target)) {
802
+ this.setBottomBarMenuOpen(false);
803
+ }
727
804
  };
728
805
  handleBrandBadgeClick = (event) => {
729
806
  event.preventDefault();
@@ -845,6 +922,21 @@ class EinblickEditRuntime {
845
922
  pending.resolve(false);
846
923
  }
847
924
  handleEditorMessage(message) {
925
+ if (this.navigatorNonce && message.nonce === this.navigatorNonce) {
926
+ if (message.type === 'navigator-closed') {
927
+ this.closeNavigator();
928
+ return;
929
+ }
930
+ if (message.type === 'navigator-open-editor') {
931
+ this.openCollectionDrawer({
932
+ collectionId: message.collectionId,
933
+ intent: message.intent,
934
+ recordId: message.recordId,
935
+ resourceSlug: message.resourceSlug,
936
+ });
937
+ return;
938
+ }
939
+ }
848
940
  if (!this.editorNonce || message.nonce !== this.editorNonce) {
849
941
  return;
850
942
  }
@@ -864,6 +956,17 @@ class EinblickEditRuntime {
864
956
  source: 'drawer',
865
957
  });
866
958
  }
959
+ else if (this.editorBinding) {
960
+ const nextBinding = {
961
+ ...this.editorBinding,
962
+ recordId: message.recordId ?? this.editorBinding.recordId,
963
+ };
964
+ void this.callbacks.onSave?.({
965
+ binding: nextBinding,
966
+ value: message.fields,
967
+ source: 'drawer',
968
+ });
969
+ }
867
970
  this.closeDrawer();
868
971
  }
869
972
  }
@@ -886,8 +989,10 @@ class EinblickEditRuntime {
886
989
  this.callbacks.onStateChange(nextState);
887
990
  this.updateBrandBadge();
888
991
  this.updateBrandPopover();
992
+ this.updateBottomBar();
889
993
  if (!nextState.isAuthenticated) {
890
994
  this.setActiveElement(null);
995
+ this.closeNavigator();
891
996
  }
892
997
  }
893
998
  ensureHoverButton() {
@@ -990,6 +1095,245 @@ class EinblickEditRuntime {
990
1095
  this.updateBrandBadge();
991
1096
  this.updateBrandPopover();
992
1097
  }
1098
+ ensureBottomBar() {
1099
+ if (this.bottomBarHost || typeof document === 'undefined') {
1100
+ return;
1101
+ }
1102
+ const host = document.createElement('div');
1103
+ host.setAttribute('data-einblick-control-bar-host', 'true');
1104
+ host.hidden = true;
1105
+ host.style.position = 'fixed';
1106
+ host.style.left = '0';
1107
+ host.style.right = '0';
1108
+ host.style.bottom = '0';
1109
+ host.style.zIndex = '2147483644';
1110
+ host.style.height = `${CONTROL_BAR_HEIGHT}px`;
1111
+ const shadowRoot = host.attachShadow({ mode: 'open' });
1112
+ const style = document.createElement('style');
1113
+ style.textContent = `
1114
+ :host {
1115
+ all: initial;
1116
+ display: block;
1117
+ height: ${CONTROL_BAR_HEIGHT}px;
1118
+ color: rgb(255, 255, 255);
1119
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1120
+ }
1121
+
1122
+ *, *::before, *::after {
1123
+ box-sizing: border-box;
1124
+ }
1125
+
1126
+ .bar {
1127
+ position: relative;
1128
+ display: flex;
1129
+ align-items: center;
1130
+ justify-content: space-between;
1131
+ gap: 1rem;
1132
+ width: 100%;
1133
+ height: ${CONTROL_BAR_HEIGHT}px;
1134
+ padding: 0 0.75rem;
1135
+ background: ${EINBLICK_ACCENT_COLOR};
1136
+ border-top: 1px solid rgba(255, 255, 255, 0.24);
1137
+ box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.18);
1138
+ }
1139
+
1140
+ .brand,
1141
+ .actions {
1142
+ display: inline-flex;
1143
+ align-items: center;
1144
+ gap: 0.6rem;
1145
+ min-width: 0;
1146
+ }
1147
+
1148
+ .mark {
1149
+ display: inline-flex;
1150
+ align-items: center;
1151
+ justify-content: center;
1152
+ flex: none;
1153
+ width: 1.35rem;
1154
+ height: 1.35rem;
1155
+ color: rgb(255, 255, 255);
1156
+ }
1157
+
1158
+ .site {
1159
+ min-width: 0;
1160
+ max-width: min(26rem, 40vw);
1161
+ overflow: hidden;
1162
+ text-overflow: ellipsis;
1163
+ white-space: nowrap;
1164
+ font: 700 0.84rem/1.1 system-ui, sans-serif;
1165
+ }
1166
+
1167
+ button {
1168
+ appearance: none;
1169
+ display: inline-flex;
1170
+ align-items: center;
1171
+ justify-content: center;
1172
+ gap: 0.35rem;
1173
+ min-width: 0;
1174
+ height: 2rem;
1175
+ margin: 0;
1176
+ border: 1px solid rgba(255, 255, 255, 0.36);
1177
+ border-radius: 0.35rem;
1178
+ background: rgba(255, 255, 255, 0.13);
1179
+ color: rgb(255, 255, 255);
1180
+ padding: 0 0.7rem;
1181
+ font: 700 0.78rem/1 system-ui, sans-serif;
1182
+ letter-spacing: 0;
1183
+ cursor: pointer;
1184
+ }
1185
+
1186
+ .icon-button {
1187
+ width: 2rem;
1188
+ padding: 0;
1189
+ }
1190
+
1191
+ .icon-button svg {
1192
+ width: 1rem;
1193
+ height: 1rem;
1194
+ }
1195
+
1196
+ button:hover,
1197
+ button[aria-expanded="true"] {
1198
+ background: rgba(255, 255, 255, 0.23);
1199
+ }
1200
+
1201
+ button:focus-visible {
1202
+ outline: 2px solid rgba(255, 255, 255, 0.72);
1203
+ outline-offset: 2px;
1204
+ }
1205
+
1206
+ .secondary {
1207
+ border-color: transparent;
1208
+ background: transparent;
1209
+ }
1210
+
1211
+ .menu {
1212
+ position: absolute;
1213
+ right: 0.5rem;
1214
+ bottom: calc(100% + 0.45rem);
1215
+ display: flex;
1216
+ min-width: 12rem;
1217
+ flex-direction: column;
1218
+ gap: 0;
1219
+ border: 1px solid rgba(255, 255, 255, 0.26);
1220
+ background: rgba(214, 78, 33, 0.98);
1221
+ box-shadow: 0 -12px 28px rgba(15, 23, 42, 0.16);
1222
+ overflow: hidden;
1223
+ }
1224
+
1225
+ .menu[hidden] {
1226
+ display: none;
1227
+ }
1228
+
1229
+ .menu button {
1230
+ width: 100%;
1231
+ justify-content: flex-start;
1232
+ height: 2.35rem;
1233
+ border: 0;
1234
+ border-radius: 0;
1235
+ background: transparent;
1236
+ padding: 0 0.8rem;
1237
+ font-size: 0.78rem;
1238
+ }
1239
+
1240
+ .menu button + button {
1241
+ border-top: 1px solid rgba(255, 255, 255, 0.18);
1242
+ }
1243
+
1244
+ @media (max-width: 640px) {
1245
+ .bar {
1246
+ gap: 0.4rem;
1247
+ padding: 0 0.5rem;
1248
+ }
1249
+
1250
+ .site {
1251
+ max-width: 32vw;
1252
+ }
1253
+
1254
+ button {
1255
+ padding: 0 0.55rem;
1256
+ }
1257
+
1258
+ .menu {
1259
+ left: auto;
1260
+ right: 0.35rem;
1261
+ min-width: 10.5rem;
1262
+ }
1263
+ }
1264
+ `;
1265
+ const bar = document.createElement('div');
1266
+ bar.className = 'bar';
1267
+ bar.setAttribute('role', 'toolbar');
1268
+ bar.setAttribute('aria-label', this.messages.brand.controlBarLabel);
1269
+ const brand = document.createElement('div');
1270
+ brand.className = 'brand';
1271
+ const mark = document.createElement('span');
1272
+ mark.className = 'mark';
1273
+ mark.innerHTML = getBrandMarkSvg(22);
1274
+ const siteName = document.createElement('span');
1275
+ siteName.className = 'site';
1276
+ brand.append(mark, siteName);
1277
+ const actions = document.createElement('div');
1278
+ actions.className = 'actions';
1279
+ const cmsButton = document.createElement('button');
1280
+ cmsButton.type = 'button';
1281
+ cmsButton.addEventListener('click', (event) => {
1282
+ event.preventDefault();
1283
+ event.stopPropagation();
1284
+ this.setBottomBarMenuOpen(false);
1285
+ this.setNavigatorOpen(!this.navigatorPanel);
1286
+ });
1287
+ const moreButton = document.createElement('button');
1288
+ moreButton.type = 'button';
1289
+ moreButton.className = 'secondary icon-button';
1290
+ moreButton.setAttribute('aria-haspopup', 'menu');
1291
+ moreButton.title = this.messages.actions.moreActions;
1292
+ moreButton.innerHTML = getMoreActionsSvg(20);
1293
+ moreButton.addEventListener('click', (event) => {
1294
+ event.preventDefault();
1295
+ event.stopPropagation();
1296
+ if (this.navigatorPanel) {
1297
+ this.closeNavigator();
1298
+ }
1299
+ this.setBottomBarMenuOpen(!this.isBottomBarMenuOpen());
1300
+ });
1301
+ const moreMenu = document.createElement('div');
1302
+ moreMenu.className = 'menu';
1303
+ moreMenu.hidden = true;
1304
+ moreMenu.setAttribute('role', 'menu');
1305
+ const openAppButton = document.createElement('button');
1306
+ openAppButton.type = 'button';
1307
+ openAppButton.setAttribute('role', 'menuitem');
1308
+ openAppButton.addEventListener('click', (event) => {
1309
+ event.preventDefault();
1310
+ event.stopPropagation();
1311
+ this.setBottomBarMenuOpen(false);
1312
+ this.openEinblickApp();
1313
+ });
1314
+ const logoutButton = document.createElement('button');
1315
+ logoutButton.type = 'button';
1316
+ logoutButton.setAttribute('role', 'menuitem');
1317
+ logoutButton.addEventListener('click', (event) => {
1318
+ event.preventDefault();
1319
+ event.stopPropagation();
1320
+ this.setBottomBarMenuOpen(false);
1321
+ void this.logoutFromEinblick();
1322
+ });
1323
+ moreMenu.append(openAppButton, logoutButton);
1324
+ actions.append(cmsButton, moreButton);
1325
+ bar.append(brand, actions, moreMenu);
1326
+ shadowRoot.append(style, bar);
1327
+ document.body.append(host);
1328
+ this.bottomBarHost = host;
1329
+ this.bottomBarSiteName = siteName;
1330
+ this.bottomBarCmsButton = cmsButton;
1331
+ this.bottomBarMoreButton = moreButton;
1332
+ this.bottomBarMoreMenu = moreMenu;
1333
+ this.bottomBarOpenAppButton = openAppButton;
1334
+ this.bottomBarLogoutButton = logoutButton;
1335
+ this.updateBottomBar();
1336
+ }
993
1337
  updateBrandBadge() {
994
1338
  if (!this.brandBadge) {
995
1339
  return;
@@ -1020,6 +1364,71 @@ class EinblickEditRuntime {
1020
1364
  ? this.messages.brand.authenticatedHint
1021
1365
  : this.messages.brand.unauthenticatedHint;
1022
1366
  }
1367
+ updateBottomBar() {
1368
+ if (!this.bottomBarHost) {
1369
+ this.applyBottomLayout(false);
1370
+ return;
1371
+ }
1372
+ const isVisible = this.chrome === 'bar' && this.state.isAuthenticated;
1373
+ this.bottomBarHost.hidden = !isVisible;
1374
+ this.bottomBarHost.style.display = isVisible ? 'block' : 'none';
1375
+ this.applyBottomLayout(isVisible);
1376
+ if (!isVisible) {
1377
+ this.setBottomBarMenuOpen(false);
1378
+ this.closeNavigator();
1379
+ return;
1380
+ }
1381
+ if (this.bottomBarSiteName) {
1382
+ this.bottomBarSiteName.textContent =
1383
+ this.state.siteName?.trim() || this.messages.brand.connectedSite;
1384
+ this.bottomBarSiteName.title = this.bottomBarSiteName.textContent;
1385
+ }
1386
+ if (this.bottomBarCmsButton) {
1387
+ this.bottomBarCmsButton.textContent = this.messages.actions.cmsCollections;
1388
+ this.bottomBarCmsButton.setAttribute('aria-expanded', this.navigatorPanel ? 'true' : 'false');
1389
+ }
1390
+ if (this.bottomBarMoreButton) {
1391
+ this.bottomBarMoreButton.setAttribute('aria-label', this.messages.actions.moreActions);
1392
+ this.bottomBarMoreButton.setAttribute('aria-expanded', this.isBottomBarMenuOpen() ? 'true' : 'false');
1393
+ }
1394
+ if (this.bottomBarOpenAppButton) {
1395
+ this.bottomBarOpenAppButton.textContent =
1396
+ this.messages.actions.goToEinblick;
1397
+ }
1398
+ if (this.bottomBarLogoutButton) {
1399
+ this.bottomBarLogoutButton.textContent = this.messages.actions.logout;
1400
+ }
1401
+ }
1402
+ applyBottomLayout(active) {
1403
+ if (typeof document === 'undefined') {
1404
+ return;
1405
+ }
1406
+ const root = document.documentElement;
1407
+ if (!active) {
1408
+ root.style.removeProperty('--einblick-editor-bottom-offset');
1409
+ root.removeAttribute('data-einblick-editor-bar-active');
1410
+ if (this.hasReservedBodyPadding) {
1411
+ if (this.originalBodyPaddingBottom) {
1412
+ document.body.style.paddingBottom = this.originalBodyPaddingBottom;
1413
+ }
1414
+ else {
1415
+ document.body.style.removeProperty('padding-bottom');
1416
+ }
1417
+ }
1418
+ this.hasReservedBodyPadding = false;
1419
+ this.originalBodyPaddingBottom = null;
1420
+ return;
1421
+ }
1422
+ root.style.setProperty('--einblick-editor-bottom-offset', `${CONTROL_BAR_HEIGHT}px`);
1423
+ root.setAttribute('data-einblick-editor-bar-active', 'true');
1424
+ if (!this.reserveBottomSpace || this.hasReservedBodyPadding) {
1425
+ return;
1426
+ }
1427
+ this.originalBodyPaddingBottom = document.body.style.paddingBottom || null;
1428
+ const currentPadding = window.getComputedStyle(document.body).paddingBottom;
1429
+ document.body.style.paddingBottom = `calc(${currentPadding} + ${CONTROL_BAR_HEIGHT}px)`;
1430
+ this.hasReservedBodyPadding = true;
1431
+ }
1023
1432
  isBrandPopoverOpen() {
1024
1433
  return !!this.brandPopover && !this.brandPopover.hidden;
1025
1434
  }
@@ -1039,6 +1448,68 @@ class EinblickEditRuntime {
1039
1448
  const url = new URL('/cms', this.appOrigin).toString();
1040
1449
  window.open(url, '_blank', 'noopener,noreferrer');
1041
1450
  }
1451
+ setNavigatorOpen(open) {
1452
+ if (open) {
1453
+ this.openNavigator();
1454
+ return;
1455
+ }
1456
+ this.closeNavigator();
1457
+ }
1458
+ openNavigator() {
1459
+ if (!this.state.isAuthenticated) {
1460
+ this.callbacks.onError?.(this.messages.errors.signInToEditContent);
1461
+ return;
1462
+ }
1463
+ if (this.navigatorPanel) {
1464
+ return;
1465
+ }
1466
+ this.setBottomBarMenuOpen(false);
1467
+ let navigatorUrl;
1468
+ const nonce = createRandomId();
1469
+ try {
1470
+ navigatorUrl = this.buildNavigatorUrl(nonce);
1471
+ }
1472
+ catch (error) {
1473
+ this.callbacks.onError?.(error instanceof Error
1474
+ ? error.message
1475
+ : this.messages.errors.openEditorFailed);
1476
+ return;
1477
+ }
1478
+ const panel = document.createElement('div');
1479
+ panel.setAttribute('data-einblick-navigator-panel', 'true');
1480
+ panel.style.position = 'fixed';
1481
+ panel.style.left = '0';
1482
+ panel.style.right = '0';
1483
+ panel.style.bottom = `${CONTROL_BAR_HEIGHT}px`;
1484
+ panel.style.height = `min(40rem, calc(100vh - ${CONTROL_BAR_HEIGHT}px))`;
1485
+ panel.style.zIndex = '2147483643';
1486
+ panel.style.borderTop = '1px solid rgba(148, 163, 184, 0.36)';
1487
+ panel.style.overflow = 'hidden';
1488
+ panel.style.background = 'rgb(255, 255, 255)';
1489
+ panel.style.boxShadow = '0 -12px 28px rgba(15, 23, 42, 0.12)';
1490
+ const iframe = document.createElement('iframe');
1491
+ iframe.src = navigatorUrl;
1492
+ iframe.title = this.messages.actions.cmsCollections;
1493
+ iframe.style.display = 'block';
1494
+ iframe.style.width = '100%';
1495
+ iframe.style.height = '100%';
1496
+ iframe.style.border = '0';
1497
+ iframe.style.background = 'rgb(255, 255, 255)';
1498
+ panel.append(iframe);
1499
+ document.body.append(panel);
1500
+ this.navigatorPanel = panel;
1501
+ this.navigatorNonce = nonce;
1502
+ this.updateBottomBar();
1503
+ }
1504
+ closeNavigator() {
1505
+ const hadNavigator = !!this.navigatorPanel || !!this.navigatorNonce;
1506
+ this.navigatorPanel?.remove();
1507
+ this.navigatorPanel = null;
1508
+ this.navigatorNonce = null;
1509
+ if (hadNavigator) {
1510
+ this.updateBottomBar();
1511
+ }
1512
+ }
1042
1513
  async logoutFromEinblick() {
1043
1514
  if (typeof window === 'undefined') {
1044
1515
  return;
@@ -1207,6 +1678,34 @@ class EinblickEditRuntime {
1207
1678
  }
1208
1679
  return url.toString();
1209
1680
  }
1681
+ buildNavigatorUrl(nonce) {
1682
+ const sessionToken = this.getActiveSessionToken();
1683
+ if (!sessionToken) {
1684
+ throw new Error(this.messages.errors.editSessionExpired);
1685
+ }
1686
+ const url = new URL('/sdk/navigator', this.appOrigin);
1687
+ url.searchParams.set('origin', window.location.origin);
1688
+ url.searchParams.set('nonce', nonce);
1689
+ url.searchParams.set('sessionToken', sessionToken);
1690
+ return url.toString();
1691
+ }
1692
+ buildCollectionEditorUrl(args) {
1693
+ const sessionToken = this.getActiveSessionToken();
1694
+ if (!sessionToken) {
1695
+ throw new Error(this.messages.errors.editSessionExpired);
1696
+ }
1697
+ const url = new URL('/sdk/editor', this.appOrigin);
1698
+ url.searchParams.set('origin', window.location.origin);
1699
+ url.searchParams.set('nonce', args.nonce);
1700
+ url.searchParams.set('sessionToken', sessionToken);
1701
+ url.searchParams.set('returnTo', window.location.href);
1702
+ url.searchParams.set('collectionId', args.collectionId);
1703
+ url.searchParams.set('intent', args.intent);
1704
+ if (args.recordId) {
1705
+ url.searchParams.set('recordId', args.recordId);
1706
+ }
1707
+ return url.toString();
1708
+ }
1210
1709
  updateHoverButtonVisibility(element) {
1211
1710
  if (!this.hoverCreateButton) {
1212
1711
  return;
@@ -1529,7 +2028,9 @@ class EinblickEditRuntime {
1529
2028
  this.callbacks.onError?.(this.messages.errors.signInToEditContent);
1530
2029
  return;
1531
2030
  }
2031
+ this.setBottomBarMenuOpen(false);
1532
2032
  this.closeInlineEditor();
2033
+ this.closeNavigator();
1533
2034
  this.closeDrawer();
1534
2035
  this.setActiveElement(null);
1535
2036
  const nonce = createRandomId();
@@ -1564,6 +2065,71 @@ class EinblickEditRuntime {
1564
2065
  document.body.append(backdrop);
1565
2066
  this.editorBackdrop = backdrop;
1566
2067
  }
2068
+ openCollectionDrawer(args) {
2069
+ if (!this.state.isAuthenticated) {
2070
+ this.callbacks.onError?.(this.messages.errors.signInToEditContent);
2071
+ return;
2072
+ }
2073
+ this.setBottomBarMenuOpen(false);
2074
+ this.closeInlineEditor();
2075
+ this.closeNavigator();
2076
+ this.closeDrawer();
2077
+ this.setActiveElement(null);
2078
+ const nonce = createRandomId();
2079
+ let editorUrl;
2080
+ try {
2081
+ editorUrl = this.buildCollectionEditorUrl({
2082
+ collectionId: args.collectionId,
2083
+ nonce,
2084
+ intent: args.intent,
2085
+ recordId: args.recordId,
2086
+ });
2087
+ }
2088
+ catch (error) {
2089
+ this.callbacks.onError?.(error instanceof Error
2090
+ ? error.message
2091
+ : this.messages.errors.openEditorFailed);
2092
+ return;
2093
+ }
2094
+ this.editorNonce = nonce;
2095
+ this.editorTarget = null;
2096
+ this.editorBinding = args.resourceSlug
2097
+ ? {
2098
+ resourceSlug: args.resourceSlug,
2099
+ recordId: args.recordId ?? '',
2100
+ resourceMode: 'collection',
2101
+ displayMode: 'drawer',
2102
+ }
2103
+ : null;
2104
+ const backdrop = document.createElement('div');
2105
+ backdrop.setAttribute('data-einblick-editor-backdrop', 'true');
2106
+ const shell = document.createElement('div');
2107
+ shell.setAttribute('data-einblick-editor-shell', 'true');
2108
+ const iframe = document.createElement('iframe');
2109
+ iframe.setAttribute('data-einblick-editor-frame', 'true');
2110
+ iframe.src = editorUrl;
2111
+ iframe.title = this.messages.editor.frameTitle;
2112
+ shell.append(iframe);
2113
+ backdrop.append(shell);
2114
+ backdrop.addEventListener('click', (event) => {
2115
+ if (event.target === backdrop) {
2116
+ this.closeDrawer();
2117
+ }
2118
+ });
2119
+ document.body.append(backdrop);
2120
+ this.editorBackdrop = backdrop;
2121
+ }
2122
+ isBottomBarMenuOpen() {
2123
+ return !!this.bottomBarMoreMenu && !this.bottomBarMoreMenu.hidden;
2124
+ }
2125
+ setBottomBarMenuOpen(open) {
2126
+ if (!this.bottomBarMoreMenu || !this.bottomBarMoreButton) {
2127
+ return;
2128
+ }
2129
+ const nextOpen = open && this.state.isAuthenticated;
2130
+ this.bottomBarMoreMenu.hidden = !nextOpen;
2131
+ this.bottomBarMoreButton.setAttribute('aria-expanded', nextOpen ? 'true' : 'false');
2132
+ }
1567
2133
  closeDrawer() {
1568
2134
  this.editorBackdrop?.remove();
1569
2135
  this.editorBackdrop = null;