@memori.ai/memori-react 8.8.0 → 8.8.2

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/Header/Header.js +8 -8
  3. package/dist/components/Header/Header.js.map +1 -1
  4. package/dist/components/MediaWidget/MediaItemWidget.css +5 -0
  5. package/dist/components/MediaWidget/MediaItemWidget.d.ts +10 -0
  6. package/dist/components/MediaWidget/MediaItemWidget.js +49 -5
  7. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  8. package/dist/components/MemoriWidget/MemoriWidget.js +12 -7
  9. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  10. package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +1 -1
  11. package/dist/components/SettingsDrawer/SettingsDrawer.js +2 -12
  12. package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  13. package/dist/context/visemeContext.d.ts +1 -1
  14. package/dist/context/visemeContext.js +1 -1
  15. package/dist/helpers/tts/useTTS.js +28 -10
  16. package/dist/helpers/tts/useTTS.js.map +1 -1
  17. package/esm/components/Header/Header.js +8 -8
  18. package/esm/components/Header/Header.js.map +1 -1
  19. package/esm/components/MediaWidget/MediaItemWidget.css +5 -0
  20. package/esm/components/MediaWidget/MediaItemWidget.d.ts +10 -0
  21. package/esm/components/MediaWidget/MediaItemWidget.js +47 -4
  22. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  23. package/esm/components/MemoriWidget/MemoriWidget.js +12 -7
  24. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  25. package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +1 -1
  26. package/esm/components/SettingsDrawer/SettingsDrawer.js +2 -12
  27. package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  28. package/esm/context/visemeContext.d.ts +1 -1
  29. package/esm/context/visemeContext.js +1 -1
  30. package/esm/helpers/tts/useTTS.js +29 -11
  31. package/esm/helpers/tts/useTTS.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/Header/Header.tsx +10 -9
  34. package/src/components/MediaWidget/MediaItemWidget.css +5 -0
  35. package/src/components/MediaWidget/MediaItemWidget.stories.tsx +78 -0
  36. package/src/components/MediaWidget/MediaItemWidget.tsx +165 -35
  37. package/src/components/MediaWidget/__snapshots__/MediaItemWidget.test.tsx.snap +128 -102
  38. package/src/components/MemoriWidget/MemoriWidget.tsx +19 -7
  39. package/src/components/SettingsDrawer/SettingsDrawer.tsx +6 -6
  40. package/src/context/visemeContext.tsx +1 -1
  41. package/src/helpers/tts/useTTS.ts +39 -16
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "8.8.0",
2
+ "version": "8.8.2",
3
3
  "name": "@memori.ai/memori-react",
4
4
  "author": "Memori Srl",
5
5
  "main": "dist/index.js",
