@atlaskit/media-common 13.0.0 → 13.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @atlaskit/media-common
2
2
 
3
+ ## 13.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`83401665664f5`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/83401665664f5) -
8
+ Fix failure to extract clientId when performing cross client copy/paste of rich text containing
9
+ media
10
+
11
+ ## 13.0.1
12
+
13
+ ### Patch Changes
14
+
15
+ - [`539f26aebdac4`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/539f26aebdac4) -
16
+ Update CopyIntent clientId cache to be mutli use following LRU principles
17
+
3
18
  ## 13.0.0
4
19
 
5
20
  ### Major Changes
@@ -10,11 +10,12 @@ exports.setClientIdForFile = exports.getClientIdForFile = exports.extractClientI
10
10
  *
11
11
  * Used for cross-client copy/paste scenarios
12
12
  *
13
- * Entries are one-time use (consumed on read), with a max size limit
14
- * to guard against build-up if entries are never consumed (should not happpen).
13
+ * Entries persist across reads to support multiple pastes of the same content.
14
+ * An LRU-style max size limit guards against unbounded growth the oldest entry
15
+ * is evicted whenever the cache exceeds CLIENT_ID_CACHE_MAX_SIZE.
15
16
  */
16
17
 
17
- var CLIENT_ID_CACHE_MAX_SIZE = 100;
18
+ var CLIENT_ID_CACHE_MAX_SIZE = 20;
18
19
  var clientIdCache = new Map();
19
20
  var setClientIdForFile = exports.setClientIdForFile = function setClientIdForFile(fileId, clientId) {
20
21
  clientIdCache.set(fileId, clientId);
@@ -29,16 +30,12 @@ var setClientIdForFile = exports.setClientIdForFile = function setClientIdForFil
29
30
  };
30
31
 
31
32
  /**
32
- * Retrieve and consume clientId for a file ID (called during copyFile)
33
+ * Retrieve clientId for a file ID (called during copyFile).
33
34
  * Returns undefined if not found.
35
+ * Entries are NOT consumed on read — the same file can be pasted multiple times.
34
36
  */
35
37
  var getClientIdForFile = exports.getClientIdForFile = function getClientIdForFile(fileId) {
36
- var clientId = clientIdCache.get(fileId);
37
- // Consume the entry after reading (one-time use)
38
- if (clientId) {
39
- clientIdCache.delete(fileId);
40
- }
41
- return clientId;
38
+ return clientIdCache.get(fileId);
42
39
  };
43
40
 
44
41
  /**
@@ -50,20 +47,65 @@ var clearClientIdCache = exports.clearClientIdCache = function clearClientIdCach
50
47
 
51
48
  /**
52
49
  * Used to store fileId to clientId mappings on paste of Editor PM Node
50
+ *
51
+ * Handles two sources of pasted HTML:
52
+ *
53
+ * 1. Editor-to-editor copy: the clientId is stored as a `data-client-id` attribute
54
+ * directly on the `<div data-node-type="media">` element, written by the editor's
55
+ * clipboard serialiser.
56
+ *
57
+ * 2. Renderer-to-editor copy (e.g. pasting from a published Confluence page): the
58
+ * renderer does not write `data-client-id` on the media div. Instead, the clientId
59
+ * is embedded inside the blob URL on the `<img>` child element, in the hash params:
60
+ * `src="blob:https://...#media-blob-url=true&id=FILE_ID&...&clientId=CLIENT_ID"`
61
+ *
62
+ * All media nodes in a single pasted HTML fragment always share the same clientId,
63
+ * so we extract it once and apply it to all file IDs found in the HTML.
64
+ */
65
+
66
+ /**
67
+ * Extract the clientId from a blob URL hash fragment (renderer-to-editor path).
68
+ * Returns undefined if not found.
53
69
  */
