@meowdown/core 0.74.2 → 0.75.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/dist/index.d.ts CHANGED
@@ -808,14 +808,23 @@ type PostEmbedSnapshot = {
808
808
  export declare function parsePostEmbedSnapshot(value: unknown): PostEmbedSnapshot | undefined;
809
809
  //#endregion
810
810
  //#region src/extensions/image.d.ts
811
- type ImageUrlResolver = (src: string) => string | undefined;
811
+ /**
812
+ * Maps a markdown `src` to a displayable URL, or `undefined` to skip rendering
813
+ * that image. May return a `Promise` when the answer needs data that is not
814
+ * loaded yet: the image appears once it resolves, and a rejection or an
815
+ * `undefined` result leaves the source without a preview.
816
+ */
817
+ type ImageUrlResolver = (src: string) => string | undefined | Promise<string | undefined>;
812
818
  /**
813
819
  * Options for {@link defineImage}.
814
820
  */
815
821
  interface ImageOptions {
816
822
  /**
817
823
  * Map a markdown `src` to a displayable URL, or `undefined` to skip rendering
818
- * that image. Defaults to `defaultResolveImageUrl`.
824
+ * that image. May return a `Promise`; while it is pending, an image whose
825
+ * `width` and `height` are both persisted shows a sized placeholder so the
826
+ * resolved image does not shift the layout. Defaults to
827
+ * `defaultResolveImageUrl`.
819
828
  */
820
829
  resolveImageUrl?: ImageUrlResolver;
821
830
  /**
@@ -1680,4 +1689,4 @@ export declare function getSelectedText(state: EditorState): string;
1680
1689
  */
1681
1690
  export declare function getVirtualElementFromRange(view: EditorView, range: PositionRange): VirtualElement;
1682
1691
  //#endregion
1683
- export { type AcceptPendingReplacementOptions, type CheckRoundTripOptions, type CodeBlockAttrs, type CodeBlockFenceStyle, type CodeToken, type DocToMarkdownOptions, type EditorConfig, type EditorExtension, type EditorExtensionOptions, type ExitBoundaryHandler, type ExitBoundaryOptions, type FileClickHandler, type FileClickPayload, type FileInfo, type FileInfoResolver, type FileLinkOptions, type FileLinkPayload, type FileLinkResolver, type FilePasteHandler, type FilePasteOptions, type FileSaveErrorHandler, type FileViewOptions, type FollowLinkHandlers, type ImageClickHandler, type ImageClickPayload, type ImageOptions, type InlineMarkContext, type InlineMarkOptions, type KaTeXRender, type LanguageItem, type LinkAttrs, type LinkClickHandler, type LinkClickPayload, type LinkCopyHandler, type LinkCopyPayload, type LinkEditHandler, type LinkEditOptions, type LinkHoverHandler, type LinkHoverOptions, type LinkPreview, type LinkPreviewResolver, type LinkUnit, type ListMarker, type MarkChunk, type MarkMode, type MarkName, type MarkdownToDocOptions, type MdFileAttrs, type MdImageAttrs, type MdLinkTextAttrs, type MdMathAttrs, type MdWikilinkAttrs, type MeowdownCodeBlockAttrs, type MeowdownHTMLCommentAttrs, type MeowdownListAttrs, type MeowdownTableCellAttrs, type NodeName, type ParsedWikiEmbed, type PendingReplacement, type PendingReplacementEvent, type PendingReplacementHandler, type PendingReplacementMode, type PendingReplacementOutcome, type PlaceholderOptions, type PositionRange, type PostEmbedSnapshot, Priority, type ReferenceDefinition, type ReferenceDefinitionIndex, type ReferenceDefinitions, type RoundTripFidelity, type SearchStatus, type SearchStatusHandler, type StartPendingReplacementOptions, type TableColumnAlign, type TagClickHandler, type TagClickPayload, type TypedEditor, type TypedMarkBuilders, type VirtualElement, type WikiEmbedOptions, type WikiEmbedResolution, type WikiEmbedResolver, type WikilinkClickHandler, type WikilinkClickPayload, type WikilinkHoverHandler, type WikilinkHoverHit, type WikilinkOptions, type WikilinkPayload, type WikilinkResolution, type WikilinkResolver, type XPostMediaClickHandler, type XPostResolver, type YouTubeVideoClickHandler, type YouTubeVideoResolver, defineCodeBlockPreviewPlugin, definePlaceholder, defineSearchStatusHandler, getSearchStatus, isCodeBlockPreviewHiddenDecoration, withPriority };
1692
+ export { type AcceptPendingReplacementOptions, type CheckRoundTripOptions, type CodeBlockAttrs, type CodeBlockFenceStyle, type CodeToken, type DocToMarkdownOptions, type EditorConfig, type EditorExtension, type EditorExtensionOptions, type ExitBoundaryHandler, type ExitBoundaryOptions, type FileClickHandler, type FileClickPayload, type FileInfo, type FileInfoResolver, type FileLinkOptions, type FileLinkPayload, type FileLinkResolver, type FilePasteHandler, type FilePasteOptions, type FileSaveErrorHandler, type FileViewOptions, type FollowLinkHandlers, type ImageClickHandler, type ImageClickPayload, type ImageOptions, type ImageUrlResolver, type InlineMarkContext, type InlineMarkOptions, type KaTeXRender, type LanguageItem, type LinkAttrs, type LinkClickHandler, type LinkClickPayload, type LinkCopyHandler, type LinkCopyPayload, type LinkEditHandler, type LinkEditOptions, type LinkHoverHandler, type LinkHoverOptions, type LinkPreview, type LinkPreviewResolver, type LinkUnit, type ListMarker, type MarkChunk, type MarkMode, type MarkName, type MarkdownToDocOptions, type MdFileAttrs, type MdImageAttrs, type MdLinkTextAttrs, type MdMathAttrs, type MdWikilinkAttrs, type MeowdownCodeBlockAttrs, type MeowdownHTMLCommentAttrs, type MeowdownListAttrs, type MeowdownTableCellAttrs, type NodeName, type ParsedWikiEmbed, type PendingReplacement, type PendingReplacementEvent, type PendingReplacementHandler, type PendingReplacementMode, type PendingReplacementOutcome, type PlaceholderOptions, type PositionRange, type PostEmbedSnapshot, Priority, type ReferenceDefinition, type ReferenceDefinitionIndex, type ReferenceDefinitions, type RoundTripFidelity, type SearchStatus, type SearchStatusHandler, type StartPendingReplacementOptions, type TableColumnAlign, type TagClickHandler, type TagClickPayload, type TypedEditor, type TypedMarkBuilders, type VirtualElement, type WikiEmbedOptions, type WikiEmbedResolution, type WikiEmbedResolver, type WikilinkClickHandler, type WikilinkClickPayload, type WikilinkHoverHandler, type WikilinkHoverHit, type WikilinkOptions, type WikilinkPayload, type WikilinkResolution, type WikilinkResolver, type XPostMediaClickHandler, type XPostResolver, type YouTubeVideoClickHandler, type YouTubeVideoResolver, defineCodeBlockPreviewPlugin, definePlaceholder, defineSearchStatusHandler, getSearchStatus, isCodeBlockPreviewHiddenDecoration, withPriority };
package/dist/index.js CHANGED
@@ -3992,12 +3992,8 @@ var ImageMarkView = class {
3992
3992
  this.#contentDOM.className = "md-image-view-content md-atom-view-content";
3993
3993
  this.#contentDOM.dataset.testid = "image-source";
3994
3994
  for (const [name, value] of Object.entries(NON_PROSE_ATTRS)) this.#contentDOM.setAttribute(name, value);
3995
- const preview = this.#renderPreview();
3996
- if (preview) {
3997
- preview.contentEditable = "false";
3998
- this.#dom.appendChild(preview);
3999
- }
4000
3995
  this.#dom.appendChild(this.#contentDOM);
3996
+ this.#mountPreview();
4001
3997
  }
4002
3998
  get dom() {
4003
3999
  return this.#dom;
@@ -4024,22 +4020,67 @@ var ImageMarkView = class {
4024
4020
  this.#destroyed = true;
4025
4021
  }
4026
4022
  /**
4027
- * Build the inline preview for the image `src`: a post-embed card or a
4028
- * resizable `<img>`.
4023
+ * Mount the inline preview for the image `src`, if any: a post-embed card, a
4024
+ * resizable `<img>`, or, while an asynchronous resolver is pending and the
4025
+ * size is persisted, an empty box of that size that the image later fills.
4029
4026
  */
4030
- #renderPreview() {
4027
+ #mountPreview() {
4031
4028
  const { src } = this.#attrs;
4032
- const wrapper = document.createElement("span");
4033
- wrapper.className = "md-image-view-preview md-atom-view-preview";
4034
4029
  const kind = matchEmbed(src);
4035
4030
  if (kind) {
4031
+ const wrapper = this.#createWrapper();
4036
4032
  wrapper.dataset.testid = `${kind}-embed`;
4037
4033
  wrapper.dataset.postEmbed = kind;
4038
4034
  wrapper.appendChild(this.#buildPostEmbed(kind, src));
4039
- return wrapper;
4035
+ this.#insertPreview(wrapper);
4036
+ return;
4037
+ }
4038
+ const resolved = (this.#resolveImageUrl ?? defaultResolveImageUrl)(src);
4039
+ if (resolved instanceof Promise) {
4040
+ const placeholder = this.#hasPersistedSize() ? this.#renderImage(null) : void 0;
4041
+ if (placeholder) this.#insertPreview(placeholder);
4042
+ resolved.then((url) => this.#settle(placeholder, url), () => this.#settle(placeholder, void 0));
4043
+ return;
4044
+ }
4045
+ if (resolved) this.#insertPreview(this.#renderImage(resolved));
4046
+ }
4047
+ #hasPersistedSize() {
4048
+ return this.#attrs.width != null && this.#attrs.height != null;
4049
+ }
4050
+ /**
4051
+ * Apply a resolver's asynchronous answer: fill the placeholder with the
4052
+ * image, mount the image from scratch, or drop the placeholder when there is
4053
+ * nothing to show. A view destroyed in the meantime (its `src` changed, or it
4054
+ * left the document) ignores the answer.
4055
+ */
4056
+ #settle(placeholder, url) {
4057
+ if (this.#destroyed) return;
4058
+ if (!url) {
4059
+ placeholder?.remove();
4060
+ this.#resizableRoot = void 0;
4061
+ return;
4040
4062
  }
4041
- const url = (this.#resolveImageUrl ?? defaultResolveImageUrl)(src);
4042
- if (!url) return void 0;
4063
+ if (placeholder && this.#resizableRoot) this.#attachImage(this.#resizableRoot, url);
4064
+ else this.#insertPreview(this.#renderImage(url));
4065
+ }
4066
+ #createWrapper() {
4067
+ const wrapper = document.createElement("span");
4068
+ wrapper.className = "md-image-view-preview md-atom-view-preview";
4069
+ return wrapper;
4070
+ }
4071
+ /**
4072
+ * Insert a preview in front of the source text.
4073
+ */
4074
+ #insertPreview(preview) {
4075
+ preview.contentEditable = "false";
4076
+ this.#dom.insertBefore(preview, this.#contentDOM);
4077
+ }
4078
+ /**
4079
+ * The resizable image preview. `null` builds the sized box without its
4080
+ * `<img>`, for an asynchronous resolver to fill through `#attachImage`.
4081
+ */
4082
+ #renderImage(url) {
4083
+ const wrapper = this.#createWrapper();
4043
4084
  wrapper.dataset.testid = "image-preview";
4044
4085
  wrapper.appendChild(this.#buildResizableImage(url));
4045
4086
  return wrapper;
@@ -4122,6 +4163,25 @@ var ImageMarkView = class {
4122
4163
  root.className = "md-image-resizable";
4123
4164
  root.dataset.testid = "image-resizable";
4124
4165
  root.setAttribute("data-loading", "");
4166
+ applySize(root, this.#attrs.width, this.#attrs.height);
4167
+ const handle = document.createElement("prosekit-resizable-handle");
4168
+ handle.className = "md-image-resize-handle";
4169
+ handle.setAttribute("position", "bottom-right");
4170
+ handle.addEventListener("click", (event) => event.stopPropagation());
4171
+ root.appendChild(handle);
4172
+ root.addEventListener("resizeEnd", (event) => {
4173
+ const { width: nextWidth, height: nextHeight } = event.detail;
4174
+ commitImageSize(this.#view, this.#contentDOM, nextWidth, nextHeight);
4175
+ });
4176
+ this.#resizableRoot = root;
4177
+ if (url != null) this.#attachImage(root, url);
4178
+ return root;
4179
+ }
4180
+ /**
4181
+ * Put the `<img>` for `url` into a resizable root built by
4182
+ * `#buildResizableImage`, in front of its handle.
4183
+ */
4184
+ #attachImage(root, url) {
4125
4185
  const image = document.createElement("img");
4126
4186
  image.src = url;
4127
4187
  image.alt = this.#attrs.alt;
@@ -4137,19 +4197,8 @@ var ImageMarkView = class {
4137
4197
  image.addEventListener("error", () => {
4138
4198
  root.removeAttribute("data-loading");
4139
4199
  });
4140
- root.appendChild(image);
4141
- const handle = document.createElement("prosekit-resizable-handle");
4142
- handle.className = "md-image-resize-handle";
4143
- handle.setAttribute("position", "bottom-right");
4144
- handle.addEventListener("click", (event) => event.stopPropagation());
4145
- root.appendChild(handle);
4146
- root.addEventListener("resizeEnd", (event) => {
4147
- const { width: nextWidth, height: nextHeight } = event.detail;
4148
- commitImageSize(this.#view, this.#contentDOM, nextWidth, nextHeight);
4149
- });
4150
- this.#resizableRoot = root;
4200
+ root.prepend(image);
4151
4201
  this.#image = image;
4152
- return root;
4153
4202
  }
4154
4203
  };
4155
4204
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/core",
3
3
  "type": "module",
4
- "version": "0.74.2",
4
+ "version": "0.75.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",