orionplace-svelte 0.6.0 → 0.6.2
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/package.json +1 -1
- package/runtime/marketplace.d.mts +12 -0
- package/runtime/marketplace.mjs +31 -2
- package/src/project-readme.mjs +6 -0
- package/templates/src/hooks.server.ts +8 -0
- package/templates/src/lib/components/orionplace/blog/BlogArticle.README.md +2 -0
- package/templates/src/lib/components/orionplace/blog/BlogArticle.svelte +239 -70
- package/templates/src/lib/components/orionplace/catalog/StorefrontCatalog.svelte +2 -2
- package/templates/src/lib/components/orionplace/products/ProductCard.svelte +17 -0
- package/templates/src/lib/components/orionplace/products/ProductGroup.svelte +6 -0
- package/templates/src/lib/composables/useOrionplacePageSeo.svelte.ts +31 -6
- package/templates/src/lib/composables/useOrionplaceProductSearch.svelte.ts +130 -11
- package/templates/src/lib/layouts/StorefrontPageHead.svelte +9 -0
- package/templates/src/lib/orionplace/navigation.svelte.ts +10 -2
- package/templates/src/lib/orionplace/pageLoad.ts +11 -1
- package/templates/src/lib/utils/orionplace/blogArticleHtml.ts +1 -0
- package/templates/src/lib/utils/orionplace/blogProductPlacements.ts +105 -0
- package/templates/src/lib/utils/orionplace/pageSeo.ts +68 -0
- package/templates/src/lib/utils/orionplace/productListLocation.ts +130 -0
- package/templates/src/routes/browse/+page.svelte +8 -1
- package/templates/src/routes/category/[id]/+page.svelte +12 -4
- package/templates/src/routes/featured/+page.svelte +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orionplace-svelte",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "SvelteKit 2 storefront for your Orionplace marketplace: setup CLI, server API gateway, typed store and ready-made catalog, cart, checkout and account components in English and Russian, plus an Android app build.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,18 @@ export declare function marketplacePublicOrigin(
|
|
|
37
37
|
tenantDomain?: string,
|
|
38
38
|
): string;
|
|
39
39
|
|
|
40
|
+
export declare function noindexPathPrefixes(value?: string): string[];
|
|
41
|
+
|
|
42
|
+
export declare function marketplaceNoindexPath(
|
|
43
|
+
pathname: string,
|
|
44
|
+
value?: string,
|
|
45
|
+
): boolean;
|
|
46
|
+
|
|
47
|
+
export declare function sitemapIndexedPages(
|
|
48
|
+
pages: MarketplaceSitemapPage[],
|
|
49
|
+
value?: string,
|
|
50
|
+
): MarketplaceSitemapPage[];
|
|
51
|
+
|
|
40
52
|
export declare function marketplaceRobots(origin: string): string;
|
|
41
53
|
|
|
42
54
|
export declare function marketplaceRobotsResponse(
|
package/runtime/marketplace.mjs
CHANGED
|
@@ -329,6 +329,7 @@ export async function forwardMarketplace(request, configuration = {}) {
|
|
|
329
329
|
// node_modules/.cache/orionplace/sitemaps/<domain>.json (ORIONPLACE_CACHE_DIR
|
|
330
330
|
// moves it). "orionplace-<framework> sitemap rebuild" rebuilds that file at
|
|
331
331
|
// once; the running storefront serves the new file without a restart.
|
|
332
|
+
// Paths in ORIONPLACE_NOINDEX_PATHS are left out when the sitemap is served.
|
|
332
333
|
|
|
333
334
|
export const SITEMAP_LIFETIME = 60 * 60 * 1000;
|
|
334
335
|
const sitemapPageSize = 100;
|
|
@@ -353,6 +354,34 @@ export function marketplacePublicOrigin(request, tenantDomain = '') {
|
|
|
353
354
|
return local || !tenantDomain ? url.origin : `https://${tenantDomain}`;
|
|
354
355
|
}
|
|
355
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Pages kept out of search engines: ORIONPLACE_NOINDEX_PATHS lists path
|
|
359
|
+
* prefixes separated by spaces or commas, for example "/catalog /category
|
|
360
|
+
* /product" on a demo or staging storefront. Those pages answer with
|
|
361
|
+
* `X-Robots-Tag: noindex, follow` and the sitemap leaves them out. Empty
|
|
362
|
+
* (the default) keeps every public page indexable.
|
|
363
|
+
*/
|
|
364
|
+
export function noindexPathPrefixes(value = environment.ORIONPLACE_NOINDEX_PATHS) {
|
|
365
|
+
return String(value ?? '')
|
|
366
|
+
.split(/[\s,]+/)
|
|
367
|
+
.map((item) => item.trim().replace(/\/+$/, ''))
|
|
368
|
+
.filter((item) => /^\/[A-Za-z0-9._~\/-]+$/.test(item));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Whether a page path is kept out of search engines (see noindexPathPrefixes). */
|
|
372
|
+
export function marketplaceNoindexPath(pathname, value = environment.ORIONPLACE_NOINDEX_PATHS) {
|
|
373
|
+
const path = String(pathname ?? '');
|
|
374
|
+
return noindexPathPrefixes(value)
|
|
375
|
+
.some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** The sitemap pages search engines may index. */
|
|
379
|
+
export function sitemapIndexedPages(pages, value = environment.ORIONPLACE_NOINDEX_PATHS) {
|
|
380
|
+
return noindexPathPrefixes(value).length
|
|
381
|
+
? pages.filter((page) => !marketplaceNoindexPath(page.path, value))
|
|
382
|
+
: pages;
|
|
383
|
+
}
|
|
384
|
+
|
|
356
385
|
/** @param {string} origin */
|
|
357
386
|
export function marketplaceRobots(origin) {
|
|
358
387
|
return `User-agent: *\nDisallow: /api/\nAllow: /\n\nSitemap: ${origin}/sitemap.xml\n`;
|
|
@@ -618,7 +647,7 @@ async function buildMarketplaceSitemap(tenant) {
|
|
|
618
647
|
/** How many <url> entries a built sitemap has. */
|
|
619
648
|
export function sitemapUrlCount(sitemap) {
|
|
620
649
|
let count = 0;
|
|
621
|
-
for (const page of sitemap.pages) {
|
|
650
|
+
for (const page of sitemapIndexedPages(sitemap.pages)) {
|
|
622
651
|
const own = page.locales
|
|
623
652
|
? sitemap.locales.filter((code) => page.locales.includes(code))
|
|
624
653
|
: sitemap.locales;
|
|
@@ -776,7 +805,7 @@ export async function marketplaceSitemapResponse(request, configuration = {}) {
|
|
|
776
805
|
}
|
|
777
806
|
}
|
|
778
807
|
const origin = marketplacePublicOrigin(request, tenant.domain);
|
|
779
|
-
return new Response(marketplaceSitemapXml(origin, sitemap.pages, sitemap.locales), {
|
|
808
|
+
return new Response(marketplaceSitemapXml(origin, sitemapIndexedPages(sitemap.pages), sitemap.locales), {
|
|
780
809
|
headers: {
|
|
781
810
|
'Content-Type': 'application/xml; charset=utf-8',
|
|
782
811
|
'Cache-Control': 'public, max-age=3600',
|
package/src/project-readme.mjs
CHANGED
|
@@ -397,6 +397,12 @@ npx orionplace-svelte sitemap rebuild shop.example.com # one domain
|
|
|
397
397
|
npx orionplace-svelte sitemap status # when each was built
|
|
398
398
|
\`\`\`
|
|
399
399
|
|
|
400
|
+
To keep a demo or staging storefront's shop pages out of search engines, set
|
|
401
|
+
\`ORIONPLACE_NOINDEX_PATHS\` in \`.env\` to path prefixes separated by spaces,
|
|
402
|
+
for example \`ORIONPLACE_NOINDEX_PATHS="/catalog /category /product"\`. Those
|
|
403
|
+
pages answer with \`X-Robots-Tag: noindex, follow\` and the sitemap leaves them
|
|
404
|
+
out; the journal and other pages stay indexable. Leave it empty on a live shop.
|
|
405
|
+
|
|
400
406
|
This setup does not add analytics.
|
|
401
407
|
|
|
402
408
|
## Android app
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Handle } from '@sveltejs/kit';
|
|
2
|
+
import { env } from '$env/dynamic/private';
|
|
3
|
+
import { marketplaceNoindexPath } from '@orionplace-runtime/marketplace.mjs';
|
|
2
4
|
|
|
3
5
|
function escapeAttribute(value: string) {
|
|
4
6
|
return value
|
|
@@ -30,6 +32,12 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
|
30
32
|
response.headers.set('Cache-Control', 'private, no-store');
|
|
31
33
|
response.headers.set('Vary', 'Host, X-Forwarded-Host, X-Front-Domain');
|
|
32
34
|
}
|
|
35
|
+
// Pages under ORIONPLACE_NOINDEX_PATHS stay out of search engines.
|
|
36
|
+
if (
|
|
37
|
+
marketplaceNoindexPath(event.url.pathname, env.ORIONPLACE_NOINDEX_PATHS)
|
|
38
|
+
) {
|
|
39
|
+
response.headers.set('X-Robots-Tag', 'noindex, follow');
|
|
40
|
+
}
|
|
33
41
|
// A page rendered while Application Load failed is the 503 error page.
|
|
34
42
|
if (
|
|
35
43
|
event.locals.orionplaceUnavailable &&
|
|
@@ -8,6 +8,8 @@ Article videos may come as the backend's own paths (`/ps3/...`). In an Android a
|
|
|
8
8
|
|
|
9
9
|
The cover and the images of the text open larger: a click, Enter or Space on one opens `ProductReviewImageViewer` at that image, with the article's other images a step away and each image's caption (its `figcaption`, else its `title`, else its `alt`) under it. The images get `role="button"`, a keyboard stop and an «Open image» name; a zoom-in cursor marks them. Images inside a link, a button or a product placement keep their own action. The helper is `src/lib/utils/orionplace/blogImageViewer.ts`.
|
|
10
10
|
|
|
11
|
+
Products the author places in the text appear at those positions: an empty block with `data-product-id="12"` shows one product card, `data-product-ids="12,15,9"` a row of up to 24 cards. The block keeps the editor's float or centred style, and `data-product-hidden-badges` (`new`, `popular`, `good-deal`) turns card badges off. Products missing from `related_products` are loaded with `products_search` and `filters.id_in`, up to 35 IDs per request, and stored with `store.setProduct()`; a block whose products are not found stays hidden. Article text styles do not reach the cards, and in a narrow article a floated card stands alone in the middle. The helper is `src/lib/utils/orionplace/blogProductPlacements.ts`.
|
|
12
|
+
|
|
11
13
|
Source: `src/lib/components/orionplace/blog/BlogArticle.svelte`.
|
|
12
14
|
|
|
13
15
|
## Inputs
|