site template cleanup 1/3

This commit is contained in:
lovebird 2025-03-18 18:31:40 +01:00
parent a64acb9f71
commit bc07c94501
37 changed files with 228 additions and 2247 deletions

File diff suppressed because one or more lines are too long

935
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,7 @@
"github-slugger": "^2.0.0",
"glob": "^11.0.1",
"got": "^14.4.6",
"imagetools": "file:packages/imagetools",
"imagetools": "file:../astro-components/packages/imagetools",
"lighthouse": "^12.3.0",
"markdown-it": "^14.1.0",
"marked": "^15.0.7",

View File

@ -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)
}

View File

@ -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"
}
}

View File

@ -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
}
}

View File

@ -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);
}
});
},
};
}

View File

@ -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);
}
});
},
};
}

View File

@ -1,3 +0,0 @@
export { file } from './file.js';
export { glob } from './glob.js';
export * from './types.js';

View File

@ -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>);
}

View File

@ -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"
}
]
}

View File

@ -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);

View File

@ -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"
}
}

View File

@ -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
}
}
}
}

View File

@ -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"
}
]
}

View File

@ -1,8 +0,0 @@
{
"shop":{
"title": "Shop",
"description": "",
"items":"${OSR_ROOT}/products/products/**/config.json",
"root":"${OSR_ROOT}/products"
}
}

View File

@ -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"
}
}
}

View File

@ -10,7 +10,7 @@ import {
I18N_SOURCE_LANGUAGE,
PRODUCT_SPECS,
RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS
} from '@/app/config.js'
} from 'config/config.js'
import { translateXLS } from '@polymech/i18n/translate_xls'
import { I18N_STORE, OSR_ROOT } from 'config/config.js'

View File

