@memori.ai/memori-react 8.5.1 → 8.6.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +24 -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 +17 -4
  13. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +64 -21
  14. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  15. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +9 -4
  16. package/dist/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
  17. package/dist/components/layouts/chat.css +1 -1
  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 +17 -4
  37. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +64 -21
  38. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  39. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js +9 -4
  40. package/esm/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.js.map +1 -1
  41. package/esm/components/layouts/chat.css +1 -1
  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 +17 -4
  56. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +191 -180
  57. package/src/components/MemoriArtifactSystem/components/ArtifactHandler/ArtifactHandler.tsx +9 -4
  58. package/src/components/layouts/chat.css +1 -1
  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');
@@ -99,30 +97,6 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
99
97
  }
100
98
  }, [state.currentArtifact]);
101
99
 
102
- /**
103
- * Handle download action
104
- */
105
- const handleDownload = useCallback(() => {
106
- // This will be handled by the ArtifactActions component
107
- console.log('Download triggered');
108
- }, []);
109
-
110
- /**
111
- * Handle print action
112
- */
113
- const handlePrint = useCallback(() => {
114
- // This will be handled by the ArtifactActions component
115
- console.log('Print triggered');
116
- }, []);
117
-
118
- /**
119
- * Handle external open action
120
- */
121
- const handleOpenExternal = useCallback(() => {
122
- // This will be handled by the ArtifactActions component
123
- console.log('External open triggered');
124
- }, []);
125
-
126
100
  /**
127
101
  * Handle fullscreen toggle
128
102
  */
@@ -139,20 +113,6 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
139
113
  closeArtifact();
140
114
  }, [closeArtifact]);
141
115
 
142
- /**
143
- * Toggle history panel
144
- */
145
- const toggleHistory = useCallback(() => {
146
- setShowHistory(prev => !prev);
147
- }, []);
148
-
149
- /**
150
- * Mobile dropdown menu handlers
151
- */
152
- const handleMobileOpenExternal = useCallback(() => {
153
- handleOpenExternal();
154
- }, [handleOpenExternal]);
155
-
156
116
  if (!state.currentArtifact) {
157
117
  return null;
158
118
  }
