@memori.ai/memori-react 8.5.2 → 8.6.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +3 -3
  3. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +2 -2
  4. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  5. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +129 -3
  6. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  7. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js +189 -26
  8. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js.map +1 -1
  9. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.d.ts +2 -0
  10. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.js +138 -17
  11. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.js.map +1 -1
  12. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +12 -0
  13. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +63 -24
  14. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  15. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +8 -3
  16. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
  17. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +2 -0
  18. package/dist/helpers/utils.d.ts +2 -0
  19. package/dist/helpers/utils.js +19 -1
  20. package/dist/helpers/utils.js.map +1 -1
  21. package/dist/locales/de.json +2 -1
  22. package/dist/locales/en.json +2 -1
  23. package/dist/locales/es.json +2 -1
  24. package/dist/locales/fr.json +2 -1
  25. package/dist/locales/it.json +2 -1
  26. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.d.ts +3 -3
  27. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +2 -2
  28. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  29. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +129 -3
  30. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  31. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js +188 -26
  32. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.js.map +1 -1
  33. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.d.ts +2 -0
  34. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.js +138 -17
  35. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.js.map +1 -1
  36. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +12 -0
  37. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +63 -24
  38. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  39. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +8 -3
  40. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
  41. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +2 -0
  42. package/esm/helpers/utils.d.ts +2 -0
  43. package/esm/helpers/utils.js +16 -0
  44. package/esm/helpers/utils.js.map +1 -1
  45. package/esm/locales/de.json +2 -1
  46. package/esm/locales/en.json +2 -1
  47. package/esm/locales/es.json +2 -1
  48. package/esm/locales/fr.json +2 -1
  49. package/esm/locales/it.json +2 -1
  50. package/package.json +1 -1
  51. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +5 -5
  52. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.tsx +141 -3
  53. package/src/components/MemoriArtifactSystem/components/ArtifactActions/hooks/useCopyArtifact.ts +211 -33
  54. package/src/components/MemoriArtifactSystem/components/ArtifactActions/utils/PDFExporter.ts +190 -26
  55. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.css +12 -0
  56. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +191 -161
  57. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +8 -3
  58. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +2 -0
  59. package/src/helpers/utils.ts +21 -0
  60. package/src/locales/de.json +2 -1
  61. package/src/locales/en.json +2 -1
  62. package/src/locales/es.json +2 -1
  63. package/src/locales/fr.json +2 -1
  64. package/src/locales/it.json +2 -1
@@ -12,7 +12,7 @@ import Close from '../../../icons/Close';
12
12
  import ArtifactActions from '../ArtifactActions/ArtifactActions';
13
13
  import { useArtifact } from '../../context/ArtifactContext';
14
14
  import ArtifactPreview from '../ArtifactPreview/ArtifactPreview';
15
- import { ArtifactTab } from '../../types/artifact.types';
15
+ import { ArtifactData, ArtifactTab } from '../../types/artifact.types';
16
16
  import cx from 'classnames';
17
17
  import Drawer from '../../../ui/Drawer';
18
18
  import Fullscreen from '../../../icons/Fullscreen';
@@ -27,11 +27,9 @@ import TabSwitch from './components/TabSwitch';
27
27
  const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
28
28
  isChatLogPanel = false,
29
29
  }) => {
30
- const { state, openArtifact, closeArtifact, toggleFullscreen } =
30
+ const { state, closeArtifact, toggleFullscreen } =
31
31
  useArtifact();
32
32
  const { t } = useTranslation();
33
- const [showHistory, setShowHistory] = useState(false);
34
- const [isAnimating, setIsAnimating] = useState(false);
35
33
  const [isMobile, setIsMobile] = useState(false);
36
34
 
37
35
  const [activeTab, setActiveTab] = useState<ArtifactTab>('preview');
@@ -65,7 +63,6 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
65
63
  setIsMobile(window.innerWidth <= 768);
66
64
  };
67
65
 
68
-
69
66
  checkMobile();
70
67
  window.addEventListener('resize', checkMobile);
71
68
 