@ -13,14 +13,23 @@ import { sync as read } from '@polymech/fs/read'
import { sync as mv } from '@polymech/fs/move'
import { logger } from '@/base/index.js'
import { removeArrayValues, removeArrays, removeBufferValues, removeEmptyObjects } from '@/base/objects.js'
import { ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT, DEFAULT_IMAGE_URL, ASSETS_LOCAL } from '../app/config.js'
import {
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 { validateFilename, sanitizeFilename } from "@polymech/fs/utils"
import { env } from './index.js'
const IMAGES_GLOB = '*.+(JPG|jpg|jpeg|png|PNG|gif)'
export const default_sort = (files: string[]): string[] => {
@ -80,7 +89,7 @@ export const default_filter_locale = async (url: string) => {
}
export const image_url = async (src, fallback = DEFAULT_IMAGE_URL) => {
if(exists(src)) return src
if (exists(src)) return src
let safeSrc = src
try {
const response = await fetch(src, { method: 'HEAD' })
@ -112,7 +121,7 @@ export const gallery = async (
logger.warn(`item gallery : item ${product} media path not found ${mediaPath}!`)
return []
}
const galleryGlob = (productConfig.gallery || {})[assetSlug]?.glob || IMAGES_GLOB
const galleryGlob = (productConfig.gallery || {})[assetSlug]?.glob || ASSETS_GLOB
let galleryFiles: any[] = files(mediaPath, galleryGlob, {
cwd: mediaPath,
absolute: false,

View File

@ -6,7 +6,7 @@ import { I18N_SOURCE_LANGUAGE } from 'config/config.js'
import { translate } from '@/base/i18n.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);

View File

@ -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

View File

@ -3,7 +3,7 @@ import "../styles/flowbite.css"
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 "../styles/global.css"

View File

@ -1,6 +1,6 @@
---
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";
---

View File

@ -1,6 +1,6 @@
---
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 {
LANGUAGES_PROD,

View File

@ -1,7 +1,7 @@
---
import Wrapper from "@/components/containers/Wrapper.astro";
import { I18N_SOURCE_LANGUAGE } from "@/app/config";
import { items } from "config/navigation.js";
import { I18N_SOURCE_LANGUAGE } from "@/config/config";
import { items } from "@/config/navigation.js";
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE;
const navItems = await items({ locale });
---

View File

@ -1,7 +1,7 @@
---
import config from "@/app/config.json"
import config from "@/config/config.json"
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 title = frontmatter?.title || config.site.title

View File

@ -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 { image_url } from "@/base/media.js"

View File

@ -1,5 +1,5 @@
import { translate } from "@/base/i18n.js"
import config from "config/config.json"
import config from "@/config/config.json"
}
/**

View File

@ -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 { Img } from "imagetools/components"
const { title, url, price, model, selected = false } = Astro.props;

View File

@ -4,7 +4,7 @@ description: Contact Plastic Hub
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"
## {frontmatter.title}

View File

@ -1,29 +1,30 @@
import * as path from 'path'
import { findUp } from 'find-up'
import { } from 'astro:content'
import { sync as read } from '@polymech/fs/read'
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 { ICADNodeSchema, IComponentConfig } from '@polymech/commons/component'
import { ContentEntryRenderFunction, ContentEntryType } from 'astro'
import { RenderedContent, DataEntry } from "astro:content"
import type { Loader, LoaderContext } from 'astro/loaders'
import {
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
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_URL,
parseBoolean,
DEFAULT_CONTACT
DEFAULT_CONTACT,
default_image
} from 'config/config.js'
import { env } from '../base/index.js'
import { gallery } from '../base/media.js';
import { env } from '@/base/index.js'
import { gallery } from '@/base/media.js';
import { get } from '@polymech/commons/component'
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 { slugify } from "@/base/strings.js"
export interface ILoaderContextEx extends LoaderContext {
entryTypes: Map<string, ContentEntryType>
}
export interface IComponentConfigEx extends IComponentConfig {
content: string
extra_resources?: string
@ -49,10 +47,6 @@ export interface IStoreItem extends DataEntry {
rendered?: RenderedContent
}
let loaderCtx: ILoaderContextEx
const renderFunctionByContentType = new WeakMap<ContentEntryType, ContentEntryRenderFunction>();
const filterBranch = (items: { rel: string, config, path }[], branch: string) => {
if (!PRODUCT_BRANCHES) {
return items
@ -69,7 +63,7 @@ export const items = (branch: string) =>
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) => {
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 data: IComponentConfigEx = item.data
const itemRel = data.rel
@ -137,33 +131,24 @@ const cad = async (item: IStoreItem, ctx: ILoaderContextEx): Promise<ICADNodeSch
return cadMeta
}
const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
const onItem = async (item: IStoreItem, ctx: LoaderContext) => {
if (!item || !item.data) {
ctx.logger.error(`Error completing ${''}: no data`);
return
}
if (!loaderCtx) {
loaderCtx = ctx
}
const { logger } = ctx
let data: IComponentConfigEx = item.data
const itemRel = data.rel
const itemRelMin = itemRel.replace('products/', '')
const itemDir = PRODUCT_DIR(itemRel)
const default_profile = env(itemRel)
data.product_rel = itemRelMin
data.assets = {
renderings: [],
gallery: []
}
data.body = (read(path.join(itemDir, 'templates/shared', 'body.md')) as string) || ""
data.resources = (read(path.join(itemDir, 'templates/shared', 'resources.md')) as string) || ""
//////////////////////////////////////////
//
// Item Shared Resources
@ -207,18 +192,16 @@ const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
item.data = data
//////////////////////////////////////////
//
// Extensions, CAD, Media, etc.
// Extensions, CAD, Media, etcetera etcetera :)
//
data.cad = await cad(item, ctx)
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.cad = await cad(item)
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)
}
export function loader(branch: string): Loader {
@ -228,8 +211,7 @@ export function loader(branch: string): Loader {
watcher,
parseData,
store,
generateDigest,
entryTypes }: ILoaderContextEx) => {
generateDigest }: LoaderContext) => {
store.clear();
let products = items(branch)
@ -259,14 +241,10 @@ export function loader(branch: string): Loader {
watcher,
parseData,
store,
generateDigest,
entryTypes
generateDigest
} as any)
storeItem.data['config'] = JSON.stringify({
...storeItem.data
}, null, 2)
storeItem.data['config'] = JSON.stringify(storeItem.data, null, 2)
store.set(storeItem)
}
}

View File

@ -1,5 +1,5 @@
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 {
"@type": "OfferShippingDetails";

View File

@ -3,7 +3,7 @@ import type { IComponentNode, IComponentConfig } from '@polymech/commons'
// 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
// 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 {
id: string
title: string

View File

@ -3,7 +3,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
import Wrapper from "@/components/containers/Wrapper.astro";
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 StoreEntries from "@/components/store/StoreEntries.astro";
import CtaOne from "@/components/cta/CtaOne.astro";

View File

@ -1,16 +1,7 @@
{
"debug": true,
"matching": [
"src/**",
"*.md",
"*.json",
"*.cjs",
"docs",
"packages/imagetools/**",
"scripts",
"meta/**",
"tests/**",
"*.mjs",
"src/",
"!node_modules/**",
"!ref/**"
]

View File

@ -19,7 +19,7 @@
"resolveJsonModule": true,
"paths": {
"@/*": ["src/*"],
"config/*": ["src/app/*"]
"config/*": ["src/config/*"]
}
},
"include": [".astro/types.d.ts", "**/*.ts", "**/*.tsx", "**/*.astro", "src/content/resources/software-as.mdx"],