@@ -166,9 +126,7 @@ const ArtifactDrawer: React.FC<{ isChatLogPanel?: boolean }> = ({
166
126
  if (isChatLogPanel) {
167
127
  return (
168
128
  <div
169
- style={
170
- isChatLogPanel ? { minHeight: '75vh', maxHeight: '75vh' } : {}
171
- }
129
+ style={{ minHeight: '75vh', maxHeight: '75vh' }}
172
130
  className="memori-artifact-panel"
173
131
  >
174
132
  {children}
@@ -210,176 +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="memori-artifact-drawer-container-actions">
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
+ >
218
236
  {/* Desktop Actions */}
219
237
  {!isMobile && (
220
238
  <>
221
- {/* {!isChatLogPanel && (
222
- <Button
223
- onClick={handleToggleFullscreen}
224
- className={cx(
225
- 'memori-artifact-drawer-fullscreen',
226
- 'memori-button--circle',
227
- 'memori-button--icon-only'
228
- )}
229
- ghost
230
- icon={
231
- state.isFullscreen ? (
232
- <Fullscreen className="memori-artifact-panel--close-icon" />
233
- ) : (
234
- <FullscreenExit className="memori-artifact-panel--close-icon" />
235
- )
236
- }
237
- title={
238
- state.isFullscreen
239
- ? t('artifact.exitFullscreen') || 'Exit Fullscreen'
240
- : t('artifact.fullscreen') || 'Fullscreen'
241
- }
242
- />
243
- )} */}
244
239
  {/* Modern Tab Switch */}
245
- <TabSwitch
246
- activeTab={activeTab}
247
- onTabChange={handleTabChange}
248
- hasPreview={hasPreview}
249
- />
240
+ {hasPreview && (
241
+ <TabSwitch
242
+ activeTab={activeTab}
243
+ onTabChange={handleTabChange}
244
+ hasPreview={hasPreview}
245
+ />
246
+ )}
250
247
  <ArtifactActions
251
248
  artifact={state.currentArtifact}
252
249
  onCopy={handleCopy}
253
- onDownload={handleDownload}
254
250
  loading={false}
255
- onPrint={handlePrint}
256
- onOpenExternal={handleOpenExternal}
257
251
  isMobile={isMobile}
258
252
  />
259
253
  <Button
260
254
  onClick={closeArtifact}
261
255
  className={cx(
262
256
  'memori-artifact-drawer--close',
263
- 'memori-button--icon-only'
257
+ 'memori-button--icon-only',
258
+ {
259
+ 'memori-artifact-drawer--close-desktop': !hasPreview,
260
+ }
264
261
  )}
265
262
  ghost
266
263
  title={t('artifact.close') || 'Close'}
267
264
  >
268
- <Close className="memori-artifact-panel--close-icon" />
265
+ <Close className="memori-artifact-panel--close-icon" />
269
266
  </Button>
270
267
  </>
271
268
  )}
272
269
  </div>
273
270
 
274
271
  {/* Top Right Header Section */}
275
- <div className="memori-artifact-drawer-top-right">
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
+ >
276
278
  {/* Mobile Dropdown Menu */}
277
279
  {isMobile && (
278
280
  <>
279
- <TabSwitch
280
- activeTab={activeTab}
281
- onTabChange={handleTabChange}
282
- hasPreview={hasPreview}
283
- />
284
- <Menu as="div" className="memori-mobile-actions-menu">
285
- <Menu.Button as="div" className="memori-mobile-actions-trigger">
286
- <Button
287
- className={cx(
288
- 'memori-button',
289
- 'memori-button--more-options',
290
- 'memori-button--icon-only'
291
- )}
292
- ghost
293
- title={t('artifact.actions') || 'Actions'}
294
- >
295
- <MenuVertical className="memori-artifact-action-icon" />
296
- </Button>
297
- </Menu.Button>
298
-
299
- <Transition
300
- as={React.Fragment}
301
- enter="memori-mobile-dropdown-enter"
302
- enterFrom="memori-mobile-dropdown-enter-from"
303
- enterTo="memori-mobile-dropdown-enter-to"
304
- leave="memori-mobile-dropdown-leave"
305
- leaveFrom="memori-mobile-dropdown-leave-from"
306
- leaveTo="memori-mobile-dropdown-leave-to"
307
- >
308
- <Menu.Items className="memori-mobile-dropdown">
309
- <div className="memori-mobile-dropdown-list">
310
- <Button
311
- onClick={handleCopy}
312
- disabled={false}
313
- className="memori-artifact-action-btn"
314
- ghost
315
- title={t('artifact.copy') || 'Copy'}
316
- >
317
- <span className="memori-artifact-action-text">
318
- {t('artifact.copy') || 'Copy'}
319
- </span>
320
- </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>
321
302
 
322
- {formats.map(format => {
323
- // Get appropriate icon based on action type
324
- const getIcon = () => {
325
- switch (format.action) {
326
- case 'copy':
327
- return (
328
- <Link className="memori-artifact-action-icon" />
329
- );
330
- case 'download':
331
- return (
332
- <Download className="memori-artifact-action-icon" />
333
- );
334
- case 'print':
335
- case 'pdf':
336
- return (
337
- <PrintIcon className="memori-artifact-action-icon" />
338
- );
339
- default:
340
- return (
341
- <Link className="memori-artifact-action-icon" />
342
- );
343
- }
344
- };
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>
345
325
 
346
- return (
347
- <Button
348
- key={format.id}
349
- onClick={() => handleCopyFormat(format)}
350
- disabled={
351
- copyState.loading &&
352
- 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
+ );
353
347
  }
354
- className="memori-artifact-action-btn"
355
- ghost
356
- icon={getIcon()}
357
- title={format.label}
358
- >
359
- <span className="memori-artifact-action-text">
360
- {format.label}
361
- </span>
362
- </Button>
363
- );
364
- })}
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
+ })}
365
369
 
366
- {/* External open action (not from hook) */}
367
- <Button
368
- onClick={handleMobileOpenExternal}
369
- disabled={false}
370
- className="memori-artifact-action-btn"
371
- ghost
372
- icon={<Link className="memori-artifact-action-icon" />}
373
- title={t('artifact.external') || 'External'}
374
- >
375
- <span className="memori-artifact-action-text">
376
- {t('artifact.external') || 'External'}
377
- </span>
378
- </Button>
379
- </div>
380
- </Menu.Items>
381
- </Transition>
382
- </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>
383
394
  </>
384
395
  )}
385
396
 
@@ -25,14 +25,19 @@ const ArtifactHandler: React.FC<ArtifactHandlerProps> = ({
25
25
  // Auto-open artifacts when detected in new messages
26
26
  useEffect(() => {
27
27
  const messageText = message.translatedText || message.text || '';
28
- if (!state.isDrawerOpen && messageText.length > 0) {
28
+ if (messageText.length > 0) {
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]);
@@ -73,7 +73,7 @@
73
73
 
74
74
  .memori-chat-layout--controls-with-artifact {
75
75
  max-height: 100%;
76
- flex: 0 0 50%;
76
+ flex: 0 0 49%;
77
77
  padding-bottom: 2.5rem;
78
78
  margin: 0;
79
79
  }
@@ -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": {