@limetech/lime-elements 39.40.0 → 39.42.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.
- package/CHANGELOG.md +20 -0
- package/dist/cjs/{image-resize-FAwzj92q.js → image-resize-BWRGvgkM.js} +42 -2
- package/dist/cjs/index.cjs.js +3 -2
- package/dist/cjs/lime-elements.cjs.js +1 -1
- package/dist/cjs/limel-action-bar_3.cjs.entry.js +1 -1
- package/dist/cjs/limel-ai-avatar.cjs.entry.js +1 -1
- package/dist/cjs/limel-callout.cjs.entry.js +1 -1
- package/dist/cjs/limel-chart.cjs.entry.js +1 -1
- package/dist/cjs/limel-chip-set.cjs.entry.js +1 -1
- package/dist/cjs/limel-chip_2.cjs.entry.js +1 -1
- package/dist/cjs/limel-code-diff.cjs.entry.js +1 -1
- package/dist/cjs/limel-code-editor.cjs.entry.js +1 -1
- package/dist/cjs/limel-collapsible-section.cjs.entry.js +1 -1
- package/dist/cjs/limel-drag-handle.cjs.entry.js +1 -1
- package/dist/cjs/limel-email-viewer.cjs.entry.js +1 -1
- package/dist/cjs/limel-file-viewer.cjs.entry.js +1 -1
- package/dist/cjs/limel-file.cjs.entry.js +84 -18
- package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +1 -1
- package/dist/cjs/limel-list-item.cjs.entry.js +1 -1
- package/dist/cjs/limel-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-profile-picture.cjs.entry.js +2 -2
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +440 -212
- package/dist/cjs/limel-snackbar.cjs.entry.js +1 -1
- package/dist/cjs/limel-table.cjs.entry.js +1 -1
- package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +1 -1
- package/dist/cjs/limel-text-editor.cjs.entry.js +2 -2
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{translations-VEsa81QY.js → translations-Bnw00bkf.js} +8 -0
- package/dist/cjs/{types-Bu6wcdI0.js → types-C7gNcwzE.js} +12 -0
- package/dist/collection/components/file/file.js +119 -18
- package/dist/collection/components/profile-picture/profile-picture.js +1 -1
- package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/inserter.js +101 -30
- package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/node.js +160 -54
- package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/view.js +6 -1
- package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +51 -5
- package/dist/collection/components/text-editor/text-editor.js +33 -3
- package/dist/collection/components/text-editor/text-editor.types.js +10 -1
- package/dist/collection/components/text-editor/utils/content-type-converter.js +30 -1
- package/dist/collection/components/text-editor/utils/html-converter.js +4 -2
- package/dist/collection/components/text-editor/utils/markdown-converter.js +9 -5
- package/dist/collection/translations/da.js +1 -0
- package/dist/collection/translations/de.js +1 -0
- package/dist/collection/translations/en.js +1 -0
- package/dist/collection/translations/fi.js +1 -0
- package/dist/collection/translations/fr.js +1 -0
- package/dist/collection/translations/nl.js +1 -0
- package/dist/collection/translations/no.js +1 -0
- package/dist/collection/translations/sv.js +1 -0
- package/dist/collection/util/image-resize.js +42 -2
- package/dist/esm/{image-resize-DPcww8rE.js → image-resize-65ma7B85.js} +42 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/lime-elements.js +1 -1
- package/dist/esm/limel-action-bar_3.entry.js +1 -1
- package/dist/esm/limel-ai-avatar.entry.js +1 -1
- package/dist/esm/limel-callout.entry.js +1 -1
- package/dist/esm/limel-chart.entry.js +1 -1
- package/dist/esm/limel-chip-set.entry.js +1 -1
- package/dist/esm/limel-chip_2.entry.js +1 -1
- package/dist/esm/limel-code-diff.entry.js +1 -1
- package/dist/esm/limel-code-editor.entry.js +1 -1
- package/dist/esm/limel-collapsible-section.entry.js +1 -1
- package/dist/esm/limel-drag-handle.entry.js +1 -1
- package/dist/esm/limel-email-viewer.entry.js +1 -1
- package/dist/esm/limel-file-viewer.entry.js +1 -1
- package/dist/esm/limel-file.entry.js +84 -18
- package/dist/esm/limel-flatpickr-adapter.entry.js +1 -1
- package/dist/esm/limel-list-item.entry.js +1 -1
- package/dist/esm/limel-picker.entry.js +1 -1
- package/dist/esm/limel-profile-picture.entry.js +2 -2
- package/dist/esm/limel-prosemirror-adapter.entry.js +440 -212
- package/dist/esm/limel-snackbar.entry.js +1 -1
- package/dist/esm/limel-table.entry.js +1 -1
- package/dist/esm/limel-text-editor-link-menu.entry.js +1 -1
- package/dist/esm/limel-text-editor.entry.js +2 -2
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{translations-EPnIW0K5.js → translations-Cb9R_zmF.js} +8 -0
- package/dist/esm/{types-BWYo6dLf.js → types-C_Yu8dOg.js} +12 -1
- package/dist/lime-elements/index.esm.js +1 -1
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-068e7087.entry.js → p-19c81ded.entry.js} +2 -2
- package/dist/lime-elements/p-1cc3b516.entry.js +1 -0
- package/dist/lime-elements/{p-ca0a8a5a.entry.js → p-1e3cdfa1.entry.js} +1 -1
- package/dist/lime-elements/p-1f35fc44.entry.js +1 -0
- package/dist/lime-elements/{p-2531a103.entry.js → p-2284caa7.entry.js} +1 -1
- package/dist/lime-elements/{p-d8cb7d42.entry.js → p-2f223e46.entry.js} +1 -1
- package/dist/lime-elements/{p-a3a2aafe.entry.js → p-4a09fb2e.entry.js} +1 -1
- package/dist/lime-elements/{p-f197036b.entry.js → p-552d0733.entry.js} +1 -1
- package/dist/lime-elements/p-65ma7B85.js +1 -0
- package/dist/lime-elements/{p-fd78537c.entry.js → p-6fbf20c6.entry.js} +1 -1
- package/dist/lime-elements/{p-5571f0de.entry.js → p-758939be.entry.js} +1 -1
- package/dist/lime-elements/{p-19347686.entry.js → p-7f7e2180.entry.js} +1 -1
- package/dist/lime-elements/{p-2a871a95.entry.js → p-89f9bd6c.entry.js} +1 -1
- package/dist/lime-elements/{p-79caf3f6.entry.js → p-9ae7d11b.entry.js} +1 -1
- package/dist/lime-elements/p-C_Yu8dOg.js +1 -0
- package/dist/lime-elements/p-Cb9R_zmF.js +1 -0
- package/dist/lime-elements/{p-6edf13b1.entry.js → p-a089b580.entry.js} +1 -1
- package/dist/lime-elements/{p-04b4e903.entry.js → p-aa080a8f.entry.js} +1 -1
- package/dist/lime-elements/{p-54961e0e.entry.js → p-c1ceade7.entry.js} +1 -1
- package/dist/lime-elements/{p-e08f770e.entry.js → p-c8116a01.entry.js} +1 -1
- package/dist/lime-elements/{p-36ccc68e.entry.js → p-c8982eef.entry.js} +1 -1
- package/dist/lime-elements/{p-d98168a1.entry.js → p-ca0b86ee.entry.js} +1 -1
- package/dist/lime-elements/{p-1525274d.entry.js → p-d7bb4310.entry.js} +1 -1
- package/dist/lime-elements/p-d7f75a6d.entry.js +1 -0
- package/dist/lime-elements/{p-bb201b0d.entry.js → p-e545c9e8.entry.js} +1 -1
- package/dist/types/components/file/file.d.ts +20 -0
- package/dist/types/components/text-editor/prosemirror-adapter/plugins/image/inserter.d.ts +2 -2
- package/dist/types/components/text-editor/prosemirror-adapter/plugins/image/node.d.ts +41 -19
- package/dist/types/components/text-editor/prosemirror-adapter/prosemirror-adapter.d.ts +13 -1
- package/dist/types/components/text-editor/text-editor.d.ts +10 -2
- package/dist/types/components/text-editor/text-editor.types.d.ts +52 -0
- package/dist/types/components/text-editor/utils/content-type-converter.d.ts +12 -0
- package/dist/types/components/text-editor/utils/html-converter.d.ts +3 -1
- package/dist/types/components/text-editor/utils/markdown-converter.d.ts +3 -1
- package/dist/types/components.d.ts +50 -8
- package/dist/types/translations/da.d.ts +1 -0
- package/dist/types/translations/de.d.ts +1 -0
- package/dist/types/translations/en.d.ts +1 -0
- package/dist/types/translations/fi.d.ts +1 -0
- package/dist/types/translations/fr.d.ts +1 -0
- package/dist/types/translations/nl.d.ts +1 -0
- package/dist/types/translations/no.d.ts +1 -0
- package/dist/types/translations/sv.d.ts +1 -0
- package/dist/types/util/image-resize.d.ts +12 -4
- package/package.json +1 -1
- package/dist/lime-elements/p-366419ee.entry.js +0 -1
- package/dist/lime-elements/p-5cf7464f.entry.js +0 -1
- package/dist/lime-elements/p-BWYo6dLf.js +0 -1
- package/dist/lime-elements/p-DPcww8rE.js +0 -1
- package/dist/lime-elements/p-EPnIW0K5.js +0 -1
- package/dist/lime-elements/p-b7d734d2.entry.js +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { r as registerInstance, c as createEvent, h, H as Host, a as getElement } from './index-BGxJfR2f.js';
|
|
2
|
-
import { E as EditorMenuTypes, L as LevelMapping, M as MouseButtons, e as editorMenuTypesArray } from './types-
|
|
2
|
+
import { E as EditorMenuTypes, L as LevelMapping, i as isInlineImageTag, M as MouseButtons, e as editorMenuTypesArray } from './types-C_Yu8dOg.js';
|
|
3
3
|
import { g as getLinkAttributes, m as markdownToHTML, s as sanitizeHTML } from './markdown-parser-Blt6ZFY0.js';
|
|
4
4
|
import { h as cloneDeep } from './cloneDeep-CL7UD3mR.js';
|
|
5
|
+
import { t as translate } from './translations-Cb9R_zmF.js';
|
|
5
6
|
import { c as decodeHTML, e as decodeHTMLStrict } from './index-CbciMfiU.js';
|
|
6
|
-
import { t as translate } from './translations-EPnIW0K5.js';
|
|
7
7
|
import { c as createRandomString } from './random-string-JbKhhoXs.js';
|
|
8
8
|
import { i as isItem } from './is-item-CrvUOVvg.js';
|
|
9
9
|
import { c as createFileInfo } from './files-CZAZotms.js';
|
|
@@ -15829,6 +15829,307 @@ const menuTranslationIDs = {
|
|
|
15829
15829
|
code: 'editor-menu.code',
|
|
15830
15830
|
};
|
|
15831
15831
|
|
|
15832
|
+
const imageCache = new Map();
|
|
15833
|
+
const IMAGE_ID_ATTRIBUTE = 'image-id';
|
|
15834
|
+
/**
|
|
15835
|
+
* The attribute names accepted for an inline-image tag. Backs the sanitizer
|
|
15836
|
+
* whitelist built in `buildContentWhitelist`, so a stored tag keeps these
|
|
15837
|
+
* attributes through parsing. The serializers here enumerate their own output
|
|
15838
|
+
* attributes separately and are not driven by this list.
|
|
15839
|
+
*/
|
|
15840
|
+
const INLINE_IMAGE_ATTRIBUTES = [
|
|
15841
|
+
IMAGE_ID_ATTRIBUTE,
|
|
15842
|
+
'width',
|
|
15843
|
+
'height',
|
|
15844
|
+
'alt',
|
|
15845
|
+
];
|
|
15846
|
+
/**
|
|
15847
|
+
* Builds the `image` NodeSpec, wired for inline-image parsing/serialization
|
|
15848
|
+
* when `inlineImages` is configured.
|
|
15849
|
+
* @param language
|
|
15850
|
+
* @param inlineImages
|
|
15851
|
+
*/
|
|
15852
|
+
function getImageNode(language, inlineImages) {
|
|
15853
|
+
return { image: createImageNodeSpec(language, inlineImages) };
|
|
15854
|
+
}
|
|
15855
|
+
/**
|
|
15856
|
+
* Builds the markdown serializer for the `image` node, emitting the inline-image
|
|
15857
|
+
* tag when `inlineImages` is configured and a `<img>` otherwise.
|
|
15858
|
+
* @param language
|
|
15859
|
+
* @param inlineImages
|
|
15860
|
+
*/
|
|
15861
|
+
function getImageNodeMarkdownSerializer(language, inlineImages) {
|
|
15862
|
+
return { image: createImageNodeMarkdownSerializer(language, inlineImages) };
|
|
15863
|
+
}
|
|
15864
|
+
/**
|
|
15865
|
+
* Applies the node's stored dimensions to a rendered `<img>` element.
|
|
15866
|
+
* @param img
|
|
15867
|
+
* @param node
|
|
15868
|
+
*/
|
|
15869
|
+
function applyImageStyles(img, node) {
|
|
15870
|
+
img.style.height = node.attrs.height;
|
|
15871
|
+
img.style.width = node.attrs.width;
|
|
15872
|
+
img.style.minHeight = node.attrs.minHeight;
|
|
15873
|
+
img.style.minWidth = node.attrs.minWidth;
|
|
15874
|
+
img.style.maxWidth = node.attrs.maxWidth;
|
|
15875
|
+
}
|
|
15876
|
+
/**
|
|
15877
|
+
* Recursively checks if a ProseMirror node or any of its descendants is an
|
|
15878
|
+
* image node.
|
|
15879
|
+
* @param node
|
|
15880
|
+
*/
|
|
15881
|
+
function hasImageNode(node) {
|
|
15882
|
+
if (node.type.name === 'image') {
|
|
15883
|
+
return true;
|
|
15884
|
+
}
|
|
15885
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
15886
|
+
const childNode = node.child(i);
|
|
15887
|
+
if (hasImageNode(childNode)) {
|
|
15888
|
+
return true;
|
|
15889
|
+
}
|
|
15890
|
+
}
|
|
15891
|
+
return false;
|
|
15892
|
+
}
|
|
15893
|
+
function createImageNodeMarkdownSerializer(language, inlineImages) {
|
|
15894
|
+
return (markdownSerializerState, node) => {
|
|
15895
|
+
const state = node.attrs.state;
|
|
15896
|
+
if (!isEditorImageState(state)) {
|
|
15897
|
+
return;
|
|
15898
|
+
}
|
|
15899
|
+
if (state === 'success') {
|
|
15900
|
+
const attrs = node.attrs;
|
|
15901
|
+
const tag = inlineImages && isInlineImageTag(inlineImages)
|
|
15902
|
+
? inlineImages
|
|
15903
|
+
: undefined;
|
|
15904
|
+
const imageHTML = tag && attrs.imageId
|
|
15905
|
+
? getInlineImageHTML(attrs, tag.tagName)
|
|
15906
|
+
: getImageHTML(attrs);
|
|
15907
|
+
markdownSerializerState.write(imageHTML);
|
|
15908
|
+
return;
|
|
15909
|
+
}
|
|
15910
|
+
const statusHTML = getStatusHTML(state, node.attrs.alt, language);
|
|
15911
|
+
markdownSerializerState.write(statusHTML);
|
|
15912
|
+
};
|
|
15913
|
+
}
|
|
15914
|
+
function escapeAttributeValue(value) {
|
|
15915
|
+
return value
|
|
15916
|
+
.replaceAll('&', '&')
|
|
15917
|
+
.replaceAll('"', '"')
|
|
15918
|
+
.replaceAll('<', '<')
|
|
15919
|
+
.replaceAll('>', '>');
|
|
15920
|
+
}
|
|
15921
|
+
// Dimensions are emitted as CSS-unit strings (e.g. `width="300px"`), matching
|
|
15922
|
+
// what `applyImageStyles` reads back when the tag is re-parsed inside the
|
|
15923
|
+
// editor. The external renderer of this microformat (the `limebb-inline-image`
|
|
15924
|
+
// building block) must therefore treat `width`/`height` as CSS lengths, not as
|
|
15925
|
+
// bare HTML pixel counts.
|
|
15926
|
+
function getInlineImageHTML(attrs, tag) {
|
|
15927
|
+
var _a;
|
|
15928
|
+
const attributes = [
|
|
15929
|
+
`${IMAGE_ID_ATTRIBUTE}="${escapeAttributeValue((_a = attrs.imageId) !== null && _a !== void 0 ? _a : '')}"`,
|
|
15930
|
+
attrs.width ? `width="${escapeAttributeValue(attrs.width)}"` : '',
|
|
15931
|
+
attrs.height ? `height="${escapeAttributeValue(attrs.height)}"` : '',
|
|
15932
|
+
attrs.alt ? `alt="${escapeAttributeValue(attrs.alt)}"` : '',
|
|
15933
|
+
].filter(Boolean);
|
|
15934
|
+
return `<${tag} ${attributes.join(' ')}></${tag}>`;
|
|
15935
|
+
}
|
|
15936
|
+
function buildInlineImageDOMAttrs(attrs) {
|
|
15937
|
+
var _a;
|
|
15938
|
+
const result = {
|
|
15939
|
+
[IMAGE_ID_ATTRIBUTE]: (_a = attrs.imageId) !== null && _a !== void 0 ? _a : '',
|
|
15940
|
+
};
|
|
15941
|
+
if (attrs.alt) {
|
|
15942
|
+
result.alt = attrs.alt;
|
|
15943
|
+
}
|
|
15944
|
+
if (attrs.width) {
|
|
15945
|
+
result.width = attrs.width;
|
|
15946
|
+
}
|
|
15947
|
+
if (attrs.height) {
|
|
15948
|
+
result.height = attrs.height;
|
|
15949
|
+
}
|
|
15950
|
+
return result;
|
|
15951
|
+
}
|
|
15952
|
+
function getStatusHTML(state, alt, language) {
|
|
15953
|
+
const key = state === 'failed' ? 'failed' : 'loading';
|
|
15954
|
+
const text = translate.get(`editor-image-view.${key}`, language, {
|
|
15955
|
+
filename: alt || 'file',
|
|
15956
|
+
});
|
|
15957
|
+
return `<span>${text}</span>`;
|
|
15958
|
+
}
|
|
15959
|
+
function getImageHTML(attrs) {
|
|
15960
|
+
const dimensions = [
|
|
15961
|
+
['height', attrs.height],
|
|
15962
|
+
['width', attrs.width],
|
|
15963
|
+
['min-height', attrs.minHeight],
|
|
15964
|
+
['min-width', attrs.minWidth],
|
|
15965
|
+
['max-width', attrs.maxWidth],
|
|
15966
|
+
];
|
|
15967
|
+
const style = dimensions
|
|
15968
|
+
.filter(([, value]) => value)
|
|
15969
|
+
.map(([property, value]) => `${property}: ${value};`)
|
|
15970
|
+
.join('');
|
|
15971
|
+
const styleAttribute = style
|
|
15972
|
+
? ` style="${escapeAttributeValue(style)}"`
|
|
15973
|
+
: '';
|
|
15974
|
+
return `<img src="${escapeAttributeValue(attrs.src)}" alt="${escapeAttributeValue(attrs.alt)}"${styleAttribute} />`;
|
|
15975
|
+
}
|
|
15976
|
+
function createImageNodeSpec(language, inlineImages) {
|
|
15977
|
+
return {
|
|
15978
|
+
group: 'inline',
|
|
15979
|
+
inline: true,
|
|
15980
|
+
attrs: {
|
|
15981
|
+
src: { default: '' },
|
|
15982
|
+
alt: { default: '' },
|
|
15983
|
+
fileInfoId: { default: '' },
|
|
15984
|
+
imageId: { default: '' },
|
|
15985
|
+
height: { default: '' },
|
|
15986
|
+
width: { default: '' },
|
|
15987
|
+
minHeight: { default: '' },
|
|
15988
|
+
minWidth: { default: '' },
|
|
15989
|
+
maxWidth: { default: '100%' },
|
|
15990
|
+
state: { default: 'success' },
|
|
15991
|
+
},
|
|
15992
|
+
toDOM: createImageToDOM(language, inlineImages),
|
|
15993
|
+
parseDOM: createImageParseDOM(inlineImages),
|
|
15994
|
+
};
|
|
15995
|
+
}
|
|
15996
|
+
function createImageToDOM(language, inlineImages) {
|
|
15997
|
+
return (node) => {
|
|
15998
|
+
if (!isEditorImageState(node.attrs.state)) {
|
|
15999
|
+
return;
|
|
16000
|
+
}
|
|
16001
|
+
if (node.attrs.state !== 'success') {
|
|
16002
|
+
return createStatusSpanForState(node.attrs.state, node, language);
|
|
16003
|
+
}
|
|
16004
|
+
// Emit the inline-image tag for HTML serialization and clipboard;
|
|
16005
|
+
// on-screen rendering comes from the NodeView, not this.
|
|
16006
|
+
const tag = inlineImages && isInlineImageTag(inlineImages)
|
|
16007
|
+
? inlineImages
|
|
16008
|
+
: undefined;
|
|
16009
|
+
if (tag && node.attrs.imageId) {
|
|
16010
|
+
return [
|
|
16011
|
+
tag.tagName,
|
|
16012
|
+
buildInlineImageDOMAttrs(node.attrs),
|
|
16013
|
+
];
|
|
16014
|
+
}
|
|
16015
|
+
return getOrCreateImageElement(node.attrs.fileInfoId, node);
|
|
16016
|
+
};
|
|
16017
|
+
}
|
|
16018
|
+
function createImageParseDOM(inlineImages) {
|
|
16019
|
+
const rules = [];
|
|
16020
|
+
const tag = inlineImages && isInlineImageTag(inlineImages)
|
|
16021
|
+
? inlineImages
|
|
16022
|
+
: undefined;
|
|
16023
|
+
if (tag) {
|
|
16024
|
+
rules.push({
|
|
16025
|
+
tag: tag.tagName,
|
|
16026
|
+
getAttrs: (dom) => {
|
|
16027
|
+
const imageId = dom.getAttribute(IMAGE_ID_ATTRIBUTE);
|
|
16028
|
+
// Skip malformed tags without an id; parsing them would
|
|
16029
|
+
// produce a broken image from an empty getUrl('').
|
|
16030
|
+
if (!imageId) {
|
|
16031
|
+
return false;
|
|
16032
|
+
}
|
|
16033
|
+
return {
|
|
16034
|
+
src: tag.getUrl(imageId),
|
|
16035
|
+
// Preserve an absent alt as empty so an id-only tag
|
|
16036
|
+
// serializes back to the same markup instead of gaining
|
|
16037
|
+
// a synthetic alt, keeping the round-trip stable.
|
|
16038
|
+
alt: dom.getAttribute('alt') || '',
|
|
16039
|
+
imageId: imageId,
|
|
16040
|
+
width: dom.getAttribute('width') || '',
|
|
16041
|
+
height: dom.getAttribute('height') || '',
|
|
16042
|
+
maxWidth: '100%',
|
|
16043
|
+
state: 'success',
|
|
16044
|
+
fileInfoId: crypto.randomUUID(),
|
|
16045
|
+
};
|
|
16046
|
+
},
|
|
16047
|
+
});
|
|
16048
|
+
}
|
|
16049
|
+
rules.push({
|
|
16050
|
+
tag: 'img',
|
|
16051
|
+
getAttrs: (dom) => {
|
|
16052
|
+
return {
|
|
16053
|
+
src: dom.getAttribute('src') || '',
|
|
16054
|
+
alt: dom.getAttribute('alt') || 'file',
|
|
16055
|
+
width: dom.style.width || '',
|
|
16056
|
+
height: dom.style.height || '',
|
|
16057
|
+
maxWidth: '100%',
|
|
16058
|
+
state: 'success',
|
|
16059
|
+
fileInfoId: crypto.randomUUID(),
|
|
16060
|
+
};
|
|
16061
|
+
},
|
|
16062
|
+
});
|
|
16063
|
+
return rules;
|
|
16064
|
+
}
|
|
16065
|
+
function isEditorImageState(state) {
|
|
16066
|
+
return state === 'loading' || state === 'failed' || state === 'success';
|
|
16067
|
+
}
|
|
16068
|
+
function getOrCreateImageElement(fileInfoId, node) {
|
|
16069
|
+
let img = imageCache.get(fileInfoId);
|
|
16070
|
+
if (img) {
|
|
16071
|
+
updateImageElement(img, node);
|
|
16072
|
+
}
|
|
16073
|
+
else {
|
|
16074
|
+
img = createImageElement(node);
|
|
16075
|
+
imageCache.set(fileInfoId, img);
|
|
16076
|
+
}
|
|
16077
|
+
return img;
|
|
16078
|
+
}
|
|
16079
|
+
function createStatusSpanForState(state, node, language) {
|
|
16080
|
+
const statusKey = state === 'failed' ? 'failed' : 'loading';
|
|
16081
|
+
return createStatusSpan(statusKey, node, language);
|
|
16082
|
+
}
|
|
16083
|
+
function createStatusSpan(key, node, language) {
|
|
16084
|
+
const text = translate.get(`editor-image-view.${key}`, language, {
|
|
16085
|
+
filename: node.attrs.alt || 'file',
|
|
16086
|
+
});
|
|
16087
|
+
const span = document.createElement('span');
|
|
16088
|
+
span.textContent = text;
|
|
16089
|
+
return span;
|
|
16090
|
+
}
|
|
16091
|
+
function updateImageElement(img, node) {
|
|
16092
|
+
img.alt = node.attrs.alt;
|
|
16093
|
+
applyImageStyles(img, node);
|
|
16094
|
+
return img;
|
|
16095
|
+
}
|
|
16096
|
+
function createImageElement(node) {
|
|
16097
|
+
const img = document.createElement('img');
|
|
16098
|
+
img.src = node.attrs.src;
|
|
16099
|
+
img.alt = node.attrs.alt;
|
|
16100
|
+
applyImageStyles(img, node);
|
|
16101
|
+
return img;
|
|
16102
|
+
}
|
|
16103
|
+
|
|
16104
|
+
/**
|
|
16105
|
+
* Build the sanitization whitelist for editor content: the consumer's custom
|
|
16106
|
+
* elements, plus (when inline images are configured) the inline-image tag so it
|
|
16107
|
+
* survives parsing into an image node. Shared by all content converters.
|
|
16108
|
+
*
|
|
16109
|
+
* @param customNodes
|
|
16110
|
+
* @param inlineImages
|
|
16111
|
+
* @private
|
|
16112
|
+
*/
|
|
16113
|
+
function buildContentWhitelist(customNodes, inlineImages) {
|
|
16114
|
+
if (!inlineImages || !isInlineImageTag(inlineImages)) {
|
|
16115
|
+
return customNodes;
|
|
16116
|
+
}
|
|
16117
|
+
// Guard the boundary independently of the upstream `validateInlineImages`
|
|
16118
|
+
// check: the tag name becomes a sanitizer whitelist key, so a built-in like
|
|
16119
|
+
// `img` (no hyphen) would hijack its rules and strip `src` from every real
|
|
16120
|
+
// image. Custom element names always contain a hyphen; built-ins never do.
|
|
16121
|
+
if (!inlineImages.tagName.includes('-')) {
|
|
16122
|
+
return customNodes;
|
|
16123
|
+
}
|
|
16124
|
+
return [
|
|
16125
|
+
...customNodes,
|
|
16126
|
+
{
|
|
16127
|
+
tagName: inlineImages.tagName,
|
|
16128
|
+
attributes: [...INLINE_IMAGE_ATTRIBUTES],
|
|
16129
|
+
},
|
|
16130
|
+
];
|
|
16131
|
+
}
|
|
16132
|
+
|
|
15832
16133
|
/* eslint-disable no-bitwise */
|
|
15833
16134
|
|
|
15834
16135
|
const decodeCache = {};
|
|
@@ -24671,173 +24972,6 @@ class MarkdownSerializerState {
|
|
|
24671
24972
|
}
|
|
24672
24973
|
}
|
|
24673
24974
|
|
|
24674
|
-
const imageCache = new Map();
|
|
24675
|
-
/**
|
|
24676
|
-
*
|
|
24677
|
-
* @param language
|
|
24678
|
-
*/
|
|
24679
|
-
function getImageNode(language) {
|
|
24680
|
-
return { image: createImageNodeSpec(language) };
|
|
24681
|
-
}
|
|
24682
|
-
/**
|
|
24683
|
-
*
|
|
24684
|
-
* @param language
|
|
24685
|
-
*/
|
|
24686
|
-
function getImageNodeMarkdownSerializer(language) {
|
|
24687
|
-
return { image: createImageNodeMarkdownSerializer(language) };
|
|
24688
|
-
}
|
|
24689
|
-
/**
|
|
24690
|
-
*
|
|
24691
|
-
* @param img
|
|
24692
|
-
* @param node
|
|
24693
|
-
*/
|
|
24694
|
-
function applyImageStyles(img, node) {
|
|
24695
|
-
img.style.height = node.attrs.height;
|
|
24696
|
-
img.style.width = node.attrs.width;
|
|
24697
|
-
img.style.minHeight = node.attrs.minHeight;
|
|
24698
|
-
img.style.minWidth = node.attrs.minWidth;
|
|
24699
|
-
img.style.maxWidth = node.attrs.maxWidth;
|
|
24700
|
-
}
|
|
24701
|
-
/**
|
|
24702
|
-
* Recursively checks if a ProseMirror node or
|
|
24703
|
-
* any of its child nodes is an image node.
|
|
24704
|
-
* @param node
|
|
24705
|
-
*/
|
|
24706
|
-
function hasImageNode(node) {
|
|
24707
|
-
if (node.type.name === 'image') {
|
|
24708
|
-
return true;
|
|
24709
|
-
}
|
|
24710
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
24711
|
-
const childNode = node.child(i);
|
|
24712
|
-
if (hasImageNode(childNode)) {
|
|
24713
|
-
return true;
|
|
24714
|
-
}
|
|
24715
|
-
}
|
|
24716
|
-
return false;
|
|
24717
|
-
}
|
|
24718
|
-
function createImageNodeMarkdownSerializer(language) {
|
|
24719
|
-
return (markdownSerializerState, node) => {
|
|
24720
|
-
const state = node.attrs.state;
|
|
24721
|
-
if (!isEditorImageState(state)) {
|
|
24722
|
-
return;
|
|
24723
|
-
}
|
|
24724
|
-
if (state === 'success') {
|
|
24725
|
-
const imageHTML = getImageHTML(node.attrs);
|
|
24726
|
-
markdownSerializerState.write(imageHTML);
|
|
24727
|
-
return;
|
|
24728
|
-
}
|
|
24729
|
-
const statusHTML = getStatusHTML(state, node.attrs.alt, language);
|
|
24730
|
-
markdownSerializerState.write(statusHTML);
|
|
24731
|
-
};
|
|
24732
|
-
}
|
|
24733
|
-
function getStatusHTML(state, alt, language) {
|
|
24734
|
-
const key = state === 'failed' ? 'failed' : 'loading';
|
|
24735
|
-
const text = translate.get(`editor-image-view.${key}`, language, {
|
|
24736
|
-
filename: alt || 'file',
|
|
24737
|
-
});
|
|
24738
|
-
return `<span>${text}</span>`;
|
|
24739
|
-
}
|
|
24740
|
-
function getImageHTML(attrs) {
|
|
24741
|
-
const style = [];
|
|
24742
|
-
if (attrs.height) {
|
|
24743
|
-
style.push(`height: ${attrs.height};`);
|
|
24744
|
-
}
|
|
24745
|
-
if (attrs.width) {
|
|
24746
|
-
style.push(`width: ${attrs.width};`);
|
|
24747
|
-
}
|
|
24748
|
-
if (attrs.minHeight) {
|
|
24749
|
-
style.push(`min-height: ${attrs.minHeight};`);
|
|
24750
|
-
}
|
|
24751
|
-
if (attrs.minWidth) {
|
|
24752
|
-
style.push(`min-width: ${attrs.minWidth};`);
|
|
24753
|
-
}
|
|
24754
|
-
if (attrs.maxWidth) {
|
|
24755
|
-
style.push(`max-width: ${attrs.maxWidth};`);
|
|
24756
|
-
}
|
|
24757
|
-
const styleAttribute = style.length > 0 ? ` style="${style.join('')}"` : '';
|
|
24758
|
-
return `<img src="${attrs.src}" alt="${attrs.alt}"${styleAttribute} />`;
|
|
24759
|
-
}
|
|
24760
|
-
function createImageNodeSpec(language) {
|
|
24761
|
-
return {
|
|
24762
|
-
group: 'inline',
|
|
24763
|
-
inline: true,
|
|
24764
|
-
attrs: {
|
|
24765
|
-
src: { default: '' },
|
|
24766
|
-
alt: { default: '' },
|
|
24767
|
-
fileInfoId: { default: '' },
|
|
24768
|
-
height: { default: '' },
|
|
24769
|
-
width: { default: '' },
|
|
24770
|
-
minHeight: { default: '' },
|
|
24771
|
-
minWidth: { default: '' },
|
|
24772
|
-
maxWidth: { default: '100%' },
|
|
24773
|
-
state: { default: 'success' },
|
|
24774
|
-
},
|
|
24775
|
-
toDOM: (node) => {
|
|
24776
|
-
if (!isEditorImageState(node.attrs.state)) {
|
|
24777
|
-
return;
|
|
24778
|
-
}
|
|
24779
|
-
if (node.attrs.state === 'success') {
|
|
24780
|
-
return getOrCreateImageElement(node.attrs.fileInfoId, node);
|
|
24781
|
-
}
|
|
24782
|
-
return createStatusSpanForState(node.attrs.state, node, language);
|
|
24783
|
-
},
|
|
24784
|
-
parseDOM: [
|
|
24785
|
-
{
|
|
24786
|
-
tag: 'img',
|
|
24787
|
-
getAttrs: (dom) => {
|
|
24788
|
-
return {
|
|
24789
|
-
src: dom.getAttribute('src') || '',
|
|
24790
|
-
alt: dom.getAttribute('alt') || 'file',
|
|
24791
|
-
width: dom.style.width || '',
|
|
24792
|
-
height: dom.style.height || '',
|
|
24793
|
-
maxWidth: '100%',
|
|
24794
|
-
state: 'success',
|
|
24795
|
-
fileInfoId: crypto.randomUUID(),
|
|
24796
|
-
};
|
|
24797
|
-
},
|
|
24798
|
-
},
|
|
24799
|
-
],
|
|
24800
|
-
};
|
|
24801
|
-
}
|
|
24802
|
-
function isEditorImageState(state) {
|
|
24803
|
-
return state === 'loading' || state === 'failed' || state === 'success';
|
|
24804
|
-
}
|
|
24805
|
-
function getOrCreateImageElement(fileInfoId, node) {
|
|
24806
|
-
let img = imageCache.get(fileInfoId);
|
|
24807
|
-
if (img) {
|
|
24808
|
-
updateImageElement(img, node);
|
|
24809
|
-
}
|
|
24810
|
-
else {
|
|
24811
|
-
img = createImageElement(node);
|
|
24812
|
-
imageCache.set(fileInfoId, img);
|
|
24813
|
-
}
|
|
24814
|
-
return img;
|
|
24815
|
-
}
|
|
24816
|
-
function createStatusSpanForState(state, node, language) {
|
|
24817
|
-
const statusKey = state === 'failed' ? 'failed' : 'loading';
|
|
24818
|
-
return createStatusSpan(statusKey, node, language);
|
|
24819
|
-
}
|
|
24820
|
-
function createStatusSpan(key, node, language) {
|
|
24821
|
-
const text = translate.get(`editor-image-view.${key}`, language, {
|
|
24822
|
-
filename: node.attrs.alt || 'file',
|
|
24823
|
-
});
|
|
24824
|
-
const span = document.createElement('span');
|
|
24825
|
-
span.textContent = text;
|
|
24826
|
-
return span;
|
|
24827
|
-
}
|
|
24828
|
-
function updateImageElement(img, node) {
|
|
24829
|
-
img.alt = node.attrs.alt;
|
|
24830
|
-
applyImageStyles(img, node);
|
|
24831
|
-
return img;
|
|
24832
|
-
}
|
|
24833
|
-
function createImageElement(node) {
|
|
24834
|
-
const img = document.createElement('img');
|
|
24835
|
-
img.src = node.attrs.src;
|
|
24836
|
-
img.alt = node.attrs.alt;
|
|
24837
|
-
applyImageStyles(img, node);
|
|
24838
|
-
return img;
|
|
24839
|
-
}
|
|
24840
|
-
|
|
24841
24975
|
/**
|
|
24842
24976
|
* Recursively checks if a ProseMirror node or
|
|
24843
24977
|
* any of its child nodes is a registered custom element node.
|
|
@@ -24873,12 +25007,12 @@ const createMarkdownSerializerFunction = (config) => {
|
|
|
24873
25007
|
state.write(tagClose);
|
|
24874
25008
|
};
|
|
24875
25009
|
};
|
|
24876
|
-
const buildMarkdownSerializer = (plugins, language) => {
|
|
25010
|
+
const buildMarkdownSerializer = (plugins, language, inlineImages) => {
|
|
24877
25011
|
const customNodes = {};
|
|
24878
25012
|
for (const plugin of plugins) {
|
|
24879
25013
|
customNodes[plugin.tagName] = createMarkdownSerializerFunction(plugin);
|
|
24880
25014
|
}
|
|
24881
|
-
const nodes = Object.assign(Object.assign(Object.assign({}, defaultMarkdownSerializer.nodes), getImageNodeMarkdownSerializer(language)), customNodes);
|
|
25015
|
+
const nodes = Object.assign(Object.assign(Object.assign({}, defaultMarkdownSerializer.nodes), getImageNodeMarkdownSerializer(language, inlineImages)), customNodes);
|
|
24882
25016
|
const marks = Object.assign(Object.assign({}, defaultMarkdownSerializer.marks), { strikethrough: {
|
|
24883
25017
|
open: '~~',
|
|
24884
25018
|
close: '~~',
|
|
@@ -24891,9 +25025,11 @@ const buildMarkdownSerializer = (plugins, language) => {
|
|
|
24891
25025
|
* @private
|
|
24892
25026
|
*/
|
|
24893
25027
|
class MarkdownConverter {
|
|
24894
|
-
constructor(plugins, language) {
|
|
25028
|
+
constructor(plugins, language, inlineImages) {
|
|
24895
25029
|
this.parseAsHTML = (text) => {
|
|
24896
|
-
return markdownToHTML(text, {
|
|
25030
|
+
return markdownToHTML(text, {
|
|
25031
|
+
whitelist: buildContentWhitelist(this.customNodes, this.inlineImages),
|
|
25032
|
+
});
|
|
24897
25033
|
};
|
|
24898
25034
|
this.serialize = (view) => {
|
|
24899
25035
|
if (view.state.doc.textContent.trim() === '' &&
|
|
@@ -24903,8 +25039,9 @@ class MarkdownConverter {
|
|
|
24903
25039
|
}
|
|
24904
25040
|
return this.markdownSerializer.serialize(view.state.doc);
|
|
24905
25041
|
};
|
|
24906
|
-
this.markdownSerializer = buildMarkdownSerializer(plugins, language);
|
|
25042
|
+
this.markdownSerializer = buildMarkdownSerializer(plugins, language, inlineImages);
|
|
24907
25043
|
this.customNodes = plugins;
|
|
25044
|
+
this.inlineImages = inlineImages;
|
|
24908
25045
|
}
|
|
24909
25046
|
}
|
|
24910
25047
|
|
|
@@ -24912,9 +25049,9 @@ class MarkdownConverter {
|
|
|
24912
25049
|
* @private
|
|
24913
25050
|
*/
|
|
24914
25051
|
class HTMLConverter {
|
|
24915
|
-
constructor(plugins) {
|
|
25052
|
+
constructor(plugins, inlineImages) {
|
|
24916
25053
|
this.parseAsHTML = (text) => {
|
|
24917
|
-
return sanitizeHTML(text, this.customNodes);
|
|
25054
|
+
return sanitizeHTML(text, buildContentWhitelist(this.customNodes, this.inlineImages));
|
|
24918
25055
|
};
|
|
24919
25056
|
this.serialize = (view) => {
|
|
24920
25057
|
if (view.state.doc.textContent.trim() === '' &&
|
|
@@ -24927,6 +25064,7 @@ class HTMLConverter {
|
|
|
24927
25064
|
return div.innerHTML;
|
|
24928
25065
|
};
|
|
24929
25066
|
this.customNodes = plugins;
|
|
25067
|
+
this.inlineImages = inlineImages;
|
|
24930
25068
|
}
|
|
24931
25069
|
}
|
|
24932
25070
|
|
|
@@ -25327,12 +25465,12 @@ const linkMarkSpec = {
|
|
|
25327
25465
|
};
|
|
25328
25466
|
|
|
25329
25467
|
const pluginKey = new PluginKey('imageInserterPlugin');
|
|
25330
|
-
const createImageInserterPlugin = (imagePastedCallback) => {
|
|
25468
|
+
const createImageInserterPlugin = (imagePastedCallback, inlineImages) => {
|
|
25331
25469
|
return new Plugin({
|
|
25332
25470
|
key: pluginKey,
|
|
25333
25471
|
props: {
|
|
25334
25472
|
handlePaste: (view, event, slice) => {
|
|
25335
|
-
return processPasteEvent(view, event, slice);
|
|
25473
|
+
return processPasteEvent(view, event, slice, inlineImages);
|
|
25336
25474
|
},
|
|
25337
25475
|
handleDOMEvents: {
|
|
25338
25476
|
imagePasted: (_, event) => {
|
|
@@ -25342,6 +25480,83 @@ const createImageInserterPlugin = (imagePastedCallback) => {
|
|
|
25342
25480
|
},
|
|
25343
25481
|
});
|
|
25344
25482
|
};
|
|
25483
|
+
/**
|
|
25484
|
+
* Runs the inline-image upload lifecycle: show a thumbnail, run the upload,
|
|
25485
|
+
* then replace it with the resizable image (carrying the file id) or a failed
|
|
25486
|
+
* state.
|
|
25487
|
+
* @param view
|
|
25488
|
+
* @param file
|
|
25489
|
+
* @param base64Data
|
|
25490
|
+
* @param fileInfo
|
|
25491
|
+
* @param inlineImages
|
|
25492
|
+
*/
|
|
25493
|
+
const runInlineImageUpload = async (view, file, base64Data, fileInfo, inlineImages) => {
|
|
25494
|
+
const upload = inlineImages.upload;
|
|
25495
|
+
if (!upload) {
|
|
25496
|
+
return;
|
|
25497
|
+
}
|
|
25498
|
+
const inserter = imageInserterFactory(view, base64Data, fileInfo);
|
|
25499
|
+
inserter.insertThumbnail();
|
|
25500
|
+
try {
|
|
25501
|
+
const uploadResult = await upload(file);
|
|
25502
|
+
replaceThumbnailWithInlineImage(view, fileInfo, uploadResult, inlineImages);
|
|
25503
|
+
}
|
|
25504
|
+
catch (error) {
|
|
25505
|
+
console.error('Inline image upload failed', fileInfo.filename, error);
|
|
25506
|
+
inserter.insertFailedThumbnail();
|
|
25507
|
+
}
|
|
25508
|
+
};
|
|
25509
|
+
/**
|
|
25510
|
+
* Walks the document for the image node correlated with `fileInfoId`, replaces
|
|
25511
|
+
* it with a freshly built node, and dispatches the change. The transaction is
|
|
25512
|
+
* only dispatched when a match is found.
|
|
25513
|
+
* @param view
|
|
25514
|
+
* @param fileInfoId
|
|
25515
|
+
* @param buildNode - builds the replacement node from the matched node.
|
|
25516
|
+
* @returns whether a matching node was found and replaced.
|
|
25517
|
+
*/
|
|
25518
|
+
const replaceImageNodeByFileInfoId = (view, fileInfoId, buildNode) => {
|
|
25519
|
+
const { state, dispatch } = view;
|
|
25520
|
+
const tr = state.tr;
|
|
25521
|
+
let found = false;
|
|
25522
|
+
state.doc.descendants((node, pos) => {
|
|
25523
|
+
if (found) {
|
|
25524
|
+
return false;
|
|
25525
|
+
}
|
|
25526
|
+
if (node.attrs.fileInfoId === fileInfoId) {
|
|
25527
|
+
tr.replaceWith(pos, pos + node.nodeSize, buildNode(node));
|
|
25528
|
+
found = true;
|
|
25529
|
+
return false;
|
|
25530
|
+
}
|
|
25531
|
+
});
|
|
25532
|
+
if (found) {
|
|
25533
|
+
dispatch(tr);
|
|
25534
|
+
}
|
|
25535
|
+
return found;
|
|
25536
|
+
};
|
|
25537
|
+
const replaceThumbnailWithInlineImage = (view, fileInfo, uploadResult, inlineImages) => {
|
|
25538
|
+
const { schema } = view.state;
|
|
25539
|
+
// Tag shape: the upload result is a stored id resolved to a src and
|
|
25540
|
+
// persisted as the id microformat. Src shape: the result is the src itself.
|
|
25541
|
+
const tag = isInlineImageTag(inlineImages) ? inlineImages : undefined;
|
|
25542
|
+
const replaced = replaceImageNodeByFileInfoId(view, fileInfo.id, () => {
|
|
25543
|
+
var _a;
|
|
25544
|
+
return schema.nodes.image.create({
|
|
25545
|
+
src: tag ? tag.getUrl(uploadResult) : uploadResult,
|
|
25546
|
+
alt: (_a = fileInfo.filename) !== null && _a !== void 0 ? _a : 'file',
|
|
25547
|
+
imageId: tag ? uploadResult : '',
|
|
25548
|
+
fileInfoId: fileInfo.id,
|
|
25549
|
+
state: 'success',
|
|
25550
|
+
maxWidth: '100%',
|
|
25551
|
+
});
|
|
25552
|
+
});
|
|
25553
|
+
if (!replaced) {
|
|
25554
|
+
// The thumbnail was removed (undo/delete) while the upload was in
|
|
25555
|
+
// flight, so the successfully stored file now has nowhere to land and
|
|
25556
|
+
// is orphaned in the backend.
|
|
25557
|
+
console.warn('Inline image uploaded but its thumbnail is gone; the stored file is now orphaned', { uploadResult, filename: fileInfo.filename });
|
|
25558
|
+
}
|
|
25559
|
+
};
|
|
25345
25560
|
const imageInserterFactory = (view, base64Data, fileInfo) => {
|
|
25346
25561
|
return {
|
|
25347
25562
|
fileInfo: fileInfo,
|
|
@@ -25359,32 +25574,12 @@ const createThumbnailInserter = (view, base64Data, fileInfo) => () => {
|
|
|
25359
25574
|
dispatch(transaction);
|
|
25360
25575
|
};
|
|
25361
25576
|
const createImageInserter = (view, fileInfo) => (src) => {
|
|
25362
|
-
const {
|
|
25363
|
-
|
|
25364
|
-
const tr = state.tr;
|
|
25365
|
-
state.doc.descendants((node, pos) => {
|
|
25366
|
-
if (node.attrs.fileInfoId === fileInfo.id) {
|
|
25367
|
-
const imageNodeAttrs = createImageNodeAttrs(src !== null && src !== void 0 ? src : node.attrs.src, fileInfo, 'success');
|
|
25368
|
-
const imageNode = schema.nodes.image.create(imageNodeAttrs);
|
|
25369
|
-
tr.replaceWith(pos, pos + node.nodeSize, imageNode);
|
|
25370
|
-
return false;
|
|
25371
|
-
}
|
|
25372
|
-
});
|
|
25373
|
-
dispatch(tr);
|
|
25577
|
+
const { schema } = view.state;
|
|
25578
|
+
replaceImageNodeByFileInfoId(view, fileInfo.id, (matched) => schema.nodes.image.create(createImageNodeAttrs(src !== null && src !== void 0 ? src : matched.attrs.src, fileInfo, 'success')));
|
|
25374
25579
|
};
|
|
25375
25580
|
const createFailedThumbnailInserter = (view, fileInfo) => () => {
|
|
25376
|
-
const {
|
|
25377
|
-
|
|
25378
|
-
const tr = state.tr;
|
|
25379
|
-
state.doc.descendants((node, pos) => {
|
|
25380
|
-
if (node.attrs.fileInfoId === fileInfo.id) {
|
|
25381
|
-
const imageNodeAttrs = createImageNodeAttrs(node.attrs.src, fileInfo, 'failed');
|
|
25382
|
-
const errorPlaceholderNode = schema.nodes.image.create(imageNodeAttrs);
|
|
25383
|
-
tr.replaceWith(pos, pos + node.nodeSize, errorPlaceholderNode);
|
|
25384
|
-
return false;
|
|
25385
|
-
}
|
|
25386
|
-
});
|
|
25387
|
-
dispatch(tr);
|
|
25581
|
+
const { schema } = view.state;
|
|
25582
|
+
replaceImageNodeByFileInfoId(view, fileInfo.id, (matched) => schema.nodes.image.create(createImageNodeAttrs(matched.attrs.src, fileInfo, 'failed')));
|
|
25388
25583
|
};
|
|
25389
25584
|
function createImageNodeAttrs(src, fileInfo, state) {
|
|
25390
25585
|
return {
|
|
@@ -25454,14 +25649,15 @@ const filterImageNodes = (fragment) => {
|
|
|
25454
25649
|
* @param view - The ProseMirror editor view.
|
|
25455
25650
|
* @param event - The paste event.
|
|
25456
25651
|
* @param slice
|
|
25652
|
+
* @param inlineImages
|
|
25457
25653
|
* @returns A boolean; True if an image file was pasted to prevent default paste behavior, otherwise false.
|
|
25458
25654
|
*/
|
|
25459
|
-
const processPasteEvent = (view, event, slice) => {
|
|
25655
|
+
const processPasteEvent = (view, event, slice, inlineImages) => {
|
|
25460
25656
|
const clipboardData = event.clipboardData;
|
|
25461
25657
|
if (!clipboardData) {
|
|
25462
25658
|
return false;
|
|
25463
25659
|
}
|
|
25464
|
-
const isImageFilePasted = handlePastedImages(view, clipboardData);
|
|
25660
|
+
const isImageFilePasted = handlePastedImages(view, clipboardData, inlineImages);
|
|
25465
25661
|
const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
|
|
25466
25662
|
if (filteredSlice.content.childCount < slice.content.childCount) {
|
|
25467
25663
|
const { state, dispatch } = view;
|
|
@@ -25476,9 +25672,10 @@ const processPasteEvent = (view, event, slice) => {
|
|
|
25476
25672
|
*
|
|
25477
25673
|
* @param view - The ProseMirror editor view
|
|
25478
25674
|
* @param clipboardData - The clipboard data transfer object containing potential image files
|
|
25675
|
+
* @param inlineImages
|
|
25479
25676
|
* @returns True if at least one valid image file was found and processed, false otherwise
|
|
25480
25677
|
*/
|
|
25481
|
-
function handlePastedImages(view, clipboardData) {
|
|
25678
|
+
function handlePastedImages(view, clipboardData, inlineImages) {
|
|
25482
25679
|
let isImageFilePasted = false;
|
|
25483
25680
|
const files = [...(clipboardData.files || [])];
|
|
25484
25681
|
for (const file of files) {
|
|
@@ -25486,8 +25683,19 @@ function handlePastedImages(view, clipboardData) {
|
|
|
25486
25683
|
isImageFilePasted = true;
|
|
25487
25684
|
const reader = new FileReader();
|
|
25488
25685
|
reader.onloadend = () => {
|
|
25686
|
+
const base64Data = reader.result;
|
|
25687
|
+
const fileInfo = createFileInfo(file);
|
|
25688
|
+
if (inlineImages) {
|
|
25689
|
+
// Once inline images are configured they own the paste
|
|
25690
|
+
// lifecycle; never fall back to the legacy imagePasted
|
|
25691
|
+
// event. Without an upload handler the paste is a no-op.
|
|
25692
|
+
if (inlineImages.upload) {
|
|
25693
|
+
runInlineImageUpload(view, file, base64Data, fileInfo, inlineImages);
|
|
25694
|
+
}
|
|
25695
|
+
return;
|
|
25696
|
+
}
|
|
25489
25697
|
view.dom.dispatchEvent(new CustomEvent('imagePasted', {
|
|
25490
|
-
detail: imageInserterFactory(view,
|
|
25698
|
+
detail: imageInserterFactory(view, base64Data, fileInfo),
|
|
25491
25699
|
}));
|
|
25492
25700
|
};
|
|
25493
25701
|
reader.readAsDataURL(file);
|
|
@@ -25651,7 +25859,12 @@ class ImageView {
|
|
|
25651
25859
|
this.img.alt = node.attrs.alt;
|
|
25652
25860
|
applyImageStyles(this.img, node);
|
|
25653
25861
|
this.img.addEventListener('load', () => {
|
|
25654
|
-
|
|
25862
|
+
// Only measure a freshly inserted image. Re-measuring one that
|
|
25863
|
+
// already has a width would rewrite it against the current
|
|
25864
|
+
// container size and emit a spurious change event on every load.
|
|
25865
|
+
if (!this.node.attrs.width) {
|
|
25866
|
+
this.persistDimensions();
|
|
25867
|
+
}
|
|
25655
25868
|
});
|
|
25656
25869
|
this.dom.append(this.img);
|
|
25657
25870
|
this.transitionBetweenStates();
|
|
@@ -27619,6 +27832,7 @@ const ProsemirrorAdapter = class {
|
|
|
27619
27832
|
}
|
|
27620
27833
|
componentWillLoad() {
|
|
27621
27834
|
this.getActionBarItems();
|
|
27835
|
+
this.validatedInlineImages = this.validateInlineImages();
|
|
27622
27836
|
this.setupContentConverter();
|
|
27623
27837
|
}
|
|
27624
27838
|
componentDidLoad() {
|
|
@@ -27644,7 +27858,7 @@ const ProsemirrorAdapter = class {
|
|
|
27644
27858
|
(_e = this.view) === null || _e === void 0 ? void 0 : _e.destroy();
|
|
27645
27859
|
}
|
|
27646
27860
|
render() {
|
|
27647
|
-
return (h(Host, { key: '
|
|
27861
|
+
return (h(Host, { key: '27949dced1fd61d1a80c42f58cf1699f05acb52e', onFocus: this.handleFocus }, h("div", { key: '75e941459326f0fb8f8d5042becd570e06cd99fa', id: "editor" }), this.renderToolbar(), this.renderLinkMenu()));
|
|
27648
27862
|
}
|
|
27649
27863
|
renderToolbar() {
|
|
27650
27864
|
if (this.actionBarItems.length === 0 || this.ui === 'no-toolbar') {
|
|
@@ -27658,12 +27872,26 @@ const ProsemirrorAdapter = class {
|
|
|
27658
27872
|
}
|
|
27659
27873
|
return (h("limel-portal", { containerId: this.portalId, visible: this.isLinkMenuOpen, openDirection: "top", inheritParentWidth: true, anchor: this.actionBarElement }, h("limel-text-editor-link-menu", { link: this.link, isOpen: this.isLinkMenuOpen, onLinkChange: this.handleLinkChange, onCancel: this.handleCancelLinkMenu, onSave: this.handleSaveLinkMenu })));
|
|
27660
27874
|
}
|
|
27875
|
+
validateInlineImages() {
|
|
27876
|
+
const config = this.inlineImages;
|
|
27877
|
+
// Require a hyphen: the tag name becomes a sanitizer whitelist key, so a
|
|
27878
|
+
// built-in like `img` would hijack its rules and strip `src` from every
|
|
27879
|
+
// real image. Custom element names always have a hyphen; built-ins never.
|
|
27880
|
+
const isValidTagName = (name) => /^[a-z][a-z0-9._-]*$/.test(name) && name.includes('-');
|
|
27881
|
+
if (config &&
|
|
27882
|
+
isInlineImageTag(config) &&
|
|
27883
|
+
!isValidTagName(config.tagName)) {
|
|
27884
|
+
console.error(`inlineImages.tagName "${config.tagName}" is not a valid custom element name; ignoring inlineImages config.`);
|
|
27885
|
+
return undefined;
|
|
27886
|
+
}
|
|
27887
|
+
return config;
|
|
27888
|
+
}
|
|
27661
27889
|
setupContentConverter() {
|
|
27662
27890
|
if (this.contentType === 'markdown') {
|
|
27663
|
-
this.contentConverter = new MarkdownConverter(this.customElements, this.language);
|
|
27891
|
+
this.contentConverter = new MarkdownConverter(this.customElements, this.language, this.validatedInlineImages);
|
|
27664
27892
|
}
|
|
27665
27893
|
else if (this.contentType === 'html') {
|
|
27666
|
-
this.contentConverter = new HTMLConverter(this.customElements);
|
|
27894
|
+
this.contentConverter = new HTMLConverter(this.customElements, this.validatedInlineImages);
|
|
27667
27895
|
}
|
|
27668
27896
|
else {
|
|
27669
27897
|
throw new Error(`Unsupported content type: ${this.contentType}. Only 'markdown' and 'html' are supported.`);
|
|
@@ -27696,7 +27924,7 @@ const ProsemirrorAdapter = class {
|
|
|
27696
27924
|
if (this.contentType === 'html') {
|
|
27697
27925
|
nodes = nodes.append(getTableNodes());
|
|
27698
27926
|
}
|
|
27699
|
-
nodes = nodes.append(getImageNode(this.language));
|
|
27927
|
+
nodes = nodes.append(getImageNode(this.language, this.validatedInlineImages));
|
|
27700
27928
|
return new Schema({
|
|
27701
27929
|
nodes: nodes,
|
|
27702
27930
|
marks: schema$1.spec.marks.append({
|
|
@@ -27724,7 +27952,7 @@ const ProsemirrorAdapter = class {
|
|
|
27724
27952
|
keymap(this.menuCommandFactory.buildKeymap()),
|
|
27725
27953
|
createTriggerPlugin(this.triggerCharacters, this.contentConverter),
|
|
27726
27954
|
createLinkPlugin(this.handleNewLinkSelection),
|
|
27727
|
-
createImageInserterPlugin(this.imagePasted.emit),
|
|
27955
|
+
createImageInserterPlugin(this.imagePasted.emit, this.validatedInlineImages),
|
|
27728
27956
|
createImageViewPlugin(this.language),
|
|
27729
27957
|
createMenuStateTrackingPlugin(editorMenuTypesArray, this.menuCommandFactory, this.updateActiveActionBarItems),
|
|
27730
27958
|
createActionBarInteractionPlugin(this.menuCommandFactory),
|