@shopware/cms-base-layer 0.0.0-canary-20250116171244
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/LICENSE +21 -0
- package/README.md +144 -0
- package/components/SwCategoryNavigation.vue +44 -0
- package/components/SwCategoryNavigationLink.vue +57 -0
- package/components/SwContactForm.vue +392 -0
- package/components/SwListingProductPrice.vue +88 -0
- package/components/SwMedia3D.vue +34 -0
- package/components/SwNewsletterForm.vue +347 -0
- package/components/SwPagination.vue +106 -0
- package/components/SwProductAddToCart.vue +93 -0
- package/components/SwProductCard.vue +285 -0
- package/components/SwProductGallery.vue +39 -0
- package/components/SwProductListingFilter.vue +42 -0
- package/components/SwProductListingFilters.vue +292 -0
- package/components/SwProductPrice.vue +99 -0
- package/components/SwProductReviews.vue +99 -0
- package/components/SwProductUnits.vue +54 -0
- package/components/SwSharedPrice.vue +19 -0
- package/components/SwSlider.vue +328 -0
- package/components/SwVariantConfigurator.vue +116 -0
- package/components/listing-filters/SwFilterPrice.vue +160 -0
- package/components/listing-filters/SwFilterProperties.vue +123 -0
- package/components/listing-filters/SwFilterRating.vue +101 -0
- package/components/listing-filters/SwFilterShippingFree.vue +104 -0
- package/components/public/cms/CmsGenericBlock.md +27 -0
- package/components/public/cms/CmsGenericBlock.vue +63 -0
- package/components/public/cms/CmsGenericElement.md +31 -0
- package/components/public/cms/CmsGenericElement.vue +38 -0
- package/components/public/cms/CmsNoComponent.vue +27 -0
- package/components/public/cms/CmsPage.md +36 -0
- package/components/public/cms/CmsPage.vue +65 -0
- package/components/public/cms/block/CmsBlockCategoryNavigation.vue +16 -0
- package/components/public/cms/block/CmsBlockCenterText.vue +26 -0
- package/components/public/cms/block/CmsBlockCrossSelling.vue +15 -0
- package/components/public/cms/block/CmsBlockCustomForm.vue +17 -0
- package/components/public/cms/block/CmsBlockDefault.vue +14 -0
- package/components/public/cms/block/CmsBlockForm.vue +17 -0
- package/components/public/cms/block/CmsBlockGalleryBuybox.vue +25 -0
- package/components/public/cms/block/CmsBlockImage.vue +16 -0
- package/components/public/cms/block/CmsBlockImageBubbleRow.vue +32 -0
- package/components/public/cms/block/CmsBlockImageCover.vue +17 -0
- package/components/public/cms/block/CmsBlockImageFourColumn.vue +29 -0
- package/components/public/cms/block/CmsBlockImageGallery.vue +18 -0
- package/components/public/cms/block/CmsBlockImageHighlightRow.vue +27 -0
- package/components/public/cms/block/CmsBlockImageSimpleGrid.vue +24 -0
- package/components/public/cms/block/CmsBlockImageSlider.vue +17 -0
- package/components/public/cms/block/CmsBlockImageText.vue +19 -0
- package/components/public/cms/block/CmsBlockImageTextBubble.vue +51 -0
- package/components/public/cms/block/CmsBlockImageTextCover.vue +25 -0
- package/components/public/cms/block/CmsBlockImageTextGallery.vue +85 -0
- package/components/public/cms/block/CmsBlockImageTextRow.vue +43 -0
- package/components/public/cms/block/CmsBlockImageThreeColumn.vue +21 -0
- package/components/public/cms/block/CmsBlockImageThreeCover.vue +27 -0
- package/components/public/cms/block/CmsBlockImageTwoColumn.vue +25 -0
- package/components/public/cms/block/CmsBlockProductDescriptionReviews.vue +15 -0
- package/components/public/cms/block/CmsBlockProductHeading.vue +26 -0
- package/components/public/cms/block/CmsBlockProductListing.vue +17 -0
- package/components/public/cms/block/CmsBlockProductSlider.vue +16 -0
- package/components/public/cms/block/CmsBlockProductThreeColumn.vue +22 -0
- package/components/public/cms/block/CmsBlockSidebarFilter.vue +17 -0
- package/components/public/cms/block/CmsBlockText.vue +15 -0
- package/components/public/cms/block/CmsBlockTextHero.vue +15 -0
- package/components/public/cms/block/CmsBlockTextOnImage.vue +20 -0
- package/components/public/cms/block/CmsBlockTextTeaser.vue +16 -0
- package/components/public/cms/block/CmsBlockTextTeaserSection.vue +21 -0
- package/components/public/cms/block/CmsBlockTextThreeColumn.vue +22 -0
- package/components/public/cms/block/CmsBlockTextTwoColumn.vue +28 -0
- package/components/public/cms/block/CmsBlockVimeoVideo.vue +17 -0
- package/components/public/cms/block/CmsBlockYoutubeVideo.vue +17 -0
- package/components/public/cms/element/CmsElementBuyBox.md +1 -0
- package/components/public/cms/element/CmsElementBuyBox.vue +190 -0
- package/components/public/cms/element/CmsElementCategoryNavigation.md +1 -0
- package/components/public/cms/element/CmsElementCategoryNavigation.vue +167 -0
- package/components/public/cms/element/CmsElementCrossSelling.md +1 -0
- package/components/public/cms/element/CmsElementCrossSelling.vue +106 -0
- package/components/public/cms/element/CmsElementCustomForm.md +1 -0
- package/components/public/cms/element/CmsElementCustomForm.vue +27 -0
- package/components/public/cms/element/CmsElementForm.md +1 -0
- package/components/public/cms/element/CmsElementForm.vue +27 -0
- package/components/public/cms/element/CmsElementImage.md +1 -0
- package/components/public/cms/element/CmsElementImage.vue +105 -0
- package/components/public/cms/element/CmsElementImageGallery.md +1 -0
- package/components/public/cms/element/CmsElementImageGallery.vue +249 -0
- package/components/public/cms/element/CmsElementImageGallery3dPlaceholder.vue +53 -0
- package/components/public/cms/element/CmsElementImageSlider.md +1 -0
- package/components/public/cms/element/CmsElementImageSlider.vue +29 -0
- package/components/public/cms/element/CmsElementManufacturerLogo.md +1 -0
- package/components/public/cms/element/CmsElementManufacturerLogo.vue +11 -0
- package/components/public/cms/element/CmsElementProductBox.md +1 -0
- package/components/public/cms/element/CmsElementProductBox.vue +14 -0
- package/components/public/cms/element/CmsElementProductDescriptionReviews.md +1 -0
- package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +109 -0
- package/components/public/cms/element/CmsElementProductListing.md +1 -0
- package/components/public/cms/element/CmsElementProductListing.vue +245 -0
- package/components/public/cms/element/CmsElementProductName.md +1 -0
- package/components/public/cms/element/CmsElementProductName.vue +10 -0
- package/components/public/cms/element/CmsElementProductSlider.md +1 -0
- package/components/public/cms/element/CmsElementProductSlider.vue +80 -0
- package/components/public/cms/element/CmsElementSidebarFilter.md +1 -0
- package/components/public/cms/element/CmsElementSidebarFilter.vue +12 -0
- package/components/public/cms/element/CmsElementText.md +1 -0
- package/components/public/cms/element/CmsElementText.vue +186 -0
- package/components/public/cms/element/CmsElementVimeoVideo.md +1 -0
- package/components/public/cms/element/CmsElementVimeoVideo.vue +63 -0
- package/components/public/cms/element/CmsElementYoutubeVideo.md +1 -0
- package/components/public/cms/element/CmsElementYoutubeVideo.vue +43 -0
- package/components/public/cms/section/CmsSectionDefault.md +3 -0
- package/components/public/cms/section/CmsSectionDefault.vue +21 -0
- package/components/public/cms/section/CmsSectionSidebar.md +3 -0
- package/components/public/cms/section/CmsSectionSidebar.vue +49 -0
- package/components/public/cms/skeleton/ProductCardSkeleton.vue +44 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +31 -0
- package/helpers/clientOnly.ts +11 -0
- package/helpers/html-to-vue/ast.ts +72 -0
- package/helpers/html-to-vue/getOptionsFromNode.test.ts +129 -0
- package/helpers/html-to-vue/getOptionsFromNode.ts +52 -0
- package/helpers/html-to-vue/renderToHtml.ts +45 -0
- package/helpers/html-to-vue/renderer.ts +56 -0
- package/helpers/media/isSpatial.ts +8 -0
- package/index.cjs +7 -0
- package/nuxt.config.ts +21 -0
- package/package.json +69 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useCmsSection } from "@shopware/composables";
|
|
3
|
+
import type { CmsSectionSidebar } from "@shopware/composables";
|
|
4
|
+
import { getTranslatedProperty } from "@shopware/helpers";
|
|
5
|
+
import { computed } from "vue";
|
|
6
|
+
import { useCategory } from "#imports";
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
content: CmsSectionSidebar;
|
|
10
|
+
}>();
|
|
11
|
+
const { getPositionContent } = useCmsSection(props.content);
|
|
12
|
+
|
|
13
|
+
const sidebarBlocks = getPositionContent("sidebar");
|
|
14
|
+
const mainBlocks = getPositionContent("main");
|
|
15
|
+
const mobileBehavior = computed(() => props.content.mobileBehavior);
|
|
16
|
+
const { category } = useCategory();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div class="cms-section-sidebar grid grid-cols-12 md:grid">
|
|
21
|
+
<div class="col-span-12 mx-8 md:mx-5 mt-8">
|
|
22
|
+
<h1 class="text-4xl font-extrabold tracking-tight text-gray-900">
|
|
23
|
+
{{ getTranslatedProperty(category, "name") }}
|
|
24
|
+
</h1>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="col-span-12 md:col-span-7 lg:col-span-9 order-1 md:order-2">
|
|
27
|
+
<CmsGenericBlock
|
|
28
|
+
v-for="cmsBlock in mainBlocks"
|
|
29
|
+
:key="cmsBlock.id"
|
|
30
|
+
class="overflow-auto"
|
|
31
|
+
:content="cmsBlock"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
<div
|
|
35
|
+
:class="{
|
|
36
|
+
'align-top col-span-12 md:col-span-5 lg:col-span-3 order-2 md:order-1':
|
|
37
|
+
mobileBehavior !== 'hidden',
|
|
38
|
+
'hidden md:block': mobileBehavior === 'hidden',
|
|
39
|
+
}"
|
|
40
|
+
>
|
|
41
|
+
<CmsGenericBlock
|
|
42
|
+
v-for="cmsBlock in sidebarBlocks"
|
|
43
|
+
:key="cmsBlock.id"
|
|
44
|
+
class="overflow-auto"
|
|
45
|
+
:content="cmsBlock"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
role="status"
|
|
4
|
+
class="max-w-sm p-4 border border-gray-200 rounded shadow animate-pulse md:p-6 dark:border-gray-700"
|
|
5
|
+
>
|
|
6
|
+
<div
|
|
7
|
+
class="flex items-center justify-center h-48 mb-4 bg-gray-300 rounded dark:bg-gray-700"
|
|
8
|
+
>
|
|
9
|
+
<svg
|
|
10
|
+
class="w-10 h-10 text-gray-200 dark:text-gray-600"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
viewBox="0 0 16 20"
|
|
15
|
+
>
|
|
16
|
+
<path
|
|
17
|
+
d="M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z"
|
|
18
|
+
/>
|
|
19
|
+
<path
|
|
20
|
+
d="M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
</div>
|
|
24
|
+
<div
|
|
25
|
+
class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"
|
|
26
|
+
></div>
|
|
27
|
+
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
|
|
28
|
+
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
|
|
29
|
+
<div class="mt-4 text-right">
|
|
30
|
+
<div
|
|
31
|
+
class="mt-8 h-2 bg-gray-200 rounded-full dark:bg-gray-700 w-24"
|
|
32
|
+
></div>
|
|
33
|
+
<button
|
|
34
|
+
disabled
|
|
35
|
+
tabindex="-1"
|
|
36
|
+
class="select-none font-sans font-bold text-center uppercase transition-all disabled:opacity-50 disabled:shadow-none disabled:pointer-events-none text-xs py-3 px-6 rounded-lg text-white shadow-gray-900/10 hover:shadow-gray-900/20 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none h-8 w-20 bg-gray-300 shadow-none hover:shadow-none"
|
|
37
|
+
type="button"
|
|
38
|
+
>
|
|
39
|
+
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
<span class="sr-only">Loading...</span>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
package/dist/index.d.mts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { defineNuxtModule } from '@nuxt/kit';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// -- Unbuild CommonJS Shims --
|
|
7
|
+
import __cjs_url__ from 'url';
|
|
8
|
+
import __cjs_path__ from 'path';
|
|
9
|
+
import __cjs_mod__ from 'module';
|
|
10
|
+
const __filename = __cjs_url__.fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = __cjs_path__.dirname(__filename);
|
|
12
|
+
const require = __cjs_mod__.createRequire(import.meta.url);
|
|
13
|
+
const nuxtModule = defineNuxtModule({
|
|
14
|
+
meta: {
|
|
15
|
+
name: "@shopware/cms-base",
|
|
16
|
+
configKey: "shopware-cms"
|
|
17
|
+
},
|
|
18
|
+
setup(_, nuxt) {
|
|
19
|
+
nuxt.hook("components:dirs", (c) => {
|
|
20
|
+
c.push({
|
|
21
|
+
path: resolve(__dirname, "../components/public"),
|
|
22
|
+
global: true
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
nuxt.options.imports.transform = nuxt.options.imports.transform || {};
|
|
26
|
+
nuxt.options.imports.transform.include = nuxt.options.imports.transform?.include || [];
|
|
27
|
+
nuxt.options.imports.transform?.include.push(/.+cms-base.+/);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export { nuxtModule as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineComponent, onMounted, ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export const ClientOnly = defineComponent({
|
|
4
|
+
setup(_, { slots }) {
|
|
5
|
+
const init = ref(false);
|
|
6
|
+
onMounted(() => {
|
|
7
|
+
init.value = true;
|
|
8
|
+
});
|
|
9
|
+
return () => (init.value && slots.default ? slots.default() : null);
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Based on the https://github.com/HCESrl/html-to-vue
|
|
4
|
+
*/
|
|
5
|
+
import { parse } from "html-to-ast";
|
|
6
|
+
import type { NodeObject } from "./getOptionsFromNode";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Visit each node in the AST - with callback (adapted from https://lihautan.com/manipulating-ast-with-javascript/)
|
|
10
|
+
* @param {*} ast html-parse-stringify AST
|
|
11
|
+
* @param {*} callback
|
|
12
|
+
*/
|
|
13
|
+
function _visitAST(ast, callback) {
|
|
14
|
+
function _visit(node, parent, key, index) {
|
|
15
|
+
callback(node, parent, key, index);
|
|
16
|
+
if (Array.isArray(node)) {
|
|
17
|
+
// node is an array
|
|
18
|
+
node.forEach((value, index) => {
|
|
19
|
+
_visit.call(this, value, node, null, index);
|
|
20
|
+
});
|
|
21
|
+
} else if (isNode(node)) {
|
|
22
|
+
const keys = Object.keys(node);
|
|
23
|
+
for (let i = 0; i < keys.length; i++) {
|
|
24
|
+
const child = node[keys[i]];
|
|
25
|
+
if (Array.isArray(child)) {
|
|
26
|
+
for (let j = 0; j < child.length; j++) {
|
|
27
|
+
_visit.call(this, child[j], node, key, j);
|
|
28
|
+
}
|
|
29
|
+
} else if (isNode(child)) {
|
|
30
|
+
_visit.call(this, child, node, key, undefined);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
_visit.call(this, ast, null, undefined, undefined);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param node html-parse-stringify AST node
|
|
41
|
+
* @returns {boolean|boolean}
|
|
42
|
+
*/
|
|
43
|
+
export function isNode(node: NodeObject) {
|
|
44
|
+
return typeof node === "object" && typeof node.type !== "undefined";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function generateAST(html) {
|
|
48
|
+
return parse(html);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Converts ast html nodes in vue components
|
|
53
|
+
* @param ast
|
|
54
|
+
* @param config
|
|
55
|
+
* @returns {*}
|
|
56
|
+
*/
|
|
57
|
+
export function rectifyAST(ast, config) {
|
|
58
|
+
const _ast = JSON.parse(JSON.stringify(ast));
|
|
59
|
+
const keys = config.extraComponentsMap
|
|
60
|
+
? Object.keys(config.extraComponentsMap)
|
|
61
|
+
: [];
|
|
62
|
+
_visitAST(_ast, (node) => {
|
|
63
|
+
// checking whether the AST has some components that has to become Vue Components
|
|
64
|
+
for (let i = 0; i < keys.length; i++) {
|
|
65
|
+
const currentKey = keys[i];
|
|
66
|
+
if (config.extraComponentsMap[currentKey].conditions(node)) {
|
|
67
|
+
node.name = currentKey;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return _ast;
|
|
72
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { getOptionsFromNode } from "./getOptionsFromNode";
|
|
3
|
+
import type { NodeObject } from "./getOptionsFromNode";
|
|
4
|
+
|
|
5
|
+
describe("getOptionsFromNode", () => {
|
|
6
|
+
const consoleErrorSpy = vi.spyOn(console, "error");
|
|
7
|
+
const urlResolverMock = vi.fn();
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.resetAllMocks();
|
|
11
|
+
consoleErrorSpy.mockImplementation(() => {});
|
|
12
|
+
urlResolverMock.mockImplementation((url) => `resolved-url${url}`);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should return empty object if node is undefined", () => {
|
|
16
|
+
const options = getOptionsFromNode(
|
|
17
|
+
undefined as unknown as NodeObject,
|
|
18
|
+
urlResolverMock,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(options).toEqual({
|
|
22
|
+
attrs: {},
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should return options object with style, classNames, and align", () => {
|
|
27
|
+
const node = {
|
|
28
|
+
attrs: {
|
|
29
|
+
style: "color: red",
|
|
30
|
+
class: "my-class",
|
|
31
|
+
align: "center",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const options = getOptionsFromNode(
|
|
36
|
+
node as unknown as NodeObject,
|
|
37
|
+
urlResolverMock,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(options).toEqual({
|
|
41
|
+
style: "color: red",
|
|
42
|
+
class: "my-class",
|
|
43
|
+
align: "center",
|
|
44
|
+
attrs: {},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should return options object without style, classNames, and align if they are not present in node.attrs", () => {
|
|
49
|
+
const node = {
|
|
50
|
+
attrs: {},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const options = getOptionsFromNode(
|
|
54
|
+
node as unknown as NodeObject,
|
|
55
|
+
urlResolverMock,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(options).toEqual({
|
|
59
|
+
attrs: {},
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should return empty object when no attrs in node", () => {
|
|
64
|
+
const node = {
|
|
65
|
+
attrs: undefined,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const options = getOptionsFromNode(
|
|
69
|
+
node as unknown as NodeObject,
|
|
70
|
+
urlResolverMock,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(options).toEqual({
|
|
74
|
+
attrs: {},
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should resolve URL if attrs.href exists", () => {
|
|
79
|
+
const node = {
|
|
80
|
+
attrs: {
|
|
81
|
+
href: "/path/to/page",
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const options = getOptionsFromNode(
|
|
86
|
+
node as unknown as NodeObject,
|
|
87
|
+
urlResolverMock,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(options.attrs.href).toEqual("resolved-url/path/to/page");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should add additional attrs to "attrs" object', () => {
|
|
94
|
+
const node = {
|
|
95
|
+
attrs: {
|
|
96
|
+
href: "/path/to/page",
|
|
97
|
+
"data-test": "test",
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const options = getOptionsFromNode(
|
|
102
|
+
node as unknown as NodeObject,
|
|
103
|
+
urlResolverMock,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(options.attrs.href).toEqual("resolved-url/path/to/page");
|
|
107
|
+
expect(options.attrs["data-test"]).toEqual("test");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should show console error if something went wrong", () => {
|
|
111
|
+
const node = {
|
|
112
|
+
attrs: {
|
|
113
|
+
href: "/path/to/page",
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const consoleErrorSpy = vi.spyOn(console, "error");
|
|
118
|
+
|
|
119
|
+
const options = getOptionsFromNode(
|
|
120
|
+
node as unknown as NodeObject,
|
|
121
|
+
undefined as unknown as (url: string) => string,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(options).toEqual({
|
|
125
|
+
attrs: {},
|
|
126
|
+
});
|
|
127
|
+
expect(consoleErrorSpy).toBeCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type NodeObject = {
|
|
2
|
+
type: string;
|
|
3
|
+
name: string;
|
|
4
|
+
attrs?: Options;
|
|
5
|
+
children: NodeObject[];
|
|
6
|
+
voidElement: boolean;
|
|
7
|
+
content: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type Options = {
|
|
11
|
+
align?: string;
|
|
12
|
+
attrs: Record<string, string>;
|
|
13
|
+
class?: string;
|
|
14
|
+
color?: string;
|
|
15
|
+
style?: string;
|
|
16
|
+
href?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function getOptionsFromNode(
|
|
20
|
+
node: NodeObject,
|
|
21
|
+
resolveUrl: (url: string) => string,
|
|
22
|
+
): Options {
|
|
23
|
+
const response: Options = {
|
|
24
|
+
attrs: {},
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
if (!node?.attrs) {
|
|
28
|
+
return response;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { align, style, class: classNames, href, ...attrs } = node.attrs;
|
|
32
|
+
|
|
33
|
+
if (align) {
|
|
34
|
+
response.align = align;
|
|
35
|
+
}
|
|
36
|
+
if (style) {
|
|
37
|
+
response.style = style;
|
|
38
|
+
}
|
|
39
|
+
if (classNames) {
|
|
40
|
+
response.class = classNames;
|
|
41
|
+
}
|
|
42
|
+
if (attrs && Object.keys(attrs).length > 0) {
|
|
43
|
+
response.attrs = attrs as unknown as Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
if (href) {
|
|
46
|
+
response.attrs.href = resolveUrl(href);
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error("[Shopware][cms][getOptionsFromNode] error", e);
|
|
50
|
+
}
|
|
51
|
+
return response;
|
|
52
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on the https://github.com/HCESrl/html-to-vue
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ComponentInternalInstance, h } from "vue";
|
|
6
|
+
import { generateAST, rectifyAST } from "./ast";
|
|
7
|
+
import { renderer } from "./renderer";
|
|
8
|
+
|
|
9
|
+
type DefaultConfig = {
|
|
10
|
+
container: {
|
|
11
|
+
type: string;
|
|
12
|
+
};
|
|
13
|
+
extraComponentsMap: Record<string, unknown>;
|
|
14
|
+
renderAnyway: boolean;
|
|
15
|
+
textTransformer: (text: string) => string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const defaultConfig: DefaultConfig = {
|
|
19
|
+
container: {
|
|
20
|
+
type: "div",
|
|
21
|
+
},
|
|
22
|
+
extraComponentsMap: {},
|
|
23
|
+
renderAnyway: false,
|
|
24
|
+
textTransformer: (text: string) => text,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function renderHtml(
|
|
28
|
+
html: string,
|
|
29
|
+
config: Partial<DefaultConfig>,
|
|
30
|
+
createElement: typeof h,
|
|
31
|
+
context: ComponentInternalInstance | null,
|
|
32
|
+
resolveUrl: (url: string) => string,
|
|
33
|
+
) {
|
|
34
|
+
const mergedConfig = Object.assign(defaultConfig, config);
|
|
35
|
+
const _ast = generateAST(html);
|
|
36
|
+
const _rectifiedAst = rectifyAST(_ast, config);
|
|
37
|
+
|
|
38
|
+
return renderer(
|
|
39
|
+
_rectifiedAst,
|
|
40
|
+
mergedConfig,
|
|
41
|
+
createElement,
|
|
42
|
+
context,
|
|
43
|
+
resolveUrl,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//@ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Based on the https://github.com/HCESrl/html-to-vue
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isNode } from "./ast";
|
|
7
|
+
import { getOptionsFromNode } from "./getOptionsFromNode";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* rendering the ast into vue render functions
|
|
11
|
+
* @param {*} ast AST generated by html-parse-stringify
|
|
12
|
+
* @param {*} config our configuration
|
|
13
|
+
* @param {*} createElement vue's createElement
|
|
14
|
+
* @param {*} context vue functional component context
|
|
15
|
+
*/
|
|
16
|
+
export function renderer(ast, config, createElement, context, resolveUrl) {
|
|
17
|
+
function _render(h, node) {
|
|
18
|
+
if (Array.isArray(node)) {
|
|
19
|
+
const nodes = [];
|
|
20
|
+
// node is an array
|
|
21
|
+
node.forEach((subnode, index) => {
|
|
22
|
+
nodes.push(_render.call(this, h, subnode, node, null, index, h));
|
|
23
|
+
});
|
|
24
|
+
return nodes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isNode(node)) {
|
|
28
|
+
// node is either a node with children or a node or a text node
|
|
29
|
+
if (node.type === "text") {
|
|
30
|
+
return config.textTransformer(node.content); // return text
|
|
31
|
+
}
|
|
32
|
+
if (node.type === "tag") {
|
|
33
|
+
const transformedNode = getOptionsFromNode(node, resolveUrl);
|
|
34
|
+
const children = [];
|
|
35
|
+
node.children.forEach((child, index) => {
|
|
36
|
+
children.push(_render.call(this, h, child, transformedNode, index));
|
|
37
|
+
});
|
|
38
|
+
// if it's an extra component use custom renderer
|
|
39
|
+
if (typeof config.extraComponentsMap[node.name] !== "undefined") {
|
|
40
|
+
return config.extraComponentsMap[node.name].renderer.call(
|
|
41
|
+
this,
|
|
42
|
+
transformedNode,
|
|
43
|
+
children,
|
|
44
|
+
h,
|
|
45
|
+
context,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
// else, create normal html element
|
|
49
|
+
return h(node.name, transformedNode, [...children]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return createElement(config.container.type, context.data, [
|
|
54
|
+
..._render.call(this, createElement, ast),
|
|
55
|
+
]);
|
|
56
|
+
}
|
package/index.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// CommonJS proxy to bypass jiti transforms from nuxt 2 and using native ESM
|
|
2
|
+
module.exports = function (...args) {
|
|
3
|
+
return import("./dist/index.mjs").then((m) => m.default.call(this, ...args));
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line
|
|
7
|
+
module.exports.meta = require("./package.json");
|
package/nuxt.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { NuxtConfig } from "@nuxt/schema";
|
|
2
|
+
import { defineNuxtConfig } from "nuxt/config";
|
|
3
|
+
export default defineNuxtConfig({
|
|
4
|
+
components: [
|
|
5
|
+
{
|
|
6
|
+
path: "./components/public",
|
|
7
|
+
pathPrefix: false,
|
|
8
|
+
// global: true,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
path: "./components/",
|
|
12
|
+
pattern: "Sw*",
|
|
13
|
+
extensions: [".vue"],
|
|
14
|
+
global: false,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
build: {
|
|
18
|
+
transpile: ["@shopware/cms-base-layer"],
|
|
19
|
+
},
|
|
20
|
+
telemetry: false,
|
|
21
|
+
}) as NuxtConfig;
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shopware/cms-base-layer",
|
|
3
|
+
"version": "0.0.0-canary-20250116171244",
|
|
4
|
+
"description": "Vue CMS Nuxt Layer for Shopware",
|
|
5
|
+
"author": "Shopware",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/shopware/frontends.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://frontends.shopware.com/framework/shopping-experiences.html",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"Shopware",
|
|
13
|
+
"Vue",
|
|
14
|
+
"CMS",
|
|
15
|
+
"Nuxt",
|
|
16
|
+
"Layer"
|
|
17
|
+
],
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/shopware/frontends/issues"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./nuxt.config.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"components",
|
|
27
|
+
"helpers",
|
|
28
|
+
"index.cjs",
|
|
29
|
+
"nuxt.config.ts"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@nuxt/kit": "3.14.1592",
|
|
33
|
+
"@tresjs/cientos": "4.0.2",
|
|
34
|
+
"@tresjs/core": "4.3.1",
|
|
35
|
+
"@vuelidate/core": "2.0.3",
|
|
36
|
+
"@vuelidate/validators": "2.0.4",
|
|
37
|
+
"@vueuse/core": "11.2.0",
|
|
38
|
+
"entities": "5.0.0",
|
|
39
|
+
"html-to-ast": "0.0.6",
|
|
40
|
+
"three": "0.166.1",
|
|
41
|
+
"vue": "3.5.13",
|
|
42
|
+
"xss": "1.0.15",
|
|
43
|
+
"@shopware/composables": "0.0.0-canary-20250116171244",
|
|
44
|
+
"@shopware/helpers": "0.0.0-canary-20250116171244",
|
|
45
|
+
"@shopware/api-client": "1.2.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@biomejs/biome": "1.8.3",
|
|
49
|
+
"@nuxt/schema": "3.14.1592",
|
|
50
|
+
"@types/three": "0.166.0",
|
|
51
|
+
"@vitest/coverage-v8": "2.1.8",
|
|
52
|
+
"nuxt": "3.14.1592",
|
|
53
|
+
"typescript": "5.6.3",
|
|
54
|
+
"unbuild": "2.0.0",
|
|
55
|
+
"vitest": "2.1.8",
|
|
56
|
+
"vue-router": "4.5.0",
|
|
57
|
+
"vue-tsc": "2.1.10",
|
|
58
|
+
"tsconfig": "0.0.0"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "unbuild",
|
|
62
|
+
"dev": "unbuild --stub",
|
|
63
|
+
"lint": "biome check .",
|
|
64
|
+
"lint:fix": "biome check . --write && pnpm run typecheck",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"test": "vitest run",
|
|
67
|
+
"test:watch": "vitest"
|
|
68
|
+
}
|
|
69
|
+
}
|