@@ -100,30 +97,6 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
100
97
  }
101
98
  }, [state.currentArtifact]);
102
99
 
103
- /**
104
- * Handle download action
105
- */
106
- const handleDownload = useCallback(() => {
107
- // This will be handled by the ArtifactActions component
108
- console.log('Download triggered');
109
- }, []);
110
-
111
- /**
112
- * Handle print action
113
- */
114
- const handlePrint = useCallback(() => {
115
- // This will be handled by the ArtifactActions component
116
- console.log('Print triggered');
117
- }, []);
118
-
119
- /**
120
- * Handle external open action
121
- */
122
- const handleOpenExternal = useCallback(() => {
123
- // This will be handled by the ArtifactActions component
124
- console.log('External open triggered');
125
- }, []);
126
-
127
100
  /**
128
101
  * Handle fullscreen toggle
129
102
  */
@@ -140,20 +113,6 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
140
113
  closeArtifact();
141
114
  }, [closeArtifact]);
142
115
 
143
- /**
144
- * Toggle history panel
145
- */
146
- const toggleHistory = useCallback(() => {
147
- setShowHistory(prev => !prev);
148
- }, []);
149
-
150
- /**
151
- * Mobile dropdown menu handlers
152
- */
153
- const handleMobileOpenExternal = useCallback(() => {
154
- handleOpenExternal();
155
- }, [handleOpenExternal]);
156
-
157
116
  if (!state.currentArtifact) {
158
117
  return null;
159
118
  }
