@limetech/lime-elements 38.13.0 → 38.13.2-dev.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.
@@ -132,9 +132,29 @@ const processPasteEvent = (view, event, slice) => {
132
132
  if (!clipboardData) {
133
133
  return false;
134
134
  }
135
+ const isImageFilePasted = handlePastedImages(view, clipboardData);
136
+ const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
137
+ if (filteredSlice.content.childCount < slice.content.childCount) {
138
+ const { state, dispatch } = view;
139
+ const tr = state.tr.replaceSelection(filteredSlice);
140
+ dispatch(tr);
141
+ return true;
142
+ }
143
+ return isImageFilePasted;
144
+ };
145
+ /**
146
+ * Processes any image files found in the clipboard data and dispatches an imagePasted event.
147
+ *
148
+ * @param view - The ProseMirror editor view
149
+ * @param clipboardData - The clipboard data transfer object containing potential image files
150
+ * @returns True if at least one valid image file was found and processed, false otherwise
151
+ */
152
+ function handlePastedImages(view, clipboardData) {
153
+ let isImageFilePasted = false;
135
154
  const files = Array.from(clipboardData.files || []);
136
155
  for (const file of files) {
137
- if (file.type.startsWith('image/')) {
156
+ if (isImageFile(file, clipboardData)) {
157
+ isImageFilePasted = true;
138
158
  const reader = new FileReader();
139
159
  reader.onloadend = () => {
140
160
  view.dom.dispatchEvent(new CustomEvent('imagePasted', {
@@ -144,13 +164,43 @@ const processPasteEvent = (view, event, slice) => {
144
164
  reader.readAsDataURL(file);
145
165
  }
146
166
  }
147
- const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
148
- if (filteredSlice.content.childCount < slice.content.childCount) {
149
- const { state, dispatch } = view;
150
- const tr = state.tr.replaceSelection(filteredSlice);
151
- dispatch(tr);
152
- return true;
167
+ return isImageFilePasted;
168
+ }
169
+ /**
170
+ * Determines if a file is an image that should be processed by the image handler.
171
+ *
172
+ * This function checks both the file's MIME type and the clipboard HTML content.
173
+ * It filters out HTML content from Excel and HTML tables, as they are not relevant for image processing.
174
+ *
175
+ * @param file - The file object to check
176
+ * @param clipboardData - The full clipboard data transfer object to examine for context
177
+ * @returns True if the file is an image that should be processed, false otherwise
178
+ */
179
+ function isImageFile(file, clipboardData) {
180
+ var _a, _b;
181
+ if (!isContentTypeImage(file)) {
182
+ return false;
153
183
  }
154
- return files.length > 0;
155
- };
184
+ const html = (_b = (_a = clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.getData('text/html')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
185
+ return !isHtmlFromExcel(html) && !isHtmlTable(html);
186
+ }
187
+ function isContentTypeImage(file) {
188
+ if (!(file === null || file === void 0 ? void 0 : file.type)) {
189
+ return false;
190
+ }
191
+ return file.type.startsWith('image/');
192
+ }
193
+ function isHtmlFromExcel(html) {
194
+ if (!html) {
195
+ return false;
196
+ }
197
+ return (html.includes('name=generator content="microsoft excel"') ||
198
+ html.includes('xmlns:x="urn:schemas-microsoft-com:office:excel"'));
199
+ }
200
+ function isHtmlTable(html) {
201
+ if (!html) {
202
+ return false;
203
+ }
204
+ return html.includes('<table');
205
+ }
156
206
  //# sourceMappingURL=inserter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"inserter.js","sourceRoot":"","sources":["../../../../../../src/components/text-editor/prosemirror-adapter/plugins/image/inserter.ts"],"names":[],"mappings":"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","sourcesContent":["import { Plugin, PluginKey } from 'prosemirror-state';\nimport { EditorView } from 'prosemirror-view';\nimport { createFileInfo } from '../../../../../util/files';\nimport { FileInfo } from '../../../../../global/shared-types/file.types';\nimport { ImageInserter, EditorImageState } from '../../../text-editor.types';\nimport { Node, Slice, Fragment } from 'prosemirror-model';\nimport { ImageNodeAttrs } from './node';\n\nexport const pluginKey = new PluginKey('imageInserterPlugin');\n\ntype ImagePastedCallback = (data: ImageInserter) => CustomEvent<ImageInserter>;\n\nexport const createImageInserterPlugin = (\n imagePastedCallback: ImagePastedCallback,\n) => {\n return new Plugin({\n key: pluginKey,\n props: {\n handlePaste: (view, event, slice) => {\n return processPasteEvent(view, event, slice);\n },\n handleDOMEvents: {\n imagePasted: (_, event) => {\n imagePastedCallback(event.detail);\n },\n },\n },\n });\n};\n\nexport const imageInserterFactory = (\n view: EditorView,\n base64Data: string,\n fileInfo: FileInfo,\n): ImageInserter => {\n return {\n fileInfo: fileInfo,\n insertThumbnail: createThumbnailInserter(view, base64Data, fileInfo),\n insertImage: createImageInserter(view, fileInfo),\n insertFailedThumbnail: createFailedThumbnailInserter(view, fileInfo),\n };\n};\n\nconst createThumbnailInserter =\n (view: EditorView, base64Data: string, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const imageNodeAttrs = createImageNodeAttrs(\n base64Data,\n fileInfo,\n 'loading',\n );\n const placeholderNode = schema.nodes.image.create(imageNodeAttrs);\n\n const transaction = state.tr.replaceSelectionWith(placeholderNode);\n\n dispatch(transaction);\n };\n\nconst createImageInserter =\n (view: EditorView, fileInfo: FileInfo) => (src?: string) => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n src ? src : node.attrs.src,\n fileInfo,\n 'success',\n );\n const imageNode = schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, imageNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nconst createFailedThumbnailInserter =\n (view: EditorView, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n node.attrs.src,\n fileInfo,\n 'failed',\n );\n const errorPlaceholderNode =\n schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, errorPlaceholderNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nfunction createImageNodeAttrs(\n src: string,\n fileInfo: FileInfo,\n state: EditorImageState,\n): ImageNodeAttrs {\n return {\n src: src,\n alt: fileInfo.filename,\n fileInfoId: fileInfo.id,\n state: state,\n };\n}\n\n/**\n * Check if a given ProseMirror node or fragment contains any image nodes.\n * @param node - The ProseMirror node or fragment to check.\n * @returns A boolean indicating whether the node contains any image nodes.\n */\nconst isImageNode = (node: Node | Fragment): boolean => {\n if (node instanceof Node) {\n if (node.type.name === 'image') {\n return true;\n }\n\n let found = false;\n node.content.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n } else if (node instanceof Fragment) {\n let found = false;\n node.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n }\n\n return false;\n};\n\n/**\n * Filter out image nodes from a ProseMirror fragment.\n * @param fragment - The ProseMirror fragment to filter.\n * @returns A new fragment with image nodes removed.\n */\nconst filterImageNodes = (fragment: Fragment): Fragment => {\n const filteredChildren: Node[] = [];\n\n fragment.forEach((child) => {\n if (!isImageNode(child)) {\n if (child.content.size > 0) {\n const filteredContent = filterImageNodes(child.content);\n const newNode = child.copy(filteredContent);\n filteredChildren.push(newNode);\n } else {\n filteredChildren.push(child);\n }\n }\n });\n\n return Fragment.fromArray(filteredChildren);\n};\n\n/**\n * Process a paste event and trigger an imagePasted event if an image file is pasted.\n * If an HTML image element is pasted, this image is filtered out from the slice content.\n *\n * @param view - The ProseMirror editor view.\n * @param event - The paste event.\n * @returns A boolean; True if an image file was pasted to prevent default paste behavior, otherwise false.\n */\nconst processPasteEvent = (\n view: EditorView,\n event: ClipboardEvent,\n slice: Slice,\n): boolean => {\n const clipboardData = event.clipboardData;\n if (!clipboardData) {\n return false;\n }\n\n const files = Array.from(clipboardData.files || []);\n for (const file of files) {\n if (file.type.startsWith('image/')) {\n const reader = new FileReader();\n reader.onloadend = () => {\n view.dom.dispatchEvent(\n new CustomEvent('imagePasted', {\n detail: imageInserterFactory(\n view,\n reader.result as string,\n createFileInfo(file),\n ),\n }),\n );\n };\n\n reader.readAsDataURL(file);\n }\n }\n\n const filteredSlice = new Slice(\n filterImageNodes(slice.content),\n slice.openStart,\n slice.openEnd,\n );\n\n if (filteredSlice.content.childCount < slice.content.childCount) {\n const { state, dispatch } = view;\n const tr = state.tr.replaceSelection(filteredSlice);\n dispatch(tr);\n\n return true;\n }\n\n return files.length > 0;\n};\n"]}
1
+ {"version":3,"file":"inserter.js","sourceRoot":"","sources":["../../../../../../src/components/text-editor/prosemirror-adapter/plugins/image/inserter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG1D,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAC;AAI9D,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACrC,mBAAwC,EAC1C,EAAE;EACA,OAAO,IAAI,MAAM,CAAC;IACd,GAAG,EAAE,SAAS;IACd,KAAK,EAAE;MACH,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAChC,OAAO,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;MACjD,CAAC;MACD,eAAe,EAAE;QACb,WAAW,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;UACtB,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;OACJ;KACJ;GACJ,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAChC,IAAgB,EAChB,UAAkB,EAClB,QAAkB,EACL,EAAE;EACf,OAAO;IACH,QAAQ,EAAE,QAAQ;IAClB,eAAe,EAAE,uBAAuB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC;IACpE,WAAW,EAAE,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChD,qBAAqB,EAAE,6BAA6B,CAAC,IAAI,EAAE,QAAQ,CAAC;GACvE,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,uBAAuB,GACzB,CAAC,IAAgB,EAAE,UAAkB,EAAE,QAAkB,EAAE,EAAE,CAAC,GAAG,EAAE;EAC/D,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;EACjC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;EAEzB,MAAM,cAAc,GAAG,oBAAoB,CACvC,UAAU,EACV,QAAQ,EACR,SAAS,CACZ,CAAC;EACF,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;EAElE,MAAM,WAAW,GAAG,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,eAAe,CAAC,CAAC;EAEnE,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEN,MAAM,mBAAmB,GACrB,CAAC,IAAgB,EAAE,QAAkB,EAAE,EAAE,CAAC,CAAC,GAAY,EAAE,EAAE;EACvD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;EACjC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;EAEzB,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;EACpB,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAChC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,QAAQ,CAAC,EAAE,EAAE;MACvC,MAAM,cAAc,GAAG,oBAAoB,CACvC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAC1B,QAAQ,EACR,SAAS,CACZ,CAAC;MACF,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;MAE5D,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;MAEpD,OAAO,KAAK,CAAC;KAChB;EACL,CAAC,CAAC,CAAC;EAEH,QAAQ,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC,CAAC;AAEN,MAAM,6BAA6B,GAC/B,CAAC,IAAgB,EAAE,QAAkB,EAAE,EAAE,CAAC,GAAG,EAAE;EAC3C,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;EACjC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;EAEzB,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;EACpB,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAChC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,QAAQ,CAAC,EAAE,EAAE;MACvC,MAAM,cAAc,GAAG,oBAAoB,CACvC,IAAI,CAAC,KAAK,CAAC,GAAG,EACd,QAAQ,EACR,QAAQ,CACX,CAAC;MACF,MAAM,oBAAoB,GACtB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;MAE9C,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;MAE/D,OAAO,KAAK,CAAC;KAChB;EACL,CAAC,CAAC,CAAC;EAEH,QAAQ,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC,CAAC;AAEN,SAAS,oBAAoB,CACzB,GAAW,EACX,QAAkB,EAClB,KAAuB;EAEvB,OAAO;IACH,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,QAAQ,CAAC,QAAQ;IACtB,UAAU,EAAE,QAAQ,CAAC,EAAE;IACvB,KAAK,EAAE,KAAK;GACf,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,IAAqB,EAAW,EAAE;EACnD,IAAI,IAAI,YAAY,IAAI,EAAE;IACtB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE;MAC5B,OAAO,IAAI,CAAC;KACf;IAED,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;MAC3B,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE;QACpB,KAAK,GAAG,IAAI,CAAC;OAChB;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;GAChB;OAAM,IAAI,IAAI,YAAY,QAAQ,EAAE;IACjC,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;MACnB,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE;QACpB,KAAK,GAAG,IAAI,CAAC;OAChB;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;GAChB;EAED,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAkB,EAAY,EAAE;EACtD,MAAM,gBAAgB,GAAW,EAAE,CAAC;EAEpC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;IACvB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;MACrB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE;QACxB,MAAM,eAAe,GAAG,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5C,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;OAClC;WAAM;QACH,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;OAChC;KACJ;EACL,CAAC,CAAC,CAAC;EAEH,OAAO,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,CACtB,IAAgB,EAChB,KAAqB,EACrB,KAAY,EACL,EAAE;EACT,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;EAC1C,IAAI,CAAC,aAAa,EAAE;IAChB,OAAO,KAAK,CAAC;GAChB;EAED,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;EAElE,MAAM,aAAa,GAAG,IAAI,KAAK,CAC3B,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,EAC/B,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,OAAO,CAChB,CAAC;EAEF,IAAI,aAAa,CAAC,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE;IAC7D,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;IACpD,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEb,OAAO,IAAI,CAAC;GACf;EAED,OAAO,iBAAiB,CAAC;AAC7B,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,kBAAkB,CACvB,IAAgB,EAChB,aAA2B;EAE3B,IAAI,iBAAiB,GAAG,KAAK,CAAC;EAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;EAEpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;IACtB,IAAI,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE;MAClC,iBAAiB,GAAG,IAAI,CAAC;MAEzB,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;MAChC,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE;QACpB,IAAI,CAAC,GAAG,CAAC,aAAa,CAClB,IAAI,WAAW,CAAC,aAAa,EAAE;UAC3B,MAAM,EAAE,oBAAoB,CACxB,IAAI,EACJ,MAAM,CAAC,MAAgB,EACvB,cAAc,CAAC,IAAI,CAAC,CACvB;SACJ,CAAC,CACL,CAAC;MACN,CAAC,CAAC;MAEF,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;KAC9B;GACJ;EAED,OAAO,iBAAiB,CAAC;AAC7B,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,WAAW,CAAC,IAAU,EAAE,aAA2B;;EACxD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE;IAC3B,OAAO,KAAK,CAAC;GAChB;EAED,MAAM,IAAI,GAAG,MAAA,MAAA,aAAa,aAAb,aAAa,uBAAb,aAAa,CAAE,OAAO,CAAC,WAAW,CAAC,0CAAE,WAAW,EAAE,mCAAI,EAAE,CAAC;EAEtE,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAU;EAClC,IAAI,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,CAAA,EAAE;IACb,OAAO,KAAK,CAAC;GAChB;EAED,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;EACjC,IAAI,CAAC,IAAI,EAAE;IACP,OAAO,KAAK,CAAC;GAChB;EAED,OAAO,CACH,IAAI,CAAC,QAAQ,CAAC,0CAA0C,CAAC;IACzD,IAAI,CAAC,QAAQ,CAAC,kDAAkD,CAAC,CACpE,CAAC;AACN,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;EAC7B,IAAI,CAAC,IAAI,EAAE;IACP,OAAO,KAAK,CAAC;GAChB;EAED,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACnC,CAAC","sourcesContent":["import { Plugin, PluginKey } from 'prosemirror-state';\nimport { EditorView } from 'prosemirror-view';\nimport { createFileInfo } from '../../../../../util/files';\nimport { FileInfo } from '../../../../../global/shared-types/file.types';\nimport { ImageInserter, EditorImageState } from '../../../text-editor.types';\nimport { Node, Slice, Fragment } from 'prosemirror-model';\nimport { ImageNodeAttrs } from './node';\n\nexport const pluginKey = new PluginKey('imageInserterPlugin');\n\ntype ImagePastedCallback = (data: ImageInserter) => CustomEvent<ImageInserter>;\n\nexport const createImageInserterPlugin = (\n imagePastedCallback: ImagePastedCallback,\n) => {\n return new Plugin({\n key: pluginKey,\n props: {\n handlePaste: (view, event, slice) => {\n return processPasteEvent(view, event, slice);\n },\n handleDOMEvents: {\n imagePasted: (_, event) => {\n imagePastedCallback(event.detail);\n },\n },\n },\n });\n};\n\nexport const imageInserterFactory = (\n view: EditorView,\n base64Data: string,\n fileInfo: FileInfo,\n): ImageInserter => {\n return {\n fileInfo: fileInfo,\n insertThumbnail: createThumbnailInserter(view, base64Data, fileInfo),\n insertImage: createImageInserter(view, fileInfo),\n insertFailedThumbnail: createFailedThumbnailInserter(view, fileInfo),\n };\n};\n\nconst createThumbnailInserter =\n (view: EditorView, base64Data: string, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const imageNodeAttrs = createImageNodeAttrs(\n base64Data,\n fileInfo,\n 'loading',\n );\n const placeholderNode = schema.nodes.image.create(imageNodeAttrs);\n\n const transaction = state.tr.replaceSelectionWith(placeholderNode);\n\n dispatch(transaction);\n };\n\nconst createImageInserter =\n (view: EditorView, fileInfo: FileInfo) => (src?: string) => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n src ? src : node.attrs.src,\n fileInfo,\n 'success',\n );\n const imageNode = schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, imageNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nconst createFailedThumbnailInserter =\n (view: EditorView, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n node.attrs.src,\n fileInfo,\n 'failed',\n );\n const errorPlaceholderNode =\n schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, errorPlaceholderNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nfunction createImageNodeAttrs(\n src: string,\n fileInfo: FileInfo,\n state: EditorImageState,\n): ImageNodeAttrs {\n return {\n src: src,\n alt: fileInfo.filename,\n fileInfoId: fileInfo.id,\n state: state,\n };\n}\n\n/**\n * Check if a given ProseMirror node or fragment contains any image nodes.\n * @param node - The ProseMirror node or fragment to check.\n * @returns A boolean indicating whether the node contains any image nodes.\n */\nconst isImageNode = (node: Node | Fragment): boolean => {\n if (node instanceof Node) {\n if (node.type.name === 'image') {\n return true;\n }\n\n let found = false;\n node.content.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n } else if (node instanceof Fragment) {\n let found = false;\n node.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n }\n\n return false;\n};\n\n/**\n * Filter out image nodes from a ProseMirror fragment.\n * @param fragment - The ProseMirror fragment to filter.\n * @returns A new fragment with image nodes removed.\n */\nconst filterImageNodes = (fragment: Fragment): Fragment => {\n const filteredChildren: Node[] = [];\n\n fragment.forEach((child) => {\n if (!isImageNode(child)) {\n if (child.content.size > 0) {\n const filteredContent = filterImageNodes(child.content);\n const newNode = child.copy(filteredContent);\n filteredChildren.push(newNode);\n } else {\n filteredChildren.push(child);\n }\n }\n });\n\n return Fragment.fromArray(filteredChildren);\n};\n\n/**\n * Process a paste event and trigger an imagePasted event if an image file is pasted.\n * If an HTML image element is pasted, this image is filtered out from the slice content.\n *\n * @param view - The ProseMirror editor view.\n * @param event - The paste event.\n * @returns A boolean; True if an image file was pasted to prevent default paste behavior, otherwise false.\n */\nconst processPasteEvent = (\n view: EditorView,\n event: ClipboardEvent,\n slice: Slice,\n): boolean => {\n const clipboardData = event.clipboardData;\n if (!clipboardData) {\n return false;\n }\n\n const isImageFilePasted = handlePastedImages(view, clipboardData);\n\n const filteredSlice = new Slice(\n filterImageNodes(slice.content),\n slice.openStart,\n slice.openEnd,\n );\n\n if (filteredSlice.content.childCount < slice.content.childCount) {\n const { state, dispatch } = view;\n const tr = state.tr.replaceSelection(filteredSlice);\n dispatch(tr);\n\n return true;\n }\n\n return isImageFilePasted;\n};\n\n/**\n * Processes any image files found in the clipboard data and dispatches an imagePasted event.\n *\n * @param view - The ProseMirror editor view\n * @param clipboardData - The clipboard data transfer object containing potential image files\n * @returns True if at least one valid image file was found and processed, false otherwise\n */\nfunction handlePastedImages(\n view: EditorView,\n clipboardData: DataTransfer,\n): boolean {\n let isImageFilePasted = false;\n const files = Array.from(clipboardData.files || []);\n\n for (const file of files) {\n if (isImageFile(file, clipboardData)) {\n isImageFilePasted = true;\n\n const reader = new FileReader();\n reader.onloadend = () => {\n view.dom.dispatchEvent(\n new CustomEvent('imagePasted', {\n detail: imageInserterFactory(\n view,\n reader.result as string,\n createFileInfo(file),\n ),\n }),\n );\n };\n\n reader.readAsDataURL(file);\n }\n }\n\n return isImageFilePasted;\n}\n\n/**\n * Determines if a file is an image that should be processed by the image handler.\n *\n * This function checks both the file's MIME type and the clipboard HTML content.\n * It filters out HTML content from Excel and HTML tables, as they are not relevant for image processing.\n *\n * @param file - The file object to check\n * @param clipboardData - The full clipboard data transfer object to examine for context\n * @returns True if the file is an image that should be processed, false otherwise\n */\nfunction isImageFile(file: File, clipboardData: DataTransfer): boolean {\n if (!isContentTypeImage(file)) {\n return false;\n }\n\n const html = clipboardData?.getData('text/html')?.toLowerCase() ?? '';\n\n return !isHtmlFromExcel(html) && !isHtmlTable(html);\n}\n\nfunction isContentTypeImage(file: File): boolean {\n if (!file?.type) {\n return false;\n }\n\n return file.type.startsWith('image/');\n}\n\nfunction isHtmlFromExcel(html: string): boolean {\n if (!html) {\n return false;\n }\n\n return (\n html.includes('name=generator content=\"microsoft excel\"') ||\n html.includes('xmlns:x=\"urn:schemas-microsoft-com:office:excel\"')\n );\n}\n\nfunction isHtmlTable(html: string): boolean {\n if (!html) {\n return false;\n }\n\n return html.includes('<table');\n}\n"]}
@@ -26454,9 +26454,29 @@ const processPasteEvent = (view, event, slice) => {
26454
26454
  if (!clipboardData) {
26455
26455
  return false;
26456
26456
  }
26457
+ const isImageFilePasted = handlePastedImages(view, clipboardData);
26458
+ const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
26459
+ if (filteredSlice.content.childCount < slice.content.childCount) {
26460
+ const { state, dispatch } = view;
26461
+ const tr = state.tr.replaceSelection(filteredSlice);
26462
+ dispatch(tr);
26463
+ return true;
26464
+ }
26465
+ return isImageFilePasted;
26466
+ };
26467
+ /**
26468
+ * Processes any image files found in the clipboard data and dispatches an imagePasted event.
26469
+ *
26470
+ * @param view - The ProseMirror editor view
26471
+ * @param clipboardData - The clipboard data transfer object containing potential image files
26472
+ * @returns True if at least one valid image file was found and processed, false otherwise
26473
+ */
26474
+ function handlePastedImages(view, clipboardData) {
26475
+ let isImageFilePasted = false;
26457
26476
  const files = Array.from(clipboardData.files || []);
26458
26477
  for (const file of files) {
26459
- if (file.type.startsWith('image/')) {
26478
+ if (isImageFile(file, clipboardData)) {
26479
+ isImageFilePasted = true;
26460
26480
  const reader = new FileReader();
26461
26481
  reader.onloadend = () => {
26462
26482
  view.dom.dispatchEvent(new CustomEvent('imagePasted', {
@@ -26466,15 +26486,45 @@ const processPasteEvent = (view, event, slice) => {
26466
26486
  reader.readAsDataURL(file);
26467
26487
  }
26468
26488
  }
26469
- const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
26470
- if (filteredSlice.content.childCount < slice.content.childCount) {
26471
- const { state, dispatch } = view;
26472
- const tr = state.tr.replaceSelection(filteredSlice);
26473
- dispatch(tr);
26474
- return true;
26489
+ return isImageFilePasted;
26490
+ }
26491
+ /**
26492
+ * Determines if a file is an image that should be processed by the image handler.
26493
+ *
26494
+ * This function checks both the file's MIME type and the clipboard HTML content.
26495
+ * It filters out HTML content from Excel and HTML tables, as they are not relevant for image processing.
26496
+ *
26497
+ * @param file - The file object to check
26498
+ * @param clipboardData - The full clipboard data transfer object to examine for context
26499
+ * @returns True if the file is an image that should be processed, false otherwise
26500
+ */
26501
+ function isImageFile(file, clipboardData) {
26502
+ var _a, _b;
26503
+ if (!isContentTypeImage(file)) {
26504
+ return false;
26475
26505
  }
26476
- return files.length > 0;
26477
- };
26506
+ const html = (_b = (_a = clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.getData('text/html')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
26507
+ return !isHtmlFromExcel(html) && !isHtmlTable(html);
26508
+ }
26509
+ function isContentTypeImage(file) {
26510
+ if (!(file === null || file === void 0 ? void 0 : file.type)) {
26511
+ return false;
26512
+ }
26513
+ return file.type.startsWith('image/');
26514
+ }
26515
+ function isHtmlFromExcel(html) {
26516
+ if (!html) {
26517
+ return false;
26518
+ }
26519
+ return (html.includes('name=generator content="microsoft excel"') ||
26520
+ html.includes('xmlns:x="urn:schemas-microsoft-com:office:excel"'));
26521
+ }
26522
+ function isHtmlTable(html) {
26523
+ if (!html) {
26524
+ return false;
26525
+ }
26526
+ return html.includes('<table');
26527
+ }
26478
26528
 
26479
26529
  const MIN_WIDTH = 10;
26480
26530
  const createImageViewPlugin = (language) => {