70
+ var extractClientIdFromBlobUrl = function extractClientIdFromBlobUrl(html) {
71
+ // Matches both:
72
+ // - blob: URLs (client-rendered renderer)
73
+ // - HTTPS URLs with #media-blob-url=true in the hash (SSR-rendered renderer)
74
+ var imgSrcRegex = /src="([^"]*#media-blob-url=true[^"]*)"/g;
75
+ var match;
76
+ while ((match = imgSrcRegex.exec(html)) !== null) {
77
+ var src = match[1];
78
+ // clientId is encoded in the hash fragment as a URL param
79
+ // e.g. blob:https://hello.atlassian.net/uuid#media-blob-url=true&id=FILE_ID&clientId=CLIENT_ID
80
+ var hash = src.includes('#') ? src.slice(src.indexOf('#') + 1) : '';
81
+ // Unescape HTML-encoded ampersands that appear in raw clipboard HTML attribute values
82
+ var decodedHash = hash.replace(/&amp;/g, '&');
83
+ var clientId = new URLSearchParams(decodedHash).get('clientId');
84
+ if (clientId) {
85
+ return clientId;
86
+ }
87
+ }
88
+ return undefined;
89
+ };
54
90
  var extractClientIdsFromHtml = exports.extractClientIdsFromHtml = function extractClientIdsFromHtml(html) {
55
- if (!html || !html.includes('data-client-id')) {
91
+ var _exec$, _exec;
92
+ if (!html) {
56
93
  return;
57
94
  }
58
- var mediaTagRegex = /<[^>]*data-node-type="media(?:Inline)?"[^>]*>/g;
95
+
96
+ // All media nodes in a single paste share the same clientId.
97
+ // Extract it once from either source:
98
+ // - Editor-to-editor: present as data-client-id="..." attribute
99
+ // - Renderer-to-editor: embedded in the blob URL hash as clientId=...
100
+ var clientId = (_exec$ = (_exec = /data-client-id="([^"]*)"/.exec(html)) === null || _exec === void 0 ? void 0 : _exec[1]) !== null && _exec$ !== void 0 ? _exec$ : extractClientIdFromBlobUrl(html);
101
+ if (!clientId) {
102
+ return;
103
+ }
104
+
105
+ // Map every file ID in the HTML to the single clientId
106
+ var fileIdRegex = /data-id="([^"]*)"/g;
59
107
  var match;
60
- while ((match = mediaTagRegex.exec(html)) !== null) {
61
- var _exec, _exec2;
62
- var tag = match[0];
63
- var fileId = (_exec = /data-id="([^"]*)"/.exec(tag)) === null || _exec === void 0 ? void 0 : _exec[1];
64
- var clientId = (_exec2 = /data-client-id="([^"]*)"/.exec(tag)) === null || _exec2 === void 0 ? void 0 : _exec2[1];
65
- if (fileId && clientId) {
66
- setClientIdForFile(fileId, clientId);
67
- }
108
+ while ((match = fileIdRegex.exec(html)) !== null) {
109
+ setClientIdForFile(match[1], clientId);
68
110
  }
69
111
  };
@@ -4,11 +4,12 @@
4
4
  *
5
5
  * Used for cross-client copy/paste scenarios
6
6
  *
7
- * Entries are one-time use (consumed on read), with a max size limit
8
- * to guard against build-up if entries are never consumed (should not happpen).
7
+ * Entries persist across reads to support multiple pastes of the same content.
8
+ * An LRU-style max size limit guards against unbounded growth the oldest entry
9
+ * is evicted whenever the cache exceeds CLIENT_ID_CACHE_MAX_SIZE.
9
10
  */
10
11
 
11
- const CLIENT_ID_CACHE_MAX_SIZE = 100;
12
+ const CLIENT_ID_CACHE_MAX_SIZE = 20;
12
13
  const clientIdCache = new Map();