@@ -167,8 +126,7 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
167
126
  if (isChatLogPanel) {
168
127
  return (
169
128
  <div
170
- style={{ minHeight: '75vh', maxHeight: '75vh' }
171
- }
129
+ style={{ minHeight: '75vh', maxHeight: '75vh' }}
172
130
  className="memori-artifact-panel"
173
131
  >
174
132
  {children}
@@ -210,157 +168,229 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
210
168
  [isChatLogPanel, handleClose, state.isDrawerOpen, state.isFullscreen]
211
169
  );
212
170
 
171
+ /**
172
+ * Get MIME type string for downloads
173
+ */
174
+ const getMimeTypeString = useCallback((mimeType: string): string => {
175
+ const mimeTypes: Record<string, string> = {
176
+ html: 'text/html',
177
+ json: 'application/json',
178
+ markdown: 'text/markdown',
179
+ css: 'text/css',
180
+ javascript: 'text/javascript',
181
+ typescript: 'text/typescript',
182
+ svg: 'image/svg+xml',
183
+ xml: 'text/xml',
184
+ text: 'text/plain',
185
+ python: 'text/x-python',
186
+ java: 'text/x-java',
187
+ cpp: 'text/x-c++',
188
+ csharp: 'text/x-csharp',
189
+ php: 'text/x-php',
190
+ ruby: 'text/x-ruby',
191
+ go: 'text/x-go',
192
+ rust: 'text/x-rust',
193
+ yaml: 'text/yaml',
194
+ sql: 'text/x-sql',
195
+ };
196
+ return mimeTypes[mimeType] || 'text/plain';
197
+ }, []);
198
+
199
+ /**
200
+ * Handle external open action
201
+ */
202
+ const handleOpenExternal = useCallback((artifact: ArtifactData) => {
203
+ try {
204
+ const mimeType = getMimeTypeString(artifact.mimeType);
205
+ const blob = new Blob([artifact.content], { type: mimeType });
206
+ const url = URL.createObjectURL(blob);
207
+
208
+ const externalWindow = window.open(url, '_blank');
209
+ if (!externalWindow) {
210
+ alert(
211
+ 'Popup blocked! Please enable popups to open the artifact in a new window.'
212
+ );
213
+ return;
214
+ }
215
+
216
+ // Cleanup URL after a delay
217
+ setTimeout(() => {
218
+ URL.revokeObjectURL(url);
219
+ }, 60000);
220
+
221
+ } catch (error) {
222
+ console.error('External open failed:', error);
223
+ }
224
+ }, []);
225
+
213
226
  // Render web split panel
214
227
  return (
215
228
  <ContentContainer>
216
229
  {/* Header */}
217
- <div className={cx("memori-artifact-drawer-container-actions", {
218
- "memori-artifact-drawer-container-actions--no-preview": !hasPreview
219
- })}>
230
+ <div
231
+ className={cx('memori-artifact-drawer-container-actions', {
232
+ 'memori-artifact-drawer-container-actions--no-preview': !hasPreview,
233
+ 'memori-artifact-drawer-container-actions--chatlog': isChatLogPanel,
234
+ })}
235
+ >
220
236
  {/* Desktop Actions */}
221
237
  {!isMobile && (
222
238
  <>
223
239
  {/* Modern Tab Switch */}
224
- {hasPreview && <TabSwitch
225
- activeTab={activeTab}
226
- onTabChange={handleTabChange}
227
- hasPreview={hasPreview}
228
- /> }
240
+ {hasPreview && (
241
+ <TabSwitch
242
+ activeTab={activeTab}
243
+ onTabChange={handleTabChange}
244
+ hasPreview={hasPreview}
245
+ />
246
+ )}
229
247
  <ArtifactActions
230
248
  artifact={state.currentArtifact}
231
249
  onCopy={handleCopy}
232
- onDownload={handleDownload}
233
250
  loading={false}
234
- onPrint={handlePrint}
235
- onOpenExternal={handleOpenExternal}
236
251
  isMobile={isMobile}
237
252
  />
238
253
  <Button
239
254
  onClick={closeArtifact}
240
255
  className={cx(
241
256
  'memori-artifact-drawer--close',
242
- 'memori-button--icon-only'
257
+ 'memori-button--icon-only',
258
+ {
259
+ 'memori-artifact-drawer--close-desktop': !hasPreview,
260
+ }
243
261
  )}
244
262
  ghost
245
263
  title={t('artifact.close') || 'Close'}
246
264
  >
247
- <Close className="memori-artifact-panel--close-icon" />
265
+ <Close className="memori-artifact-panel--close-icon" />
248
266
  </Button>
249
267
  </>
250
268
  )}
251
269
  </div>
252
270
 
253
271
  {/* Top Right Header Section */}
254
- <div className={cx("memori-artifact-drawer-top-right", {
255
- "memori-artifact-drawer-top-right--no-preview": !hasPreview
256
- })}>
272
+ <div
273
+ className={cx('memori-artifact-drawer-top-right', {
274
+ 'memori-artifact-drawer-top-right--no-preview': !hasPreview,
275
+ 'memori-artifact-drawer-top-right--chatlog': isChatLogPanel,
276
+ })}
277
+ >
257
278
  {/* Mobile Dropdown Menu */}
258
279
  {isMobile && (
259
280
  <>
260
- {hasPreview && <TabSwitch
261
- activeTab={activeTab}
262
- onTabChange={handleTabChange}
263
- hasPreview={hasPreview}
264
- /> }
265
- <Menu as="div" className="memori-mobile-actions-menu">
266
- <Menu.Button as="div" className="memori-mobile-actions-trigger">
267
- <Button
268
- className={cx(
269
- 'memori-button',
270
- 'memori-button--more-options',
271
- 'memori-button--icon-only'
272
- )}
273
- ghost
274
- title={t('artifact.actions') || 'Actions'}
275
- >
276
- <MenuVertical className="memori-artifact-action-icon" />
277
- </Button>
278
- </Menu.Button>
279
-
280
- <Transition
281
- as={React.Fragment}
282
- enter="memori-mobile-dropdown-enter"
283
- enterFrom="memori-mobile-dropdown-enter-from"
284
- enterTo="memori-mobile-dropdown-enter-to"
285
- leave="memori-mobile-dropdown-leave"
286
- leaveFrom="memori-mobile-dropdown-leave-from"
287
- leaveTo="memori-mobile-dropdown-leave-to"
288
- >
289
- <Menu.Items className="memori-mobile-dropdown">
290
- <div className="memori-mobile-dropdown-list">
291
- <Button
292
- onClick={handleCopy}
293
- disabled={false}
294
- className="memori-artifact-action-btn"
295
- ghost
296
- title={t('artifact.copy') || 'Copy'}
297
- >
298
- <span className="memori-artifact-action-text">
299
- {t('artifact.copy') || 'Copy'}
300
- </span>
301
- </Button>
281
+ {hasPreview && (
282
+ <TabSwitch
283
+ activeTab={activeTab}
284
+ onTabChange={handleTabChange}
285
+ hasPreview={hasPreview}
286
+ />
287
+ )}
288
+ <Menu as="div" className="memori-mobile-actions-menu">
289
+ <Menu.Button as="div" className="memori-mobile-actions-trigger">
290
+ <Button
291
+ className={cx(
292
+ 'memori-button',
293
+ 'memori-button--more-options',
294
+ 'memori-button--icon-only'
295
+ )}
296
+ ghost
297
+ title={t('artifact.actions') || 'Actions'}
298
+ >
299
+ <MenuVertical className="memori-artifact-action-icon" />
300
+ </Button>
301
+ </Menu.Button>
302
302
 
303
- {formats.map(format => {
304
- // Get appropriate icon based on action type
305
- const getIcon = () => {
306
- switch (format.action) {
307
- case 'copy':
308
- return (
309
- <Link className="memori-artifact-action-icon" />
310
- );
311
- case 'download':
312
- return (
313
- <Download className="memori-artifact-action-icon" />
314
- );
315
- case 'print':
316
- case 'pdf':
317
- return (
318
- <PrintIcon className="memori-artifact-action-icon" />
319
- );
320
- default:
321
- return (
322
- <Link className="memori-artifact-action-icon" />
323
- );
324
- }
325
- };
303
+ <Transition
304
+ as={React.Fragment}
305
+ enter="memori-mobile-dropdown-enter"
306
+ enterFrom="memori-mobile-dropdown-enter-from"
307
+ enterTo="memori-mobile-dropdown-enter-to"
308
+ leave="memori-mobile-dropdown-leave"
309
+ leaveFrom="memori-mobile-dropdown-leave-from"
310
+ leaveTo="memori-mobile-dropdown-leave-to"
311
+ >
312
+ <Menu.Items className="memori-mobile-dropdown">
313
+ <div className="memori-mobile-dropdown-list">
314
+ <Button
315
+ onClick={handleCopy}
316
+ disabled={false}
317
+ className="memori-artifact-action-btn"
318
+ ghost
319
+ title={t('artifact.copy') || 'Copy'}
320
+ >
321
+ <span className="memori-artifact-action-text">
322
+ {t('artifact.copy') || 'Copy'}
323
+ </span>
324
+ </Button>
326
325
 
327
- return (
328
- <Button
329
- key={format.id}
330
- onClick={() => handleCopyFormat(format)}
331
- disabled={
332
- copyState.loading &&
333
- copyState.activeFormat === format.id
326
+ {formats.map(format => {
327
+ // Get appropriate icon based on action type
328
+ const getIcon = () => {
329
+ switch (format.action) {
330
+ case 'copy':
331
+ return (
332
+ <Link className="memori-artifact-action-icon" />
333
+ );
334
+ case 'download':
335
+ return (
336
+ <Download className="memori-artifact-action-icon" />
337
+ );
338
+ case 'print':
339
+ case 'pdf':
340
+ return (
341
+ <PrintIcon className="memori-artifact-action-icon" />
342
+ );
343
+ default:
344
+ return (
345
+ <Link className="memori-artifact-action-icon" />
346
+ );
334
347
  }
335
- className="memori-artifact-action-btn"
336
- ghost
337
- icon={getIcon()}
338
- title={format.label}
339
- >
340
- <span className="memori-artifact-action-text">
341
- {format.label}
342
- </span>
343
- </Button>
344
- );
345
- })}
348
+ };
349
+
350
+ return (
351
+ <Button
352
+ key={format.id}
353
+ onClick={() => handleCopyFormat(format)}
354
+ disabled={
355
+ copyState.loading &&
356
+ copyState.activeFormat === format.id
357
+ }
358
+ className="memori-artifact-action-btn"
359
+ ghost
360
+ icon={getIcon()}
361
+ title={format.label}
362
+ >
363
+ <span className="memori-artifact-action-text">
364
+ {format.label}
365
+ </span>
366
+ </Button>
367
+ );
368
+ })}
346
369
 
347
- {/* External open action (not from hook) */}
348
- <Button
349
- onClick={handleMobileOpenExternal}
350
- disabled={false}
351
- className="memori-artifact-action-btn"
352
- ghost
353
- icon={<Link className="memori-artifact-action-icon" />}
354
- title={t('artifact.external') || 'External'}
355
- >
356
- <span className="memori-artifact-action-text">
357
- {t('artifact.external') || 'External'}
358
- </span>
359
- </Button>
360
- </div>
361
- </Menu.Items>
362
- </Transition>
363
- </Menu>
370
+ {/* External open action (not from hook) */}
371
+ <Button
372
+ onClick={() => handleOpenExternal(state.currentArtifact ?? {
373
+ content: '',
374
+ mimeType: '',
375
+ title: '',
376
+ timestamp: new Date(),
377
+ size: 0,
378
+ id: '',
379
+ })}
380
+ disabled={false}
381
+ className="memori-artifact-action-btn"
382
+ ghost
383
+ icon={<Link className="memori-artifact-action-icon" />}
384
+ title={t('artifact.external') || 'External'}
385
+ >
386
+ <span className="memori-artifact-action-text">
387
+ {t('artifact.external') || 'External'}
388
+ </span>
389
+ </Button>
390
+ </div>
391
+ </Menu.Items>
392
+ </Transition>
393
+ </Menu>
364
394
  </>
365
395
  )}
366
396
 
@@ -29,10 +29,15 @@ const ArtifactHandler: React.FC<ArtifactHandlerProps> = ({
29
29
  const artifacts = detectArtifacts(messageText);
30
30
 
31
31
  if (artifacts.length > 0) {
32
- setTimeout(() => {
33
- openArtifact(artifacts[0]);
32
+ if(isChatlogPanel){
33
+ // openArtifact(artifacts[0]);
34
34
  setCurrentArtifact(artifacts[0]);
35
- }, 100);
35
+ } else {
36
+ setTimeout(() => {
37
+ openArtifact(artifacts[0]);
38
+ setCurrentArtifact(artifacts[0]);
39
+ }, 100);
40
+ }
36
41
  }
37
42
  }
38
43
  }, [message]);
@@ -112,10 +112,12 @@
112
112
 
113
113
  /* Markdown Preview */
114
114
  .memori-artifact-preview-markdown {
115
+ height: 100%;
115
116
  padding: 1rem;
116
117
  color: var(--memori-text-color, #333);
117
118
  font-family: var(--memori-font-family);
118
119
  line-height: 1.6;
120
+ overflow-y: scroll;
119
121
  }
120
122
 
121
123
  .memori-artifact-preview-markdown h1,
@@ -68,6 +68,27 @@ export const isAndroid = (): boolean => {
68
68
  return isAndroid;
69
69
  };
70
70
 
71
+ export const isSafari = (): boolean => {
72
+ if (typeof navigator === 'undefined') return false;
73
+
74
+ const userAgent = navigator.userAgent;
75
+ const isSafariUA = userAgent.includes('Safari') && !userAgent.includes('Chrome');
76
+ const isWebKit = 'WebKit' in window && !('Chrome' in window);
77
+
78
+ return isSafariUA || isWebKit;
79
+ };
80
+
81
+ export const isSafariIOS = (): boolean => {
82
+ if (typeof navigator === 'undefined') return false;
83
+
84
+ const userAgent = navigator.userAgent;
85
+ return (
86
+ userAgent.includes('Safari') &&
87
+ !userAgent.includes('Chrome') &&
88
+ /iPad|iPhone|iPod/.test(userAgent)
89
+ );
90
+ };
91
+
71
92
  export const pwdRegEx =
72
93
  // eslint-disable-next-line no-useless-escape
73
94
  /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$_:;|,~+=\{\}\[\]%^&*-]).{8,}$/;
@@ -224,7 +224,8 @@
224
224
  "copyWithoutSyntaxHighlighting": "Ohne Syntaxhervorhebung kopieren",
225
225
  "copyTextContent": "Textinhalt kopieren",
226
226
  "pdfExportNotSupported": "PDF-Export wird in diesem Browser nicht unterstützt",
227
- "popupBlocked": "Popup blockiert! Bitte aktivieren Sie Popups zum Drucken."
227
+ "popupBlocked": "Popup blockiert! Bitte aktivieren Sie Popups zum Drucken.",
228
+ "safariPdfInstructions": "PDF-Export in neuem Fenster geöffnet. Bitte verwenden Sie Cmd+P (Mac) oder Strg+P (Windows) zum Drucken und als PDF speichern."
228
229
  },
