generated from polymech/site-template
stuff like that :)
This commit is contained in:
parent
7bc95ea8b9
commit
97eb1c5e8a
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-03-19T22:56:22.498Z"
|
||||
"default": "2025-03-20T07:39:49.013Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
File diff suppressed because one or more lines are too long
29
dev.code-workspace
Normal file
29
dev.code-workspace
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../astro-components"
|
||||
},
|
||||
{
|
||||
"path": "../polymech-mono/packages/commons"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.associations": {
|
||||
"*.json.liquid": "json",
|
||||
"*.yaml.liquid": "yaml",
|
||||
"*.md.liquid": "markdown",
|
||||
"*.js.liquid": "liquid-javascript",
|
||||
"*.css.liquid": "liquid-css",
|
||||
"*.scss.liquid": "liquid-scss",
|
||||
"*.embeddedhtml": "html",
|
||||
"*.cps": "javascript",
|
||||
"*.h": "c",
|
||||
"XASM2INT.C": "cpp",
|
||||
"XASM.H": "cpp",
|
||||
"TNC_f1.H": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,10 +18,19 @@ export const I18N_SOURCE_LANGUAGE = 'en'
|
||||
export const I18N_CACHE = true
|
||||
export const I18N_ASSET_PATH = "${SRC_DIR}/${SRC_NAME}-${DST_LANG}${SRC_EXT}"
|
||||
|
||||
// Library - Howtos
|
||||
|
||||
// Products
|
||||
export const HOWTO_ROOT = () => path.resolve(resolve("./public/resources/howtos"))
|
||||
export const HOWTO_GLOB = '**/config.json'
|
||||
|
||||
|
||||
// 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'
|
||||
export const ENABLED_PRODUCTS = "${OSR_ROOT}/products/config/machines.json"
|
||||
export const PRODUCT_SPECS = (rel) => `${PRODUCT_ROOT()}/${rel}/specs.xlsx`
|
||||
|
||||
// Product compiler
|
||||
export const PRODUCT_CONFIG = (product) =>
|
||||
@ -35,9 +44,9 @@ export const PRODUCTS_TARGET_SRC = './src/content/en/retail'
|
||||
export const PRODUCTS_TARGET = (lang) => `./content/${lang}/products`
|
||||
|
||||
// Product assets
|
||||
|
||||
export const ASSETS_LOCAL = false
|
||||
export const ASSETS_GLOB = '*.+(JPG|jpg|jpeg|png|PNG|gif)'
|
||||
|
||||
// OSRL - Language
|
||||
export const IS_DEV = true
|
||||
export const OSRL_ENV = 'astro-release'
|
||||
@ -47,10 +56,6 @@ export const OSRL_MODULE_NAME = 'polymech.io'
|
||||
export const OSRL_PRODUCT_PROFILE = './src/config/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/'
|
||||
|
||||
@ -105,11 +110,11 @@ export const parseBoolean = (value: string): boolean => {
|
||||
}
|
||||
/////////////////////////////////////////////
|
||||
//
|
||||
// Rendering
|
||||
// Rendering - Store
|
||||
|
||||
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
|
||||
|
||||
36
src/model/commons.ts
Normal file
36
src/model/commons.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import * as path from 'path'
|
||||
import { z } from "zod"
|
||||
import normalizeUrl from 'normalize-url'
|
||||
|
||||
import { PFilterValid } from '@polymech/commons/filter'
|
||||
|
||||
import { forward_slash, pathInfoEx, resolve, readOSRConfig, } from '@polymech/commons'
|
||||
|
||||
export const find_items = (nodes: string[], options) => {
|
||||
nodes = nodes.filter(options.filter)
|
||||
return nodes.map((c) => {
|
||||
const root = resolve(options.root, false, {})
|
||||
return {
|
||||
rel: forward_slash(`${path.relative(root, path.parse(c).dir)}`),
|
||||
path: forward_slash(`${options.root}/${path.relative(root, c)}`),
|
||||
config: readOSRConfig(c)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const get = (src, root, type) => {
|
||||
const srcInfo = pathInfoEx(src, false, { absolute: true})
|
||||
return srcInfo
|
||||
/*
|
||||
switch (type) {
|
||||
case "howtos": {
|
||||
const options = {
|
||||
filter: "howtos"
|
||||
root
|
||||
}
|
||||
return find_items(srcInfo.FILES, options)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@ -1,28 +1,27 @@
|
||||
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 { 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
|
||||
parseBoolean,
|
||||
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'
|
||||
@ -33,9 +32,8 @@ 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 const ITEM_TYPE = 'product'
|
||||
|
||||
export interface IComponentConfigEx extends IComponentConfig {
|
||||
content: string
|
||||
extra_resources?: string
|
||||
@ -48,10 +46,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
|
||||
@ -68,7 +62,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')) {
|
||||
@ -91,8 +85,25 @@ const onComponent = async (item: IStoreItem, ctx: ILoaderContextEx) => {
|
||||
}
|
||||
*/
|
||||
}
|
||||
export const defaults = async (data: IComponentConfigEx, cwd: string, root:string): Promise<IComponentConfigEx> => {
|
||||
let defaultsJSON = await findUp('defaults.json', {
|
||||
stopAt: root,
|
||||
cwd: cwd
|
||||
});
|
||||
try {
|
||||
if (defaultsJSON) {
|
||||
data = {
|
||||
...read(defaultsJSON, 'json') as any,
|
||||
...data,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
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
|
||||
@ -136,58 +147,24 @@ const cad = async (item: IStoreItem, ctx: ILoaderContextEx): Promise<ICADNodeSch
|
||||
return cadMeta
|
||||
}
|
||||
|
||||
export const getRenderFunction = async (fileName: string) => {
|
||||
let entryType = loaderCtx.entryTypes.get(path.parse(fileName).ext) as ContentEntryType
|
||||
let _render = renderFunctionByContentType.get(entryType) as ContentEntryRenderFunction
|
||||
|
||||
if (!_render) {
|
||||
_render = await (entryType as any).getRenderFunction({})
|
||||
renderFunctionByContentType.set(entryType, _render)
|
||||
}
|
||||
return _render
|
||||
}
|
||||
export const renderMarkup = async (content, data: any, fileName: string = 'template.md') => {
|
||||
if (!loaderCtx) {
|
||||
debugger
|
||||
log.error('Loader context not set')
|
||||
return
|
||||
}
|
||||
const _render = await getRenderFunction(fileName)
|
||||
if (!_render) {
|
||||
log.error('No render function')
|
||||
return
|
||||
}
|
||||
return _render({
|
||||
body: content,
|
||||
data,
|
||||
filePath: fileName,
|
||||
} as any)
|
||||
}
|
||||
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 = data.rel.replace('products/', '')
|
||||
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,45 +184,26 @@ const onItem = async (item: IStoreItem, ctx: ILoaderContextEx) => {
|
||||
//
|
||||
// Variables
|
||||
//
|
||||
let defaultsJSON = await findUp('defaults.json', {
|
||||
stopAt: PRODUCT_ROOT(),
|
||||
cwd: itemDir
|
||||
})
|
||||
try {
|
||||
if (defaultsJSON) {
|
||||
data = {
|
||||
...read(defaultsJSON, 'json') as any,
|
||||
...data,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error reading defaults.json: ${error.message}`);
|
||||
}
|
||||
|
||||
data = await defaults(data, itemDir, PRODUCT_ROOT());
|
||||
data = {
|
||||
...data,
|
||||
...default_profile.variables,
|
||||
product_rel_min: itemRelMin.replace('products/', ''),
|
||||
product_rel_min: itemRelMin,
|
||||
}
|
||||
data = resolveConfig(data as Record<string, string>) as IComponentConfigEx
|
||||
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.renderings.length && (data.thumbnail =
|
||||
{
|
||||
alt: data.assets.renderings[0].alt,
|
||||
url: data.assets.renderings[0].url,
|
||||
src: data.assets.renderings[0].url
|
||||
})
|
||||
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)
|
||||
}
|
||||
|
||||
export function loader(branch: string): Loader {
|
||||
@ -255,8 +213,7 @@ export function loader(branch: string): Loader {
|
||||
watcher,
|
||||
parseData,
|
||||
store,
|
||||
generateDigest,
|
||||
entryTypes }: ILoaderContextEx) => {
|
||||
generateDigest }: LoaderContext) => {
|
||||
|
||||
store.clear();
|
||||
let products = items(branch)
|
||||
@ -268,7 +225,7 @@ export function loader(branch: string): Loader {
|
||||
slug: id,
|
||||
id,
|
||||
title: product.name,
|
||||
type: 'product',
|
||||
type: ITEM_TYPE,
|
||||
highlights: [],
|
||||
components: [],
|
||||
...product,
|
||||
@ -286,25 +243,23 @@ 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)
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: "store-loader",
|
||||
name: `astro:store:${ITEM_TYPE}`,
|
||||
load
|
||||
};
|
||||
}
|
||||
|
||||
export const group_path = (item) => item.id.split("/")[1]
|
||||
|
||||
const group_label = async (text: string, locale) => await translate(slugify(text), I18N_SOURCE_LANGUAGE, locale)
|
||||
|
||||
const group = async (items, locale) => {
|
||||
return items.reduce(async (accPromise, item) => {
|
||||
const acc = await accPromise
|
||||
@ -319,4 +274,13 @@ const group = async (items, locale) => {
|
||||
}, {})
|
||||
}
|
||||
|
||||
export const group_by_path = async (items, locale): Promise<IComponentConfigEx[]> => await group(items, locale)
|
||||
export const group_by_path = async (items, locale): Promise<IComponentConfigEx[]> => await group(items, locale)
|
||||
|
||||
export const mailto = (to: string, subject: string, body: string): string => {
|
||||
const encode = (str: string) => encodeURIComponent(str).replace(/%20/g, '+');
|
||||
return `mailto:${encode(to)}?subject=${encode(subject)}&body=${encode(body)}`;
|
||||
}
|
||||
|
||||
export const item_checkout = async (item: IComponentConfig) => {
|
||||
return `mailto:${DEFAULT_CONTACT}?subject=${item.name}&body=${""}`
|
||||
}
|
||||
@ -1,63 +1,220 @@
|
||||
import * as path from 'path'
|
||||
import { findUp } from 'find-up'
|
||||
import { sync as read } from '@polymech/fs/read'
|
||||
import { sync as exists } from '@polymech/fs/exists'
|
||||
import { filesEx, forward_slash, resolveConfig } from '@polymech/commons'
|
||||
import { ICADNodeSchema, IComponentConfig } from '@polymech/commons/component'
|
||||
import { RenderedContent, DataEntry } from "astro:content"
|
||||
import type { Loader, LoaderContext } from 'astro/loaders'
|
||||
import { get } from '@polymech/commons/component'
|
||||
import {
|
||||
CAD_MAIN_MATCH, PRODUCT_BRANCHES,
|
||||
CAD_EXTENSIONS, CAD_MODEL_EXT, PRODUCT_DIR, PRODUCT_GLOB,
|
||||
PRODUCT_ROOT, CAD_EXPORT_CONFIGURATIONS,
|
||||
CAD_DEFAULT_CONFIGURATION,
|
||||
CAD_URL,
|
||||
parseBoolean,
|
||||
DEFAULT_CONTACT,
|
||||
default_image,
|
||||
HOWTO_ROOT,
|
||||
HOWTO_GLOB
|
||||
} from 'config/config.js'
|
||||
|
||||
import { env } from '@/base/index.js'
|
||||
import { gallery } from '@/base/media.js';
|
||||
|
||||
import { PFilterValid } from '@polymech/commons/filter'
|
||||
|
||||
import { IAssemblyData } from '@polymech/cad'
|
||||
import { logger as log } from '@/base/index.js'
|
||||
import { translate } from "@/base/i18n.js"
|
||||
import { I18N_SOURCE_LANGUAGE } from "config/config.js"
|
||||
import { slugify } from "@/base/strings.js"
|
||||
|
||||
|
||||
export const ITEM_TYPE = 'howto'
|
||||
export const items = (branch: string) => get(`${HOWTO_ROOT()}/${HOWTO_GLOB}`, HOWTO_ROOT(), ITEM_TYPE)
|
||||
|
||||
export const defaults = async (data: any, cwd: string, root:string): Promise<IComponentConfigEx> => {
|
||||
let defaultsJSON = await findUp('defaults.json', {
|
||||
stopAt: root,
|
||||
cwd: cwd
|
||||
});
|
||||
try {
|
||||
if (defaultsJSON) {
|
||||
data = {
|
||||
...read(defaultsJSON, 'json') as any,
|
||||
...data,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
|
||||
const onItem = async (item: any, ctx: LoaderContext) => {
|
||||
return item
|
||||
if (!item || !item.data) {
|
||||
ctx.logger.error(`Error completing ${''}: no data`);
|
||||
return
|
||||
}
|
||||
let data: any = 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
|
||||
//
|
||||
let resourcesDefaultPath = await findUp('resources.md', {
|
||||
stopAt: PRODUCT_ROOT(),
|
||||
cwd: itemDir
|
||||
}) || ""
|
||||
data.shared = (read(resourcesDefaultPath) as string) || ""
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// Readme
|
||||
//
|
||||
let readmePath = path.join(itemDir, 'Readme.md')
|
||||
data.readme = (read(readmePath) as string) || ""
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// Variables
|
||||
//
|
||||
data = await defaults(data, itemDir, PRODUCT_ROOT());
|
||||
data = {
|
||||
...data,
|
||||
...default_profile.variables,
|
||||
product_rel_min: itemRelMin,
|
||||
}
|
||||
// data = resolveConfig(data as Record<string, string>) as IComponentConfigEx
|
||||
item.data = data
|
||||
//////////////////////////////////////////
|
||||
//
|
||||
// Extensions, CAD, Media, etcetera etcetera :)
|
||||
//
|
||||
//data.cad = await cad(item)
|
||||
data.assets.gallery = await gallery('media/gallery', data.rel)
|
||||
}
|
||||
|
||||
export function loader(branch: string): Loader {
|
||||
const load = async ({
|
||||
config,
|
||||
logger,
|
||||
watcher,
|
||||
parseData,
|
||||
store,
|
||||
generateDigest }: LoaderContext) => {
|
||||
|
||||
store.clear();
|
||||
let products = items(branch)
|
||||
for (const item of products) {
|
||||
const product: any = item.config
|
||||
const id = product.slug;
|
||||
const data = {
|
||||
rel: item.rel,
|
||||
slug: id,
|
||||
id,
|
||||
title: product.name,
|
||||
type: ITEM_TYPE,
|
||||
highlights: [],
|
||||
components: [],
|
||||
...product,
|
||||
}
|
||||
//const parsedData = await parseData({ id, data: data });
|
||||
const storeItem = {
|
||||
digest: await generateDigest(data),
|
||||
filePath: id,
|
||||
assetImports: [],
|
||||
id: `${item.rel}`,
|
||||
data: data
|
||||
}
|
||||
await onItem(storeItem, {
|
||||
logger,
|
||||
watcher,
|
||||
parseData,
|
||||
store,
|
||||
generateDigest
|
||||
} as any)
|
||||
|
||||
storeItem.data['config'] = JSON.stringify(storeItem.data, null, 2)
|
||||
store.set(storeItem)
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: `astro:store:${ITEM_TYPE}`,
|
||||
load
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export interface IHowto {
|
||||
_createdBy: string
|
||||
mentions: any[]
|
||||
_deleted: boolean
|
||||
fileLink: string
|
||||
slug: string
|
||||
_modified: string
|
||||
previousSlugs: string[]
|
||||
_created: string
|
||||
description: string
|
||||
votedUsefulBy: string[]
|
||||
creatorCountry: string
|
||||
total_downloads: number
|
||||
title: string
|
||||
time: string
|
||||
files: any[]
|
||||
difficulty_level: string
|
||||
_id: string
|
||||
tags?: Tags
|
||||
total_views: number
|
||||
_contentModifiedTimestamp: string
|
||||
cover_image: CoverImage
|
||||
comments: any[]
|
||||
moderatorFeedback: string
|
||||
steps: Step[]
|
||||
moderation: string
|
||||
}
|
||||
_createdBy: string
|
||||
mentions: any[]
|
||||
_deleted: boolean
|
||||
fileLink: string
|
||||
slug: string
|
||||
_modified: string
|
||||
previousSlugs: string[]
|
||||
_created: string
|
||||
description: string
|
||||
votedUsefulBy: string[]
|
||||
creatorCountry: string
|
||||
total_downloads: number
|
||||
title: string
|
||||
time: string
|
||||
files: any[]
|
||||
difficulty_level: string
|
||||
_id: string
|
||||
tags?: Tags
|
||||
total_views: number
|
||||
_contentModifiedTimestamp: string
|
||||
cover_image: CoverImage
|
||||
comments: any[]
|
||||
moderatorFeedback: string
|
||||
steps: Step[]
|
||||
moderation: string
|
||||
}
|
||||
|
||||
export interface Tags {
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
export interface CoverImage {
|
||||
name: string
|
||||
downloadUrl: string
|
||||
type: string
|
||||
fullPath: string
|
||||
updated: string
|
||||
size: number
|
||||
timeCreated: string
|
||||
contentType: string
|
||||
}
|
||||
|
||||
export interface Step {
|
||||
title: string
|
||||
text: string
|
||||
images: Image[]
|
||||
_animationKey: string
|
||||
}
|
||||
export interface Image {
|
||||
updated: string
|
||||
size: number
|
||||
fullPath: string
|
||||
timeCreated: string
|
||||
name: string
|
||||
downloadUrl: string
|
||||
contentType: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface Tags {
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
export interface CoverImage {
|
||||
name: string
|
||||
downloadUrl: string
|
||||
type: string
|
||||
fullPath: string
|
||||
updated: string
|
||||
size: number
|
||||
timeCreated: string
|
||||
contentType: string
|
||||
}
|
||||
|
||||
export interface Step {
|
||||
title: string
|
||||
text: string
|
||||
images: Image[]
|
||||
_animationKey: string
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
updated: string
|
||||
size: number
|
||||
fullPath: string
|
||||
timeCreated: string
|
||||
name: string
|
||||
downloadUrl: string
|
||||
contentType: string
|
||||
type: string
|
||||
}
|
||||
|
||||
|
||||
30
src/pages/api/image-proxy.ts
Normal file
30
src/pages/api/image-proxy.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
|
||||
export const GET: APIRoute = async ({ url }) => {
|
||||
// Extract the full image URL from query parameters
|
||||
const searchParams = new URL(url).searchParams;
|
||||
const imageUrl = searchParams.get('url');
|
||||
if (!imageUrl) {
|
||||
return new Response('Missing image URL', { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch the image from Firebase
|
||||
const response = await fetch(imageUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
return new Response('Failed to fetch image', { status: response.status });
|
||||
}
|
||||
|
||||
// Get the content type of the image
|
||||
const contentType = response.headers.get('content-type') || 'image/jpeg';
|
||||
|
||||
// Stream the image response
|
||||
return new Response(response.body, {
|
||||
headers: { 'Content-Type': contentType },
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
return new Response('Error fetching image', { status: 500 });
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user