13
14
  export const setClientIdForFile = (fileId, clientId) => {
14
15
  clientIdCache.set(fileId, clientId);
@@ -23,16 +24,12 @@ export const setClientIdForFile = (fileId, clientId) => {
23
24
  };
24
25
 
25
26
  /**
26
- * Retrieve and consume clientId for a file ID (called during copyFile)
27
+ * Retrieve clientId for a file ID (called during copyFile).
27
28
  * Returns undefined if not found.
29
+ * Entries are NOT consumed on read — the same file can be pasted multiple times.
28
30
  */
29
31
  export const getClientIdForFile = fileId => {
30
- const clientId = clientIdCache.get(fileId);
31
- // Consume the entry after reading (one-time use)
32
- if (clientId) {
33
- clientIdCache.delete(fileId);
34
- }
35
- return clientId;
32
+ return clientIdCache.get(fileId);
36
33
  };
37
34
 
38
35
  /**
@@ -44,20 +41,65 @@ export const clearClientIdCache = () => {
44
41
 
45
42
  /**
46
43
  * Used to store fileId to clientId mappings on paste of Editor PM Node
44
+ *
45
+ * Handles two sources of pasted HTML:
46
+ *
47
+ * 1. Editor-to-editor copy: the clientId is stored as a `data-client-id` attribute
48
+ * directly on the `<div data-node-type="media">` element, written by the editor's
49
+ * clipboard serialiser.
50
+ *
51
+ * 2. Renderer-to-editor copy (e.g. pasting from a published Confluence page): the
52
+ * renderer does not write `data-client-id` on the media div. Instead, the clientId
53
+ * is embedded inside the blob URL on the `<img>` child element, in the hash params:
54
+ * `src="blob:https://...#media-blob-url=true&id=FILE_ID&...&clientId=CLIENT_ID"`
55
+ *
56
+ * All media nodes in a single pasted HTML fragment always share the same clientId,
57
+ * so we extract it once and apply it to all file IDs found in the HTML.
58
+ */
59
+
60
+ /**
61
+ * Extract the clientId from a blob URL hash fragment (renderer-to-editor path).
62
+ * Returns undefined if not found.
47
63
  */
64
+ const extractClientIdFromBlobUrl = html => {
65
+ // Matches both:
66
+ // - blob: URLs (client-rendered renderer)
67
+ // - HTTPS URLs with #media-blob-url=true in the hash (SSR-rendered renderer)
68
+ const imgSrcRegex = /src="([^"]*#media-blob-url=true[^"]*)"/g;
69
+ let match;
70
+ while ((match = imgSrcRegex.exec(html)) !== null) {
71
+ const src = match[1];
72
+ // clientId is encoded in the hash fragment as a URL param
73
+ // e.g. blob:https://hello.atlassian.net/uuid#media-blob-url=true&id=FILE_ID&clientId=CLIENT_ID
74
+ const hash = src.includes('#') ? src.slice(src.indexOf('#') + 1) : '';
75
+ // Unescape HTML-encoded ampersands that appear in raw clipboard HTML attribute values
76
+ const decodedHash = hash.replace(/&amp;/g, '&');
77
+ const clientId = new URLSearchParams(decodedHash).get('clientId');
78
+ if (clientId) {
79
+ return clientId;
80
+ }
81
+ }
82
+ return undefined;
83
+ };
48
84
  export const extractClientIdsFromHtml = html => {
49
- if (!html || !html.includes('data-client-id')) {
85
+ var _exec$, _exec;
86
+ if (!html) {
50
87
  return;
51
88
  }
52
- const mediaTagRegex = /<[^>]*data-node-type="media(?:Inline)?"[^>]*>/g;
89
+
90
+ // All media nodes in a single paste share the same clientId.
91
+ // Extract it once from either source:
92
+ // - Editor-to-editor: present as data-client-id="..." attribute
93
+ // - Renderer-to-editor: embedded in the blob URL hash as clientId=...
94
+ const clientId = (_exec$ = (_exec = /data-client-id="([^"]*)"/.exec(html)) === null || _exec === void 0 ? void 0 : _exec[1]) !== null && _exec$ !== void 0 ? _exec$ : extractClientIdFromBlobUrl(html);
95
+ if (!clientId) {
96
+ return;
97
+ }
98
+
99
+ // Map every file ID in the HTML to the single clientId
100
+ const fileIdRegex = /data-id="([^"]*)"/g;
53
101
  let match;
54
- while ((match = mediaTagRegex.exec(html)) !== null) {
55
- var _exec, _exec2;
56
- const tag = match[0];
57
- const fileId = (_exec = /data-id="([^"]*)"/.exec(tag)) === null || _exec === void 0 ? void 0 : _exec[1];
58
- const clientId = (_exec2 = /data-client-id="([^"]*)"/.exec(tag)) === null || _exec2 === void 0 ? void 0 : _exec2[1];
59
- if (fileId && clientId) {
60
- setClientIdForFile(fileId, clientId);
61
- }
102
+ while ((match = fileIdRegex.exec(html)) !== null) {
103
+ setClientIdForFile(match[1], clientId);
62
104
  }
63
105
  };
@@ -4,11 +4,12 @@
4
4
  *
5
5
  * Used for cross-client copy/paste scenarios
6
6
  *
7
- * Entries are one-time use (consumed on read), with a max size limit
8
- * to guard against build-up if entries are never consumed (should not happpen).
7
+ * Entries persist across reads to support multiple pastes of the same content.
8
+ * An LRU-style max size limit guards against unbounded growth the oldest entry
9
+ * is evicted whenever the cache exceeds CLIENT_ID_CACHE_MAX_SIZE.
9
10
  */
10
11
 