@@ -224,17 +224,18 @@ const Header: React.FC<Props> = ({
224
224
  fullScreenHandler ||
225
225
  (() => {
226
226
  if (!document.fullscreenElement) {
227
- const mainContent =
228
- document.querySelector('main#content') ||
229
- document.querySelector('body');
230
227
  const memoriWidget = document.querySelector('.memori-widget');
231
- if (mainContent) {
228
+ if (memoriWidget) {
232
229
  // Set white background before entering fullscreen
233
- if (memoriWidget) {
234
- (memoriWidget as HTMLElement).style.backgroundColor =
235
- '#FFFFFF';
236
- }
237
- mainContent.requestFullscreen().catch(err => {
230
+ (memoriWidget as HTMLElement).style.backgroundColor = '#FFFFFF';
231
+
232
+ memoriWidget.requestFullscreen().then(() => {
233
+ // Move portals inside fullscreen element
234
+ const portals = document.querySelectorAll('[data-headlessui-portal]');
235
+ portals.forEach(portal => {
236
+ memoriWidget.appendChild(portal);
237
+ });
238
+ }).catch(err => {
238
239
  console.warn('Error attempting to enable fullscreen:', err);
239
240
  });
240
241
  }
@@ -165,6 +165,7 @@ a.memori-media-item--link {
165
165
  }
166
166
 
167
167
  .memori-media-item-preview--content {
168
+ min-height: 50vh;
168
169
  max-height: 50vh;
169
170
  padding: 1rem;
170
171
  border: 1px solid var(--memori-border);
@@ -174,6 +175,10 @@ a.memori-media-item--link {
174
175
  overflow-y: auto;
175
176
  }
176
177
 
178
+ .memori-media-item--snippet{
179
+ min-width: 450px;
180
+ }
181
+
177
182
  .memori-media-item-preview--text {
178
183
  color: var(--memori-color-text);
179
184
  font-family: inherit;
@@ -106,6 +106,84 @@ ImagesGrid.args = {
106
106
  })),
107
107
  };
108
108
 
109
+ export const JavaScript = Template.bind({});
110
+ JavaScript.args = {
111
+ items: [
112
+ {
113
+ mediumID: '65ca4a6d-f20b-402e-9d79-5e470f247928',
114
+ mimeType: 'text/javascript',
115
+ title: 'JavaScript',
116
+ content: `[
117
+ {
118
+ "name": "France",
119
+ "capital": "Paris",
120
+ "population": 67364357,
121
+ "area": 551695,
122
+ "currency": "Euro",
123
+ "languages": [
124
+ "French"
125
+ ],
126
+ "region": "Europe",
127
+ "subregion": "Western Europe",
128
+ "flag": "https://upload.wikimedia.org/wikipedia/commons/c/c3/Flag_of_France.svg"
129
+ },
130
+ {
131
+ "name": "Germany",
132
+ "capital": "Berlin",
133
+ "population": 83240525,
134
+ "area": 357022,
135
+ "currency": "Euro",
136
+ "languages": [
137
+ "German"
138
+ ],
139
+ "region": "Europe",
140
+ "subregion": "Western Europe",
141
+ "flag": "https://upload.wikimedia.org/wikipedia/commons/b/ba/Flag_of_Germany.svg"
142
+ },
143
+ {
144
+ "name": "United States",
145
+ "capital": "Washington, D.C.",
146
+ "population": 331893745,
147
+ "area": 9833517,
148
+ "currency": "USD",
149
+ "languages": [
150
+ "English"
151
+ ],
152
+ "region": "Americas",
153
+ "subregion": "Northern America",
154
+ "flag": "https://upload.wikimedia.org/wikipedia/commons/a/a4/Flag_of_the_United_States.svg"
155
+ },
156
+ {
157
+ "name": "Belgium",
158
+ "capital": "Brussels",
159
+ "population": 11589623,
160
+ "area": 30528,
161
+ "currency": "Euro",
162
+ "languages": [
163
+ "Flemish",
164
+ "French",
165
+ "German"
166
+ ],
167
+ "region": "Europe",
168
+ "subregion": "Western Europe",
169
+ "flag": "https://upload.wikimedia.org/wikipedia/commons/6/65/Flag_of_Belgium.svg"
170
+ ]`,
171
+ },
172
+ ],
173
+ };
174
+
175
+ export const longTXT = Template.bind({});
176
+ longTXT.args = {
177
+ items: [
178
+ {
179
+ mediumID: '65ca4a6d-f20b-402e-9d79-5e470f247928',
180
+ mimeType: 'text/plain',
181
+ title: 'Long Text',
182
+ content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.',
183
+ },
184
+ ],
185
+ };
186
+
109
187
  export const WithCustomMediaRenderer = Template.bind({});
110
188
  WithCustomMediaRenderer.args = {
111
189
  items: [
@@ -65,6 +65,8 @@ export const RenderMediaItem = ({
65
65
  return customRenderer;
66
66
  }
67
67
 
68
+ const isCodeSnippet = prismSyntaxLangs.map(l => l.mimeType).includes(item.mimeType);
69
+
68
70
  const renderMediaContent = (item: Medium) => {
69
71
  switch (item.mimeType) {
70
72
  case 'image/jpeg':
@@ -160,6 +162,38 @@ export const RenderMediaItem = ({
160
162
  }
161
163
  };
162
164
 
165
+ // Handle code snippets with modal
166
+ if (isCodeSnippet && item.content) {
167
+ return (
168
+ <>
169
+ <a
170
+ className="memori-media-item--link"
171
+ href="#"
172
+ onClick={e => {
173
+ e.preventDefault();
174
+ setModalOpen(true);
175
+ }}
176
+ title={item.title}
177
+ >
178
+ <Card hoverable cover={renderMediaContent(item)} title={item.title} />
179
+ </a>
180
+
181
+ <Modal
182
+ open={modalOpen}
183
+ onClose={() => setModalOpen(false)}
184
+ title={item.title}
185
+ className="memori-media-item-preview--modal"
186
+ width="80%"
187
+ widthMd="90%"
188
+ >
189
+ <div className="memori-media-item-preview--content">
190
+ <Snippet medium={item} showCopyButton={true} />
191
+ </div>
192
+ </Modal>
193
+ </>
194
+ );
195
+ }
196
+
163
197
  if (!item.url && item?.type === 'document' && item.content) {
164
198
  return (
165
199
  <>
@@ -183,7 +217,13 @@ export const RenderMediaItem = ({
183
217
  width="60%"
184
218
  widthMd="70%"
185
219
  footer={
186
- <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.5rem' }}>
220
+ <div
221
+ style={{
222
+ display: 'flex',
223
+ justifyContent: 'flex-end',
224
+ gap: '0.5rem',
225
+ }}
226
+ >
187
227
  <Button
188
228
  onClick={async () => {
189
229
  try {
@@ -328,6 +368,50 @@ export const RenderMediaItem = ({
328
368
  }
329
369
  };
330
370
 
371
+ export const RenderSnippetItem = ({
372
+ item,
373
+ sessionID: _sessionID,
374
+ tenantID: _tenantID,
375
+ baseURL: _baseURL,
376
+ apiURL: _apiURL,
377
+ onClick,
378
+ }: {
379
+ item: Medium & { type: string };
380
+ sessionID?: string;
381
+ tenantID?: string;
382
+ baseURL?: string;
383
+ apiURL?: string;
384
+ onClick?: (mediumID: string) => void;
385
+ }) => {
386
+ return (
387
+ <>
388
+ <a
389
+ href="#"
390
+ onClick={e => {
391
+ e.preventDefault();
392
+ if (onClick) {
393
+ console.log('Snippet item.mediumID:', item.mediumID);
394
+ onClick(item.mediumID);
395
+ console.log('clicked snippet');
396
+ }
397
+ }}
398
+ className="memori-media-item--link"
399
+ title={item.title}
400
+ >
401
+ <Card
402
+ hoverable
403
+ // onClick={() => setModalOpen(true)}
404
+ className="memori-media-item--card memori-media-item--snippet"
405
+ >
406
+ <div className="memori-media-item--snippet-preview">
407
+ <Snippet showCopyButton={false} preview={true} medium={item} />
408
+ </div>
409
+ </Card>
410
+ </a>
411
+ </>
412
+ );
413
+ };
414
+
331
415
  const MediaItemWidget: React.FC<Props> = ({
332
416
  items,
333
417
  sessionID,
@@ -391,13 +475,19 @@ const MediaItemWidget: React.FC<Props> = ({
391
475
  m => m.mimeType === 'text/css' && !!m.properties?.executable
392
476
  );
393
477
 
478
+ console.log('openModalMedium', openModalMedium);
479
+ console.log('codeSnippets', codeSnippets);
480
+ console.log('nonCodeDisplayMedia', nonCodeDisplayMedia);
481
+
394
482
  return (
395
483
  <Transition appear show as="div" className="memori-media-items">
396
484
  {!!nonCodeDisplayMedia.length && (
397
- <div className={cx('memori-media-items--grid', {
398
- 'memori-media-items--user': fromUser,
399
- 'memori-media-items--agent': !fromUser,
400
- })}>
485
+ <div
486
+ className={cx('memori-media-items--grid', {
487
+ 'memori-media-items--user': fromUser,
488
+ 'memori-media-items--agent': !fromUser,
489
+ })}
490
+ >
401
491
  {nonCodeDisplayMedia.map((item: Medium, index: number) => (
402
492
  <Transition.Child
403
493
  as="div"
@@ -417,7 +507,7 @@ const MediaItemWidget: React.FC<Props> = ({
417
507
  baseURL={baseURL}
418
508
  apiURL={apiURL}
419
509
  onClick={mediumID => {
420
- setOpenModalMedium(media.find(m => m.mediumID === mediumID));
510
+ setOpenModalMedium(nonCodeDisplayMedia.find(m => m.mediumID === mediumID));
421
511
  }}
422
512
  item={{
423
513
  ...item,
@@ -432,21 +522,48 @@ const MediaItemWidget: React.FC<Props> = ({
432
522
  ))}
433
523
  </div>
434
524
  )}
435
- {codeSnippets.map(medium => (
436
- <Transition.Child
437
- as="div"
438
- className="memori-media-item--snippet"
439
- key={medium.mediumID}
440
- enter="ease-out duration-500"
441
- enterFrom="opacity-0 translate-y-1"
442
- enterTo="opacity-1 translate-y-0"
443
- leave="ease-in duration-300"
444
- leaveFrom="opacity-1"
445
- leaveTo="opacity-0"
525
+ {!!codeSnippets.length && (
526
+ <div
527
+ className={cx('memori-media-items--grid', {
528
+ 'memori-media-items--user': fromUser,
529
+ 'memori-media-items--agent': !fromUser,
530
+ })}
446
531
  >
447
- <Snippet key={medium.mediumID} medium={medium} />
448
- </Transition.Child>
449
- ))}
532
+ {codeSnippets.map((item: Medium, index: number) => (
533
+ <Transition.Child
534
+ as="div"
535
+ className="memori-media-item"
536
+ key={item.mediumID + '&index=' + index}
537
+ enter={`ease-out duration-500 delay-${index * 100}`}
538
+ enterFrom="opacity-0 scale-95"
539
+ enterTo="opacity-1 scale-100"
540
+ leave="ease-in duration-300"
541
+ leaveFrom="opacity-1 scale-100"
542
+ leaveTo="opacity-0 scale-95"
543
+ >
544
+ <RenderSnippetItem
545
+ sessionID={sessionID}
546
+ tenantID={tenantID}
547
+ baseURL={baseURL}
548
+ apiURL={apiURL}
549
+ onClick={mediumID => {
550
+ console.log('Snippet clicked, mediumID:', mediumID);
551
+ const foundMedium = codeSnippets.find(m => m.mediumID === mediumID);
552
+ console.log('Found medium:', foundMedium);
553
+ setOpenModalMedium(foundMedium);
554
+ }}
555
+ item={{
556
+ ...item,
557
+ title: item.title,
558
+ url: item.url,
559
+ content: item.content,
560
+ type: 'document',
561
+ }}
562
+ />
563
+ </Transition.Child>
564
+ ))}
565
+ </div>
566
+ )}
450
567
  {cssExecutableCode.map(medium => (
451
568
  <style
452
569
  key={medium.mediumID}
@@ -454,7 +571,7 @@ const MediaItemWidget: React.FC<Props> = ({
454
571
  ></style>
455
572
  ))}
456
573
 
457
- {openModalMedium?.mediumID && (
574
+ {openModalMedium && (
458
575
  <Modal
459
576
  width="100%"
460
577
  widthMd="100%"
@@ -463,21 +580,34 @@ const MediaItemWidget: React.FC<Props> = ({
463
580
  onClose={() => setOpenModalMedium(undefined)}
464
581
  footer={null}
465
582
  >
466
- <RenderMediaItem
467
- isChild
468
- sessionID={sessionID}
469
- tenantID={tenantID}
470
- baseURL={baseURL}
471
- apiURL={apiURL}
472
- item={{
473
- ...openModalMedium,
474
- title: openModalMedium.title,
475
- url: openModalMedium.url,
476
- content: openModalMedium.content,
477
- type: 'document',
583
+ {prismSyntaxLangs
584
+ .map(l => l.mimeType)
585
+ .includes(openModalMedium.mimeType) ? (
586
+ <div
587
+ style={{
588
+ minHeight: '100%',
589
+ background: 'none',
478
590
  }}
479
- customMediaRenderer={customMediaRenderer}
480
- />
591
+ className="memori-media-item-preview--content">
592
+ <Snippet preview={false} medium={openModalMedium} />
593
+ </div>
594
+ ) : (
595
+ <RenderMediaItem
596
+ isChild
597
+ sessionID={sessionID}
598
+ tenantID={tenantID}
599
+ baseURL={baseURL}
600
+ apiURL={apiURL}
601
+ item={{
602
+ ...openModalMedium,
603
+ title: openModalMedium.title,
604
+ url: openModalMedium.url,
605
+ content: openModalMedium.content,
606
+ type: 'document',
607
+ }}
608
+ customMediaRenderer={customMediaRenderer}
609
+ />
610
+ )}
481
611
  </Modal>
482
612
  )}
483
613
  </Transition>
@@ -20,66 +20,79 @@ exports[`renders MediaItemWidget unchanged with css snippet to show 1`] = `
20
20
  class="memori-media-items"
21
21
  >
22
22
  <div
23
- class="memori-media-item--snippet ease-out duration-500 opacity-0 translate-y-1"
23
+ class="memori-media-items--grid memori-media-items--agent"
24
24
  >
25
25
  <div
26
- class="memori-snippet"
26
+ class="memori-media-item ease-out duration-500 delay-0 opacity-0 scale-95"
27
27
  >
28
- <div
29
- class="memori-snippet--content"
28
+ <a
29
+ class="memori-media-item--link"
30
+ href="#"
31
+ title="Snippet"
30
32
  >
31
- <pre
32
- aria-labelledby="#snippet-65ca4a6d-f20b-402e-9d79-5e470f247927"
33
- class="line-numbers"
33
+ <div
34
+ class="memori-card memori-media-item--card memori-media-item--snippet memori-card--hoverable"
34
35
  >
35
- <code
36
- class="language-scss"
36
+ <div
37
+ class="memori-spin"
37
38
  >
38
- body{
39
+ <div
40
+ class="memori-card--content"
41
+ >
42
+ <div
43
+ class="memori-card--children"
44
+ >
45
+ <div
46
+ class="memori-media-item--snippet-preview"
47
+ >
48
+ <div
49
+ class="memori-snippet"
50
+ >
51
+ <div
52
+ class="memori-snippet--content"
53
+ >
54
+ <pre
55
+ aria-labelledby="#snippet-65ca4a6d-f20b-402e-9d79-5e470f247927"
56
+ class="line-numbers"
57
+ >
58
+ <code
59
+ class="language-scss"
60
+ >
61
+ body{
39
62
  background-color: #f00;
40
63
  }
41
- </code>
42
- </pre>
43
- <button
44
- class="memori-button memori-button--ghost memori-button--rounded memori-button--icon-only memori-snippet--copy-button"
45
- title="copy"
46
- >
47
- <span
48
- class="memori-button--icon"
49
- >
50
- <svg
51
- aria-hidden="true"
52
- fill="none"
53
- focusable="false"
54
- role="img"
55
- stroke="currentColor"
56
- stroke-linecap="round"
57
- stroke-linejoin="round"
58
- stroke-width="1.5"
59
- viewBox="0 0 24 24"
60
- xmlns="http://www.w3.org/2000/svg"
64
+ </code>
65
+ </pre>
66
+ </div>
67
+ <p
68
+ class="memori-snippet--caption"
69
+ id="snippet-65ca4a6d-f20b-402e-9d79-5e470f247927"
70
+ >
71
+ Snippet
72
+ </p>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ <div
78
+ class="memori-spin--spinner"
61
79
  >
62
- <rect
63
- height="14"
64
- rx="2"
65
- ry="2"
66
- width="14"
67
- x="8"
68
- y="8"
69
- />
70
- <path
71
- d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
72
- />
73
- </svg>
74
- </span>
75
- </button>
76
- </div>
77
- <p
78
- class="memori-snippet--caption"
79
- id="snippet-65ca4a6d-f20b-402e-9d79-5e470f247927"
80
- >
81
- Snippet
82
- </p>
80
+ <svg
81
+ aria-hidden="true"
82
+ class="memori-loading-icon"
83
+ focusable="false"
84
+ role="img"
85
+ viewBox="0 0 1024 1024"
86
+ xmlns="http://www.w3.org/2000/svg"
87
+ >
88
+ <path
89
+ d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
90
+ />
91
+ </svg>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </a>
83
96
  </div>
84
97
  </div>
85
98
  </div>
@@ -262,64 +275,77 @@ exports[`renders MediaItemWidget unchanged with js snippet to show 1`] = `
262
275
  class="memori-media-items"
263
276
  >
264
277
  <div
265
- class="memori-media-item--snippet ease-out duration-500 opacity-0 translate-y-1"
278
+ class="memori-media-items--grid memori-media-items--agent"
266
279
  >
267
280
  <div
268
- class="memori-snippet"
281
+ class="memori-media-item ease-out duration-500 delay-0 opacity-0 scale-95"
269
282
  >
270
- <div
271
- class="memori-snippet--content"
283
+ <a
284
+ class="memori-media-item--link"
285
+ href="#"
286
+ title="Snippet"
272
287
  >
273
- <pre
274
- aria-labelledby="#snippet-a669fadb-12c0-469b-9b6c-34db22d371ca"
275
- class="line-numbers"
276
- >
277
- <code
278
- class="language-jsx"
279
- >
280
- console.log("Hello World!");
281
- </code>
282
- </pre>
283
- <button
284
- class="memori-button memori-button--ghost memori-button--rounded memori-button--icon-only memori-snippet--copy-button"
285
- title="copy"
288
+ <div
289
+ class="memori-card memori-media-item--card memori-media-item--snippet memori-card--hoverable"
286
290
  >
287
- <span
288
- class="memori-button--icon"
291
+ <div
292
+ class="memori-spin"
289
293
  >
290
- <svg
291
- aria-hidden="true"
292
- fill="none"
293
- focusable="false"
294
- role="img"
295
- stroke="currentColor"
296
- stroke-linecap="round"
297
- stroke-linejoin="round"
298
- stroke-width="1.5"
299
- viewBox="0 0 24 24"
300
- xmlns="http://www.w3.org/2000/svg"
294
+ <div
295
+ class="memori-card--content"
301
296
  >
302
- <rect
303
- height="14"
304
- rx="2"
305
- ry="2"
306
- width="14"
307
- x="8"
308
- y="8"
309
- />
310
- <path
311
- d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
312
- />
313
- </svg>
314
- </span>
315
- </button>
316
- </div>
317
- <p
318
- class="memori-snippet--caption"
319
- id="snippet-a669fadb-12c0-469b-9b6c-34db22d371ca"
320
- >
321
- Snippet
322
- </p>
297
+ <div
298
+ class="memori-card--children"
299
+ >
300
+ <div
301
+ class="memori-media-item--snippet-preview"
302
+ >
303
+ <div
304
+ class="memori-snippet"
305
+ >
306
+ <div
307
+ class="memori-snippet--content"
308
+ >
309
+ <pre
310
+ aria-labelledby="#snippet-a669fadb-12c0-469b-9b6c-34db22d371ca"
311
+ class="line-numbers"
312
+ >
313
+ <code
314
+ class="language-jsx"
315
+ >
316
+ console.log("Hello World!");
317
+ </code>
318
+ </pre>
319
+ </div>
320
+ <p
321
+ class="memori-snippet--caption"
322
+ id="snippet-a669fadb-12c0-469b-9b6c-34db22d371ca"
323
+ >
324
+ Snippet
325
+ </p>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ <div
331
+ class="memori-spin--spinner"
332
+ >
333
+ <svg
334
+ aria-hidden="true"
335
+ class="memori-loading-icon"
336
+ focusable="false"
337
+ role="img"
338
+ viewBox="0 0 1024 1024"
339
+ xmlns="http://www.w3.org/2000/svg"
340
+ >
341
+ <path
342
+ d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
343
+ />
344
+ </svg>
345
+ </div>
346
+ </div>
347
+ </div>
348
+ </a>
323
349
  </div>
324
350
  </div>
325
351
  </div>