@linagora/twake-mui 10.5.0 → 11.0.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 (71) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/components/ActionsMenu/Actions/call.d.ts +2 -0
  3. package/dist/components/ActionsMenu/Actions/call.js +16 -0
  4. package/dist/components/ActionsMenu/Actions/copyToClipboard.d.ts +2 -0
  5. package/dist/components/ActionsMenu/Actions/copyToClipboard.js +32 -0
  6. package/dist/components/ActionsMenu/Actions/divider.d.ts +2 -0
  7. package/dist/components/ActionsMenu/Actions/divider.js +7 -0
  8. package/dist/components/ActionsMenu/Actions/download.d.ts +15 -0
  9. package/dist/components/ActionsMenu/Actions/download.js +18 -0
  10. package/dist/components/ActionsMenu/Actions/editAttribute.d.ts +2 -0
  11. package/dist/components/ActionsMenu/Actions/editAttribute.js +12 -0
  12. package/dist/components/ActionsMenu/Actions/emailTo.d.ts +2 -0
  13. package/dist/components/ActionsMenu/Actions/emailTo.js +16 -0
  14. package/dist/components/ActionsMenu/Actions/exportToText.d.ts +6 -0
  15. package/dist/components/ActionsMenu/Actions/exportToText.js +28 -0
  16. package/dist/components/ActionsMenu/Actions/favorites.d.ts +12 -0
  17. package/dist/components/ActionsMenu/Actions/favorites.js +44 -0
  18. package/dist/components/ActionsMenu/Actions/helpers.d.ts +24 -0
  19. package/dist/components/ActionsMenu/Actions/helpers.js +128 -0
  20. package/dist/components/ActionsMenu/Actions/logger.d.ts +3 -0
  21. package/dist/components/ActionsMenu/Actions/logger.js +2 -0
  22. package/dist/components/ActionsMenu/Actions/makeAction.d.ts +11 -0
  23. package/dist/components/ActionsMenu/Actions/makeAction.js +19 -0
  24. package/dist/components/ActionsMenu/Actions/modify.d.ts +2 -0
  25. package/dist/components/ActionsMenu/Actions/modify.js +21 -0
  26. package/dist/components/ActionsMenu/Actions/others.d.ts +2 -0
  27. package/dist/components/ActionsMenu/Actions/others.js +11 -0
  28. package/dist/components/ActionsMenu/Actions/pdfHelpers.d.ts +35 -0
  29. package/dist/components/ActionsMenu/Actions/pdfHelpers.js +85 -0
  30. package/dist/components/ActionsMenu/Actions/print.d.ts +13 -0
  31. package/dist/components/ActionsMenu/Actions/print.js +42 -0
  32. package/dist/components/ActionsMenu/Actions/smsTo.d.ts +2 -0
  33. package/dist/components/ActionsMenu/Actions/smsTo.js +16 -0
  34. package/dist/components/ActionsMenu/Actions/viewInContacts.d.ts +2 -0
  35. package/dist/components/ActionsMenu/Actions/viewInContacts.js +21 -0
  36. package/dist/components/ActionsMenu/Actions/viewInDrive.d.ts +2 -0
  37. package/dist/components/ActionsMenu/Actions/viewInDrive.js +22 -0
  38. package/dist/components/ActionsMenu/ActionsItems.d.ts +17 -0
  39. package/dist/components/ActionsMenu/ActionsItems.js +27 -0
  40. package/dist/components/ActionsMenu/ActionsMenu.stories.d.ts +16 -0
  41. package/dist/components/ActionsMenu/ActionsMenu.stories.js +168 -0
  42. package/dist/components/ActionsMenu/ActionsMenuButton.d.ts +8 -0
  43. package/dist/components/ActionsMenu/ActionsMenuButton.js +18 -0
  44. package/dist/components/ActionsMenu/ActionsMenuItem.d.ts +16 -0
  45. package/dist/components/ActionsMenu/ActionsMenuItem.js +23 -0
  46. package/dist/components/ActionsMenu/ActionsMenuMobileHeader.d.ts +6 -0
  47. package/dist/components/ActionsMenu/ActionsMenuMobileHeader.js +15 -0
  48. package/dist/components/ActionsMenu/ActionsMenuWrapper.d.ts +11 -0
  49. package/dist/components/ActionsMenu/ActionsMenuWrapper.js +41 -0
  50. package/dist/components/ActionsMenu/helpers.d.ts +8 -0
  51. package/dist/components/ActionsMenu/helpers.js +21 -0
  52. package/dist/components/ActionsMenu/index.d.ts +21 -0
  53. package/dist/components/ActionsMenu/index.js +30 -0
  54. package/dist/components/ActionsMenu/locales/de.json +32 -0
  55. package/dist/components/ActionsMenu/locales/en.json +32 -0
  56. package/dist/components/ActionsMenu/locales/es.json +32 -0
  57. package/dist/components/ActionsMenu/locales/fr.json +32 -0
  58. package/dist/components/ActionsMenu/locales/index.d.ts +231 -0
  59. package/dist/components/ActionsMenu/locales/index.js +15 -0
  60. package/dist/components/ActionsMenu/locales/it.json +32 -0
  61. package/dist/components/ActionsMenu/locales/ru.json +32 -0
  62. package/dist/components/ActionsMenu/locales/vi.json +32 -0
  63. package/dist/components/ActionsMenu/types.d.ts +85 -0
  64. package/dist/components/ActionsMenu/types.js +1 -0
  65. package/dist/components/BottomSheet/index.js +5 -2
  66. package/dist/components/SpaceOverlay/SpaceOverlay.d.ts +1 -1
  67. package/dist/index.d.ts +40 -2
  68. package/dist/index.js +25 -1
  69. package/package.json +5 -1
  70. /package/dist/components/SpaceOverlay/{spaceOverlay.d.ts → connectSpaceOverlay.d.ts} +0 -0
  71. /package/dist/components/SpaceOverlay/{spaceOverlay.js → connectSpaceOverlay.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ # @linagora/twake-mui [11.0.0](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@10.5.1...@linagora/twake-mui@11.0.0) (2026-10-08)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **twake-mui:** Reposition BottomSheet when the window is resized ([7036375](https://github.com/linagora/twake-ui/commit/70363755a1f61e2ca77d1fa3ac9132e3e33fd00e))
7
+
8
+
9
+ ### Features
10
+
11
+ * **twake-mui:** Add ActionsMenu locales ([f0e09cb](https://github.com/linagora/twake-ui/commit/f0e09cb0f6ae7e8c3e988bc5b62fc7190c07f475))
12
+ * **twake-mui:** Migrate ActionsMenu from cozy-ui ([7b12778](https://github.com/linagora/twake-ui/commit/7b12778d86948e5c53fe9b2e16555ed966e64dc6))
13
+ * **twake-mui:** Migrate ActionsMenu predefined actions from cozy-ui ([7f66436](https://github.com/linagora/twake-ui/commit/7f6643633dfa1d6e65f402ee26af223e3ac5d440))
14
+
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ * **twake-mui:** cozy-intent is now a required peer dependency of
19
+ @linagora/twake-mui. Apps must install cozy-intent >=2.29.1.
20
+
21
+ ## @linagora/twake-mui [10.5.1](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@10.5.0...@linagora/twake-mui@10.5.1) (2026-10-07)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * **twake-mui:** Rename spaceOverlay.ts to connectSpaceOverlay.ts ([20754d1](https://github.com/linagora/twake-ui/commit/20754d1efdc12bf1f40331042de0ff16035f6aee))
27
+
1
28
  # @linagora/twake-mui [10.5.0](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@10.4.1...@linagora/twake-mui@10.5.0) (2026-10-07)
2
29
 
3
30
 
@@ -0,0 +1,2 @@
1
+ import { Action } from '../types';
2
+ export declare const call: () => Action;
@@ -0,0 +1,16 @@
1
+ import { Telephone } from '@linagora/twake-icons';
2
+ import { makeAction } from './makeAction';
3
+ import { getActionsI18n } from '../locales';
4
+ export const call = () => {
5
+ const { t } = getActionsI18n();
6
+ return makeAction({
7
+ name: 'call',
8
+ label: t('ActionsMenu.call'),
9
+ icon: Telephone,
10
+ action: (docs) => {
11
+ const target = docs[0]?.phone?.[0]?.number;
12
+ if (target)
13
+ window.open(`tel:${target}`, '_self');
14
+ }
15
+ });
16
+ };
@@ -0,0 +1,2 @@
1
+ import { Action } from '../types';
2
+ export declare const copyToClipboard: () => Action;
@@ -0,0 +1,32 @@
1
+ import { Copy } from '@linagora/twake-icons';
2
+ import { makeAction } from './makeAction';
3
+ import { getActionsI18n } from '../locales';
4
+ export const copyToClipboard = () => {
5
+ const { t } = getActionsI18n();
6
+ return makeAction({
7
+ name: 'copyToClipboard',
8
+ label: t('ActionsMenu.copyToClipboard.copy'),
9
+ icon: Copy,
10
+ action: async (_docs, { showAlert, copyValue }) => {
11
+ if (!copyValue)
12
+ return false;
13
+ try {
14
+ await navigator.clipboard.writeText(copyValue);
15
+ showAlert({
16
+ message: t('ActionsMenu.copyToClipboard.success'),
17
+ severity: 'success',
18
+ variant: 'filled'
19
+ });
20
+ return true;
21
+ }
22
+ catch {
23
+ showAlert({
24
+ message: t('ActionsMenu.copyToClipboard.error'),
25
+ severity: 'error',
26
+ variant: 'filled'
27
+ });
28
+ return false;
29
+ }
30
+ }
31
+ });
32
+ };
@@ -0,0 +1,2 @@
1
+ import { Action } from '../types';
2
+ export declare const divider: () => Action;
@@ -0,0 +1,7 @@
1
+ import { Divider } from '@mui/material';
2
+ import React, { forwardRef } from 'react';
3
+ const DividerAction = forwardRef((_props, ref) => (React.createElement(Divider, { ref: ref, sx: { my: 1 } })));
4
+ DividerAction.displayName = 'divider';
5
+ export const divider = () => {
6
+ return { name: 'divider', Component: DividerAction };
7
+ };
@@ -0,0 +1,15 @@
1
+ import type { WebviewService } from 'cozy-intent';
2
+ import { Action, ActionDocument } from '../types';
3
+ export interface DownloadFileOptions<Client> {
4
+ client: Client;
5
+ file: ActionDocument;
6
+ url?: string;
7
+ webviewIntent?: WebviewService;
8
+ driveId?: string;
9
+ }
10
+ export interface DownloadActionOptions<Client> {
11
+ client: Client;
12
+ encryptedUrl?: string;
13
+ downloadFile: (options: DownloadFileOptions<Client>) => unknown;
14
+ }
15
+ export declare const download: <Client>({ client, encryptedUrl, downloadFile }: DownloadActionOptions<Client>) => Action;
@@ -0,0 +1,18 @@
1
+ import { Download } from '@linagora/twake-icons';
2
+ import { makeAction } from './makeAction';
3
+ import { getActionsI18n } from '../locales';
4
+ export const download = ({ client, encryptedUrl, downloadFile }) => {
5
+ const { t } = getActionsI18n();
6
+ return makeAction({
7
+ name: 'download',
8
+ label: t('ActionsMenu.download'),
9
+ icon: Download,
10
+ action: (docs, { webviewIntent, driveId }) => downloadFile({
11
+ client,
12
+ file: docs[0],
13
+ url: encryptedUrl,
14
+ webviewIntent,
15
+ driveId
16
+ })
17
+ });
18
+ };
@@ -0,0 +1,2 @@
1
+ import { Action } from '../types';
2
+ export declare const editAttribute: () => Action;
@@ -0,0 +1,12 @@
1
+ import { TextInfo } from '@linagora/twake-icons';
2
+ import { makeAction } from './makeAction';
3
+ import { getActionsI18n } from '../locales';
4
+ export const editAttribute = () => {
5
+ const { t } = getActionsI18n();
6
+ return makeAction({
7
+ name: 'editAttribute',
8
+ label: t('ActionsMenu.editAttribute'),
9
+ icon: TextInfo,
10
+ action: (docs, { editAttributeCallback }) => editAttributeCallback(docs)
11
+ });
12
+ };
@@ -0,0 +1,2 @@
1
+ import { Action } from '../types';
2
+ export declare const emailTo: () => Action;
@@ -0,0 +1,16 @@
1
+ import { Email } from '@linagora/twake-icons';
2
+ import { makeAction } from './makeAction';
3
+ import { getActionsI18n } from '../locales';
4
+ export const emailTo = () => {
5
+ const { t } = getActionsI18n();
6
+ return makeAction({
7
+ name: 'emailTo',
8
+ label: t('ActionsMenu.emailTo'),
9
+ icon: Email,
10
+ action: (docs) => {
11
+ const target = docs[0]?.email?.[0]?.address;
12
+ if (target)
13
+ window.open(`mailto:${target}`, '_self');
14
+ }
15
+ });
16
+ };
@@ -0,0 +1,6 @@
1
+ import { Action, ActionDocument } from '../types';
2
+ export interface ExportToTextActionOptions {
3
+ exportedText?: string;
4
+ file?: ActionDocument;
5
+ }
6
+ export declare const exportToText: ({ exportedText, file }: ExportToTextActionOptions) => Action;
@@ -0,0 +1,28 @@
1
+ import { Export } from '@linagora/twake-icons';
2
+ import { downloadBlob } from './helpers';
3
+ import { logger } from './logger';
4
+ import { makeAction } from './makeAction';
5
+ import { makePdfBlobFromText } from './pdfHelpers';
6
+ import { getActionsI18n } from '../locales';
7
+ export const exportToText = ({ exportedText, file }) => {
8
+ const { t } = getActionsI18n();
9
+ return makeAction({
10
+ name: 'exportToText',
11
+ label: t('ActionsMenu.exportToText'),
12
+ icon: Export,
13
+ displayCondition: () => !!exportedText,
14
+ action: async () => {
15
+ try {
16
+ const blob = await makePdfBlobFromText(exportedText ?? '');
17
+ const baseName = file?.name
18
+ ? `${file.name.replace(/\.[^/.]+$/, '')}_export`
19
+ : 'export';
20
+ return downloadBlob(blob, `${baseName}.pdf`);
21
+ }
22
+ catch (error) {
23
+ logger.error(error);
24
+ return false;
25
+ }
26
+ }
27
+ });
28
+ };
@@ -0,0 +1,12 @@
1
+ import { Action, ActionDocument, ShowAlert } from '../types';
2
+ export interface FavoritesActionOptions {
3
+ client: {
4
+ save: (doc: ActionDocument) => Promise<unknown>;
5
+ };
6
+ showAlert: ShowAlert;
7
+ splitFilename: (doc: ActionDocument) => {
8
+ filename: string;
9
+ };
10
+ }
11
+ export declare const addToFavorites: (options: FavoritesActionOptions) => Action;
12
+ export declare const removeFromFavorites: (options: FavoritesActionOptions) => Action;
@@ -0,0 +1,44 @@
1
+ import { Star, StarOutline } from '@linagora/twake-icons';
2
+ import { makeAction } from './makeAction';
3
+ import { getActionsI18n } from '../locales';
4
+ const makeFavoriteAction = ({ favorite, client, showAlert, splitFilename }) => {
5
+ const { t } = getActionsI18n();
6
+ const scope = favorite ? 'add' : 'remove';
7
+ return makeAction({
8
+ name: favorite ? 'addToFavorites' : 'removeFromFavorites',
9
+ label: t(`ActionsMenu.favorites.${scope}.label`),
10
+ icon: favorite ? StarOutline : Star,
11
+ displayCondition: docs => docs.length > 0 &&
12
+ docs.every(doc => !!doc.cozyMetadata?.favorite !== favorite),
13
+ action: async (docs) => {
14
+ try {
15
+ for (const doc of docs) {
16
+ await client.save({
17
+ ...doc,
18
+ cozyMetadata: { ...doc.cozyMetadata, favorite }
19
+ });
20
+ }
21
+ const { filename } = splitFilename(docs[0]);
22
+ showAlert({
23
+ message: t(`ActionsMenu.favorites.${scope}.success`, {
24
+ filename,
25
+ smart_count: docs.length
26
+ }),
27
+ severity: 'success'
28
+ });
29
+ }
30
+ catch {
31
+ showAlert({
32
+ message: t('ActionsMenu.favorites.error'),
33
+ severity: 'error'
34
+ });
35
+ }
36
+ }
37
+ });
38
+ };
39
+ export const addToFavorites = (options) => {
40
+ return makeFavoriteAction({ ...options, favorite: true });
41
+ };
42
+ export const removeFromFavorites = (options) => {
43
+ return makeFavoriteAction({ ...options, favorite: false });
44
+ };
@@ -0,0 +1,24 @@
1
+ import type { PDFDocument } from 'pdf-lib';
2
+ import { ActionDocument, ActionFactory, ActionObject, GenerateWebLink, WebLinkClient } from '../types';
3
+ /** Builds every action with the same options, skipping falsy entries */
4
+ export declare const makeActions: <Options extends object>(actions?: (ActionFactory<Options> | false | null | undefined)[], options?: Options) => ActionObject[];
5
+ export interface AppWebLinkOptions {
6
+ client: WebLinkClient;
7
+ generateWebLink: GenerateWebLink;
8
+ slug: string;
9
+ hash: string;
10
+ }
11
+ export declare const makeAppWebLink: ({ client, generateWebLink, slug, hash }: AppWebLinkOptions) => string;
12
+ export declare const makeBase64FromFile: (file: Blob) => Promise<string | null>;
13
+ export declare const fileToArrayBuffer: (file: Blob) => Promise<ArrayBuffer | Uint8Array>;
14
+ /** Adds a PDF or an image file to the document, and returns its bytes */
15
+ export declare const addFileToPdf: (pdfDoc: PDFDocument, file: Blob) => Promise<Uint8Array>;
16
+ export type FetchBlobFileById<Client> = (client: Client, id: string) => Promise<Blob>;
17
+ export interface MakePdfBlobOptions<Client> {
18
+ client: Client;
19
+ docs: ActionDocument[];
20
+ fetchBlobFileById: FetchBlobFileById<Client>;
21
+ }
22
+ /** Fetches the files of the docs and merges them into a single PDF */
23
+ export declare const makePdfBlob: <Client>({ client, docs, fetchBlobFileById }: MakePdfBlobOptions<Client>) => Promise<Blob>;
24
+ export declare const downloadBlob: (blob: Blob | null, filename: string) => boolean;
@@ -0,0 +1,128 @@
1
+ // Should guarantee good resolution for different uses (printing, downloading, etc.)
2
+ const MAX_RESIZE_IMAGE_SIZE = 3840;
3
+ const MAX_IMAGE_SIDE_SIZE = 1920;
4
+ /** Builds every action with the same options, skipping falsy entries */
5
+ export const makeActions = (actions = [], options) => {
6
+ // SAFETY: a factory that needs options reads them as its own contract
7
+ const factoryOptions = options ?? {};
8
+ return actions
9
+ .filter((actionFn) => !!actionFn)
10
+ .map(actionFn => {
11
+ const action = actionFn(factoryOptions);
12
+ const name = action.name || actionFn.name;
13
+ return { [name]: action };
14
+ });
15
+ };
16
+ export const makeAppWebLink = ({ client, generateWebLink, slug, hash }) => {
17
+ return generateWebLink({
18
+ slug,
19
+ cozyUrl: client.getStackClient().uri,
20
+ subDomainType: client.getInstanceOptions().subdomain,
21
+ pathname: '/',
22
+ hash
23
+ });
24
+ };
25
+ const readFileAs = (file, method) => {
26
+ return new Promise((resolve, reject) => {
27
+ const reader = new FileReader();
28
+ reader.onerror = reject;
29
+ reader.onload = () => resolve(reader.result);
30
+ reader[method](file);
31
+ });
32
+ };
33
+ export const makeBase64FromFile = async (file) => {
34
+ const result = await readFileAs(file, 'readAsDataURL');
35
+ return typeof result === 'string' ? result : null;
36
+ };
37
+ const computeImageScaleRatio = (image) => {
38
+ const longerSideSizeInPixel = Math.max(image.height, image.width);
39
+ return MAX_RESIZE_IMAGE_SIZE < longerSideSizeInPixel
40
+ ? MAX_RESIZE_IMAGE_SIZE / longerSideSizeInPixel
41
+ : 1;
42
+ };
43
+ const resizeImage = (fileDataUri) => {
44
+ return new Promise((resolve, reject) => {
45
+ const image = new Image();
46
+ image.src = fileDataUri;
47
+ image.onerror = reject;
48
+ image.onload = () => {
49
+ const canvas = document.createElement('canvas');
50
+ const scaleRatio = computeImageScaleRatio(image);
51
+ const scaledWidth = scaleRatio * image.width;
52
+ const scaledHeight = scaleRatio * image.height;
53
+ const quality = scaledWidth >= MAX_IMAGE_SIDE_SIZE ||
54
+ scaledHeight >= MAX_IMAGE_SIDE_SIZE
55
+ ? 0.35
56
+ : 0.75;
57
+ canvas.width = scaledWidth;
58
+ canvas.height = scaledHeight;
59
+ canvas.getContext('2d')?.drawImage(image, 0, 0, scaledWidth, scaledHeight);
60
+ resolve(canvas.toDataURL('image/jpeg', quality));
61
+ };
62
+ });
63
+ };
64
+ const addImageToPdf = async (pdfDoc, file) => {
65
+ const fileDataUri = await makeBase64FromFile(file);
66
+ if (fileDataUri === null)
67
+ throw new Error('Cannot read the image file');
68
+ const resizedImage = await resizeImage(fileDataUri);
69
+ const img = await pdfDoc.embedJpg(resizedImage);
70
+ const page = pdfDoc.addPage([img.width, img.height]);
71
+ const { width: pageWidth, height: pageHeight } = page.getSize();
72
+ page.drawImage(img, {
73
+ x: pageWidth / 2 - img.width / 2,
74
+ y: pageHeight / 2 - img.height / 2,
75
+ width: img.width,
76
+ height: img.height
77
+ });
78
+ };
79
+ export const fileToArrayBuffer = async (file) => {
80
+ if ('arrayBuffer' in file)
81
+ return await file.arrayBuffer();
82
+ const result = await readFileAs(file, 'readAsArrayBuffer');
83
+ if (!(result instanceof ArrayBuffer)) {
84
+ throw new Error('Cannot read the file');
85
+ }
86
+ return new Uint8Array(result);
87
+ };
88
+ const addPdfToPdf = async (pdfDoc, file) => {
89
+ const { PDFDocument } = await import('pdf-lib');
90
+ const pdfToAdd = await fileToArrayBuffer(file);
91
+ const document = await PDFDocument.load(pdfToAdd);
92
+ const copiedPages = await pdfDoc.copyPages(document, document.getPageIndices());
93
+ copiedPages.forEach(page => pdfDoc.addPage(page));
94
+ };
95
+ /** Adds a PDF or an image file to the document, and returns its bytes */
96
+ export const addFileToPdf = async (pdfDoc, file) => {
97
+ if (file.type === 'application/pdf') {
98
+ await addPdfToPdf(pdfDoc, file);
99
+ }
100
+ else {
101
+ await addImageToPdf(pdfDoc, file);
102
+ }
103
+ return await pdfDoc.save();
104
+ };
105
+ /** Fetches the files of the docs and merges them into a single PDF */
106
+ export const makePdfBlob = async ({ client, docs, fetchBlobFileById }) => {
107
+ const { PDFDocument } = await import('pdf-lib');
108
+ const pdfDoc = await PDFDocument.create();
109
+ for (const doc of docs) {
110
+ const blob = await fetchBlobFileById(client, doc._id);
111
+ await addFileToPdf(pdfDoc, blob);
112
+ }
113
+ const pdfBytes = await pdfDoc.save();
114
+ return new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' });
115
+ };
116
+ export const downloadBlob = (blob, filename) => {
117
+ if (!blob || !filename)
118
+ return false;
119
+ const url = URL.createObjectURL(blob);
120
+ const a = document.createElement('a');
121
+ a.href = url;
122
+ a.download = filename;
123
+ a.style.display = 'none';
124
+ document.body.appendChild(a);
125
+ a.click();
126
+ a.remove();
127
+ return true;
128
+ };
@@ -0,0 +1,3 @@
1
+ export declare const logger: {
2
+ error: (...args: unknown[]) => void;
3
+ };
@@ -0,0 +1,2 @@
1
+ import minilog from 'cozy-minilog';
2
+ export const logger = minilog('twake-mui/ActionsMenu');
@@ -0,0 +1,11 @@
1
+ import { Action, ActionComponent, ActionComponentSlotProps } from '../types';
2
+ export interface MakeActionComponentOptions {
3
+ name: string;
4
+ label?: string;
5
+ icon?: Action['icon'];
6
+ componentProps?: ActionComponentSlotProps;
7
+ }
8
+ export declare const makeActionComponent: ({ name, label, icon, componentProps }: MakeActionComponentOptions) => ActionComponent;
9
+ export type MakeActionOptions = Omit<Action, 'Component'> & Partial<Pick<Action, 'Component'>>;
10
+ /** Makes an action, with a default `Component` when none is given */
11
+ export declare const makeAction: ({ Component, ...action }: MakeActionOptions) => Action;
@@ -0,0 +1,19 @@
1
+ import { Icon } from '@linagora/twake-icons';
2
+ import { ListItemIcon } from '@mui/material';
3
+ import React, { forwardRef } from 'react';
4
+ import ListItemText from '../../ListItemText';
5
+ import ActionsMenuItem from '../ActionsMenuItem';
6
+ export const makeActionComponent = ({ name, label, icon, componentProps }) => {
7
+ const Component = forwardRef((props, ref) => (React.createElement(ActionsMenuItem, { ...props, ref: ref },
8
+ React.createElement(ListItemIcon, null, icon !== undefined && React.createElement(Icon, { ...componentProps?.Icon, icon: icon })),
9
+ React.createElement(ListItemText, { primary: label, ...componentProps?.ListItemText }))));
10
+ Component.displayName = name;
11
+ return Component;
12
+ };
13
+ /** Makes an action, with a default `Component` when none is given */
14
+ export const makeAction = ({ Component, ...action }) => {
15
+ return {
16
+ ...action,
17
+ Component: Component ?? makeActionComponent(action)
18
+ };
19
+ };
@@ -0,0 +1,2 @@
1
+ import { Action, WebLinkActionOptions } from '../types';
2
+ export declare const modify: ({ client, generateWebLink }: WebLinkActionOptions) => Action;
@@ -0,0 +1,21 @@
1
+ import { Pen } from '@linagora/twake-icons';
2
+ import { makeAppWebLink } from './helpers';
3
+ import { makeAction } from './makeAction';
4
+ import { getActionsI18n } from '../locales';
5
+ export const modify = ({ client, generateWebLink }) => {
6
+ const { t } = getActionsI18n();
7
+ return makeAction({
8
+ name: 'modify',
9
+ label: t('ActionsMenu.modify'),
10
+ icon: Pen,
11
+ action: (docs) => {
12
+ const webLink = makeAppWebLink({
13
+ client,
14
+ generateWebLink,
15
+ slug: 'contacts',
16
+ hash: `/${docs[0]._id}/edit`
17
+ });
18
+ window.open(webLink, '_blank');
19
+ }
20
+ });
21
+ };
@@ -0,0 +1,2 @@
1
+ import { Action } from '../types';
2
+ export declare const others: () => Action;
@@ -0,0 +1,11 @@
1
+ import { Dots } from '@linagora/twake-icons';
2
+ import { makeAction } from './makeAction';
3
+ import { getActionsI18n } from '../locales';
4
+ export const others = () => {
5
+ const { t } = getActionsI18n();
6
+ return makeAction({
7
+ name: 'others',
8
+ label: t('ActionsMenu.others'),
9
+ icon: Dots
10
+ });
11
+ };
@@ -0,0 +1,35 @@
1
+ import type { PDFDocument, PDFFont, PDFPage } from 'pdf-lib';
2
+ export interface WrapTextToLinesOptions {
3
+ font: PDFFont;
4
+ fontSize: number;
5
+ /** Max line width, in PDF units */
6
+ maxLineWidth: number;
7
+ line: string;
8
+ }
9
+ export declare const wrapTextToLines: ({ font, fontSize, maxLineWidth, line }: WrapTextToLinesOptions) => string[];
10
+ export interface PdfCursor {
11
+ page: PDFPage;
12
+ y: number;
13
+ }
14
+ export interface PushNewPageIfNeededOptions extends PdfCursor {
15
+ pdfDoc: PDFDocument;
16
+ requiredHeight: number;
17
+ marginTop: number;
18
+ marginBottom: number;
19
+ }
20
+ export declare const pushNewPageIfNeeded: ({ pdfDoc, page, y, requiredHeight, marginTop, marginBottom }: PushNewPageIfNeededOptions) => PdfCursor;
21
+ export interface DrawWrappedTextOptions extends PdfCursor {
22
+ pdfDoc: PDFDocument;
23
+ text: string;
24
+ font: PDFFont;
25
+ fontSize: number;
26
+ lineHeight: number;
27
+ marginX: number;
28
+ /** Top and bottom margin */
29
+ marginY: number;
30
+ /** Max line width, in PDF units */
31
+ maxLineWidth: number;
32
+ }
33
+ export declare const drawWrappedText: ({ pdfDoc, page, y, text, font, fontSize, lineHeight, marginX, marginY, maxLineWidth }: DrawWrappedTextOptions) => PdfCursor;
34
+ /** Creates an A4 PDF from a plain text */
35
+ export declare const makePdfBlobFromText: (text: string) => Promise<Blob>;
@@ -0,0 +1,85 @@
1
+ const A4_PAGE_SIZE = [595.28, 841.89];
2
+ const TEXT_PDF_CONFIG = {
3
+ fontSize: 12,
4
+ lineHeight: 16,
5
+ marginX: 40,
6
+ marginY: 40,
7
+ pageSize: A4_PAGE_SIZE
8
+ };
9
+ export const wrapTextToLines = ({ font, fontSize, maxLineWidth, line }) => {
10
+ if (!line)
11
+ return [];
12
+ if (fontSize <= 0 || maxLineWidth <= 0)
13
+ return [line];
14
+ return line.split(' ').reduce((lines, word) => {
15
+ const currentLine = lines.at(-1);
16
+ if (currentLine === undefined)
17
+ return [word];
18
+ const testLine = `${currentLine} ${word}`;
19
+ if (font.widthOfTextAtSize(testLine, fontSize) <= maxLineWidth) {
20
+ lines[lines.length - 1] = testLine;
21
+ }
22
+ else {
23
+ lines.push(word);
24
+ }
25
+ return lines;
26
+ }, []);
27
+ };
28
+ export const pushNewPageIfNeeded = ({ pdfDoc, page, y, requiredHeight, marginTop, marginBottom }) => {
29
+ if (y - requiredHeight >= marginBottom)
30
+ return { page, y };
31
+ const { width, height } = page.getSize();
32
+ return {
33
+ page: pdfDoc.addPage([width, height]),
34
+ y: height - marginTop
35
+ };
36
+ };
37
+ export const drawWrappedText = ({ pdfDoc, page, y, text, font, fontSize, lineHeight, marginX, marginY, maxLineWidth }) => {
38
+ let cursor = { page, y };
39
+ for (const paragraph of text.split(/\r?\n/)) {
40
+ for (const line of wrapTextToLines({
41
+ font,
42
+ fontSize,
43
+ maxLineWidth,
44
+ line: paragraph
45
+ })) {
46
+ cursor = pushNewPageIfNeeded({
47
+ pdfDoc,
48
+ ...cursor,
49
+ requiredHeight: lineHeight,
50
+ marginTop: marginY,
51
+ marginBottom: marginY
52
+ });
53
+ cursor.page.drawText(line, {
54
+ x: marginX,
55
+ y: cursor.y - fontSize,
56
+ size: fontSize,
57
+ font
58
+ });
59
+ cursor = { ...cursor, y: cursor.y - lineHeight };
60
+ }
61
+ cursor = { ...cursor, y: cursor.y - lineHeight / 2 };
62
+ }
63
+ return cursor;
64
+ };
65
+ /** Creates an A4 PDF from a plain text */
66
+ export const makePdfBlobFromText = async (text) => {
67
+ const { PDFDocument, StandardFonts } = await import('pdf-lib');
68
+ const pdfDoc = await PDFDocument.create();
69
+ const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
70
+ const { fontSize, lineHeight, marginX, marginY, pageSize } = TEXT_PDF_CONFIG;
71
+ drawWrappedText({
72
+ pdfDoc,
73
+ page: pdfDoc.addPage(pageSize),
74
+ y: pageSize[1] - marginY,
75
+ text,
76
+ font,
77
+ fontSize,
78
+ lineHeight,
79
+ marginX,
80
+ marginY,
81
+ maxLineWidth: pageSize[0] - marginX * 2
82
+ });
83
+ const pdfBytes = await pdfDoc.save();
84
+ return new Blob([new Uint8Array(pdfBytes)], { type: 'application/pdf' });
85
+ };
@@ -0,0 +1,13 @@
1
+ import { FetchBlobFileById } from './helpers';
2
+ import { Action, ActionDocument } from '../types';
3
+ export interface PrintClient {
4
+ collection: (doctype: 'io.cozy.files') => {
5
+ getDownloadLinkById: (id: string, filename?: string) => Promise<string>;
6
+ };
7
+ }
8
+ export interface PrintActionOptions<Client extends PrintClient> {
9
+ client: Client;
10
+ fetchBlobFileById: FetchBlobFileById<Client>;
11
+ isFile: (doc: ActionDocument) => boolean;
12
+ }
13
+ export declare const print: <Client extends PrintClient>({ client, fetchBlobFileById, isFile }: PrintActionOptions<Client>) => Action;