@blogmcp/mdx-render-vue 0.1.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/_virtual/_plugin-vue_export-helper.js +10 -0
- package/dist/_virtual/constants.js +4 -0
- package/dist/_virtual/index.js +5 -0
- package/dist/_virtual/index2.js +4 -0
- package/dist/composables/index.d.ts +11 -0
- package/dist/composables/index.js +24 -0
- package/dist/composables/useAudioPlayer.d.ts +22 -0
- package/dist/composables/useAudioPlayer.js +58 -0
- package/dist/composables/useCallout.d.ts +43 -0
- package/dist/composables/useCallout.js +71 -0
- package/dist/composables/useCodeBlock.d.ts +23 -0
- package/dist/composables/useCodeBlock.js +67 -0
- package/dist/composables/useGallery.d.ts +37 -0
- package/dist/composables/useGallery.js +84 -0
- package/dist/composables/useMedia.d.ts +18 -0
- package/dist/composables/useMedia.js +9 -0
- package/dist/composables/useYouTube.d.ts +31 -0
- package/dist/composables/useYouTube.js +32 -0
- package/dist/edit/AlertEdit.vue.d.ts +12 -0
- package/dist/edit/AlertEdit.vue.js +4 -0
- package/dist/edit/AlertEdit.vue2.js +18 -0
- package/dist/edit/AudioPlayerEdit.vue.d.ts +3 -0
- package/dist/edit/AudioPlayerEdit.vue.js +117 -0
- package/dist/edit/AudioPlayerEdit.vue2.js +4 -0
- package/dist/edit/BadgeEdit.vue.d.ts +2 -0
- package/dist/edit/BadgeEdit.vue.js +4 -0
- package/dist/edit/BadgeEdit.vue2.js +13 -0
- package/dist/edit/ButtonEdit.vue.d.ts +2 -0
- package/dist/edit/ButtonEdit.vue.js +4 -0
- package/dist/edit/ButtonEdit.vue2.js +13 -0
- package/dist/edit/CalloutEdit.vue.d.ts +13 -0
- package/dist/edit/CalloutEdit.vue.js +85 -0
- package/dist/edit/CalloutEdit.vue2.js +4 -0
- package/dist/edit/CardEdit.vue.d.ts +12 -0
- package/dist/edit/CardEdit.vue.js +4 -0
- package/dist/edit/CardEdit.vue2.js +18 -0
- package/dist/edit/CodeBlockEdit.vue.d.ts +12 -0
- package/dist/edit/CodeBlockEdit.vue.js +7 -0
- package/dist/edit/CodeBlockEdit.vue2.js +89 -0
- package/dist/edit/GalleryEdit.vue.d.ts +3 -0
- package/dist/edit/GalleryEdit.vue.js +64 -0
- package/dist/edit/GalleryEdit.vue2.js +4 -0
- package/dist/edit/IconEdit.vue.d.ts +2 -0
- package/dist/edit/IconEdit.vue.js +4 -0
- package/dist/edit/IconEdit.vue2.js +13 -0
- package/dist/edit/TooltipEdit.vue.d.ts +16 -0
- package/dist/edit/TooltipEdit.vue.js +4 -0
- package/dist/edit/TooltipEdit.vue2.js +24 -0
- package/dist/edit/YouTubeEdit.vue.d.ts +3 -0
- package/dist/edit/YouTubeEdit.vue.js +41 -0
- package/dist/edit/YouTubeEdit.vue2.js +4 -0
- package/dist/edit/index.d.ts +29 -0
- package/dist/edit/index.js +35 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +75 -0
- package/dist/mdx-render-vue.css +31 -0
- package/dist/node_modules/@ark-ui/vue/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/factory.js +35 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/presence/use-presence-context.js +6 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/presence/use-presence.js +40 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow-tip.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow-tip.vue_vue_type_script_setup_true_lang.js +21 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow.vue_vue_type_script_setup_true_lang.js +21 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-content.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-content.vue_vue_type_script_setup_true_lang.js +25 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-context.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-context.vue_vue_type_script_setup_true_lang.js +14 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-positioner.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-positioner.vue_vue_type_script_setup_true_lang.js +23 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root-provider.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root-provider.vue_vue_type_script_setup_true_lang.js +38 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root.vue_vue_type_script_setup_true_lang.js +68 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-trigger.js +5 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-trigger.vue_vue_type_script_setup_true_lang.js +25 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip.js +22 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/use-tooltip-context.js +6 -0
- package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/use-tooltip.js +36 -0
- package/dist/node_modules/@ark-ui/vue/dist/providers/environment/use-environment-context.js +13 -0
- package/dist/node_modules/@ark-ui/vue/dist/providers/locale/use-locale-context.js +12 -0
- package/dist/node_modules/@ark-ui/vue/dist/utils/clean-props.js +8 -0
- package/dist/node_modules/@ark-ui/vue/dist/utils/create-context.js +14 -0
- package/dist/node_modules/@ark-ui/vue/dist/utils/dynamic.js +34 -0
- package/dist/node_modules/@ark-ui/vue/dist/utils/unref-element.js +8 -0
- package/dist/node_modules/@ark-ui/vue/dist/utils/use-forward-expose.js +46 -0
- package/dist/node_modules/@ark-ui/vue/dist/utils/use-render-strategy.js +5 -0
- package/dist/node_modules/@braintree/sanitize-url/dist/constants.js +20 -0
- package/dist/node_modules/@braintree/sanitize-url/dist/index.js +75 -0
- package/dist/node_modules/@floating-ui/core/dist/floating-ui.core.js +728 -0
- package/dist/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +629 -0
- package/dist/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +176 -0
- package/dist/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +151 -0
- package/dist/node_modules/@iconify/vue/dist/iconify.js +1691 -0
- package/dist/node_modules/@zag-js/anatomy/dist/create-anatomy.js +29 -0
- package/dist/node_modules/@zag-js/core/dist/create-machine.js +28 -0
- package/dist/node_modules/@zag-js/core/dist/merge-props.js +55 -0
- package/dist/node_modules/@zag-js/core/dist/scope.js +20 -0
- package/dist/node_modules/@zag-js/core/dist/state.js +176 -0
- package/dist/node_modules/@zag-js/core/dist/types.js +11 -0
- package/dist/node_modules/@zag-js/dom-query/dist/chunk-QZ7TP4HQ.js +6 -0
- package/dist/node_modules/@zag-js/dom-query/dist/computed-style.js +11 -0
- package/dist/node_modules/@zag-js/dom-query/dist/event.js +37 -0
- package/dist/node_modules/@zag-js/dom-query/dist/node.js +64 -0
- package/dist/node_modules/@zag-js/dom-query/dist/overflow.js +28 -0
- package/dist/node_modules/@zag-js/dom-query/dist/platform.js +23 -0
- package/dist/node_modules/@zag-js/dom-query/dist/query.js +6 -0
- package/dist/node_modules/@zag-js/dom-query/dist/raf.js +52 -0
- package/dist/node_modules/@zag-js/dom-query/dist/set.js +22 -0
- package/dist/node_modules/@zag-js/dom-query/dist/shared.js +8 -0
- package/dist/node_modules/@zag-js/focus-visible/dist/index.js +159 -0
- package/dist/node_modules/@zag-js/popper/dist/get-anchor.js +39 -0
- package/dist/node_modules/@zag-js/popper/dist/get-placement.js +296 -0
- package/dist/node_modules/@zag-js/popper/dist/get-styles.js +47 -0
- package/dist/node_modules/@zag-js/popper/dist/middleware.js +79 -0
- package/dist/node_modules/@zag-js/popper/dist/placement.js +7 -0
- package/dist/node_modules/@zag-js/presence/dist/presence.connect.js +18 -0
- package/dist/node_modules/@zag-js/presence/dist/presence.machine.js +175 -0
- package/dist/node_modules/@zag-js/tooltip/dist/tooltip.anatomy.js +7 -0
- package/dist/node_modules/@zag-js/tooltip/dist/tooltip.connect.js +155 -0
- package/dist/node_modules/@zag-js/tooltip/dist/tooltip.dom.js +24 -0
- package/dist/node_modules/@zag-js/tooltip/dist/tooltip.machine.js +416 -0
- package/dist/node_modules/@zag-js/tooltip/dist/tooltip.store.js +9 -0
- package/dist/node_modules/@zag-js/types/dist/prop-types.js +14 -0
- package/dist/node_modules/@zag-js/utils/dist/array.js +7 -0
- package/dist/node_modules/@zag-js/utils/dist/equal.js +36 -0
- package/dist/node_modules/@zag-js/utils/dist/functions.js +11 -0
- package/dist/node_modules/@zag-js/utils/dist/guard.js +26 -0
- package/dist/node_modules/@zag-js/utils/dist/object.js +16 -0
- package/dist/node_modules/@zag-js/utils/dist/store.js +44 -0
- package/dist/node_modules/@zag-js/utils/dist/warning.js +31 -0
- package/dist/node_modules/@zag-js/vue/dist/bindable.js +48 -0
- package/dist/node_modules/@zag-js/vue/dist/machine.js +258 -0
- package/dist/node_modules/@zag-js/vue/dist/normalize-props.js +42 -0
- package/dist/node_modules/@zag-js/vue/dist/refs.js +14 -0
- package/dist/node_modules/@zag-js/vue/dist/track.js +22 -0
- package/dist/node_modules/dompurify/dist/purify.es.js +1190 -0
- package/dist/registry.d.ts +76 -0
- package/dist/registry.js +164 -0
- package/dist/render/alert/Alert.vue.d.ts +18 -0
- package/dist/render/alert/Alert.vue.js +4 -0
- package/dist/render/alert/Alert.vue2.js +43 -0
- package/dist/render/alert/index.d.ts +2 -0
- package/dist/render/audioplayer/AudioPlayerDefault.vue.d.ts +3 -0
- package/dist/render/audioplayer/AudioPlayerDefault.vue.js +116 -0
- package/dist/render/audioplayer/AudioPlayerDefault.vue2.js +4 -0
- package/dist/render/audioplayer/index.d.ts +5 -0
- package/dist/render/audioplayer/index.js +5 -0
- package/dist/render/badge/Badge.vue.d.ts +10 -0
- package/dist/render/badge/Badge.vue.js +4 -0
- package/dist/render/badge/Badge.vue2.js +28 -0
- package/dist/render/badge/index.d.ts +2 -0
- package/dist/render/button/Button.vue.d.ts +13 -0
- package/dist/render/button/Button.vue.js +4 -0
- package/dist/render/button/Button.vue2.js +42 -0
- package/dist/render/button/index.d.ts +2 -0
- package/dist/render/callout/CalloutDefault.vue.d.ts +13 -0
- package/dist/render/callout/CalloutDefault.vue.js +86 -0
- package/dist/render/callout/CalloutDefault.vue2.js +4 -0
- package/dist/render/callout/CalloutMinimal.vue.d.ts +13 -0
- package/dist/render/callout/CalloutMinimal.vue.js +27 -0
- package/dist/render/callout/CalloutMinimal.vue2.js +4 -0
- package/dist/render/callout/index.d.ts +6 -0
- package/dist/render/callout/index.js +7 -0
- package/dist/render/card/Card.vue.d.ts +15 -0
- package/dist/render/card/Card.vue.js +4 -0
- package/dist/render/card/Card.vue2.js +29 -0
- package/dist/render/card/index.d.ts +2 -0
- package/dist/render/codeblock/CodeBlockDefault.vue.d.ts +12 -0
- package/dist/render/codeblock/CodeBlockDefault.vue.js +7 -0
- package/dist/render/codeblock/CodeBlockDefault.vue2.js +92 -0
- package/dist/render/codeblock/index.d.ts +5 -0
- package/dist/render/codeblock/index.js +5 -0
- package/dist/render/gallery/GalleryGrid.vue.d.ts +3 -0
- package/dist/render/gallery/GalleryGrid.vue.js +125 -0
- package/dist/render/gallery/GalleryGrid.vue2.js +4 -0
- package/dist/render/gallery/GalleryLightbox.vue.d.ts +16 -0
- package/dist/render/gallery/GalleryLightbox.vue.js +7 -0
- package/dist/render/gallery/GalleryLightbox.vue2.js +170 -0
- package/dist/render/gallery/index.d.ts +6 -0
- package/dist/render/gallery/index.js +7 -0
- package/dist/render/icon/Icon.vue.d.ts +9 -0
- package/dist/render/icon/Icon.vue.js +4 -0
- package/dist/render/icon/Icon.vue2.js +25 -0
- package/dist/render/icon/index.d.ts +2 -0
- package/dist/render/index.d.ts +40 -0
- package/dist/render/index.js +63 -0
- package/dist/render/tooltip/Tooltip.vue.d.ts +18 -0
- package/dist/render/tooltip/Tooltip.vue.js +4 -0
- package/dist/render/tooltip/Tooltip.vue2.js +58 -0
- package/dist/render/tooltip/index.d.ts +2 -0
- package/dist/render/youtube/YouTubeDefault.vue.d.ts +3 -0
- package/dist/render/youtube/YouTubeDefault.vue.js +79 -0
- package/dist/render/youtube/YouTubeDefault.vue2.js +4 -0
- package/dist/render/youtube/index.d.ts +5 -0
- package/dist/render/youtube/index.js +5 -0
- package/dist/useAudioPlayer.d.ts +22 -0
- package/dist/useCallout.d.ts +46 -0
- package/dist/useCodeBlock.d.ts +25 -0
- package/dist/useGallery.d.ts +44 -0
- package/dist/useMedia.d.ts +16 -0
- package/dist/useYouTube.d.ts +32 -0
- package/package.json +125 -0
- package/src/composables/index.ts +16 -0
- package/src/composables/useAudioPlayer.ts +84 -0
- package/src/composables/useCallout.ts +103 -0
- package/src/composables/useCodeBlock.ts +90 -0
- package/src/composables/useGallery.ts +143 -0
- package/src/composables/useMedia.ts +23 -0
- package/src/composables/useYouTube.ts +58 -0
- package/src/edit/AlertEdit.vue +9 -0
- package/src/edit/AudioPlayerEdit.vue +92 -0
- package/src/edit/BadgeEdit.vue +7 -0
- package/src/edit/ButtonEdit.vue +10 -0
- package/src/edit/CalloutEdit.vue +107 -0
- package/src/edit/CardEdit.vue +9 -0
- package/src/edit/CodeBlockEdit.vue +76 -0
- package/src/edit/GalleryEdit.vue +65 -0
- package/src/edit/IconEdit.vue +7 -0
- package/src/edit/TooltipEdit.vue +15 -0
- package/src/edit/YouTubeEdit.vue +33 -0
- package/src/edit/index.ts +32 -0
- package/src/index.ts +62 -0
- package/src/registry.ts +272 -0
- package/src/render/alert/Alert.vue +32 -0
- package/src/render/alert/index.ts +2 -0
- package/src/render/audioplayer/AudioPlayerDefault.vue +89 -0
- package/src/render/audioplayer/index.ts +8 -0
- package/src/render/badge/Badge.vue +25 -0
- package/src/render/badge/index.ts +2 -0
- package/src/render/button/Button.vue +41 -0
- package/src/render/button/index.ts +2 -0
- package/src/render/callout/CalloutDefault.vue +101 -0
- package/src/render/callout/CalloutMinimal.vue +31 -0
- package/src/render/callout/index.ts +9 -0
- package/src/render/card/Card.vue +15 -0
- package/src/render/card/index.ts +2 -0
- package/src/render/codeblock/CodeBlockDefault.vue +76 -0
- package/src/render/codeblock/index.ts +8 -0
- package/src/render/gallery/GalleryGrid.vue +162 -0
- package/src/render/gallery/GalleryLightbox.vue +162 -0
- package/src/render/gallery/index.ts +9 -0
- package/src/render/icon/Icon.vue +22 -0
- package/src/render/icon/index.ts +2 -0
- package/src/render/index.ts +76 -0
- package/src/render/tooltip/Tooltip.vue +35 -0
- package/src/render/tooltip/index.ts +2 -0
- package/src/render/youtube/YouTubeDefault.vue +72 -0
- package/src/render/youtube/index.ts +8 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ComputedRef } from 'vue';
|
|
2
|
+
import { GalleryProps } from '@blogmcp/mdx-schema';
|
|
3
|
+
import { GalleryTileLayout } from '@blogmcp/mdx-schema/styles';
|
|
4
|
+
import { galleryTileLayout } from '@blogmcp/mdx-schema/styles';
|
|
5
|
+
import { MediaObject } from '@blogmcp/mdx-schema';
|
|
6
|
+
|
|
7
|
+
export { GalleryTileLayout }
|
|
8
|
+
|
|
9
|
+
export { galleryTileLayout }
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Grid column class mappings
|
|
13
|
+
*/
|
|
14
|
+
export declare const gridColsClasses: Record<number, string>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type guard for MediaObject
|
|
18
|
+
*/
|
|
19
|
+
export declare function isMediaObject(value: unknown): value is MediaObject;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if a URL points to a video file based on extension
|
|
23
|
+
*/
|
|
24
|
+
export declare function isVideoUrl(src: string): boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Composable for Gallery component logic
|
|
28
|
+
*/
|
|
29
|
+
export declare function useGallery(props: GalleryProps): {
|
|
30
|
+
effectiveColumns: ComputedRef<1 | 2 | 3 | 4>;
|
|
31
|
+
gridColsClass: ComputedRef<string>;
|
|
32
|
+
gapClass: ComputedRef<string>;
|
|
33
|
+
tileLayout: ComputedRef<GalleryTileLayout>;
|
|
34
|
+
getImageSrc: (index: number) => string;
|
|
35
|
+
getImageAlt: (index: number) => string;
|
|
36
|
+
getImageDescription: (index: number) => string | undefined;
|
|
37
|
+
getImageCaption: (index: number) => string | undefined;
|
|
38
|
+
isVideo: (index: number) => boolean;
|
|
39
|
+
getVideoPoster: (index: number) => string | undefined;
|
|
40
|
+
hasImages: ComputedRef<boolean>;
|
|
41
|
+
imageCount: ComputedRef<number>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Component } from 'vue';
|
|
2
|
+
import { InjectionKey } from 'vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Injection key for the optimized image component.
|
|
6
|
+
* The host app should `provide(IMG_COMPONENT_KEY, NuxtImg)`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const IMG_COMPONENT_KEY: InjectionKey<Component | string>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Returns the provided image component (e.g. NuxtImg),
|
|
12
|
+
* or falls back to plain `'img'` when nothing is provided.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useOptimizedImg(): Component | string;
|
|
15
|
+
|
|
16
|
+
export { }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ComputedRef } from 'vue';
|
|
2
|
+
import { YouTubeProps } from '@blogmcp/mdx-schema';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Composable for YouTube component logic
|
|
6
|
+
*/
|
|
7
|
+
export declare function useYouTube(props: YouTubeProps): {
|
|
8
|
+
embedUrl: ComputedRef<string>;
|
|
9
|
+
iframeTitle: ComputedRef<string>;
|
|
10
|
+
hasTitle: ComputedRef<boolean>;
|
|
11
|
+
thumbnailUrl: ComputedRef<string>;
|
|
12
|
+
iframeAttrs: ComputedRef< {
|
|
13
|
+
src: string;
|
|
14
|
+
title: string;
|
|
15
|
+
frameborder: string;
|
|
16
|
+
allow: string;
|
|
17
|
+
allowfullscreen: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
YOUTUBE_IFRAME_ALLOW: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* YouTube embed URL base
|
|
24
|
+
*/
|
|
25
|
+
export declare const YOUTUBE_EMBED_BASE = "https://www.youtube.com/embed/";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default iframe allow attributes
|
|
29
|
+
*/
|
|
30
|
+
export declare const YOUTUBE_IFRAME_ALLOW = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture";
|
|
31
|
+
|
|
32
|
+
export { }
|
package/package.json
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blogmcp/mdx-render-vue",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vue renderer for MDX components — edit + render SFCs wired to @blogmcp/mdx-schema",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./src/index.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./edit": {
|
|
18
|
+
"types": "./src/edit/index.ts",
|
|
19
|
+
"import": "./dist/edit/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./render": {
|
|
22
|
+
"types": "./src/render/index.ts",
|
|
23
|
+
"import": "./dist/render/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./composables": {
|
|
26
|
+
"types": "./src/composables/index.ts",
|
|
27
|
+
"import": "./dist/composables/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./registry": {
|
|
30
|
+
"types": "./src/registry.ts",
|
|
31
|
+
"import": "./dist/registry.js"
|
|
32
|
+
},
|
|
33
|
+
"./render/callout": {
|
|
34
|
+
"types": "./src/render/callout/index.ts",
|
|
35
|
+
"import": "./dist/render/callout/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./render/codeblock": {
|
|
38
|
+
"types": "./src/render/codeblock/index.ts",
|
|
39
|
+
"import": "./dist/render/codeblock/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./render/gallery": {
|
|
42
|
+
"types": "./src/render/gallery/index.ts",
|
|
43
|
+
"import": "./dist/render/gallery/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./render/youtube": {
|
|
46
|
+
"types": "./src/render/youtube/index.ts",
|
|
47
|
+
"import": "./dist/render/youtube/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./render/audioplayer": {
|
|
50
|
+
"types": "./src/render/audioplayer/index.ts",
|
|
51
|
+
"import": "./dist/render/audioplayer/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./composables/gallery": {
|
|
54
|
+
"types": "./src/composables/useGallery.ts",
|
|
55
|
+
"import": "./dist/composables/useGallery.js"
|
|
56
|
+
},
|
|
57
|
+
"./composables/media": {
|
|
58
|
+
"types": "./src/composables/useMedia.ts",
|
|
59
|
+
"import": "./dist/composables/useMedia.js"
|
|
60
|
+
},
|
|
61
|
+
"./composables/callout": {
|
|
62
|
+
"types": "./src/composables/useCallout.ts",
|
|
63
|
+
"import": "./dist/composables/useCallout.js"
|
|
64
|
+
},
|
|
65
|
+
"./composables/codeblock": {
|
|
66
|
+
"types": "./src/composables/useCodeBlock.ts",
|
|
67
|
+
"import": "./dist/composables/useCodeBlock.js"
|
|
68
|
+
},
|
|
69
|
+
"./composables/youtube": {
|
|
70
|
+
"types": "./src/composables/useYouTube.ts",
|
|
71
|
+
"import": "./dist/composables/useYouTube.js"
|
|
72
|
+
},
|
|
73
|
+
"./composables/audioplayer": {
|
|
74
|
+
"types": "./src/composables/useAudioPlayer.ts",
|
|
75
|
+
"import": "./dist/composables/useAudioPlayer.js"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"files": [
|
|
79
|
+
"dist",
|
|
80
|
+
"src"
|
|
81
|
+
],
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "vite build && vue-tsc --emitDeclarationOnly",
|
|
84
|
+
"dev": "vite build --watch",
|
|
85
|
+
"test": "vitest run",
|
|
86
|
+
"test:watch": "vitest",
|
|
87
|
+
"test:e2e": "playwright test --config=test/e2e/playwright.config.ts",
|
|
88
|
+
"test:e2e:ui": "playwright test --config=test/e2e/playwright.config.ts --ui",
|
|
89
|
+
"harness": "vite --config test/e2e/harness/vite.config.ts --port 5099 --open",
|
|
90
|
+
"typecheck": "vue-tsc --noEmit"
|
|
91
|
+
},
|
|
92
|
+
"dependencies": {
|
|
93
|
+
"@blogmcp/mdx-schema": "^0.1.0",
|
|
94
|
+
"@blogmcp/sanitize": "^0.1.0",
|
|
95
|
+
"@ark-ui/vue": "^5.0.0",
|
|
96
|
+
"@iconify/vue": "^5.0.0"
|
|
97
|
+
},
|
|
98
|
+
"peerDependencies": {
|
|
99
|
+
"vue": "^3.4.0"
|
|
100
|
+
},
|
|
101
|
+
"devDependencies": {
|
|
102
|
+
"@types/node": "^20.10.0",
|
|
103
|
+
"@vitejs/plugin-vue": "^6.0.0",
|
|
104
|
+
"@vitest/utils": "3.2.4",
|
|
105
|
+
"@vue/test-utils": "^2.4.0",
|
|
106
|
+
"happy-dom": "^17.4.0",
|
|
107
|
+
"typescript": "^5.3.3",
|
|
108
|
+
"vite": "^7.3.0",
|
|
109
|
+
"vite-plugin-dts": "^4.0.0",
|
|
110
|
+
"vitest": "^3.0.0",
|
|
111
|
+
"autoprefixer": "^10.4.0",
|
|
112
|
+
"postcss": "^8.4.0",
|
|
113
|
+
"tailwindcss": "^3.4.0",
|
|
114
|
+
"vue": "^3.5.0",
|
|
115
|
+
"vue-router": "^4.5.0",
|
|
116
|
+
"vue-tsc": "^2.0.0"
|
|
117
|
+
},
|
|
118
|
+
"keywords": [
|
|
119
|
+
"mdx",
|
|
120
|
+
"vue",
|
|
121
|
+
"components",
|
|
122
|
+
"markdown"
|
|
123
|
+
],
|
|
124
|
+
"license": "MIT"
|
|
125
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MDX Components Composables
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for MDX components.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { useCallout, calloutConfigs, calloutIconPaths } from './useCallout'
|
|
8
|
+
export { useCodeBlock, getLanguageLabel, languageLabels } from './useCodeBlock'
|
|
9
|
+
export { useGallery, gridColsClasses, isMediaObject, isVideoUrl } from './useGallery'
|
|
10
|
+
export {
|
|
11
|
+
useYouTube,
|
|
12
|
+
YOUTUBE_EMBED_BASE,
|
|
13
|
+
YOUTUBE_IFRAME_ALLOW,
|
|
14
|
+
} from './useYouTube'
|
|
15
|
+
export { useAudioPlayer } from './useAudioPlayer'
|
|
16
|
+
export { useOptimizedImg, IMG_COMPONENT_KEY } from './useMedia'
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPlayer Composable
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for AudioPlayer component variants.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computed, ref } from 'vue'
|
|
8
|
+
import type { AudioPlayerProps } from '@blogmcp/mdx-schema'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Composable for AudioPlayer component logic
|
|
12
|
+
*/
|
|
13
|
+
export function useAudioPlayer(props: AudioPlayerProps) {
|
|
14
|
+
const hasTitle = computed(() => !!props.title)
|
|
15
|
+
|
|
16
|
+
const displayTitle = computed(() => props.title || props.slug || 'Audio')
|
|
17
|
+
|
|
18
|
+
const showTranscription = computed(() => props.showTranscription ?? false)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build audio URL from slug.
|
|
22
|
+
* Uses the KB API media file endpoint.
|
|
23
|
+
*/
|
|
24
|
+
const audioUrl = computed(() => {
|
|
25
|
+
if (!props.slug) return ''
|
|
26
|
+
return `/api/media/file/${props.slug}`
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build transcription API URL from slug.
|
|
31
|
+
*/
|
|
32
|
+
const transcriptionUrl = computed(() => {
|
|
33
|
+
if (!props.slug) return ''
|
|
34
|
+
return `/api/media/transcription/${props.slug}`
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Transcription state (for components that load transcription)
|
|
39
|
+
*/
|
|
40
|
+
const transcriptionText = ref<string | null>(null)
|
|
41
|
+
const transcriptionSummary = ref<string | null>(null)
|
|
42
|
+
const transcriptionLoading = ref(false)
|
|
43
|
+
const transcriptionExpanded = ref(false)
|
|
44
|
+
|
|
45
|
+
async function loadTranscription() {
|
|
46
|
+
if (!props.slug || transcriptionText.value !== null) return
|
|
47
|
+
transcriptionLoading.value = true
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(transcriptionUrl.value)
|
|
50
|
+
if (response.ok) {
|
|
51
|
+
const data = await response.json()
|
|
52
|
+
transcriptionText.value = data.fullText || data.full_text || ''
|
|
53
|
+
transcriptionSummary.value = data.summary || null
|
|
54
|
+
} else {
|
|
55
|
+
transcriptionText.value = ''
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
transcriptionText.value = ''
|
|
59
|
+
} finally {
|
|
60
|
+
transcriptionLoading.value = false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toggleTranscription() {
|
|
65
|
+
transcriptionExpanded.value = !transcriptionExpanded.value
|
|
66
|
+
if (transcriptionExpanded.value && transcriptionText.value === null) {
|
|
67
|
+
loadTranscription()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
hasTitle,
|
|
73
|
+
displayTitle,
|
|
74
|
+
showTranscription,
|
|
75
|
+
audioUrl,
|
|
76
|
+
transcriptionUrl,
|
|
77
|
+
transcriptionText,
|
|
78
|
+
transcriptionSummary,
|
|
79
|
+
transcriptionLoading,
|
|
80
|
+
transcriptionExpanded,
|
|
81
|
+
loadTranscription,
|
|
82
|
+
toggleTranscription,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callout Composable
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for Callout component variants.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computed } from 'vue'
|
|
8
|
+
import type { CalloutType, CalloutProps } from '@blogmcp/mdx-schema'
|
|
9
|
+
|
|
10
|
+
export interface CalloutConfig {
|
|
11
|
+
type: CalloutType
|
|
12
|
+
icon: string
|
|
13
|
+
colors: {
|
|
14
|
+
bg: string
|
|
15
|
+
border: string
|
|
16
|
+
text: string
|
|
17
|
+
iconColor: string
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default callout configurations for each type
|
|
23
|
+
*/
|
|
24
|
+
export const calloutConfigs: Record<CalloutType, Omit<CalloutConfig, 'type'>> = {
|
|
25
|
+
info: {
|
|
26
|
+
icon: 'info-circle',
|
|
27
|
+
colors: {
|
|
28
|
+
bg: 'bg-blue-900/20',
|
|
29
|
+
border: 'border-blue-500',
|
|
30
|
+
text: 'text-blue-200',
|
|
31
|
+
iconColor: 'text-blue-400',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
warning: {
|
|
35
|
+
icon: 'warning-triangle',
|
|
36
|
+
colors: {
|
|
37
|
+
bg: 'bg-yellow-900/20',
|
|
38
|
+
border: 'border-yellow-500',
|
|
39
|
+
text: 'text-yellow-200',
|
|
40
|
+
iconColor: 'text-yellow-400',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
tip: {
|
|
44
|
+
icon: 'lightbulb',
|
|
45
|
+
colors: {
|
|
46
|
+
bg: 'bg-green-900/20',
|
|
47
|
+
border: 'border-green-500',
|
|
48
|
+
text: 'text-green-200',
|
|
49
|
+
iconColor: 'text-green-400',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
danger: {
|
|
53
|
+
icon: 'alert-circle',
|
|
54
|
+
colors: {
|
|
55
|
+
bg: 'bg-red-900/20',
|
|
56
|
+
border: 'border-red-500',
|
|
57
|
+
text: 'text-red-200',
|
|
58
|
+
iconColor: 'text-red-400',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* SVG path data for callout icons
|
|
65
|
+
*/
|
|
66
|
+
export const calloutIconPaths: Record<string, string> = {
|
|
67
|
+
'info-circle': 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
68
|
+
'warning-triangle':
|
|
69
|
+
'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
|
|
70
|
+
lightbulb:
|
|
71
|
+
'M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z',
|
|
72
|
+
'alert-circle': 'M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Composable for Callout component logic
|
|
77
|
+
*/
|
|
78
|
+
export function useCallout(props: CalloutProps) {
|
|
79
|
+
const effectiveType = computed<CalloutType>(() => props.type || 'info')
|
|
80
|
+
|
|
81
|
+
const config = computed(() => ({
|
|
82
|
+
type: effectiveType.value,
|
|
83
|
+
...calloutConfigs[effectiveType.value],
|
|
84
|
+
}))
|
|
85
|
+
|
|
86
|
+
const containerClasses = computed(() => [
|
|
87
|
+
config.value.colors.bg,
|
|
88
|
+
config.value.colors.border,
|
|
89
|
+
config.value.colors.text,
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
const iconPath = computed(() => calloutIconPaths[config.value.icon])
|
|
93
|
+
|
|
94
|
+
const iconClasses = computed(() => config.value.colors.iconColor)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
effectiveType,
|
|
98
|
+
config,
|
|
99
|
+
containerClasses,
|
|
100
|
+
iconPath,
|
|
101
|
+
iconClasses,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeBlock Composable
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for CodeBlock component variants.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computed, ref } from 'vue'
|
|
8
|
+
import type { CodeBlockProps } from '@blogmcp/mdx-schema'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Language label mappings
|
|
12
|
+
*/
|
|
13
|
+
export const languageLabels: Record<string, string> = {
|
|
14
|
+
js: 'JavaScript',
|
|
15
|
+
javascript: 'JavaScript',
|
|
16
|
+
ts: 'TypeScript',
|
|
17
|
+
typescript: 'TypeScript',
|
|
18
|
+
vue: 'Vue',
|
|
19
|
+
html: 'HTML',
|
|
20
|
+
css: 'CSS',
|
|
21
|
+
json: 'JSON',
|
|
22
|
+
python: 'Python',
|
|
23
|
+
py: 'Python',
|
|
24
|
+
bash: 'Bash',
|
|
25
|
+
sh: 'Shell',
|
|
26
|
+
sql: 'SQL',
|
|
27
|
+
text: 'Plain Text',
|
|
28
|
+
rust: 'Rust',
|
|
29
|
+
go: 'Go',
|
|
30
|
+
java: 'Java',
|
|
31
|
+
cpp: 'C++',
|
|
32
|
+
c: 'C',
|
|
33
|
+
csharp: 'C#',
|
|
34
|
+
php: 'PHP',
|
|
35
|
+
ruby: 'Ruby',
|
|
36
|
+
swift: 'Swift',
|
|
37
|
+
kotlin: 'Kotlin',
|
|
38
|
+
yaml: 'YAML',
|
|
39
|
+
yml: 'YAML',
|
|
40
|
+
xml: 'XML',
|
|
41
|
+
markdown: 'Markdown',
|
|
42
|
+
md: 'Markdown',
|
|
43
|
+
graphql: 'GraphQL',
|
|
44
|
+
dockerfile: 'Dockerfile',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get display label for a language
|
|
49
|
+
*/
|
|
50
|
+
export function getLanguageLabel(language?: string): string {
|
|
51
|
+
if (!language) return 'Plain Text'
|
|
52
|
+
const lang = language.toLowerCase()
|
|
53
|
+
if (languageLabels[lang]) return languageLabels[lang]
|
|
54
|
+
// Capitalize first letter as fallback
|
|
55
|
+
return lang.charAt(0).toUpperCase() + lang.slice(1)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Composable for CodeBlock component logic
|
|
60
|
+
*/
|
|
61
|
+
export function useCodeBlock(props: CodeBlockProps) {
|
|
62
|
+
const copied = ref(false)
|
|
63
|
+
|
|
64
|
+
const languageLabel = computed(() => getLanguageLabel(props.language))
|
|
65
|
+
|
|
66
|
+
const languageClass = computed(() => `language-${props.language || 'text'}`)
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Copy code to clipboard
|
|
70
|
+
* @param codeContent - The code text to copy (passed from component)
|
|
71
|
+
*/
|
|
72
|
+
async function copyCode(codeContent: string) {
|
|
73
|
+
try {
|
|
74
|
+
await navigator.clipboard.writeText(codeContent)
|
|
75
|
+
copied.value = true
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
copied.value = false
|
|
78
|
+
}, 2000)
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Failed to copy code:', error)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
copied,
|
|
86
|
+
languageLabel,
|
|
87
|
+
languageClass,
|
|
88
|
+
copyCode,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gallery Composable
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for Gallery component variants.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { computed } from 'vue'
|
|
8
|
+
import type { GalleryProps, MediaObject } from '@blogmcp/mdx-schema'
|
|
9
|
+
import { galleryTileLayout, type GalleryTileLayout } from '@blogmcp/mdx-schema/styles'
|
|
10
|
+
|
|
11
|
+
// Re-export the shared (framework-agnostic) tile-layout helper so existing
|
|
12
|
+
// importers of this composable keep working. The logic lives once in
|
|
13
|
+
// @blogmcp/mdx-schema/styles and is mirrored by the React renderer.
|
|
14
|
+
export { galleryTileLayout, type GalleryTileLayout }
|
|
15
|
+
|
|
16
|
+
const VIDEO_EXTENSIONS = /\.(mp4|webm|mov|avi|mkv|m4v)(\?.*)?$/i
|
|
17
|
+
const KB_MEDIA_FILE_RE = /\/api\/media\/file\/([^/?#]+)/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Type guard for MediaObject
|
|
21
|
+
*/
|
|
22
|
+
export function isMediaObject(value: unknown): value is MediaObject {
|
|
23
|
+
return typeof value === 'object' && value !== null && 'src' in value && typeof (value as MediaObject).src === 'string'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a URL points to a video file based on extension
|
|
28
|
+
*/
|
|
29
|
+
export function isVideoUrl(src: string): boolean {
|
|
30
|
+
return VIDEO_EXTENSIONS.test(src)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Grid column class mappings
|
|
35
|
+
*/
|
|
36
|
+
export const gridColsClasses: Record<number, string> = {
|
|
37
|
+
1: 'grid-cols-1',
|
|
38
|
+
2: 'grid-cols-2',
|
|
39
|
+
3: 'grid-cols-3',
|
|
40
|
+
4: 'grid-cols-4',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Composable for Gallery component logic
|
|
45
|
+
*/
|
|
46
|
+
export function useGallery(props: GalleryProps) {
|
|
47
|
+
const effectiveColumns = computed(() => props.columns || 2)
|
|
48
|
+
|
|
49
|
+
const gridColsClass = computed(() => {
|
|
50
|
+
const cols = effectiveColumns.value
|
|
51
|
+
return gridColsClasses[cols] || 'grid-cols-2'
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const gapClass = computed(() => {
|
|
55
|
+
const gap = props.gap || 4
|
|
56
|
+
return `gap-${gap}`
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
/** Inline tile/media styles + sizing class from aspectRatio/fit/position. */
|
|
60
|
+
const tileLayout = computed(() => galleryTileLayout(props))
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the image source URL at a given index
|
|
64
|
+
*/
|
|
65
|
+
function getImageSrc(index: number): string {
|
|
66
|
+
const image = props.images[index]
|
|
67
|
+
return isMediaObject(image) ? image.src : image
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generate alt text for an image, preferring MediaObject.alt
|
|
72
|
+
*/
|
|
73
|
+
function getImageAlt(index: number): string {
|
|
74
|
+
const image = props.images[index]
|
|
75
|
+
if (isMediaObject(image)) return image.alt || `Gallery image ${index + 1}`
|
|
76
|
+
return `Gallery image ${index + 1}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the description for an image (only MediaObject has descriptions)
|
|
81
|
+
*/
|
|
82
|
+
function getImageDescription(index: number): string | undefined {
|
|
83
|
+
const image = props.images[index]
|
|
84
|
+
return isMediaObject(image) ? image.description : undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Caption shown below a gallery item: prefer the description, fall back to the
|
|
89
|
+
* alt text. Many authors fill only one of the two, so the caption uses whichever
|
|
90
|
+
* is present (description first — it's the richer field). Returns undefined for
|
|
91
|
+
* plain string images with no metadata (no caption rendered). NOTE: uses the
|
|
92
|
+
* real authored alt, never the `getImageAlt` "Gallery image N" placeholder.
|
|
93
|
+
*/
|
|
94
|
+
function getImageCaption(index: number): string | undefined {
|
|
95
|
+
const image = props.images[index]
|
|
96
|
+
if (!isMediaObject(image)) return undefined
|
|
97
|
+
return image.description || image.alt || undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if the item at index is a video (by type field or URL extension)
|
|
102
|
+
*/
|
|
103
|
+
function isVideo(index: number): boolean {
|
|
104
|
+
const image = props.images[index]
|
|
105
|
+
if (isMediaObject(image)) {
|
|
106
|
+
if (image.type === 'video') return true
|
|
107
|
+
if (image.type === 'image') return false
|
|
108
|
+
return isVideoUrl(image.src)
|
|
109
|
+
}
|
|
110
|
+
return isVideoUrl(image)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get poster URL for a video (KB media videos only)
|
|
115
|
+
*/
|
|
116
|
+
function getVideoPoster(index: number): string | undefined {
|
|
117
|
+
const src = getImageSrc(index)
|
|
118
|
+
const match = src.match(KB_MEDIA_FILE_RE)
|
|
119
|
+
return match ? `/api/media/poster/${match[1]}` : undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if images array is valid
|
|
124
|
+
*/
|
|
125
|
+
const hasImages = computed(() => props.images && props.images.length > 0)
|
|
126
|
+
|
|
127
|
+
const imageCount = computed(() => props.images?.length || 0)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
effectiveColumns,
|
|
131
|
+
gridColsClass,
|
|
132
|
+
gapClass,
|
|
133
|
+
tileLayout,
|
|
134
|
+
getImageSrc,
|
|
135
|
+
getImageAlt,
|
|
136
|
+
getImageDescription,
|
|
137
|
+
getImageCaption,
|
|
138
|
+
isVideo,
|
|
139
|
+
getVideoPoster,
|
|
140
|
+
hasImages,
|
|
141
|
+
imageCount,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Composables
|
|
3
|
+
*
|
|
4
|
+
* Image optimization helpers for MDX components.
|
|
5
|
+
* Uses provide/inject so the host app (blog) can supply NuxtImg
|
|
6
|
+
* while the md-editor (no @nuxt/image) gets a plain 'img' fallback.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { inject, type InjectionKey, type Component } from 'vue'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Injection key for the optimized image component.
|
|
13
|
+
* The host app should `provide(IMG_COMPONENT_KEY, NuxtImg)`.
|
|
14
|
+
*/
|
|
15
|
+
export const IMG_COMPONENT_KEY: InjectionKey<Component | string> = Symbol('ImgComponent')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns the provided image component (e.g. NuxtImg),
|
|
19
|
+
* or falls back to plain `'img'` when nothing is provided.
|
|
20
|
+
*/
|
|
21
|
+
export function useOptimizedImg(): Component | string {
|
|
22
|
+
return inject(IMG_COMPONENT_KEY, 'img')
|
|
23
|
+
}
|