229
230
  "upload": {
230
231
  "loginRequired": "Login erforderlich",
@@ -235,7 +235,8 @@
235
235
  "copyWithoutSyntaxHighlighting": "Copy without syntax highlighting",
236
236
  "copyTextContent": "Copy text content",
237
237
  "pdfExportNotSupported": "PDF export is not supported in this browser",
238
- "popupBlocked": "Popup blocked! Please enable popups to print."
238
+ "popupBlocked": "Popup blocked! Please enable popups to print.",
239
+ "safariPdfInstructions": "PDF export opened in new window. Please use Cmd+P (Mac) or Ctrl+P (Windows) to print and save as PDF."
239
240
  },
240
241
  "upload": {
241
242
  "loginRequired": "Login required",
@@ -224,7 +224,8 @@
224
224
  "copyWithoutSyntaxHighlighting": "Copiar sin resaltado de sintaxis",
225
225
  "copyTextContent": "Copiar contenido de texto",
226
226
  "pdfExportNotSupported": "La exportación PDF no es compatible con este navegador",
227
- "popupBlocked": "¡Popup bloqueado! Por favor, habilita los popups para imprimir."
227
+ "popupBlocked": "¡Popup bloqueado! Por favor, habilita los popups para imprimir.",
228
+ "safariPdfInstructions": "Exportación PDF abierta en nueva ventana. Por favor, usa Cmd+P (Mac) o Ctrl+P (Windows) para imprimir y guardar como PDF."
228
229
  },