11
- var CLIENT_ID_CACHE_MAX_SIZE = 100;
12
+ var CLIENT_ID_CACHE_MAX_SIZE = 20;
12
13
  var clientIdCache = new Map();
13
14
  export var setClientIdForFile = function setClientIdForFile(fileId, clientId) {
14
15
  clientIdCache.set(fileId, clientId);
@@ -23,16 +24,12 @@ export var setClientIdForFile = function setClientIdForFile(fileId, clientId) {
23
24
  };
24
25
 
25
26
  /**
26
- * Retrieve and consume clientId for a file ID (called during copyFile)
27
+ * Retrieve clientId for a file ID (called during copyFile).
27
28
  * Returns undefined if not found.
29
+ * Entries are NOT consumed on read — the same file can be pasted multiple times.
28
30
  */
29
31
  export var getClientIdForFile = function getClientIdForFile(fileId) {
30
- var clientId = clientIdCache.get(fileId);
31
- // Consume the entry after reading (one-time use)
32
- if (clientId) {
33
- clientIdCache.delete(fileId);
34
- }
35
- return clientId;
32
+ return clientIdCache.get(fileId);
36
33
  };
37
34
 
38
35
  /**
@@ -44,20 +41,65 @@ export var clearClientIdCache = function clearClientIdCache() {
44
41
 
45
42
  /**
46
43
  * Used to store fileId to clientId mappings on paste of Editor PM Node
44
+ *
45
+ * Handles two sources of pasted HTML:
46
+ *
47
+ * 1. Editor-to-editor copy: the clientId is stored as a `data-client-id` attribute
48
+ * directly on the `<div data-node-type="media">` element, written by the editor's
49
+ * clipboard serialiser.
50
+ *
51
+ * 2. Renderer-to-editor copy (e.g. pasting from a published Confluence page): the
52
+ * renderer does not write `data-client-id` on the media div. Instead, the clientId
53
+ * is embedded inside the blob URL on the `<img>` child element, in the hash params:
54
+ * `src="blob:https://...#media-blob-url=true&id=FILE_ID&...&clientId=CLIENT_ID"`
55
+ *
56
+ * All media nodes in a single pasted HTML fragment always share the same clientId,
57
+ * so we extract it once and apply it to all file IDs found in the HTML.
58
+ */
59
+
60
+ /**
61
+ * Extract the clientId from a blob URL hash fragment (renderer-to-editor path).
62
+ * Returns undefined if not found.
47
63
  */
64
+ var extractClientIdFromBlobUrl = function extractClientIdFromBlobUrl(html) {
65
+ // Matches both:
66
+ // - blob: URLs (client-rendered renderer)
67
+ // - HTTPS URLs with #media-blob-url=true in the hash (SSR-rendered renderer)
68
+ var imgSrcRegex = /src="([^"]*#media-blob-url=true[^"]*)"/g;
69
+ var match;
70
+ while ((match = imgSrcRegex.exec(html)) !== null) {
71
+ var src = match[1];
72
+ // clientId is encoded in the hash fragment as a URL param
73
+ // e.g. blob:https://hello.atlassian.net/uuid#media-blob-url=true&id=FILE_ID&clientId=CLIENT_ID
74
+ var hash = src.includes('#') ? src.slice(src.indexOf('#') + 1) : '';
75
+ // Unescape HTML-encoded ampersands that appear in raw clipboard HTML attribute values
76
+ var decodedHash = hash.replace(/&amp;/g, '&');
77
+ var clientId = new URLSearchParams(decodedHash).get('clientId');
78
+ if (clientId) {
79
+ return clientId;
80
+ }
81
+ }
82
+ return undefined;
83
+ };
48
84
  export var extractClientIdsFromHtml = function extractClientIdsFromHtml(html) {
49
- if (!html || !html.includes('data-client-id')) {
85
+ var _exec$, _exec;
86
+ if (!html) {
50
87
  return;
51
88
  }
52
- var mediaTagRegex = /<[^>]*data-node-type="media(?:Inline)?"[^>]*>/g;
89
+
90
+ // All media nodes in a single paste share the same clientId.
91
+ // Extract it once from either source:
92
+ // - Editor-to-editor: present as data-client-id="..." attribute
93
+ // - Renderer-to-editor: embedded in the blob URL hash as clientId=...
94
+ var clientId = (_exec$ = (_exec = /data-client-id="([^"]*)"/.exec(html)) === null || _exec === void 0 ? void 0 : _exec[1]) !== null && _exec$ !== void 0 ? _exec$ : extractClientIdFromBlobUrl(html);
95
+ if (!clientId) {
96
+ return;
97
+ }
98
+
99
+ // Map every file ID in the HTML to the single clientId
100
+ var fileIdRegex = /data-id="([^"]*)"/g;
53
101
  var match;
