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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orionplace-svelte",
3
- "version": "0.6.0",
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(
@@ -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',
@@ -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