229
230
  "upload": {
230
231
  "loginRequired": "Connexion requise",
@@ -233,7 +233,8 @@
233
233
  "copyWithoutSyntaxHighlighting": "Copier sans coloration syntaxique",
234
234
  "copyTextContent": "Copier le contenu texte",
235
235
  "pdfExportNotSupported": "L'exportation PDF n'est pas prise en charge dans ce navigateur",
236
- "popupBlocked": "Popup bloqué ! Veuillez activer les popups pour imprimer."
236
+ "popupBlocked": "Popup bloqué ! Veuillez activer les popups pour imprimer.",
237
+ "safariPdfInstructions": "Export PDF ouvert dans une nouvelle fenêtre. Veuillez utiliser Cmd+P (Mac) ou Ctrl+P (Windows) pour imprimer et sauvegarder en PDF."
237
238
  },
238
239
  "upload": {
239
240
  "loginRequired": "Connexion requise",
@@ -238,7 +238,8 @@
238
238
  "copyWithoutSyntaxHighlighting": "Copia senza evidenziazione sintassi",
239
239
  "copyTextContent": "Copia contenuto testo",
240
240
  "pdfExportNotSupported": "L'esportazione PDF non è supportata in questo browser",
241
- "popupBlocked": "Popup bloccato! Abilita i popup per stampare."
241
+ "popupBlocked": "Popup bloccato! Abilita i popup per stampare.",
242
+ "safariPdfInstructions": "Esportazione PDF aperta in una nuova finestra. Usa Cmd+P (Mac) o Ctrl+P (Windows) per stampare e salvare come PDF."
242
243
 
243
244
  },
244
245
  "media": {