site template cleanup 1/3
This commit is contained in:
parent
a64acb9f71
commit
bc07c94501
File diff suppressed because one or more lines are too long
935
package-lock.json
generated
935
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -53,7 +53,7 @@
|
|||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
"glob": "^11.0.1",
|
"glob": "^11.0.1",
|
||||||
"got": "^14.4.6",
|
"got": "^14.4.6",
|
||||||
"imagetools": "file:packages/imagetools",
|
"imagetools": "file:../astro-components/packages/imagetools",
|
||||||
"lighthouse": "^12.3.0",
|
"lighthouse": "^12.3.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"marked": "^15.0.7",
|
"marked": "^15.0.7",
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import cli from 'yargs'
|
|
||||||
import { hideBin } from 'yargs/helpers'
|
|
||||||
import { } from './network.js'
|
|
||||||
|
|
||||||
const argv = cli(hideBin(process.argv)).parse()
|
|
||||||
|
|
||||||
export const options = () => {
|
|
||||||
console.log('Options: ', argv)
|
|
||||||
}
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
{
|
|
||||||
"site": {
|
|
||||||
"title": "Polymech",
|
|
||||||
"base_url": "https://polymech.io/",
|
|
||||||
"description" : "",
|
|
||||||
"base_path": "/",
|
|
||||||
"trailing_slash": false,
|
|
||||||
"favicon": "/images/favicon.png",
|
|
||||||
"logo": "/images/logo.png",
|
|
||||||
"logo_darkmode": "/images/logo-darkmode.png",
|
|
||||||
"logo_width": "150",
|
|
||||||
"logo_height": "33",
|
|
||||||
"logo_text": "Astrofront",
|
|
||||||
"image": {
|
|
||||||
"default": "/images/default-image.png",
|
|
||||||
"error": "/images/error-image.png",
|
|
||||||
"alt": "Astrofront",
|
|
||||||
"src": "https://assets.osr-plastic.org/machines//assets/newsletter/common/products/extruders/overview-3.jpg"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"footer_left": [
|
|
||||||
{
|
|
||||||
"href": "/infopages/privacy",
|
|
||||||
"text": "Privacy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "/infopages/contact",
|
|
||||||
"text": "Contact"
|
|
||||||
} ,
|
|
||||||
{
|
|
||||||
"href": "/newsletter/newsletter_2024_09_en-hugo-release.html",
|
|
||||||
"text": "Newsletter"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"footer_right": [
|
|
||||||
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"search": true,
|
|
||||||
"account": true,
|
|
||||||
"sticky_header": true,
|
|
||||||
"theme_switcher": true,
|
|
||||||
"default_theme": "system"
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"contact_form_action": "#",
|
|
||||||
"copyright": "Designed And Developed by [Themefisher](https://themefisher.com/)"
|
|
||||||
},
|
|
||||||
"navigation_button": {
|
|
||||||
"enable": true,
|
|
||||||
"label": "Get Started",
|
|
||||||
"link": "https://github.com/themefisher/astrofront"
|
|
||||||
},
|
|
||||||
"ecommerce": {
|
|
||||||
"brand": "Polymech",
|
|
||||||
"currencySymbol": "",
|
|
||||||
"currencyCode": "EUR"
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"country": "Spain",
|
|
||||||
"city": "Barcelona",
|
|
||||||
"author": "Polymech",
|
|
||||||
"author_bio": "I am in, if its true",
|
|
||||||
"author_url": "https://polymech.io/",
|
|
||||||
"image": "/images/og-image.png",
|
|
||||||
"description": "Polymech is a plastic prototyping company that offers product design services.",
|
|
||||||
"keywords": "Plastic, Prototyping, Product Design, Opensource"
|
|
||||||
},
|
|
||||||
"shopify": {
|
|
||||||
"currencySymbol": "",
|
|
||||||
"currencyCode": "EUR",
|
|
||||||
"collections": {
|
|
||||||
"hero_slider": "hidden-homepage-carousel",
|
|
||||||
"featured_products": "featured-products"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pages":{
|
|
||||||
"home":{
|
|
||||||
"hero": "https://assets.osr-plastic.org/machines//assets/newsletter/common/products/extruders/overview-3.jpg",
|
|
||||||
"blog":{
|
|
||||||
"store": "resources"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tracking": {
|
|
||||||
"googleAnalytics": "G-RW6Q6EG3J0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,170 +0,0 @@
|
|||||||
import * as path from 'path'
|
|
||||||
import { IMAGE_PRESET, E_BROADBAND_SPEED } from "./network.js"
|
|
||||||
import { resolve, template } from '@polymech/commons'
|
|
||||||
import { sync as read } from '@polymech/fs/read'
|
|
||||||
import { sanitizeUri } from 'micromark-util-sanitize-uri'
|
|
||||||
|
|
||||||
export const OSR_ROOT = () => path.resolve(resolve("${OSR_ROOT}"))
|
|
||||||
|
|
||||||
export const LOGGING_NAMESPACE = 'polymech-site'
|
|
||||||
export const TRANSLATE_CONTENT = true
|
|
||||||
export const LANGUAGES = ['en', 'ar', 'de', 'ja', 'es', 'zh', 'fr']
|
|
||||||
export const LANGUAGES_PROD = ['en', 'es', 'ar', 'de', 'ja', 'zh', 'fr', 'nl', 'it', 'pt']
|
|
||||||
export const isRTL = (lang) => lang === 'ar'
|
|
||||||
|
|
||||||
// i18n constants
|
|
||||||
export const I18N_STORE = (root, lang) => `${root}/i18n-store/store-${lang}.json`
|
|
||||||
export const I18N_SOURCE_LANGUAGE = 'en'
|
|
||||||
export const I18N_CACHE = true
|
|
||||||
export const I18N_ASSET_PATH = "${SRC_DIR}/${SRC_NAME}-${DST_LANG}${SRC_EXT}"
|
|
||||||
|
|
||||||
// Products
|
|
||||||
export const PRODUCT_ROOT = () => path.resolve(resolve("${OSR_ROOT}/products"))
|
|
||||||
export const PRODUCT_BRANCHES = read(path.join(PRODUCT_ROOT(), 'config/machines.json'), 'json')
|
|
||||||
export const PRODUCT_GLOB = '**/config.json'
|
|
||||||
|
|
||||||
// Product compiler
|
|
||||||
export const PRODUCT_CONFIG = (product) =>
|
|
||||||
path.resolve(resolve(`${PRODUCT_ROOT()}/${product}/config.json`, false,
|
|
||||||
{
|
|
||||||
product
|
|
||||||
}))
|
|
||||||
export const PRODUCT_DIR = (product) => path.resolve(resolve(`${PRODUCT_ROOT()}/${product}`))
|
|
||||||
export const PRODUCT_HUGO_TEMPLATE = './osr/hugo/root.html'
|
|
||||||
export const PRODUCTS_TARGET_SRC = './src/content/en/retail'
|
|
||||||
export const PRODUCTS_TARGET = (lang) => `./content/${lang}/products`
|
|
||||||
|
|
||||||
// Product assets
|
|
||||||
|
|
||||||
export const ASSETS_LOCAL = false
|
|
||||||
|
|
||||||
// OSRL - Language
|
|
||||||
export const IS_DEV = true
|
|
||||||
export const OSRL_ENV = 'astro-release'
|
|
||||||
export const OSRL_ENV_DEV = 'astro-debug'
|
|
||||||
export const OSRL_ENVIRONMENT = IS_DEV ? OSRL_ENV_DEV : OSRL_ENV
|
|
||||||
export const OSRL_MODULE_NAME = 'polymech.io'
|
|
||||||
export const OSRL_PRODUCT_PROFILE = './src/app/profile.json'
|
|
||||||
export const OSRL_LANG_FLAVOR = 'osr'
|
|
||||||
|
|
||||||
// Products
|
|
||||||
export const ENABLED_PRODUCTS = "${OSR_ROOT}/products/config/machines.json"
|
|
||||||
export const PRODUCT_SPECS = (rel) => `${PRODUCT_ROOT()}/${rel}/specs.xlsx`
|
|
||||||
|
|
||||||
// Tasks
|
|
||||||
export const TASK_CONFIG_LOG_DIRECTORY = './config/'
|
|
||||||
|
|
||||||
// Task: compile:content
|
|
||||||
export const TASK_COMPILE_CONTENT = true
|
|
||||||
export const TASK_COMPILE_CONTENT_CACHE = false
|
|
||||||
|
|
||||||
// Task - Logging
|
|
||||||
export const TASK_LOG_DIRECTORY = './logs/'
|
|
||||||
|
|
||||||
// Task - Retail Config
|
|
||||||
export const REGISTER_PRODUCT_TASKS = true
|
|
||||||
export const RETAIL_PRODUCT_BRANCH = 'site'
|
|
||||||
export const PROJECTS_BRANCH = 'projects'
|
|
||||||
export const RETAIL_COMPILE_CACHE = false
|
|
||||||
export const RETAIL_MEDIA_CACHE = true
|
|
||||||
export const RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS = 'info'
|
|
||||||
|
|
||||||
export const ConvertProductMedia = true
|
|
||||||
export const TranslateProductAssets = false
|
|
||||||
export const PopulateProductDefaults = true
|
|
||||||
|
|
||||||
// CAD
|
|
||||||
export const CAD_MAIN_MATCH = (product) => `${product}/cad*/*Global*.+(SLDASM)`
|
|
||||||
export const CAD_CAM_MAIN_MATCH = (product) => `${product}/cad*/*-CNC*.+(SLDASM)`
|
|
||||||
|
|
||||||
export const CAD_CACHE = true
|
|
||||||
export const CAD_EXPORT_CONFIGURATIONS = true
|
|
||||||
export const CAD_EXPORT_SUB_COMPONENTS = true
|
|
||||||
export const CAD_MODEL_FILE_PATH = (SOURCE, CONFIGURATION = '') =>
|
|
||||||
SOURCE.replace('.json', `${CONFIGURATION ? '-' + CONFIGURATION : ''}.tree.json`)
|
|
||||||
export const CAD_DEFAULT_CONFIGURATION = 'Default'
|
|
||||||
export const CAD_RENDERER = 'solidworks'
|
|
||||||
export const CAD_RENDERER_VIEW = 'Render'
|
|
||||||
export const CAD_RENDERER_QUALITY = 1
|
|
||||||
export const CAD_EXTENSIONS = ['.STEP', '.html']
|
|
||||||
export const CAD_MODEL_EXT = '.tree.json'
|
|
||||||
|
|
||||||
export const CAD_URL = (file: string, variables: Record<string, string>) =>
|
|
||||||
sanitizeUri(template("${OSR_MACHINES_ASSETS_URL}/${file}", { file, ...variables }))
|
|
||||||
|
|
||||||
export const ASSET_URL = (file: string, variables: Record<string, string>) =>
|
|
||||||
sanitizeUri(template("${OSR_MACHINES_ASSETS_URL}/products/${product_rel_min}/${file}", { file, ...variables }))
|
|
||||||
|
|
||||||
export const ITEM_ASSET_URL = (variables: Record<string, string>) =>
|
|
||||||
template("${OSR_MACHINES_ASSETS_URL}/${ITEM_REL}/${assetPath}/${filePath}", variables)
|
|
||||||
|
|
||||||
|
|
||||||
//back compat - osr-cad
|
|
||||||
export const parseBoolean = (value: string): boolean => {
|
|
||||||
return value === '1' || value.toLowerCase() === 'true';
|
|
||||||
}
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Rendering
|
|
||||||
export const SHOW_DESCRIPTION = false
|
|
||||||
export const SHOW_LICENSE = true
|
|
||||||
export const SHOW_RENDERINGS = true
|
|
||||||
|
|
||||||
export const SHOW_TABS = false
|
|
||||||
export const SHOW_GALLERY = true
|
|
||||||
export const SHOW_FILES = true
|
|
||||||
export const SHOW_SPECS = true
|
|
||||||
export const SHOW_CHECKOUT = true
|
|
||||||
export const SHOW_CONTACT = true
|
|
||||||
export const SHOW_3D_PREVIEW = true
|
|
||||||
export const SHOW_RESOURCES = true
|
|
||||||
export const SHOW_DEBUG = false
|
|
||||||
export const SHOW_SAMPLES = true
|
|
||||||
export const SHOW_README = false
|
|
||||||
export const SHOW_RELATED = true
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Plugins
|
|
||||||
|
|
||||||
// RSS
|
|
||||||
export const RSS_CONFIG =
|
|
||||||
{
|
|
||||||
title: 'Polymech RSS Feed',
|
|
||||||
description: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Defaults
|
|
||||||
export const DEFAULT_IMAGE_URL = 'https://picsum.photos/640/640'
|
|
||||||
export const default_image = () => {
|
|
||||||
return {
|
|
||||||
alt: 'none',
|
|
||||||
src: DEFAULT_IMAGE_URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const DEFAULT_LICENSE = `CERN Open Source Hardware License`
|
|
||||||
export const DEFAULT_CONTACT = `sales@plastic-hub.com`
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Optimization
|
|
||||||
|
|
||||||
export const O_IMAGE = IMAGE_PRESET[E_BROADBAND_SPEED.MEDIUM]
|
|
||||||
export const IMAGE_SETTINGS =
|
|
||||||
{
|
|
||||||
GALLERY: {
|
|
||||||
SHOW_TITLE: true,
|
|
||||||
SHOW_DESCRIPTION: false,
|
|
||||||
SIZES_THUMB: O_IMAGE.sizes_thumbs,
|
|
||||||
SIZES_LARGE: O_IMAGE.sizes_large,
|
|
||||||
SIZES_REGULAR: O_IMAGE.sizes_medium
|
|
||||||
},
|
|
||||||
LIGHTBOX: {
|
|
||||||
SHOW_TITLE: true,
|
|
||||||
SHOW_DESCRIPTION: true,
|
|
||||||
SIZES_THUMB: O_IMAGE.sizes_thumbs,
|
|
||||||
SIZES_LARGE: O_IMAGE.sizes_large,
|
|
||||||
SIZES_REGULAR: O_IMAGE.sizes_medium
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import { promises as fs, existsSync } from 'node:fs';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import yaml from 'js-yaml';
|
|
||||||
import { posixRelative } from '../utils.js';
|
|
||||||
import type { Loader, LoaderContext } from './types.js';
|
|
||||||
|
|
||||||
export interface FileOptions {
|
|
||||||
/**
|
|
||||||
* the parsing function to use for this data
|
|
||||||
* @default JSON.parse or yaml.load, depending on the extension of the file
|
|
||||||
* */
|
|
||||||
parser?: (
|
|
||||||
text: string,
|
|
||||||
) => Record<string, Record<string, unknown>> | Array<Record<string, unknown>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads entries from a JSON file. The file must contain an array of objects that contain unique `id` fields, or an object with string keys.
|
|
||||||
* @param fileName The path to the JSON file to load, relative to the content directory.
|
|
||||||
* @param options Additional options for the file loader
|
|
||||||
*/
|
|
||||||
export function file(fileName: string, options?: FileOptions): Loader {
|
|
||||||
if (fileName.includes('*')) {
|
|
||||||
// TODO: AstroError
|
|
||||||
throw new Error('Glob patterns are not supported in `file` loader. Use `glob` loader instead.');
|
|
||||||
}
|
|
||||||
|
|
||||||
let parse: ((text: string) => any) | null = null;
|
|
||||||
|
|
||||||
const ext = fileName.split('.').at(-1);
|
|
||||||
if (ext === 'json') {
|
|
||||||
parse = JSON.parse;
|
|
||||||
} else if (ext === 'yml' || ext === 'yaml') {
|
|
||||||
parse = (text) =>
|
|
||||||
yaml.load(text, {
|
|
||||||
filename: fileName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (options?.parser) parse = options.parser;
|
|
||||||
|
|
||||||
if (parse === null) {
|
|
||||||
// TODO: AstroError
|
|
||||||
throw new Error(
|
|
||||||
`No parser found for file '${fileName}'. Try passing a parser to the \`file\` loader.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function syncData(filePath: string, { logger, parseData, store, config }: LoaderContext) {
|
|
||||||
let data: Array<Record<string, unknown>> | Record<string, Record<string, unknown>>;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const contents = await fs.readFile(filePath, 'utf-8');
|
|
||||||
data = parse!(contents);
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(`Error reading data from ${fileName}`);
|
|
||||||
logger.debug(error.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedFilePath = posixRelative(fileURLToPath(config.root), filePath);
|
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
if (data.length === 0) {
|
|
||||||
logger.warn(`No items found in ${fileName}`);
|
|
||||||
}
|
|
||||||
logger.debug(`Found ${data.length} item array in ${fileName}`);
|
|
||||||
store.clear();
|
|
||||||
for (const rawItem of data) {
|
|
||||||
const id = (rawItem.id ?? rawItem.slug)?.toString();
|
|
||||||
if (!id) {
|
|
||||||
logger.error(`Item in ${fileName} is missing an id or slug field.`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const parsedData = await parseData({ id, data: rawItem, filePath });
|
|
||||||
store.set({ id, data: parsedData, filePath: normalizedFilePath });
|
|
||||||
}
|
|
||||||
} else if (typeof data === 'object') {
|
|
||||||
const entries = Object.entries<Record<string, unknown>>(data);
|
|
||||||
logger.debug(`Found object with ${entries.length} entries in ${fileName}`);
|
|
||||||
store.clear();
|
|
||||||
for (const [id, rawItem] of entries) {
|
|
||||||
const parsedData = await parseData({ id, data: rawItem, filePath });
|
|
||||||
store.set({ id, data: parsedData, filePath: normalizedFilePath });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error(`Invalid data in ${fileName}. Must be an array or object.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'file-loader',
|
|
||||||
load: async (context) => {
|
|
||||||
const { config, logger, watcher } = context;
|
|
||||||
logger.debug(`Loading data from ${fileName}`);
|
|
||||||
const url = new URL(fileName, config.root);
|
|
||||||
if (!existsSync(url)) {
|
|
||||||
logger.error(`File not found: ${fileName}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const filePath = fileURLToPath(url);
|
|
||||||
|
|
||||||
await syncData(filePath, context);
|
|
||||||
|
|
||||||
watcher?.add(filePath);
|
|
||||||
|
|
||||||
watcher?.on('change', async (changedPath) => {
|
|
||||||
if (changedPath === filePath) {
|
|
||||||
logger.info(`Reloading data from ${fileName}`);
|
|
||||||
await syncData(filePath, context);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,358 +0,0 @@
|
|||||||
import { promises as fs, existsSync } from 'node:fs';
|
|
||||||
import { relative } from 'node:path';
|
|
||||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
||||||
import fastGlob from 'fast-glob';
|
|
||||||
import { bold, green } from 'kleur/colors';
|
|
||||||
import micromatch from 'micromatch';
|
|
||||||
import pLimit from 'p-limit';
|
|
||||||
import type { ContentEntryRenderFunction, ContentEntryType } from '../../types/public/content.js';
|
|
||||||
import type { RenderedContent } from '../data-store.js';
|
|
||||||
import { getContentEntryIdAndSlug, posixRelative } from '../utils.js';
|
|
||||||
import type { Loader } from './types.js';
|
|
||||||
|
|
||||||
export interface GenerateIdOptions {
|
|
||||||
/** The path to the entry file, relative to the base directory. */
|
|
||||||
entry: string;
|
|
||||||
|
|
||||||
/** The base directory URL. */
|
|
||||||
base: URL;
|
|
||||||
/** The parsed, unvalidated data of the entry. */
|
|
||||||
data: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GlobOptions {
|
|
||||||
/** The glob pattern to match files, relative to the base directory */
|
|
||||||
pattern: string | Array<string>;
|
|
||||||
/** The base directory to resolve the glob pattern from. Relative to the root directory, or an absolute file URL. Defaults to `.` */
|
|
||||||
base?: string | URL;
|
|
||||||
/**
|
|
||||||
* Function that generates an ID for an entry. Default implementation generates a slug from the entry path.
|
|
||||||
* @returns The ID of the entry. Must be unique per collection.
|
|
||||||
**/
|
|
||||||
generateId?: (options: GenerateIdOptions) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateIdDefault({ entry, base, data }: GenerateIdOptions): string {
|
|
||||||
if (data.slug) {
|
|
||||||
return data.slug as string;
|
|
||||||
}
|
|
||||||
const entryURL = new URL(encodeURI(entry), base);
|
|
||||||
const { slug } = getContentEntryIdAndSlug({
|
|
||||||
entry: entryURL,
|
|
||||||
contentDir: base,
|
|
||||||
collection: '',
|
|
||||||
});
|
|
||||||
return slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPrefix(pattern: string | Array<string>, prefix: string) {
|
|
||||||
if (Array.isArray(pattern)) {
|
|
||||||
return pattern.some((p) => p.startsWith(prefix));
|
|
||||||
}
|
|
||||||
return pattern.startsWith(prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads multiple entries, using a glob pattern to match files.
|
|
||||||
* @param pattern A glob pattern to match files, relative to the content directory.
|
|
||||||
*/
|
|
||||||
export function glob(globOptions: GlobOptions): Loader;
|
|
||||||
/** @private */
|
|
||||||
export function glob(
|
|
||||||
globOptions: GlobOptions & {
|
|
||||||
/** @deprecated */
|
|
||||||
_legacy?: true;
|
|
||||||
},
|
|
||||||
): Loader;
|
|
||||||
|
|
||||||
export function glob(globOptions: GlobOptions): Loader {
|
|
||||||
if (checkPrefix(globOptions.pattern, '../')) {
|
|
||||||
throw new Error(
|
|
||||||
'Glob patterns cannot start with `../`. Set the `base` option to a parent directory instead.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (checkPrefix(globOptions.pattern, '/')) {
|
|
||||||
throw new Error(
|
|
||||||
'Glob patterns cannot start with `/`. Set the `base` option to a parent directory or use a relative path instead.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateId = globOptions?.generateId ?? generateIdDefault;
|
|
||||||
|
|
||||||
const fileToIdMap = new Map<string, string>();
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'glob-loader',
|
|
||||||
load: async ({ config, logger, watcher, parseData, store, generateDigest, entryTypes }) => {
|
|
||||||
const renderFunctionByContentType = new WeakMap<
|
|
||||||
ContentEntryType,
|
|
||||||
ContentEntryRenderFunction
|
|
||||||
>();
|
|
||||||
|
|
||||||
const untouchedEntries = new Set(store.keys());
|
|
||||||
const isLegacy = (globOptions as any)._legacy;
|
|
||||||
// If global legacy collection handling flag is *not* enabled then this loader is used to emulate them instead
|
|
||||||
const emulateLegacyCollections = !config.legacy.collections;
|
|
||||||
async function syncData(
|
|
||||||
entry: string,
|
|
||||||
base: URL,
|
|
||||||
entryType?: ContentEntryType,
|
|
||||||
oldId?: string,
|
|
||||||
) {
|
|
||||||
if (!entryType) {
|
|
||||||
logger.warn(`No entry type found for ${entry}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fileUrl = new URL(encodeURI(entry), base);
|
|
||||||
const contents = await fs.readFile(fileUrl, 'utf-8').catch((err) => {
|
|
||||||
logger.error(`Error reading ${entry}: ${err.message}`);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!contents && contents !== '') {
|
|
||||||
logger.warn(`No contents found for ${entry}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { body, data } = await entryType.getEntryInfo({
|
|
||||||
contents,
|
|
||||||
fileUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = generateId({ entry, base, data });
|
|
||||||
|
|
||||||
if (oldId && oldId !== id) {
|
|
||||||
store.delete(oldId);
|
|
||||||
}
|
|
||||||
|
|
||||||
let legacyId: string | undefined;
|
|
||||||
|
|
||||||
if (isLegacy) {
|
|
||||||
const entryURL = new URL(encodeURI(entry), base);
|
|
||||||
const legacyOptions = getContentEntryIdAndSlug({
|
|
||||||
entry: entryURL,
|
|
||||||
contentDir: base,
|
|
||||||
collection: '',
|
|
||||||
});
|
|
||||||
legacyId = legacyOptions.id;
|
|
||||||
}
|
|
||||||
untouchedEntries.delete(id);
|
|
||||||
|
|
||||||
const existingEntry = store.get(id);
|
|
||||||
|
|
||||||
const digest = generateDigest(contents);
|
|
||||||
const filePath = fileURLToPath(fileUrl);
|
|
||||||
|
|
||||||
if (existingEntry && existingEntry.digest === digest && existingEntry.filePath) {
|
|
||||||
if (existingEntry.deferredRender) {
|
|
||||||
store.addModuleImport(existingEntry.filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingEntry.assetImports?.length) {
|
|
||||||
// Add asset imports for existing entries
|
|
||||||
store.addAssetImports(existingEntry.assetImports, existingEntry.filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
fileToIdMap.set(filePath, id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const relativePath = posixRelative(fileURLToPath(config.root), filePath);
|
|
||||||
|
|
||||||
const parsedData = await parseData({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
filePath,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (entryType.getRenderFunction) {
|
|
||||||
if (isLegacy && data.layout) {
|
|
||||||
logger.error(
|
|
||||||
`The Markdown "layout" field is not supported in content collections in Astro 5. Ignoring layout for ${JSON.stringify(entry)}. Enable "legacy.collections" if you need to use the layout field.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let render = renderFunctionByContentType.get(entryType);
|
|
||||||
if (!render) {
|
|
||||||
render = await entryType.getRenderFunction(config);
|
|
||||||
// Cache the render function for this content type, so it can re-use parsers and other expensive setup
|
|
||||||
renderFunctionByContentType.set(entryType, render);
|
|
||||||
}
|
|
||||||
let rendered: RenderedContent | undefined = undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
rendered = await render?.({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
body,
|
|
||||||
filePath,
|
|
||||||
digest,
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error(`Error rendering ${entry}: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
store.set({
|
|
||||||
id,
|
|
||||||
data: parsedData,
|
|
||||||
body,
|
|
||||||
filePath: relativePath,
|
|
||||||
digest,
|
|
||||||
rendered,
|
|
||||||
assetImports: rendered?.metadata?.imagePaths,
|
|
||||||
legacyId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: add an explicit way to opt in to deferred rendering
|
|
||||||
} else if ('contentModuleTypes' in entryType) {
|
|
||||||
store.set({
|
|
||||||
id,
|
|
||||||
data: parsedData,
|
|
||||||
body,
|
|
||||||
filePath: relativePath,
|
|
||||||
digest,
|
|
||||||
deferredRender: true,
|
|
||||||
legacyId,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
store.set({ id, data: parsedData, body, filePath: relativePath, digest, legacyId });
|
|
||||||
}
|
|
||||||
|
|
||||||
fileToIdMap.set(filePath, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseDir = globOptions.base ? new URL(globOptions.base, config.root) : config.root;
|
|
||||||
|
|
||||||
if (!baseDir.pathname.endsWith('/')) {
|
|
||||||
baseDir.pathname = `${baseDir.pathname}/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filePath = fileURLToPath(baseDir);
|
|
||||||
const relativePath = relative(fileURLToPath(config.root), filePath);
|
|
||||||
|
|
||||||
const exists = existsSync(baseDir);
|
|
||||||
|
|
||||||
if (!exists) {
|
|
||||||
// We warn and don't return because we will still set up the watcher in case the directory is created later
|
|
||||||
logger.warn(`The base directory "${fileURLToPath(baseDir)}" does not exist.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = await fastGlob(globOptions.pattern, {
|
|
||||||
cwd: fileURLToPath(baseDir),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (exists && files.length === 0) {
|
|
||||||
logger.warn(
|
|
||||||
`No files found matching "${globOptions.pattern}" in directory "${relativePath}"`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function configForFile(file: string) {
|
|
||||||
const ext = file.split('.').at(-1);
|
|
||||||
if (!ext) {
|
|
||||||
logger.warn(`No extension found for ${file}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return entryTypes.get(`.${ext}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const limit = pLimit(10);
|
|
||||||
const skippedFiles: Array<string> = [];
|
|
||||||
|
|
||||||
const contentDir = new URL('content/', config.srcDir);
|
|
||||||
|
|
||||||
function isInContentDir(file: string) {
|
|
||||||
const fileUrl = new URL(file, baseDir);
|
|
||||||
return fileUrl.href.startsWith(contentDir.href);
|
|
||||||
}
|
|
||||||
|
|
||||||
const configFiles = new Set(
|
|
||||||
['config.js', 'config.ts', 'config.mjs'].map((file) => new URL(file, contentDir).href),
|
|
||||||
);
|
|
||||||
|
|
||||||
function isConfigFile(file: string) {
|
|
||||||
const fileUrl = new URL(file, baseDir);
|
|
||||||
return configFiles.has(fileUrl.href);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
files.map((entry) => {
|
|
||||||
if (isConfigFile(entry)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!emulateLegacyCollections && isInContentDir(entry)) {
|
|
||||||
skippedFiles.push(entry);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return limit(async () => {
|
|
||||||
const entryType = configForFile(entry);
|
|
||||||
await syncData(entry, baseDir, entryType);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const skipCount = skippedFiles.length;
|
|
||||||
|
|
||||||
if (skipCount > 0) {
|
|
||||||
const patternList = Array.isArray(globOptions.pattern)
|
|
||||||
? globOptions.pattern.join(', ')
|
|
||||||
: globOptions.pattern;
|
|
||||||
|
|
||||||
logger.warn(
|
|
||||||
`The glob() loader cannot be used for files in ${bold('src/content')} when legacy mode is enabled.`,
|
|
||||||
);
|
|
||||||
if (skipCount > 10) {
|
|
||||||
logger.warn(
|
|
||||||
`Skipped ${green(skippedFiles.length)} files that matched ${green(patternList)}.`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Skipped the following files that matched ${green(patternList)}:`);
|
|
||||||
skippedFiles.forEach((file) => logger.warn(`• ${green(file)}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove entries that were not found this time
|
|
||||||
untouchedEntries.forEach((id) => store.delete(id));
|
|
||||||
|
|
||||||
if (!watcher) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
watcher.add(filePath);
|
|
||||||
|
|
||||||
const matchesGlob = (entry: string) =>
|
|
||||||
!entry.startsWith('../') && micromatch.isMatch(entry, globOptions.pattern);
|
|
||||||
|
|
||||||
const basePath = fileURLToPath(baseDir);
|
|
||||||
|
|
||||||
async function onChange(changedPath: string) {
|
|
||||||
const entry = posixRelative(basePath, changedPath);
|
|
||||||
if (!matchesGlob(entry)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entryType = configForFile(changedPath);
|
|
||||||
const baseUrl = pathToFileURL(basePath);
|
|
||||||
const oldId = fileToIdMap.get(changedPath);
|
|
||||||
await syncData(entry, baseUrl, entryType, oldId);
|
|
||||||
logger.info(`Reloaded data from ${green(entry)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
watcher.on('change', onChange);
|
|
||||||
|
|
||||||
watcher.on('add', onChange);
|
|
||||||
|
|
||||||
watcher.on('unlink', async (deletedPath) => {
|
|
||||||
const entry = posixRelative(basePath, deletedPath);
|
|
||||||
if (!matchesGlob(entry)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const id = fileToIdMap.get(deletedPath);
|
|
||||||
if (id) {
|
|
||||||
store.delete(id);
|
|
||||||
fileToIdMap.delete(deletedPath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export { file } from './file.js';
|
|
||||||
export { glob } from './glob.js';
|
|
||||||
export * from './types.js';
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import type { FSWatcher } from 'vite';
|
|
||||||
import type { ZodSchema } from 'zod';
|
|
||||||
import type { AstroIntegrationLogger } from '../../core/logger/core.js';
|
|
||||||
import type { AstroConfig } from '../../types/public/config.js';
|
|
||||||
import type { ContentEntryType } from '../../types/public/content.js';
|
|
||||||
import type { DataStore, MetaStore } from '../mutable-data-store.js';
|
|
||||||
|
|
||||||
export type { DataStore, MetaStore };
|
|
||||||
|
|
||||||
export interface ParseDataOptions<TData extends Record<string, unknown>> {
|
|
||||||
/** The ID of the entry. Unique per collection */
|
|
||||||
id: string;
|
|
||||||
/** The raw, unvalidated data of the entry */
|
|
||||||
data: TData;
|
|
||||||
/** An optional file path, where the entry represents a local file. */
|
|
||||||
filePath?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoaderContext {
|
|
||||||
/** The unique name of the collection */
|
|
||||||
collection: string;
|
|
||||||
/** A database to store the actual data */
|
|
||||||
store: DataStore;
|
|
||||||
/** A simple KV store, designed for things like sync tokens */
|
|
||||||
meta: MetaStore;
|
|
||||||
logger: AstroIntegrationLogger;
|
|
||||||
/** Astro config, with user config and merged defaults */
|
|
||||||
config: AstroConfig;
|
|
||||||
/** Validates and parses the data according to the collection schema */
|
|
||||||
parseData<TData extends Record<string, unknown>>(props: ParseDataOptions<TData>): Promise<TData>;
|
|
||||||
|
|
||||||
/** Generates a non-cryptographic content digest. This can be used to check if the data has changed */
|
|
||||||
generateDigest(data: Record<string, unknown> | string): string;
|
|
||||||
|
|
||||||
/** When running in dev, this is a filesystem watcher that can be used to trigger updates */
|
|
||||||
watcher?: FSWatcher;
|
|
||||||
|
|
||||||
/** If the loader has been triggered by an integration, this may optionally contain extra data set by that integration */
|
|
||||||
refreshContextData?: Record<string, unknown>;
|
|
||||||
/** @internal */
|
|
||||||
entryTypes: Map<string, ContentEntryType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Loader {
|
|
||||||
/** Unique name of the loader, e.g. the npm package name */
|
|
||||||
name: string;
|
|
||||||
/** Do the actual loading of the data */
|
|
||||||
load: (context: LoaderContext) => Promise<void>;
|
|
||||||
/** Optionally, define the schema of the data. Will be overridden by user-defined schema */
|
|
||||||
schema?: ZodSchema | Promise<ZodSchema> | (() => ZodSchema | Promise<ZodSchema>);
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
{
|
|
||||||
"main": [
|
|
||||||
{
|
|
||||||
"name": "Home",
|
|
||||||
"url": "/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Products",
|
|
||||||
"url": "/products"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Pages",
|
|
||||||
"url": "",
|
|
||||||
"hasChildren": true,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "About",
|
|
||||||
"url": "/about"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Contact",
|
|
||||||
"url": "/contact"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "404 Page",
|
|
||||||
"url": "/404"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Contact",
|
|
||||||
"url": "/contact"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"footer": [
|
|
||||||
{
|
|
||||||
"name": "About",
|
|
||||||
"url": "/about"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Products",
|
|
||||||
"url": "/products"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Contact",
|
|
||||||
"url": "/contact"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"footerCopyright": [
|
|
||||||
{
|
|
||||||
"name": "Privacy & Policy",
|
|
||||||
"url": "/privacy-policy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Terms of Service",
|
|
||||||
"url": "/terms-services"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import { translate } from '../base/i18n.js'
|
|
||||||
import { I18N_SOURCE_LANGUAGE } from 'config/config.js'
|
|
||||||
import config from "./config.json"
|
|
||||||
import pMap from 'p-map'
|
|
||||||
|
|
||||||
export const items = async (opts: { locale: string }) => {
|
|
||||||
const _T = async (text: string) => await translate(text, I18N_SOURCE_LANGUAGE, opts.locale)
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"href": `/${opts.locale}`,
|
|
||||||
"title": _T("Home"),
|
|
||||||
"ariaLabel": "Home",
|
|
||||||
"class": "hover:text-orange-600"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": `/${opts.locale}/infopages/resources`,
|
|
||||||
"title": _T("Resources"),
|
|
||||||
"ariaLabel": "Resources",
|
|
||||||
"class": "hover:text-orange-600"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
const isAbsoluteUrl = (url: string): boolean => /^[a-zA-Z]+:/.test(url);
|
|
||||||
|
|
||||||
const createFooterLinks = async (items: any[], locale: string) => {
|
|
||||||
const _T = async (text: string) => await translate(text, I18N_SOURCE_LANGUAGE, locale);
|
|
||||||
|
|
||||||
return await pMap(items, async (item) => {
|
|
||||||
const translatedText = await _T(item.text); // Single translation call
|
|
||||||
|
|
||||||
return {
|
|
||||||
"href": isAbsoluteUrl(item.href) ? item.href : `/${locale}${item.href}`,
|
|
||||||
"title": translatedText, // Use cached translation
|
|
||||||
"ariaLabel": translatedText, // Use cached translation
|
|
||||||
"class": "hover:text-orange-600"
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const footer_left = (locale: string) => createFooterLinks(config.footer_left, locale);
|
|
||||||
export const footer_right = (locale: string) => createFooterLinks(config.footer_right, locale);
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import { z } from "zod"
|
|
||||||
/////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Optimizations
|
|
||||||
|
|
||||||
// Image optimization (imagetools breakpoints & min widths)
|
|
||||||
|
|
||||||
export enum E_BROADBAND_SPEED {
|
|
||||||
SLOW = "slow",
|
|
||||||
MEDIUM = "medium",
|
|
||||||
FAST = "fast",
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageConfigSchema = z.object({
|
|
||||||
sizes_medium: z.string(),
|
|
||||||
sizes_thumbs: z.string(),
|
|
||||||
sizes_large: z.string(),
|
|
||||||
})
|
|
||||||
const imagesSchema = z.object({
|
|
||||||
[E_BROADBAND_SPEED.SLOW]: imageConfigSchema,
|
|
||||||
[E_BROADBAND_SPEED.MEDIUM]: imageConfigSchema,
|
|
||||||
[E_BROADBAND_SPEED.FAST]: imageConfigSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
type Images = z.infer<typeof imagesSchema>;
|
|
||||||
|
|
||||||
export const IMAGE_PRESET: Images =
|
|
||||||
{
|
|
||||||
[E_BROADBAND_SPEED.SLOW]: {
|
|
||||||
// For 2g connections: smaller image widths help performance. (Middle East & Africa)
|
|
||||||
sizes_medium: "(min-width: 100px) 100px, 100vw",
|
|
||||||
sizes_thumbs: "(min-width: 80px) 80px, 80vw",
|
|
||||||
sizes_large: "(min-width: 320px) 320px, 320vw",
|
|
||||||
},
|
|
||||||
[E_BROADBAND_SPEED.MEDIUM]:
|
|
||||||
{
|
|
||||||
// For 3g connections: a moderate size image for a balance of quality and speed.
|
|
||||||
sizes_medium: "(min-width: 400px) 400px, 400vw",
|
|
||||||
sizes_thumbs: "(min-width: 120px) 120px, 120vw",
|
|
||||||
sizes_large: "(min-width: 1024px) 1024px, 1024vw",
|
|
||||||
},
|
|
||||||
[E_BROADBAND_SPEED.FAST]:
|
|
||||||
{
|
|
||||||
// For 4g connections: larger images for high-resolution displays.
|
|
||||||
sizes_medium: "(min-width: 1024px) 1024px, 1024vw",
|
|
||||||
sizes_thumbs: "(min-width: 180px) 180px, 180vw",
|
|
||||||
sizes_large: "(min-width: 1200px) 1200px, 1200vw"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"includes": [],
|
|
||||||
"variables": {
|
|
||||||
"PRODUCT_ROOT": "${root}/${product}/",
|
|
||||||
"abs_url": "https://assets.osr-plastic.org",
|
|
||||||
"CACHE": "${root}/cache/",
|
|
||||||
"CACHE_URL": "${abs_url}/cache/",
|
|
||||||
"GIT_REPO": "https://git.polymech.io/",
|
|
||||||
"OSR_MACHINES_ASSETS_URL":"https://assets.osr-plastic.org",
|
|
||||||
"PRODUCTS_ASSETS_URL":"https://assets.osr-plastic.org/${product_rel}",
|
|
||||||
"OSR_FILES_WEB":"https://files.polymech.io/files/machines",
|
|
||||||
"PRODUCTS_FILES_URL":"${OSR_FILES_WEB}/${product_rel}",
|
|
||||||
"DISCORD":"https://discord.gg/s8K7yKwBRc"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"astro-release":{
|
|
||||||
"includes": [
|
|
||||||
"${PRODUCT_ROOT}"
|
|
||||||
],
|
|
||||||
"variables": {
|
|
||||||
"OSR_MACHINES_ASSETS_URL":"https://assets.osr-plastic.org/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"astro-debug":{
|
|
||||||
"includes": [
|
|
||||||
"${PRODUCT_ROOT}"
|
|
||||||
],
|
|
||||||
"variables": {
|
|
||||||
"OSR_MACHINES_ASSETS_URL":"https://assets.osr-plastic.org",
|
|
||||||
"showCart": false,
|
|
||||||
"showPrice": false,
|
|
||||||
"showResources": false,
|
|
||||||
"showShipping": false,
|
|
||||||
"showPaymentTerms": false,
|
|
||||||
"showHowtos": false,
|
|
||||||
"showRenderings": true,
|
|
||||||
"debug": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"main": [
|
|
||||||
{
|
|
||||||
"name": "facebook",
|
|
||||||
"icon": "FaFacebookF",
|
|
||||||
"link": "https://www.facebook.com/themefisher"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "twitter",
|
|
||||||
"icon": "FaXTwitter",
|
|
||||||
"link": "https://x.com/themefisher"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "linkedin",
|
|
||||||
"icon": "FaLinkedinIn",
|
|
||||||
"link": "https://bd.linkedin.com/company/themefisher"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "github",
|
|
||||||
"icon": "FaGithub",
|
|
||||||
"link": "https://github.com/themefisher/astrofront"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"shop":{
|
|
||||||
"title": "Shop",
|
|
||||||
"description": "",
|
|
||||||
"items":"${OSR_ROOT}/products/products/**/config.json",
|
|
||||||
"root":"${OSR_ROOT}/products"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"colors": {
|
|
||||||
"default": {
|
|
||||||
"theme_color": {
|
|
||||||
"primary": "#121212",
|
|
||||||
"body": "#fff",
|
|
||||||
"border": "#eaeaea",
|
|
||||||
"theme_light": "#f2f2f2",
|
|
||||||
"theme_dark": "#000"
|
|
||||||
},
|
|
||||||
"text_color": {
|
|
||||||
"default": "#444",
|
|
||||||
"dark": "#000",
|
|
||||||
"light": "#666"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"darkmode": {
|
|
||||||
"theme_color": {
|
|
||||||
"primary": "#fff",
|
|
||||||
"body": "#252525",
|
|
||||||
"border": "#3E3E3E",
|
|
||||||
"theme_light": "#222222",
|
|
||||||
"theme_dark": "#000"
|
|
||||||
},
|
|
||||||
"text_color": {
|
|
||||||
"default": "#DDD",
|
|
||||||
"dark": "#fff",
|
|
||||||
"light": "#DDD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fonts": {
|
|
||||||
"font_family": {
|
|
||||||
"primary": "Karla:wght@400;500;700",
|
|
||||||
"primary_type": "sans-serif",
|
|
||||||
"secondary": "",
|
|
||||||
"secondary_type": ""
|
|
||||||
},
|
|
||||||
"font_size": {
|
|
||||||
"base": "16",
|
|
||||||
"scale": "1.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
I18N_SOURCE_LANGUAGE,
|
I18N_SOURCE_LANGUAGE,
|
||||||
PRODUCT_SPECS,
|
PRODUCT_SPECS,
|
||||||
RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS
|
RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS
|
||||||
} from '@/app/config.js'
|
} from 'config/config.js'
|
||||||
|
|
||||||
import { translateXLS } from '@polymech/i18n/translate_xls'
|
import { translateXLS } from '@polymech/i18n/translate_xls'
|
||||||
import { I18N_STORE, OSR_ROOT } from 'config/config.js'
|
import { I18N_STORE, OSR_ROOT } from 'config/config.js'
|
||||||
|
|||||||
@ -13,14 +13,23 @@ import { sync as read } from '@polymech/fs/read'
|
|||||||
import { sync as mv } from '@polymech/fs/move'
|
import { sync as mv } from '@polymech/fs/move'
|
||||||
import { logger } from '@/base/index.js'
|
import { logger } from '@/base/index.js'
|
||||||
|
|
||||||
import { removeArrayValues, removeArrays, removeBufferValues, removeEmptyObjects } from '@/base/objects.js'
|
import {
|
||||||
import { ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT, DEFAULT_IMAGE_URL, ASSETS_LOCAL } from '../app/config.js'
|
removeArrayValues,
|
||||||
|
removeArrays,
|
||||||
|
removeBufferValues,
|
||||||
|
removeEmptyObjects
|
||||||
|
} from '@/base/objects.js'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT,
|
||||||
|
DEFAULT_IMAGE_URL, ASSETS_LOCAL,
|
||||||
|
ASSETS_GLOB
|
||||||
|
} from 'config/config.js'
|
||||||
import { GalleryImage, MetaJSON } from './images.js'
|
import { GalleryImage, MetaJSON } from './images.js'
|
||||||
|
|
||||||
import { validateFilename, sanitizeFilename } from "@polymech/fs/utils"
|
import { validateFilename, sanitizeFilename } from "@polymech/fs/utils"
|
||||||
|
|
||||||
import { env } from './index.js'
|
import { env } from './index.js'
|
||||||
const IMAGES_GLOB = '*.+(JPG|jpg|jpeg|png|PNG|gif)'
|
|
||||||
|
|
||||||
|
|
||||||
export const default_sort = (files: string[]): string[] => {
|
export const default_sort = (files: string[]): string[] => {
|
||||||
@ -112,7 +121,7 @@ export const gallery = async (
|
|||||||
logger.warn(`item gallery : item ${product} media path not found ${mediaPath}!`)
|
logger.warn(`item gallery : item ${product} media path not found ${mediaPath}!`)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const galleryGlob = (productConfig.gallery || {})[assetSlug]?.glob || IMAGES_GLOB
|
const galleryGlob = (productConfig.gallery || {})[assetSlug]?.glob || ASSETS_GLOB
|
||||||
let galleryFiles: any[] = files(mediaPath, galleryGlob, {
|
let galleryFiles: any[] = files(mediaPath, galleryGlob, {
|
||||||
cwd: mediaPath,
|
cwd: mediaPath,
|
||||||
absolute: false,
|
absolute: false,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { I18N_SOURCE_LANGUAGE } from 'config/config.js'
|
|||||||
import { translate } from '@/base/i18n.js'
|
import { translate } from '@/base/i18n.js'
|
||||||
import { item_defaults } from '@/base/index.js'
|
import { item_defaults } from '@/base/index.js'
|
||||||
|
|
||||||
import config from "../app/config.json" with { "type": "json" }
|
import config from "../config/config.json" with { "type": "json" }
|
||||||
|
|
||||||
const keywords = (keywords: string) => keywords.split(',').map(k => k.trim()).filter(Boolean);
|
const keywords = (keywords: string) => keywords.split(',').map(k => k.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
git checkout --orphan latest_branch
|
|
||||||
git add -A
|
|
||||||
git commit -am "init v0.1.3"
|
|
||||||
git branch -D master
|
|
||||||
git branch -m master
|
|
||||||
git push -f origin master
|
|
||||||
git gc --aggressive --prune=all
|
|
||||||
@ -3,7 +3,7 @@ import "../styles/flowbite.css"
|
|||||||
|
|
||||||
import { LANGUAGES_PROD } from "config/config.js"
|
import { LANGUAGES_PROD } from "config/config.js"
|
||||||
|
|
||||||
import config from "config/config.json"
|
import config from "@/config/config.json"
|
||||||
import { plainify } from "../base/strings.js"
|
import { plainify } from "../base/strings.js"
|
||||||
|
|
||||||
import "../styles/global.css"
|
import "../styles/global.css"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
const { title, url, pubDate, description, image } = Astro.props;
|
const { title, url, pubDate, description, image } = Astro.props;
|
||||||
import { DEFAULT_IMAGE_URL } from "@/app/config";
|
import { DEFAULT_IMAGE_URL } from "@/config/config";
|
||||||
import { Img } from "imagetools/components";
|
import { Img } from "imagetools/components";
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import Wrapper from "@/components/containers/Wrapper.astro"
|
import Wrapper from "@/components/containers/Wrapper.astro"
|
||||||
import { footer_left, footer_right } from "@/app/navigation.js"
|
import { footer_left, footer_right } from "@/config/navigation.js"
|
||||||
import { ISO_LANGUAGE_LABELS } from "@polymech/i18n"
|
import { ISO_LANGUAGE_LABELS } from "@polymech/i18n"
|
||||||
import {
|
import {
|
||||||
LANGUAGES_PROD,
|
LANGUAGES_PROD,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||||
import { I18N_SOURCE_LANGUAGE } from "@/app/config";
|
import { I18N_SOURCE_LANGUAGE } from "@/config/config";
|
||||||
import { items } from "config/navigation.js";
|
import { items } from "@/config/navigation.js";
|
||||||
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE;
|
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE;
|
||||||
const navItems = await items({ locale });
|
const navItems = await items({ locale });
|
||||||
---
|
---
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import config from "@/app/config.json"
|
import config from "@/config/config.json"
|
||||||
import { get } from "@/model/registry.js"
|
import { get } from "@/model/registry.js"
|
||||||
import { default_image } from "@/app/config.js"
|
import { default_image } from "config/config.js"
|
||||||
|
|
||||||
const { frontmatter } = Astro.props
|
const { frontmatter } = Astro.props
|
||||||
const title = frontmatter?.title || config.site.title
|
const title = frontmatter?.title || config.site.title
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { DEFAULT_IMAGE_URL } from '@/app/config.js'
|
import { DEFAULT_IMAGE_URL } from 'config/config.js'
|
||||||
import { Picture } from "imagetools/components"
|
import { Picture } from "imagetools/components"
|
||||||
import { image_url } from "@/base/media.js"
|
import { image_url } from "@/base/media.js"
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { translate } from "@/base/i18n.js"
|
import { translate } from "@/base/i18n.js"
|
||||||
import config from "config/config.json"
|
import config from "@/config/config.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { default_image } from "@/app/config.js";
|
import { default_image } from "config/config.js";
|
||||||
import Translate from "@/components/polymech/i18n.astro";
|
import Translate from "@/components/polymech/i18n.astro";
|
||||||
import { Img } from "imagetools/components"
|
import { Img } from "imagetools/components"
|
||||||
const { title, url, price, model, selected = false } = Astro.props;
|
const { title, url, price, model, selected = false } = Astro.props;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ description: Contact Plastic Hub
|
|||||||
layout: '@layouts/Markup.astro'
|
layout: '@layouts/Markup.astro'
|
||||||
---
|
---
|
||||||
|
|
||||||
import { default_image } from '@/app/config.js'
|
import { default_image } from 'config/config.js'
|
||||||
import GalleryK from "@/components/polymech/GalleryK.astro"
|
import GalleryK from "@/components/polymech/GalleryK.astro"
|
||||||
|
|
||||||
## {frontmatter.title}
|
## {frontmatter.title}
|
||||||
|
|||||||
@ -1,29 +1,30 @@
|
|||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { findUp } from 'find-up'
|
import { findUp } from 'find-up'
|
||||||
import { } from 'astro:content'
|
import { } from 'astro:content'
|
||||||
|
|
||||||
import { sync as read } from '@polymech/fs/read'
|
import { sync as read } from '@polymech/fs/read'
|
||||||
import { sync as exists } from '@polymech/fs/exists'
|
import { sync as exists } from '@polymech/fs/exists'
|
||||||
|
import { validateFilename, validatePath, sanitizeFilename } from '@polymech/fs/utils'
|
||||||
|
|
||||||
import { filesEx, forward_slash, resolveConfig } from '@polymech/commons'
|
import { filesEx, forward_slash, resolveConfig } from '@polymech/commons'
|
||||||
import { ICADNodeSchema, IComponentConfig } from '@polymech/commons/component'
|
import { ICADNodeSchema, IComponentConfig } from '@polymech/commons/component'
|
||||||
|
|
||||||
import { ContentEntryRenderFunction, ContentEntryType } from 'astro'
|
|
||||||
import { RenderedContent, DataEntry } from "astro:content"
|
import { RenderedContent, DataEntry } from "astro:content"
|
||||||
|
|
||||||
import type { Loader, LoaderContext } from 'astro/loaders'
|
import type { Loader, LoaderContext } from 'astro/loaders'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
|
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
|
||||||
CAD_EXTENSIONS, CAD_MODEL_EXT, PRODUCT_DIR, PRODUCT_GLOB,
|
CAD_EXTENSIONS, CAD_MODEL_EXT, PRODUCT_DIR, PRODUCT_GLOB,
|
||||||
PRODUCT_ROOT, RETAIL_PRODUCT_BRANCH, CAD_EXPORT_CONFIGURATIONS,
|
PRODUCT_ROOT, CAD_EXPORT_CONFIGURATIONS,
|
||||||
CAD_DEFAULT_CONFIGURATION,
|
CAD_DEFAULT_CONFIGURATION,
|
||||||
CAD_URL,
|
CAD_URL,
|
||||||
parseBoolean,
|
parseBoolean,
|
||||||
DEFAULT_CONTACT
|
DEFAULT_CONTACT,
|
||||||
|
default_image
|
||||||
} from 'config/config.js'
|
} from 'config/config.js'
|
||||||
|
|
||||||
import { env } from '../base/index.js'
|
import { env } from '@/base/index.js'
|
||||||
import { gallery } from '../base/media.js';
|
import { gallery } from '@/base/media.js';
|
||||||
|
|
||||||
import { get } from '@polymech/commons/component'
|
import { get } from '@polymech/commons/component'
|
||||||
import { PFilterValid } from '@polymech/commons/filter'
|
import { PFilterValid } from '@polymech/commons/filter'
|
||||||
@ -34,9 +35,6 @@ import { translate } from "@/base/i18n.js"
|
|||||||
import { I18N_SOURCE_LANGUAGE } from "config/config.js"
|
import { I18N_SOURCE_LANGUAGE } from "config/config.js"
|
||||||
import { slugify } from "@/base/strings.js"
|
import { slugify } from "@/base/strings.js"
|
||||||
|
|
||||||
export interface ILoaderContextEx extends LoaderContext {
|
|
||||||
entryTypes: Map<string, ContentEntryType>
|
|
||||||
}
|
|
||||||
export interface IComponentConfigEx extends IComponentConfig {
|
export interface IComponentConfigEx extends IComponentConfig {
|
||||||
content: string
|
content: string
|
||||||
extra_resources?: string
|
extra_resources?: string
|
||||||
@ -49,10 +47,6 @@ export interface IStoreItem extends DataEntry {
|
|||||||
rendered?: RenderedContent
|
rendered?: RenderedContent
|
||||||
}
|
}
|
||||||
|
|
||||||
let loaderCtx: ILoaderContextEx
|
|
||||||
|
|
||||||
const renderFunctionByContentType = new WeakMap<ContentEntryType, ContentEntryRenderFunction>();
|
|
||||||
|
|
||||||
const filterBranch = (items: { rel: string, config, path }[], branch: string) => {
|
const filterBranch = (items: { rel: string, config, path }[], branch: string) => {
|
||||||
if (!PRODUCT_BRANCHES) {
|
if (!PRODUCT_BRANCHES) {
|
||||||
return items
|
return items
|
||||||
@ -69,7 +63,7 @@ export const items = (branch: string) =>
|
|||||||
PRODUCT_ROOT(), PFilterValid.marketplace_component), branch)
|
PRODUCT_ROOT(), PFilterValid.marketplace_component), branch)
|
||||||
|
|
||||||
|
|
||||||
const onComponent = async (item: IStoreItem, ctx: ILoaderContextEx) => {
|
const onComponent = async (item: IStoreItem, ctx: LoaderContext) => {
|
||||||
/*
|
/*
|
||||||
const onNode = async (data: INodeCallback, configuration: string) => {
|
const onNode = async (data: INodeCallback, configuration: string) => {
|
||||||
if (!CAD_EXPORT_SUB_COMPONENTS || !data.target.endsWith('.json')) {
|
if (!CAD_EXPORT_SUB_COMPONENTS || !data.target.endsWith('.json')) {
|
||||||
@ -93,7 +87,7 @@ const onComponent = async (item: IStoreItem, ctx: ILoaderContextEx) => {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
const cad = async (item: IStoreItem, ctx: ILoaderContextEx): Promise<ICADNodeSchema[]> => {
|
const cad = async (item: IStoreItem): Promise<ICADNodeSchema[]> => {
|
||||||
const root = PRODUCT_ROOT()
|
const root = PRODUCT_ROOT()
|
||||||
const data: IComponentConfigEx = item.data
|
const data: IComponentConfigEx = item.data
|
||||||
const itemRel = data.rel
|
const itemRel = data.rel
|
||||||
@ -137,33 +131,24 @@ const cad = async (item: IStoreItem, ctx: ILoaderContextEx): Promise<ICADNodeSch
|
|||||||
return cadMeta
|
return cadMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onItem = async (item: IStoreItem, ctx: LoaderContext) => {
|
||||||
|
|
||||||
const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
|
|
||||||
if (!item || !item.data) {
|
if (!item || !item.data) {
|
||||||
ctx.logger.error(`Error completing ${''}: no data`);
|
ctx.logger.error(`Error completing ${''}: no data`);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!loaderCtx) {
|
|
||||||
loaderCtx = ctx
|
|
||||||
}
|
|
||||||
const { logger } = ctx
|
const { logger } = ctx
|
||||||
let data: IComponentConfigEx = item.data
|
let data: IComponentConfigEx = item.data
|
||||||
|
|
||||||
const itemRel = data.rel
|
const itemRel = data.rel
|
||||||
const itemRelMin = itemRel.replace('products/', '')
|
const itemRelMin = itemRel.replace('products/', '')
|
||||||
const itemDir = PRODUCT_DIR(itemRel)
|
const itemDir = PRODUCT_DIR(itemRel)
|
||||||
const default_profile = env(itemRel)
|
const default_profile = env(itemRel)
|
||||||
|
|
||||||
data.product_rel = itemRelMin
|
data.product_rel = itemRelMin
|
||||||
data.assets = {
|
data.assets = {
|
||||||
renderings: [],
|
renderings: [],
|
||||||
gallery: []
|
gallery: []
|
||||||
}
|
}
|
||||||
|
|
||||||
data.body = (read(path.join(itemDir, 'templates/shared', 'body.md')) as string) || ""
|
data.body = (read(path.join(itemDir, 'templates/shared', 'body.md')) as string) || ""
|
||||||
data.resources = (read(path.join(itemDir, 'templates/shared', 'resources.md')) as string) || ""
|
data.resources = (read(path.join(itemDir, 'templates/shared', 'resources.md')) as string) || ""
|
||||||
|
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Item Shared Resources
|
// Item Shared Resources
|
||||||
@ -207,18 +192,16 @@ const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
|
|||||||
item.data = data
|
item.data = data
|
||||||
//////////////////////////////////////////
|
//////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Extensions, CAD, Media, etc.
|
// Extensions, CAD, Media, etcetera etcetera :)
|
||||||
//
|
//
|
||||||
data.cad = await cad(item, ctx)
|
data.cad = await cad(item)
|
||||||
|
|
||||||
data.assets.renderings = await gallery('renderings', data.rel) as []
|
|
||||||
data.assets.gallery = await gallery('media/gallery', data.rel) as []
|
|
||||||
data.image = data.assets.renderings[0] || {}
|
|
||||||
data.assets.showcase = await gallery('media/showcase', data.rel) as []
|
|
||||||
data.assets.samples = await gallery('media/samples', data.rel) as []
|
|
||||||
|
|
||||||
|
data.assets.renderings = await gallery('renderings', data.rel)
|
||||||
|
data.assets.gallery = await gallery('media/gallery', data.rel)
|
||||||
|
data.image = data.assets.renderings[0] || default_image()
|
||||||
|
data.assets.showcase = await gallery('media/showcase', data.rel)
|
||||||
|
data.assets.samples = await gallery('media/samples', data.rel)
|
||||||
data.checkout = await item_checkout(data)
|
data.checkout = await item_checkout(data)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loader(branch: string): Loader {
|
export function loader(branch: string): Loader {
|
||||||
@ -228,8 +211,7 @@ export function loader(branch: string): Loader {
|
|||||||
watcher,
|
watcher,
|
||||||
parseData,
|
parseData,
|
||||||
store,
|
store,
|
||||||
generateDigest,
|
generateDigest }: LoaderContext) => {
|
||||||
entryTypes }: ILoaderContextEx) => {
|
|
||||||
|
|
||||||
store.clear();
|
store.clear();
|
||||||
let products = items(branch)
|
let products = items(branch)
|
||||||
@ -259,14 +241,10 @@ export function loader(branch: string): Loader {
|
|||||||
watcher,
|
watcher,
|
||||||
parseData,
|
parseData,
|
||||||
store,
|
store,
|
||||||
generateDigest,
|
generateDigest
|
||||||
entryTypes
|
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
storeItem.data['config'] = JSON.stringify({
|
storeItem.data['config'] = JSON.stringify(storeItem.data, null, 2)
|
||||||
...storeItem.data
|
|
||||||
}, null, 2)
|
|
||||||
|
|
||||||
store.set(storeItem)
|
store.set(storeItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { IComponentNode, IComponentConfig } from '@polymech/commons'
|
import type { IComponentNode, IComponentConfig } from '@polymech/commons'
|
||||||
import config from "@/app/config.json" with { type: "json" }
|
import config from "@/config/config.json" with { type: "json" }
|
||||||
|
|
||||||
interface ShippingDetails {
|
interface ShippingDetails {
|
||||||
"@type": "OfferShippingDetails";
|
"@type": "OfferShippingDetails";
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { IComponentNode, IComponentConfig } from '@polymech/commons'
|
|||||||
// https://developers.google.com/search/docs/appearance/structured-data/merchant-listing
|
// https://developers.google.com/search/docs/appearance/structured-data/merchant-listing
|
||||||
// https://search.google.com/test/rich-results/result/r%2Fmerchant-listings?id=zK-WH_1fFMHIUY3yHqHj6Q
|
// https://search.google.com/test/rich-results/result/r%2Fmerchant-listings?id=zK-WH_1fFMHIUY3yHqHj6Q
|
||||||
// import tag from "@types/google-publisher-tag"
|
// import tag from "@types/google-publisher-tag"
|
||||||
import config from "@/app/config.json" with { type: "json" }
|
import config from "@/config/config.json" with { type: "json" }
|
||||||
interface GoogleMerchantProduct {
|
interface GoogleMerchantProduct {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
|
|||||||
|
|
||||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||||
import { LANGUAGES_PROD } from "config/config.js";
|
import { LANGUAGES_PROD } from "config/config.js";
|
||||||
import config from "config/config.json";
|
import config from "@/config/config.json";
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import StoreEntries from "@/components/store/StoreEntries.astro";
|
import StoreEntries from "@/components/store/StoreEntries.astro";
|
||||||
import CtaOne from "@/components/cta/CtaOne.astro";
|
import CtaOne from "@/components/cta/CtaOne.astro";
|
||||||
|
|||||||
@ -1,16 +1,7 @@
|
|||||||
{
|
{
|
||||||
"debug": true,
|
"debug": true,
|
||||||
"matching": [
|
"matching": [
|
||||||
"src/**",
|
"src/",
|
||||||
"*.md",
|
|
||||||
"*.json",
|
|
||||||
"*.cjs",
|
|
||||||
"docs",
|
|
||||||
"packages/imagetools/**",
|
|
||||||
"scripts",
|
|
||||||
"meta/**",
|
|
||||||
"tests/**",
|
|
||||||
"*.mjs",
|
|
||||||
"!node_modules/**",
|
"!node_modules/**",
|
||||||
"!ref/**"
|
"!ref/**"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"],
|
||||||
"config/*": ["src/app/*"]
|
"config/*": ["src/config/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [".astro/types.d.ts", "**/*.ts", "**/*.tsx", "**/*.astro", "src/content/resources/software-as.mdx"],
|
"include": [".astro/types.d.ts", "**/*.ts", "**/*.tsx", "**/*.astro", "src/content/resources/software-as.mdx"],
|
||||||
|
|||||||
Reference in New Issue
Block a user