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