54
- while ((match = mediaTagRegex.exec(html)) !== null) {
55
- var _exec, _exec2;
56
- var tag = match[0];
57
- var fileId = (_exec = /data-id="([^"]*)"/.exec(tag)) === null || _exec === void 0 ? void 0 : _exec[1];
58
- var clientId = (_exec2 = /data-client-id="([^"]*)"/.exec(tag)) === null || _exec2 === void 0 ? void 0 : _exec2[1];
59
- if (fileId && clientId) {
60
- setClientIdForFile(fileId, clientId);
61
- }
102
+ while ((match = fileIdRegex.exec(html)) !== null) {
103
+ setClientIdForFile(match[1], clientId);
62
104
  }
63
105
  };
@@ -4,20 +4,19 @@
4
4
  *
5
5
  * Used for cross-client copy/paste scenarios
6
6
  *
7
- * Entries are one-time use (consumed on read), with a max size limit
8
- * to guard against build-up if entries are never consumed (should not happpen).
7
+ * Entries persist across reads to support multiple pastes of the same content.
8
+ * An LRU-style max size limit guards against unbounded growth the oldest entry
9
+ * is evicted whenever the cache exceeds CLIENT_ID_CACHE_MAX_SIZE.
9
10
  */
10
11
  export declare const setClientIdForFile: (fileId: string, clientId: string) => void;
11
12
  /**
12
- * Retrieve and consume clientId for a file ID (called during copyFile)
13
+ * Retrieve clientId for a file ID (called during copyFile).
13
14
  * Returns undefined if not found.
15
+ * Entries are NOT consumed on read — the same file can be pasted multiple times.
14
16
  */
15
17
  export declare const getClientIdForFile: (fileId: string) => string | undefined;
16
18
  /**
17
19
  * Clear all cached clientIds (for testing purposes)
18
20
  */
19
21
  export declare const clearClientIdCache: () => void;
20
- /**
21
- * Used to store fileId to clientId mappings on paste of Editor PM Node
22
- */
23
22
  export declare const extractClientIdsFromHtml: (html: string) => void;
@@ -4,20 +4,19 @@
4
4
  *
5
5
  * Used for cross-client copy/paste scenarios
6
6
  *
7
- * Entries are one-time use (consumed on read), with a max size limit
8
- * to guard against build-up if entries are never consumed (should not happpen).
7
+ * Entries persist across reads to support multiple pastes of the same content.
8
+ * An LRU-style max size limit guards against unbounded growth the oldest entry
9
+ * is evicted whenever the cache exceeds CLIENT_ID_CACHE_MAX_SIZE.
9
10
  */
10
11
  export declare const setClientIdForFile: (fileId: string, clientId: string) => void;
11
12
  /**
12
- * Retrieve and consume clientId for a file ID (called during copyFile)
13
+ * Retrieve clientId for a file ID (called during copyFile).
13
14
  * Returns undefined if not found.
15
+ * Entries are NOT consumed on read — the same file can be pasted multiple times.
14
16
  */
15
17
  export declare const getClientIdForFile: (fileId: string) => string | undefined;
16
18
  /**
17
19
  * Clear all cached clientIds (for testing purposes)
18
20
  */
19
21
  export declare const clearClientIdCache: () => void;
20
- /**
21
- * Used to store fileId to clientId mappings on paste of Editor PM Node
22
- */
23
22
  export declare const extractClientIdsFromHtml: (html: string) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/media-common",
3
- "version": "13.0.0",
3
+ "version": "13.0.2",
4
4
  "description": "Includes common utilities used by other media packages",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -31,7 +31,7 @@
31
31
  "dependencies": {
32
32
  "@atlaskit/analytics-gas-types": "^5.1.0",
33
33
  "@atlaskit/analytics-namespaced-context": "^7.2.0",
34
- "@atlaskit/analytics-next": "^11.1.0",
34
+ "@atlaskit/analytics-next": "^11.2.0",
35
35
  "@atlaskit/link": "^3.3.0",
36
36
  "@atlaskit/section-message": "^8.12.0",
37
37
  "@babel/runtime": "^7.0.0",