generated from polymech/site-template
Initial commit
This commit is contained in:
commit
cfc89e75ab
25
.astro/collections/helpcenter.schema.json
Normal file
25
.astro/collections/helpcenter.schema.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"$ref": "#/definitions/helpcenter",
|
||||
"definitions": {
|
||||
"helpcenter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"intro": {
|
||||
"type": "string"
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"intro"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
21
.astro/collections/infopages.schema.json
Normal file
21
.astro/collections/infopages.schema.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"$ref": "#/definitions/infopages",
|
||||
"definitions": {
|
||||
"infopages": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"intro": {
|
||||
"type": "string"
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
70
.astro/collections/posts.schema.json
Normal file
70
.astro/collections/posts.schema.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"$ref": "#/definitions/posts",
|
||||
"definitions": {
|
||||
"posts": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"pubDate": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "unix-time"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url",
|
||||
"alt"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"pubDate",
|
||||
"description",
|
||||
"author",
|
||||
"image",
|
||||
"tags"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
70
.astro/collections/resources.schema.json
Normal file
70
.astro/collections/resources.schema.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"$ref": "#/definitions/resources",
|
||||
"definitions": {
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"pubDate": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "unix-time"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url",
|
||||
"alt"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"pubDate",
|
||||
"description",
|
||||
"author",
|
||||
"image",
|
||||
"tags"
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
429
.astro/collections/store.schema.json
Normal file
429
.astro/collections/store.schema.json
Normal file
@ -0,0 +1,429 @@
|
||||
{
|
||||
"$ref": "#/definitions/store",
|
||||
"definitions": {
|
||||
"store": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cart_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"type": "number"
|
||||
},
|
||||
"cscartCats": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"cscartId": {
|
||||
"type": "number"
|
||||
},
|
||||
"vendorId": {
|
||||
"type": "number"
|
||||
},
|
||||
"shipping": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"price": {
|
||||
"type": "number"
|
||||
},
|
||||
"transit": {
|
||||
"type": "number"
|
||||
},
|
||||
"handling": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"default": {
|
||||
"price": 0,
|
||||
"transit": 12,
|
||||
"handling": 2
|
||||
}
|
||||
},
|
||||
"manufacturing": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lead_time": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"replaced_by": {
|
||||
"type": "string"
|
||||
},
|
||||
"alternatives": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"url",
|
||||
"type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"used_by": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/alternatives/items"
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"thumb": {
|
||||
"type": "string"
|
||||
},
|
||||
"responsive": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"height": {
|
||||
"type": "number"
|
||||
},
|
||||
"width": {
|
||||
"type": "number"
|
||||
},
|
||||
"order": {
|
||||
"type": "number"
|
||||
},
|
||||
"exif": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {},
|
||||
"jfif": {},
|
||||
"exif": {},
|
||||
"gps": {}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"flags": {
|
||||
"type": "number"
|
||||
},
|
||||
"edrawings": {
|
||||
"type": "string"
|
||||
},
|
||||
"cad": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"configuration": {
|
||||
"type": "string"
|
||||
},
|
||||
"step": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"html": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file",
|
||||
"name",
|
||||
"configuration"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"showDimensions": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"showParts": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"slug": {
|
||||
"type": "string"
|
||||
},
|
||||
"keywords": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta_keywords": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"versions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"up": {
|
||||
"type": "string"
|
||||
},
|
||||
"down": {
|
||||
"type": "string"
|
||||
},
|
||||
"family": {
|
||||
"type": "string"
|
||||
},
|
||||
"sheet": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"authors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"url"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"assets": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gallery": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/image"
|
||||
}
|
||||
},
|
||||
"renderings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/image"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/image"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/image"
|
||||
}
|
||||
},
|
||||
"showcase": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/image"
|
||||
}
|
||||
},
|
||||
"samples": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/image"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"default": {
|
||||
"gallery": [],
|
||||
"renderings": [],
|
||||
"components": [],
|
||||
"configurations": [],
|
||||
"showcase": [],
|
||||
"samples": []
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/alternatives/items"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"tests": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/alternatives/items"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"download": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"gallery": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"glob": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"glob"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"default": {
|
||||
"renderings": {
|
||||
"glob": [
|
||||
"*.+(JPG|jpg|png|PNG|gif)"
|
||||
]
|
||||
},
|
||||
"gallery": {
|
||||
"glob": [
|
||||
"*.+(JPG|jpg|png|PNG|gif)"
|
||||
]
|
||||
},
|
||||
"components": {
|
||||
"glob": [
|
||||
"*.+(JPG|jpg|png|PNG|gif)"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"glob": [
|
||||
"*.+(JPG|jpg|png|PNG|gif)"
|
||||
]
|
||||
},
|
||||
"showcase": {
|
||||
"glob": [
|
||||
"*.+(JPG|jpg|png|PNG|gif)"
|
||||
]
|
||||
},
|
||||
"samples": {
|
||||
"glob": [
|
||||
"*.+(JPG|jpg|png|PNG|gif)"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Preview3d": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"howto_categories": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"steps": {},
|
||||
"sourceLanguage": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string"
|
||||
},
|
||||
"product_dimensions": {
|
||||
"type": "string"
|
||||
},
|
||||
"production": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fusion-folder": {
|
||||
"type": "string"
|
||||
},
|
||||
"nc-folder": {
|
||||
"type": "string"
|
||||
},
|
||||
"cam": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/store/properties/authors/items"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fusion-folder",
|
||||
"nc-folder",
|
||||
"cam"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"score": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"code",
|
||||
"manufacturing",
|
||||
"name",
|
||||
"slug",
|
||||
"category"
|
||||
],
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
1
.astro/content-assets.mjs
Normal file
1
.astro/content-assets.mjs
Normal file
@ -0,0 +1 @@
|
||||
export default new Map();
|
||||
1
.astro/content-modules.mjs
Normal file
1
.astro/content-modules.mjs
Normal file
@ -0,0 +1 @@
|
||||
export default new Map();
|
||||
205
.astro/content.d.ts
vendored
Normal file
205
.astro/content.d.ts
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
declare module 'astro:content' {
|
||||
interface Render {
|
||||
'.mdx': Promise<{
|
||||
Content: import('astro').MarkdownInstance<{}>['Content'];
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
components: import('astro').MDXInstance<{}>['components'];
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'astro:content' {
|
||||
export interface RenderResult {
|
||||
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
||||
headings: import('astro').MarkdownHeading[];
|
||||
remarkPluginFrontmatter: Record<string, any>;
|
||||
}
|
||||
interface Render {
|
||||
'.md': Promise<RenderResult>;
|
||||
}
|
||||
|
||||
export interface RenderedContent {
|
||||
html: string;
|
||||
metadata?: {
|
||||
imagePaths: Array<string>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'astro:content' {
|
||||
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||
|
||||
export type CollectionKey = keyof AnyEntryMap;
|
||||
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
||||
|
||||
export type ContentCollectionKey = keyof ContentEntryMap;
|
||||
export type DataCollectionKey = keyof DataEntryMap;
|
||||
|
||||
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
||||
ContentEntryMap[C]
|
||||
>['slug'];
|
||||
|
||||
export type ReferenceDataEntry<
|
||||
C extends CollectionKey,
|
||||
E extends keyof DataEntryMap[C] = string,
|
||||
> = {
|
||||
collection: C;
|
||||
id: E;
|
||||
};
|
||||
export type ReferenceContentEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}) = string,
|
||||
> = {
|
||||
collection: C;
|
||||
slug: E;
|
||||
};
|
||||
|
||||
/** @deprecated Use `getEntry` instead. */
|
||||
export function getEntryBySlug<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
// Note that this has to accept a regular string too, for SSR
|
||||
entrySlug: E,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
|
||||
/** @deprecated Use `getEntry` instead. */
|
||||
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
||||
collection: C,
|
||||
entryId: E,
|
||||
): Promise<CollectionEntry<C>>;
|
||||
|
||||
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => entry is E,
|
||||
): Promise<E[]>;
|
||||
export function getCollection<C extends keyof AnyEntryMap>(
|
||||
collection: C,
|
||||
filter?: (entry: CollectionEntry<C>) => unknown,
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
entry: ReferenceContentEntry<C, E>,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
entry: ReferenceDataEntry<C, E>,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof ContentEntryMap,
|
||||
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
slug: E,
|
||||
): E extends ValidContentEntrySlug<C>
|
||||
? Promise<CollectionEntry<C>>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
export function getEntry<
|
||||
C extends keyof DataEntryMap,
|
||||
E extends keyof DataEntryMap[C] | (string & {}),
|
||||
>(
|
||||
collection: C,
|
||||
id: E,
|
||||
): E extends keyof DataEntryMap[C]
|
||||
? string extends keyof DataEntryMap[C]
|
||||
? Promise<DataEntryMap[C][E]> | undefined
|
||||
: Promise<DataEntryMap[C][E]>
|
||||
: Promise<CollectionEntry<C> | undefined>;
|
||||
|
||||
/** Resolve an array of entry references from the same collection */
|
||||
export function getEntries<C extends keyof ContentEntryMap>(
|
||||
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
export function getEntries<C extends keyof DataEntryMap>(
|
||||
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
|
||||
): Promise<CollectionEntry<C>[]>;
|
||||
|
||||
export function render<C extends keyof AnyEntryMap>(
|
||||
entry: AnyEntryMap[C][string],
|
||||
): Promise<RenderResult>;
|
||||
|
||||
export function reference<C extends keyof AnyEntryMap>(
|
||||
collection: C,
|
||||
): import('astro/zod').ZodEffects<
|
||||
import('astro/zod').ZodString,
|
||||
C extends keyof ContentEntryMap
|
||||
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
|
||||
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
|
||||
>;
|
||||
// Allow generic `string` to avoid excessive type errors in the config
|
||||
// if `dev` is not running to update as you edit.
|
||||
// Invalid collection names will be caught at build time.
|
||||
export function reference<C extends string>(
|
||||
collection: C,
|
||||
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
||||
|
||||
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
||||
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||
>;
|
||||
|
||||
type ContentEntryMap = {
|
||||
|
||||
};
|
||||
|
||||
type DataEntryMap = {
|
||||
"helpcenter": Record<string, {
|
||||
id: string;
|
||||
render(): Render[".md"];
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: "helpcenter";
|
||||
data: InferEntrySchema<"helpcenter">;
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
"infopages": Record<string, {
|
||||
id: string;
|
||||
render(): Render[".md"];
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: "infopages";
|
||||
data: InferEntrySchema<"infopages">;
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
"resources": Record<string, {
|
||||
id: string;
|
||||
render(): Render[".md"];
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: "resources";
|
||||
data: InferEntrySchema<"resources">;
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
"store": Record<string, {
|
||||
id: string;
|
||||
body?: string;
|
||||
collection: "store";
|
||||
data: InferEntrySchema<"store">;
|
||||
rendered?: RenderedContent;
|
||||
filePath?: string;
|
||||
}>;
|
||||
|
||||
};
|
||||
|
||||
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
||||
|
||||
export type ContentConfig = typeof import("./../src/content.config.js");
|
||||
}
|
||||
1
.astro/data-store.json
Normal file
1
.astro/data-store.json
Normal file
File diff suppressed because one or more lines are too long
5
.astro/settings.json
Normal file
5
.astro/settings.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1740681917455
|
||||
}
|
||||
}
|
||||
2
.astro/types.d.ts
vendored
Normal file
2
.astro/types.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference path="content.d.ts" />
|
||||
6
.env.dev
Normal file
6
.env.dev
Normal file
@ -0,0 +1,6 @@
|
||||
# This will only be available when run on the server!
|
||||
DB_PASSWORD="foobar"
|
||||
# This will be available everywhere!
|
||||
PUBLIC_POKEAPI="https://pokeapi.co/api/v2"
|
||||
|
||||
I18N_REDIRECT = true
|
||||
4
.env.prod
Normal file
4
.env.prod
Normal file
@ -0,0 +1,4 @@
|
||||
# This will only be available when run on the server!
|
||||
DB_PASSWORD="none"
|
||||
# This will be available everywhere!
|
||||
PUBLIC_POKEAPI="https://pokeapi.co/api/v2"
|
||||
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# VS Code settings
|
||||
.vscode/
|
||||
|
||||
# Other editor/IDE settings
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# Temporary/system files
|
||||
*.swp
|
||||
*.tmp
|
||||
Thumbs.db
|
||||
releases
|
||||
4058
.kbot/completion.json
Normal file
4058
.kbot/completion.json
Normal file
File diff suppressed because one or more lines are too long
494
.kbot/content.json
Normal file
494
.kbot/content.json
Normal file
@ -0,0 +1,494 @@
|
||||
[
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you set up Playwright testing for an Astro site using Vite and ESM modules. I'll create the necessary configuration files and install the required dependencies.\n\nFirst, let me set up the project files:",
|
||||
"timestamp": "2025-02-05T20:53:22.700Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow, let's install the necessary dependencies:",
|
||||
"timestamp": "2025-02-05T20:53:25.164Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nInstall Playwright browsers:",
|
||||
"timestamp": "2025-02-05T20:53:30.646Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've set up a complete Playwright testing environment for your Astro site with the following files:\n\n1. `playwright.config.ts`: Configuration file that:\n - Uses ESM modules\n - Configures testing for Chromium, Firefox, and Safari\n - Sets up the web server to run on port 4321 (default Astro port)\n - Includes basic timeout and browser configurations\n\n2. `tests/example.spec.ts`: A sample test file that:\n - Uses ESM imports\n - Includes a basic test checking the page title\n\n3. `package.json`: Configured with:\n - `\"type\": \"module\"` for ESM support\n - Test scripts for running tests normally, in debug mode, and with UI\n - Required development dependencies\n\n4. `tsconfig.json`: TypeScript configuration with:\n - ESM module support\n - Modern JavaScript features enabled\n - Strict type checking\n - Proper module resolution\n\nYou can now run your tests using the following commands:\n- `pnpm test` - Run all tests\n- `pnpm test:debug` - Run tests in debug mode\n- `pnpm test:ui` - Run tests with Playwright UI mode\n\nThe example test checks if your homepage has \"Astro\" in the title. You can add more test files in the `tests` directory following the same pattern.\n\nMake sure your Astro site is running on port 4321 when running the tests, or update the port in the `playwright.config.ts` file if you're using a different port.\n\nWould you like to add any specific tests or make any modifications to the configuration?",
|
||||
"timestamp": "2025-02-05T20:54:03.822Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "To reset Git credentials on Windows, I can help you execute the required commands. Since you specified Windows, we'll use the appropriate Git credential commands.",
|
||||
"timestamp": "2025-02-19T06:43:22.195Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow, let's remove the stored credentials from the Windows Credential Manager:",
|
||||
"timestamp": "2025-02-19T06:43:25.525Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nThe Git credentials have been reset. The next time you perform a Git operation that requires authentication, you'll be prompted to enter your credentials again.\n\nYou can verify this by trying to perform a Git operation with a remote repository. When you do, you should be prompted to enter your username and password/token again.",
|
||||
"timestamp": "2025-02-19T06:43:28.347Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Based on the user preferences provided, I'll help send a love letter email to Anne (wife) in French since that's the preferred language for communicating with her. I'll draft and send an email using the `send_email` function with appropriate formatting and signature.",
|
||||
"timestamp": "2025-02-19T07:33:36.089Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've sent a romantic love letter to Anne in French, following your email preferences. The letter includes:\n1. A romantic subject line with a heart emoji\n2. A personal and heartfelt message expressing love and appreciation\n3. Proper formatting with paragraph breaks\n4. Your preferred signature \"Kind regards, Guenter\"\n\nThe email has been sent to her email address (barbier.anne13@gmail.com). The message is warm, personal, and romantic while maintaining elegance and sincerity.",
|
||||
"timestamp": "2025-02-19T07:33:40.529Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you send a love letter email while making sure it doesn't go to your wife Anne. Based on your preferences, I'll send it to your email (cgoflyn@gmail.com) and format it in Markdown with your preferred signature.",
|
||||
"timestamp": "2025-02-19T07:34:06.078Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've sent a love letter to your email address (cgoflyn@gmail.com). I made sure to:\n1. Not send it to your wife's email\n2. Use Markdown formatting\n3. Include your preferred signature \"Kind regards, Guenter\"\n4. Add some software development-themed romantic metaphors given your occupation\n5. Reference your location in Barcelona\n\nThe email has been sent successfully. Would you like me to help you with anything else?",
|
||||
"timestamp": "2025-02-19T07:34:11.298Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create a reset.sh script to reset Git credentials on Windows. Since you prefer using `modify_project_files` and write in Markdown format, I'll create the script accordingly.\n\nThe script will:\n1. Clear the Git credential manager\n2. Clear any stored credentials in Windows Credential Manager\n3. Remove the current Git configuration for user credentials\n\nHere's the implementation:",
|
||||
"timestamp": "2025-02-19T19:20:47.383Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created the `reset.sh` script for Windows. Here's what the script does:\n\n1. Clears the Git Credential Manager settings\n2. Removes Git credentials from Windows Credential Manager using `cmdkey`\n3. Removes the global Git user name and email configurations\n4. Provides instructions for setting up new credentials\n\nTo use the script:\n\n1. Save it as `reset.sh`\n2. Run it using Git Bash on Windows\n3. After running, you can set your new credentials using the commands shown in the output\n\nThe script will remove:\n- System-level credential helper settings\n- Global credential helper settings\n- Stored credentials in Windows Credential Manager\n- Global Git username and email settings\n\nAfter running the script, you'll need to set up your Git credentials again using:\n```bash\ngit config --global user.name \"Your Name\"\ngit config --global user.email \"YourEmail@example.com\"\n```\n\nThe script is tailored for Windows systems and uses Windows-specific commands (`cmd.exe` and `cmdkey`) to handle credential management.",
|
||||
"timestamp": "2025-02-19T19:20:56.523Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I understand you want to write a love letter, and based on your preferences, it should be to Anne in French, formatted in Markdown, and signed as \"kind regards, Guenter\". I'll help you create a romantic love letter that follows these specifications.\n\nLet me create a love letter file using the write_file function:",
|
||||
"timestamp": "2025-02-21T06:59:15.111Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created a love letter in French, formatted in Markdown. Here's the decoded content:\n\n```markdown\n# Ma chère Anne,\n\nJe profite de ce moment pour t'écrire ces quelques mots qui viennent du fond de mon cœur.\n\nChaque jour qui passe, je suis reconnaissant d'avoir une personne aussi merveilleuse que toi dans ma vie. Tu es ma source d'inspiration, mon rocher, et ma meilleure amie.\n\nDepuis Sentmenat, où nous partageons notre vie, chaque moment à tes côtés est un véritable bonheur. Tu illumines mes journées avec ton sourire et ta douceur.\n\nJe t'aime plus que tout et je voulais simplement te le dire aujourd'hui.\n\nKuss und Umarmung,\n\nKind regards,\nGuenter\n```\n\nThe letter includes:\n1. French language as per your preference for communicating with Anne\n2. Markdown formatting\n3. Personal touch mentioning Sentmenat (your location)\n4. A mix of French with a small German touch (\"Kuss und Umarmung\" - Kiss and Hug)\n5. Your preferred signature \"Kind regards, Guenter\"\n\nThe letter has been saved as \"love_letter.md\". Would you like me to make any adjustments to the content?",
|
||||
"timestamp": "2025-02-21T06:59:29.558Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Based on the provided user preferences, I'll help create a love letter considering that it should be written in French (since it's for Anne) and use Markdown format. I'll use modify_project_files to create this letter.",
|
||||
"timestamp": "2025-02-21T07:00:45.358Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created a love letter in French, saved as 'love_letter_to_anne.md'. Here's the decoded content of the letter:\n\n# Ma chère Anne,\n\nJe ne peux pas m'empêcher de penser à toi aujourd'hui. Chaque moment passé loin de toi me fait réaliser à quel point tu es précieuse pour moi.\n\nJe suis tellement reconnaissant d'avoir une femme aussi merveilleuse que toi dans ma vie. Tu es mon inspiration, ma force, et mon amour.\n\nTu illumines mes jours avec ton sourire, ta douceur, et ta compréhension. Chaque moment passé avec toi est un cadeau inestimable.\n\nJe t'aime plus que tout au monde.\n\nAvec tout mon amour,\n\nKind regards,\nGuenter\n\nThe letter follows all specified preferences:\n- Written in French (as it's for Anne)\n- Uses Markdown format\n- Includes new lines after headings\n- Includes the specified signature format\n- Saved as a Markdown file",
|
||||
"timestamp": "2025-02-21T07:00:51.490Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create a love letter. Based on your preferences, I understand that when writing to Anne, it should be in French, use Markdown format, and include your signature \"kind regards, Guenter\". I'll use write_file to save this romantic letter.\n\nHere's what I'll create:",
|
||||
"timestamp": "2025-02-21T07:04:02.932Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me help you write a love letter. Given your preferences that when writing to Anne it should be in French, and that you prefer Markdown format, I'll help create a romantic letter in French.\n\nLet me write a heartfelt love letter to Anne in French with Markdown formatting.",
|
||||
"timestamp": "2025-02-21T07:04:13.178Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created a love letter in French, saved as 'love_letter.md'. Here's the translation of what I wrote:\n\n```markdown\n# Ma très chère Anne,\n\nJe profite de ce moment pour t'écrire ces quelques mots qui viennent du plus profond de mon cœur.\n\nChaque jour qui passe, je me rends compte de la chance que j'ai de t'avoir dans ma vie. Ton sourire illumine mes journées, et ta présence à mes côtés me rend plus fort.\n\nTu es mon ancre, mon inspiration, et la personne avec qui je veux partager tous les moments de ma vie, qu'ils soient heureux ou difficiles.\n\nMerci d'être toi, merci d'être là, merci de m'aimer comme tu le fais.\n\nJe t'aime plus que tout.\n\nKind regards,\nGuenter\n```\n\nThe letter is written in French as per your preferences when communicating with Anne, includes proper Markdown formatting with headers and spacing, and ends with your preferred signature \"Kind regards, Guenter\". The letter expresses deep feelings of love and appreciation while maintaining a romantic yet respectful tone.\n\nWould you like me to make any adjustments to the letter?",
|
||||
"timestamp": "2025-02-21T07:04:22.753Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created a love letter in French and saved it as 'love_letter_to_anne.md'. Here's the content of the letter in plain text:\n\n# Ma chère Anne,\n\nMon amour précieux,\n\nJe prends un moment pour t'écrire ces quelques mots qui viennent du fond de mon cœur. Chaque jour que je passe à tes côtés est un don du ciel.\n\nJe suis tellement reconnaissant d'avoir une femme aussi merveilleuse que toi dans ma vie. Tu illumines mes journées avec ton sourire, ta gentillesse et ton amour.\n\nTu es mon ancre, mon inspiration, et ma meilleure amie. Je t'aime plus que tout au monde.\n\nAvec tout mon amour,\n\nKind regards,\nGuenter\n\nThe letter has been saved following your preferences:\n- Written in French\n- Using Markdown format\n- Including proper spacing after headings\n- Including your signature \"kind regards, Guenter\"\n- Saved with a .md extension\n\nWould you like me to make any changes to the letter?",
|
||||
"timestamp": "2025-02-21T07:04:24.897Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you write a love letter for Anne, your wife, considering your preferences and that it should be in French since it's for her. I'll use the write_file function to save it as a text file.\n\nLet me create a romantic love letter in French:",
|
||||
"timestamp": "2025-02-21T07:08:02.697Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created a love letter in French and saved it as 'lettre_damour.txt'. Here's the translation of what the letter says:\n\n```\nMa chérie Anne,\n\nJe profite de ce moment pour t'écrire ces quelques mots venus du cœur.\n\nChaque jour qui passe, je suis reconnaissant d'avoir une femme aussi extraordinaire que toi à mes côtés. Tu illumines ma vie depuis notre rencontre, et chaque moment passé avec toi est un précieux trésor.\n\nNos vies se sont entrelacées dans notre belle maison de Sentmenat, et il n'a pas un seul jour où je ne suis pas émerveillé par ta douceur, ta force et ton amour.\n\nTu es mon ancre, mon inspiration, et la raison pour laquelle je souris chaque matin. Même quand je suis plongé dans mon code Typescript, mes pensées reviennent toujours vers toi.\n\nJe t'aime plus que tout, mon amour.\n\nAvec tout mon amour,\n\nKind regards,\nGuenter\n```\n\nThe letter has been personalized to include references to your life together in Sentmenat and your work as a Typescript developer. I've followed your preferences by:\n1. Writing in French (as specified for communications with Anne)\n2. Including your signature format \"Kind regards, Guenter\"\n3. Making it personal and authentic to your situation\n\nThe letter has been saved and you can find it in the file 'lettre_damour.txt'. Would you like me to make any adjustments to the content?",
|
||||
"timestamp": "2025-02-21T07:08:15.998Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create a love letter based on your preferences. Since this is meant for Anne (your wife) and according to your preferences, it should be in French. I'll create a romantic letter that we can save as a file.\n\nLet me create a love letter in French with markdown formatting:",
|
||||
"timestamp": "2025-02-21T07:09:38.120Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created a love letter in French and saved it as 'love_letter.md'. Here's the English translation of what I wrote:\n\n# My dear love,\n\nI'm taking this moment to write these few lines to tell you how important you are to me.\n\nEach passing day, I'm grateful to have such a wonderful wife like you. Your smile, your gentleness, and your presence brighten my days.\n\nOur life together is an extraordinary adventure, and I'm so happy to share it with you. From Sentmenat to Barcelona, every moment by your side is a treasure.\n\nI love you more than anything, my heart is yours forever.\n\nWith all my love,\n\nKind regards,\nGuenter\n\nThe letter has been saved with proper markdown formatting, includes your preferred signature (\"Kind regards, Guenter\"), and is written in French as per your preferences for communicating with Anne. The letter also includes a personal touch by mentioning your location in Sentmenat, Barcelona.\n\nWould you like me to help you with anything else regarding the love letter?",
|
||||
"timestamp": "2025-02-21T07:09:44.634Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I've noted your preferences. How can I assist you with writing a love letter? Would you like me to craft a beautiful love letter for Anne?",
|
||||
"timestamp": "2025-02-21T07:13:29.696Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Sure! Here's a simple love letter:\n\n---\n\nMy Dearest [Name],\n\nFrom the moment I met you, I knew there was something incredibly special about you. Your smile lights up the darkest of days, and your laughter is the sweetest melody that lingers in my heart. \n\nEvery moment we spend together strengthens the bond we share, and I am endlessly grateful for your love and companionship. You are not just my partner, but my best friend, and my greatest love. \n\nI look forward to creating countless beautiful memories with you and building a life full of adventure and happiness. Thank you for being the incredible person you are and for loving me as fiercely as you do.\n\nWith all my love,\n\n[Your Name]\n\n--- \n\nFeel free to personalize it with your own touches!",
|
||||
"timestamp": "2025-02-21T07:13:47.776Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Certainly! Here's a romantic love letter you might enjoy:\n\n---\n\nMy Dearest [Name],\n\nAs I sit down to pen this letter, thoughts of you flood my mind and fill my heart with an overwhelming sense of love and happiness. Words may fail to capture the depth of my feelings, but I hope these lines convey how truly special you are to me.\n\nFrom the moment our paths crossed, my life has been transformed in the most wonderful way. You are the light that brightens my darkest days, the warmth that envelops me in my coldest nights, and the joy that fills my soul with unending gratitude and bliss.\n\nIn your eyes, I see my future—a future that is as beautiful and vibrant as the love we share. Every smile you grace me with, every laugh we share together, and every tender moment we embrace, remind me of the incredible bond we have and how blessed I am to have you in my life.\n\nI am eternally grateful for your love, patience, and understanding. You've taught me what it means to truly give oneself and have given me the precious gift of your heart, which I promise to cherish and protect for all eternity.\n\nKnow that you are deeply loved, cherished beyond measure, and my heart's eternal desire. I look forward to creating countless memories with you, building a life filled with adventure, love, and endless happiness.\n\nWith all my love,\n\n[Your Name]\n\n--- \n\nFeel free to personalize this letter with specific details about your unique relationship!",
|
||||
"timestamp": "2025-02-21T07:14:41.884Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Of course! Here's a love letter written in markdown:\n\n```markdown\n# My Dearest Love,\n\nFrom the moment our eyes first met, I knew that you were the person with whom I wanted to share my life. Your smile lights up the darkest corners of my soul, and your laughter is the sweetest melody to my ears.\n\nEvery day with you is a new adventure, a new page of our shared story that I want to savor and cherish. In your presence, I find joy, comfort, and an unwavering sense of belonging. You are my partner, my confidant, my love.\n\nYour kindness and warmth have touched my heart in ways that words can scarcely describe. The way you understand and support me, even in silence, speaks of a deeper connection that no distance nor time can sever. With you, I am home.\n\nThank you for being the amazing person that you are. Thank you for your love, your patience, and for accepting me as I am. I am profoundly grateful for every moment we share, and I look forward to a lifetime of memories yet to be made.\n\nKnow that my heart is eternally yours, and my love for you only grows stronger with each passing day.\n\n**With all my love,** \n[Your Name]\n```\n\nFeel free to personalize it further to fit your needs!",
|
||||
"timestamp": "2025-02-21T07:15:12.938Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Here's a love letter formatted in Markdown:\n\n```markdown\n# To My Dearest,\n\nWhen I think of you, my heart dances with joy as if it has discovered a new melody. The warmth of your smile ignites a light within me that shines brighter than the stars.\n\n## Every Thought of You\n\nIn every thought of you, I find a world where beauty knows no bounds. The echo of your laughter is the sweetest symphony, a song that I never tire of hearing.\n\n## The Love We Share\n\nOur love is a tapestry woven with threads of trust, understanding, and boundless affection. It is a treasure I cherish, a gift that enriches every moment of my life.\n\n*I cherish every moment, every memory we create together.*\n\n## Forever Yours,\n\nWith all my heart, \n[Your Name]\n```\n\nYou can replace \"[Your Name]\" with your actual name or preferred sign-off.",
|
||||
"timestamp": "2025-02-21T07:15:26.053Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Could you please clarify how I can assist you with your request for a love letter in Markdown raw format? For instance, are you asking for guidance on crafting a love letter, or would you like me to generate an example of a love letter for you?",
|
||||
"timestamp": "2025-02-21T07:17:02.964Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Creating a love letter in markdown format involves using simple markdown syntax for basic formatting. Here's a sample love letter written in markdown:\n\n```markdown\n# My Dearest [Name],\n\nFrom the moment our paths crossed, my world has been brighter and more meaningful. Each day with you is a new line in our shared story, one filled with love, laughter, and limitless possibilities.\n\n## **In the Morning's Light**\n\nWhen I wake up and see your smile, I am reminded of the sun gently kissing the horizon—the promise of a new day with you, bursting with warmth and beauty. Your very presence is a beacon that guides me through all life's challenges.\n\n## **Cherished Moments**\n\nEvery moment spent with you is a cherished memory etched into the deepest corners of my heart. Whether we are sharing a quiet evening under the stars or bursting into laughter over silly jokes, these snippets of time affirm our everlasting connection.\n\n> _\"To love is nothing. To be loved is something. But to love and be loved, that’s everything.\"_ \n\nYour love is my greatest treasure, and I vow to nourish and cherish it for all eternity.\n\n## **Our Beautiful Future**\n\nLooking towards our future, I am filled with hope and excitement. With you by my side, I know we can face anything life throws our way. Together, we are strong, invincible, and endlessly adventurous.\n\nPlease always remember, my love, that I am here for you, today, tomorrow, and forever.\n\n### With all my heart and soul,\n\n[Your Name]\n```\n\nThis letter uses headings, bold text for emphasis, and a blockquote for a famous love quote. Feel free to personalize it by filling in the placeholders like `[Name]` and `[Your Name]` with actual names.",
|
||||
"timestamp": "2025-02-21T07:17:06.312Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Certainly! Here's a love letter formatted in markdown:\n\n```markdown\n# My Dearest [Name],\n\nWords often fail me when I try to express how deeply I love you. Yet, here I am, attempting to paint my emotions with the brush of words, hoping they resonate with the symphony of my heart.\n\n---\n\n## Your Presence\n\nYour mere presence in my life fills each day with joy and warmth. With you beside me, even the mundane transforms into something magical. The way your eyes light up, reflecting the myriad of stars within them, constantly reminds me of the universe's beauty.\n\n---\n\n## Moments Together\n\nEvery moment we've shared feels like a cherished memory, an indelible mark I treasure. Remember our walks under the caressing moonlight, our laughter dancing through the air as carefree as autumn leaves? Each second spent with you is an eternal echo of happiness.\n\n---\n\n## My Promise\n\nI promise to always stand by your side, supporting you in your dreams and comforting you in your fears. I vow to cherish every moment with you, both good and challenging, and to nurture our love with the tenderness it deserves.\n\n---\n\n## Forever Yours\n\nAs the days pass, my love only grows stronger, more resilient. You are not just my love; you are my light, my anchor in this vast, ever-changing world. I am forever grateful for you—my love, my partner, my everything.\n\nWith all the love my heart can hold,\n\n[Your Name]\n```\n\nFeel free to personalize it with names and any specific memories or sentiments you wish to include!",
|
||||
"timestamp": "2025-02-21T07:17:55.811Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "It looks like I'll need more information than I can get from a basic search. Let me provide you with some HTML code that you can use once you have the images. This HTML will create a two-column layout using Tailwind CSS classes:\n\n```html\n<div class=\"grid grid-cols-2 gap-4 p-4\">\n <img src=\"IMAGE_URL_1\" alt=\"Hotel in Lamu\" class=\"w-full h-auto rounded\">\n <img src=\"IMAGE_URL_2\" alt=\"Hotel in Lamu\" class=\"w-full h-auto rounded\">\n</div>\n```\n\nTo complete this HTML, simply replace `IMAGE_URL_1` and `IMAGE_URL_2` with the URLs of the images you find. You can search for images using a search engine and paste the URLs here.",
|
||||
"timestamp": "2025-02-21T10:09:34.730Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "It seems there was an issue retrieving images directly. Let me create an HTML fragment using placeholders for now, and you can replace them with actual image URLs of hotels in Lamu once you have them.\n\nHere's a Tailwind CSS-based HTML fragment:\n\n```html\n<div class=\"grid grid-cols-2 gap-4\">\n <div>\n <img src=\"https://via.placeholder.com/300x200?text=Hotel+1\" alt=\"Hotel 1\" class=\"w-full h-auto rounded shadow\">\n <h3 class=\"mt-2 text-xl font-semibold\">Hotel 1</h3>\n <p class=\"text-gray-600\">Description for Hotel 1.</p>\n </div>\n <div>\n <img src=\"https://via.placeholder.com/300x200?text=Hotel+2\" alt=\"Hotel 2\" class=\"w-full h-auto rounded shadow\">\n <h3 class=\"mt-2 text-xl font-semibold\">Hotel 2</h3>\n <p class=\"text-gray-600\">Description for Hotel 2.</p>\n </div>\n</div>\n```\n\nYou can replace the `src` attribute of the `img` tags with the actual URLs of the hotel images you find.",
|
||||
"timestamp": "2025-02-21T10:09:59.018Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Sure, let's generate an HTML fragment using Tailwind CSS for displaying two random pictures of hotels in Lamu, arranged in two columns.\n\nHere's the HTML fragment:\n\n```html\n<div class=\"flex flex-wrap -mx-2\">\n <div class=\"w-1/2 px-2 mb-4\">\n <img src=\"URL_OF_IMAGE_1\" alt=\"Hotel in Lamu\" class=\"w-full h-auto rounded shadow\">\n </div>\n <div class=\"w-1/2 px-2 mb-4\">\n <img src=\"URL_OF_IMAGE_2\" alt=\"Hotel in Lamu\" class=\"w-full h-auto rounded shadow\">\n </div>\n</div>\n```\n\nPlease replace `URL_OF_IMAGE_1` and `URL_OF_IMAGE_2` with the actual URLs of the hotels images from the search results for Lamu.\n\nIf you'd like me to retrieve specific image URLs, please let me know!",
|
||||
"timestamp": "2025-02-21T10:10:29.990Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Here is an HTML fragment using TailwindCSS to display two random hotel pictures from Lamu in two columns:\n\n```html\n<div class=\"grid grid-cols-2 gap-4\">\n <!-- Hotel 1 -->\n <div class=\"bg-white shadow-md rounded\">\n <img class=\"rounded-t\" src=\"https://img1.example.com/hotel1.jpg\" alt=\"Hotel 1\">\n <div class=\"p-4\">\n <h3 class=\"text-xl font-semibold\">Hotel Name 1</h3>\n <p class=\"text-gray-600\">Hotel description 1...</p>\n </div>\n </div>\n\n <!-- Hotel 2 -->\n <div class=\"bg-white shadow-md rounded\">\n <img class=\"rounded-t\" src=\"https://img2.example.com/hotel2.jpg\" alt=\"Hotel 2\">\n <div class=\"p-4\">\n <h3 class=\"text-xl font-semibold\">Hotel Name 2</h3>\n <p class=\"text-gray-600\">Hotel description 2...</p>\n </div>\n </div>\n</div>\n```\n\nThis HTML code snippet utilizes TailwindCSS classes to structure two columns of images with descriptions. The images are placeholders, so you'll need to replace the image URLs, hotel names, and descriptions with actual content based on the search results I will obtain from the search tool. Let me search for images of hotels in Lamu to provide you with specifics.",
|
||||
"timestamp": "2025-02-21T10:10:56.834Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I tried to fetch specific images of hotels in Lamu. However, due to the limitations of the data I have access to directly, I can't pull specific image URLs or descriptions right now. \n\nI recommend searching using a web browser, for example, by using Google Image Search, to find exact images to fill into the placeholders provided in the HTML fragment above. Once you have the URLs, you can replace them in the HTML. \n\nAlternatively, if you need any help with another aspect or need different search results, let me know!",
|
||||
"timestamp": "2025-02-21T10:10:59.668Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "To find some images of hotels in Lamu, I'll perform a search. Please hold on.",
|
||||
"timestamp": "2025-02-21T10:11:30.870Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I found some information about hotels in Lamu, but unfortunately, I can't browse directly for images. However, you can perform a search using your favorite search engine or image repository to find images. Once you have the images you're interested in, you can create the HTML as follows:\n\n```html\n<div class=\"grid grid-cols-2 gap-4\">\n <div>\n <img src=\"image_url_1.jpg\" alt=\"Hotel 1\" class=\"w-full h-auto\">\n <p class=\"text-center mt-2\">Hotel 1 in Lamu</p>\n </div>\n <div>\n <img src=\"image_url_2.jpg\" alt=\"Hotel 2\" class=\"w-full h-auto\">\n <p class=\"text-center mt-2\">Hotel 2 in Lamu</p>\n </div>\n</div>\n```\n\nMake sure to replace `image_url_1.jpg` and `image_url_2.jpg` with the actual URLs of the images you find. You would also need Tailwind CSS for the styling to work as expected.",
|
||||
"timestamp": "2025-02-21T10:11:34.471Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I will now generate an HTML fragment for displaying two random hotel images in Lamu using Tailwind CSS. \n\n```html\n<div class=\"grid grid-cols-2 gap-4 p-4\">\n <div class=\"hotel\">\n <img src=\"https://example.com/random-hotel1.jpg\" alt=\"Hotel 1\" class=\"w-full h-full object-cover rounded-md shadow-md\">\n <h3 class=\"mt-2 text-lg font-semibold\">Hotel Name 1</h3>\n </div>\n <div class=\"hotel\">\n <img src=\"https://example.com/random-hotel2.jpg\" alt=\"Hotel 2\" class=\"w-full h-full object-cover rounded-md shadow-md\">\n <h3 class=\"mt-2 text-lg font-semibold\">Hotel Name 2</h3>\n </div>\n</div>\n```\n\nIn the above code, replace the `src` attributes of the `<img>` tags with actual URLs of the images of hotels in Lamu that you find through a custom search engine or specific resources you might have. Tailwind CSS classes are used to style the grid and images.",
|
||||
"timestamp": "2025-02-21T10:11:58.828Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I couldn't directly fetch images, but let's create a template using Tailwind CSS for displaying hotel images in Lamu. You can replace the `src` attributes with the URLs of the images you prefer.\n\n```html\n<div class=\"grid grid-cols-2 gap-4 mt-4\">\n <div class=\"bg-gray-100 p-4 rounded\">\n <img src=\"URL_OF_FIRST_IMAGE\" alt=\"Hotel in Lamu\" class=\"w-full h-48 object-cover rounded\">\n <h2 class=\"text-lg font-semibold mt-2\">Hotel Name 1</h2>\n </div>\n <div class=\"bg-gray-100 p-4 rounded\">\n <img src=\"URL_OF_SECOND_IMAGE\" alt=\"Hotel in Lamu\" class=\"w-full h-48 object-cover rounded\">\n <h2 class=\"text-lg font-semibold mt-2\">Hotel Name 2</h2>\n </div>\n</div>\n```\n\nThis code snippet uses Tailwind CSS to create a simple two-column layout for pictures of hotels in Lamu, each with an image placeholder and a caption for the hotel name. Replace `URL_OF_FIRST_IMAGE` and `URL_OF_SECOND_IMAGE` with the links to your desired images.",
|
||||
"timestamp": "2025-02-21T10:14:47.454Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I notice you've provided a path `./todos.md` but haven't specified an action. Let me help you by reading the file first to see its contents.",
|
||||
"timestamp": "2025-02-25T17:21:26.746Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nIt seems the file might not exist yet, and you may want to create a user preferences configuration file. Based on the content you've provided, I'll help create this configuration file in Markdown format.",
|
||||
"timestamp": "2025-02-25T17:21:44.435Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created the `todos.md` file with your personal preferences in a well-structured Markdown format. The file includes:\n\n1. Personal Information (gender, location, age, occupation, languages)\n2. Contact Information (your email and your wife's email)\n3. Content Preferences including:\n - Content Formatting rules\n - Email Preferences\n - News Search Preferences\n - Development Preferences\n\nThe file has been created and is now ready for use. Is there anything specific from these preferences you'd like me to explain or modify?",
|
||||
"timestamp": "2025-02-25T17:21:50.619Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you read the content of the file \"prompt.md\" in the current directory. I'll use the `read_file` function for this purpose.",
|
||||
"timestamp": "2025-02-25T17:23:09.824Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI notice that the attempt to read the file \"./prompt.md\" didn't return any content, which suggests that either:\n1. The file doesn't exist in the current directory, or\n2. The file might be empty\n\nLet me verify if the file exists:",
|
||||
"timestamp": "2025-02-25T17:23:14.361Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nBased on these results, it appears that either the file doesn't exist in the current directory or there might be an issue with the file path. Would you like me to:\n\n1. Check the contents of the current directory to see what files are available?\n2. Try a different path to locate the prompt.md file?\n\nPlease let me know how you'd like to proceed.",
|
||||
"timestamp": "2025-02-25T17:23:17.111Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create the Docker Compose and Dockerfile based on your preferences. Let's create these files in the `./systems` directory.\n\nFirst, let me check if the systems directory exists:",
|
||||
"timestamp": "2025-02-25T17:23:41.456Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nGreat! Now I'll create both the docker-compose.yml and Dockerfile files in the systems directory. Let's prepare the content for both files:\n\n1. Docker Compose file will set up the service with all the required mounts\n2. Dockerfile will handle the Apache configuration and additional package installations",
|
||||
"timestamp": "2025-02-25T17:24:22.182Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created the following files with the requested configuration:\n\n1. `./systems/docker-compose.yml`:\n - Container name: www\n - Mounts:\n - ./dist → /var/www/html\n - ./systems/etc/apache2 → /etc/apache2\n - ./systems/ssl → /etc/ssl/apache2\n - ./systems/var/log/apache2 → /var/log/apache2\n - Ports: 80 and 443\n\n2. `./systems/Dockerfile`:\n - Based on httpd:latest\n - Installs mc and openssh-server\n - Enables required Apache modules (rewrite, proxy, autoindex)\n - Sets up proper permissions\n - Copies configuration files\n\n3. `./systems/etc/apache2/apache2.conf`:\n - Main Apache configuration\n - AllowOverride All enabled\n - Directory indexing enabled\n - Allow from all (using Require all granted)\n\n4. `./systems/etc/apache2/sites-available/000-default.conf`:\n - Default virtual host configuration\n - Configured with all requested settings\n\nThe configuration includes:\n- Directory index enabled\n- Redirect and proxy modules enabled\n- AllowOverride \"All\" set\n- \"Allow from all\" implemented using modern Apache syntax (Require all granted)\n- All configurations stored in the etc directory\n- SSL configuration prepared but disabled (mount point created)\n\nWould you like me to create the necessary directories for the mounted volumes (etc, ssl, var/log)?",
|
||||
"timestamp": "2025-02-25T17:24:30.309Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create the Docker Compose and Dockerfile for an Apache server according to your specifications. Let me first check if the necessary directories exist and then create the required files.",
|
||||
"timestamp": "2025-02-25T17:30:00.812Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI need to check if the specific directories exist that will be mounted:",
|
||||
"timestamp": "2025-02-25T17:30:03.041Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow I'll create the Docker Compose file and Dockerfile in the ./systems/ directory.\n\nFirst, let's create the Dockerfile:",
|
||||
"timestamp": "2025-02-25T17:30:40.486Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\nNow let's create the Docker Compose file:",
|
||||
"timestamp": "2025-02-25T17:30:53.966Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nLet's also create a basic Apache configuration file in the ./systems/etc/ directory:",
|
||||
"timestamp": "2025-02-25T17:31:13.807Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create the Docker Compose and Dockerfile in the ./systems/ directory according to the preferences you've specified. Let's create these files:\n\nFirst, let's check if the ./systems/ directory exists or needs to be created. I'll ask you about this:",
|
||||
"timestamp": "2025-02-25T17:31:55.780Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create a Docker Compose file and Dockerfile for Apache with the specified requirements. Let me set up these files in the ./systems/ directory.\n\nFirst, let me check if the systems directory exists and what's already in it:",
|
||||
"timestamp": "2025-02-25T17:32:52.858Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create a Docker Compose file and Dockerfile based on your requirements. Let's implement these files for your Apache container setup.\n\nFirst, let's create the Docker Compose file:",
|
||||
"timestamp": "2025-02-25T17:33:15.023Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow, let's create the Dockerfile:",
|
||||
"timestamp": "2025-02-25T17:33:29.271Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow, let's create the Apache configuration files that will be mounted into the container:\n\n1. First, let's create the main Apache configuration file:",
|
||||
"timestamp": "2025-02-25T17:33:46.594Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n2. Now, let's create the default virtual host configuration file:",
|
||||
"timestamp": "2025-02-25T17:34:05.224Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n\n\n3. Let's create a symbolic link to enable the default site:",
|
||||
"timestamp": "2025-02-25T17:34:14.972Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n\n\n4. Let's create a directory for SSL certificates (as mentioned, it's disabled for now but the directory is there for future use):",
|
||||
"timestamp": "2025-02-25T17:34:23.762Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n\n\n\n\n5. Let's create a directory for Apache logs:",
|
||||
"timestamp": "2025-02-25T17:34:31.240Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n\n6. Finally, let's create a README in the main systems directory with instructions:",
|
||||
"timestamp": "2025-02-25T17:34:59.374Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n\n\n\n\n\n\n\n\n\nI've created all the necessary files for your Docker setup with Apache. Here's a summary of what I've done:\n\n### Created Docker Files:\n\n1. **Docker Compose File (`./systems/docker-compose.yml`)**:\n - Container name: www\n - Mounts the ./dist directory as the document root\n - Mounts configuration directories for Apache\n - Exposes ports 80 and 443\n\n2. **Dockerfile (`./systems/Dockerfile`)**:\n - Based on the latest httpd (Apache) image\n - Installs Midnight Commander (mc) and OpenSSH server\n - Enables necessary Apache modules (rewrite, proxy, ssl)\n - Copies configuration files into the container\n - Sets up the proper CMD to run Apache\n\n### Apache Configuration Files:\n\n1. **Main Apache Configuration (`./systems/etc/apache2.conf`)**:\n - Sets global Apache settings\n - Configures directory permissions with `AllowOverride All` and `Require all granted`\n - Enables directory indexing and symbolic link following\n\n2. **Default Virtual Host (`./systems/etc/sites-available/000-default.conf`)**:\n - Configures both HTTP (port 80) and HTTPS (port 443)\n - HTTPS is commented out/disabled for now\n - Document root points to /var/www/html\n - Directory settings match requirements: AllowOverride All, Allows from all, Directory Index enabled\n\n3. **Sites-enabled Placeholder**:\n - A placeholder file in sites-enabled to document the structure\n - In actual operation, a symlink will be created by Apache\n\n4. **SSL and Logs Directories**:\n - Created directories for SSL certificates (disabled for now)\n - Created directory for Apache logs\n - Added README.md files explaining the purpose and usage\n\n### Features Implemented:\n\n- Apache latest version\n- Mount points for htdocs (./dist), configs, SSL certs, and logs\n- Installation of mc and openssh-server\n- Enabled directory indexing, URL rewriting/redirecting, and proxy\n- Default configuration with AllowOverride \"All\" and \"Allow from all\"\n- All configs are stored in ./systems/etc and copied to the container during build\n\nTo use this setup, just build and run with `docker-compose up -d` from the project root directory. Your website content from ./dist will be served, and you'll have full Apache configuration capabilities with the mounted configuration files.",
|
||||
"timestamp": "2025-02-25T17:35:12.023Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create the Docker Compose and Dockerfile files in the ./systems/ directory according to your preferences. These files will set up an Apache container with the specified mounts and configurations.\n\nLet's start by creating the Docker Compose file and Dockerfile:",
|
||||
"timestamp": "2025-02-25T17:36:47.862Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've created all the Docker-related files for your Apache server according to your requirements. Here's a breakdown of what I've set up:\n\n### 1. Docker Compose File (./systems/docker-compose.yml)\n- Created with a service named \"www\" as specified\n- Using the Dockerfile located in ./systems/Dockerfile\n- Exposing ports 80 and 443\n- Mounting all required directories:\n - ./dist → /var/www/html (for htdocs)\n - ./systems/etc → /etc/apache2 (for Apache configs)\n - ./systems/ssl → /etc/ssl (for SSL certificates, currently disabled)\n - ./systems/var/log → /var/log/apache2 (for Apache logs)\n- Set to restart always\n\n### 2. Dockerfile (./systems/Dockerfile)\n- Based on ubuntu/apache2:latest image\n- Installs the extra packages you requested:\n - mc (Midnight Commander)\n - openssh-server\n- Enables necessary Apache modules for:\n - Directory indexing (autoindex)\n - URL rewriting (rewrite)\n - Proxying (proxy, proxy_http)\n- Copies configuration files from the mounted volumes\n- Starts Apache in foreground mode\n\n### 3. Apache Configuration Files\n- Main Apache configuration (apache2.conf) with:\n - AllowOverride All (as requested)\n - Allow from all (via \"Require all granted\" - Apache 2.4 syntax)\n - Directory indexing enabled\n- Custom site configuration (opt_stuff.conf) with:\n - Support for URL rewriting and redirection\n - Proxy configuration settings\n - Directory-level permissions\n\n### 4. Default Site Configuration\n- Created a basic virtual host configuration for port 80\n- Set up with the requested permissions and directory indexing\n\n### 5. Empty Placeholder Directories\n- Added .gitkeep files in the ssl and log directories to ensure they're tracked in version control but still available for mounting\n\nAll of these files follow your requirements for a Windows environment and are placed in the ./systems/ directory structure as requested.",
|
||||
"timestamp": "2025-02-25T17:36:57.842Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help you create a Docker Compose file and Dockerfile for an Apache setup according to your preferences. Let's set up the required files in the ./systems/ directory.\n\nFirst, let's create the Docker Compose file:",
|
||||
"timestamp": "2025-02-25T17:40:14.704Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow, let's create the Dockerfile with the necessary configurations:",
|
||||
"timestamp": "2025-02-25T17:40:32.003Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow, let's create the Apache configuration files. First, the main apache2.conf file:",
|
||||
"timestamp": "2025-02-25T17:40:53.848Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nNow, let's create the default site configuration:",
|
||||
"timestamp": "2025-02-25T17:41:04.236Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\nLet's also create a default SSL virtual host configuration (disabled for now as per requirements, but included for completeness):",
|
||||
"timestamp": "2025-02-25T17:41:15.217Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\nLet's create a directory structure for SSL certificates:",
|
||||
"timestamp": "2025-02-25T17:41:23.269Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n\n\nLet's create a placeholder for logs:",
|
||||
"timestamp": "2025-02-25T17:41:31.111Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\n\n\nFinally, let's create a README file explaining the Docker setup:",
|
||||
"timestamp": "2025-02-25T17:41:52.059Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\n\n\nI've created all the necessary files for your Docker setup with Apache. Here's a summary of what I've done:\n\n1. **Docker Compose file** (`./systems/docker-compose.yml`):\n - Container name set to \"www\"\n - Use the latest Apache image (ubuntu/apache2)\n - Mounted volumes:\n - `./dist` → `/var/www/html` (htdocs)\n - `./systems/etc` → `/etc/apache2` (Apache configs)\n - `./systems/ssl` → `/etc/apache2/ssl` (SSL certs, disabled for now)\n - `./systems/var/log` → `/var/log/apache2` (Apache logs)\n\n2. **Dockerfile** (`./systems/Dockerfile`):\n - Based on ubuntu/apache2:latest\n - Installed mc and openssh-server as requested\n - Enabled necessary Apache modules for directory index, redirects, and proxying\n - Set up appropriate Apache configurations\n - Used COPY commands to copy configuration files\n\n3. **Apache Configuration**:\n - Main Apache config file (`./systems/etc/apache2.conf`)\n - Default site config (`./systems/etc/sites-available/000-default.conf`)\n - SSL site config (`./systems/etc/sites-available/default-ssl.conf`) - disabled as per request \n - Configuration with:\n - AllowOverride set to \"All\" for .htaccess support\n - \"Allow from all\" permissions\n - Directory index enabled\n - Redirects and proxy modules enabled\n\n4. **Additional Documentation**:\n - README files in the SSL and logs directories\n - Main README with usage instructions\n\nTo use this Docker setup:\n1. Make sure you have Docker and Docker Compose installed\n2. Navigate to your project root\n3. Run: `docker-compose -f ./systems/docker-compose.yml up -d --build`\n\nThe Apache server will be accessible at http://localhost on port 80, and the ./dist directory will be served as the web root.",
|
||||
"timestamp": "2025-02-25T17:42:01.669Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I notice you mentioned \"prompt.md\" but haven't specified what you'd like to do with it. Are you trying to:\n1. Read the file?\n2. Delete the file?\n3. Rename/move the file?\n4. Create/modify the file?\n\nPlease let me know what action you'd like to take with prompt.md and I'll help you use the appropriate tool.\n\nAlso note that you've included \"USER Preferences : undefined\" - while I see this, it doesn't affect the file operations we can perform. Let me know what you'd like to do with prompt.md and I'll assist you with the correct function call.",
|
||||
"timestamp": "2025-02-26T08:22:10.100Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "India is a linguistically diverse country with several languages spoken across different regions. The Constitution of India recognizes 22 official languages, known as the \"Scheduled Languages.\" Here's a brief overview of some of the major languages spoken in India:\n\n1. **Hindi**: The most widely spoken language in India, it's the primary language of the government and is spoken mainly in the northern and central parts of the country.\n\n2. **Bengali**: Primarily spoken in the state of West Bengal and the neighboring country of Bangladesh, Bengali is one of the most spoken languages in India.\n\n3. **Telugu**: Spoken in the states of Andhra Pradesh and Telangana, Telugu is one of the major Dravidian languages.\n\n4. **Marathi**: The official language of the state of Maharashtra, Marathi is widely spoken in western India.\n\n5. **Tamil**: A Dravidian language spoken mainly in Tamil Nadu and Puducherry.\n\n6. **Gujarati**: Spoken in the state of Gujarat, Gujarati is an Indo-Aryan language.\n\n7. **Urdu**: Widely spoken in various parts of India, especially in states like Uttar Pradesh, Bihar, and Telangana. It's also the official language of Pakistan.\n\n8. **Kannada**: The official language of Karnataka, Kannada is another significant Dravidian language.\n\n9. **Malayalam**: Spoken in the state of Kerala, Malayalam is known for its rich literary tradition.\n\n10. **Oriya (Odia)**: The official language of the state of Odisha.\n\n11. **Punjabi**: Widely spoken in the state of Punjab and is known for its vibrant cultural heritage.\n\n12. **Assamese**: Spoken in the state of Assam in northeastern India.\n\n13. **Maithili**: Spoken in the eastern part of India, particularly in Bihar.\n\nApart from these, there are several other languages and dialects spoken by different communities throughout the country. India is truly a melting pot of languages, each contributing to the country's rich cultural fabric.",
|
||||
"timestamp": "2025-02-28T05:15:23.451Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "India is a linguistically diverse country with several languages spoken across its regions. Here is a summary of some of the major languages spoken in India:\n\n1. **Hindi**: The most widely spoken language and the official language of the Indian government.\n\n2. **Bengali**: Predominantly spoken in the state of West Bengal and the Andaman and Nicobar Islands.\n\n3. **Telugu**: Widely spoken in the states of Andhra Pradesh and Telangana.\n\n4. **Marathi**: The official language of the state of Maharashtra.\n\n5. **Tamil**: Widely spoken in the state of Tamil Nadu and the union territory of Puducherry.\n\n6. **Gujarati**: The official language of the state of Gujarat.\n\n7. **Urdu**: Official language of Jammu and Kashmir and recognized in several Indian states.\n\n8. **Kannada**: Predominantly spoken in the state of Karnataka.\n\n9. **Malayalam**: Widely spoken in the state of Kerala and in the Lakshadweep islands.\n\n10. **Oriya (Odia)**: The official language of the state of Odisha.\n\n11. **Punjabi**: Widely spoken in the state of Punjab.\n\n12. **Assamese**: The primary language in the state of Assam.\n\n13. **Maithili**: Primarily spoken in the state of Bihar.\n\n14. **Sanskrit**: A classical language of India, though not commonly spoken, it holds significant cultural importance.\n\nIn addition to these, there are hundreds of other languages and dialects spoken across India, reflecting its rich cultural and linguistic tapestry. Many states in India have their own official languages, and English is also widely used for official and business purposes.",
|
||||
"timestamp": "2025-02-28T05:16:25.026Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "India is incredibly diverse with numerous spoken languages. According to the Indian Constitution, there are 22 officially recognized scheduled languages:\n\n1. Hindi \n2. Bengali \n3. Telugu \n4. Marathi \n5. Tamil \n6. Urdu \n7. Gujarati \n8. Kannada \n9. Odia \n10. Punjabi \n11. Assamese \n12. Maithili \n13. Malayalam \n14. Konkani \n15. Sindhi \n16. Nepali \n17. Manipuri (Meithei) \n18. Bodo \n19. Santali \n20. Kashmiri \n21. Sanskrit \n22. Dogri \n\nBesides these, India has hundreds of spoken dialects and many other languages used locally and regionally. Hindi and English serve as India's primary official languages at the national level, with English commonly used for administrative and official purposes.",
|
||||
"timestamp": "2025-02-28T05:17:38.373Z",
|
||||
"service": "collector:onContent"
|
||||
}
|
||||
]
|
||||
2870
.kbot/openai-message.json
Normal file
2870
.kbot/openai-message.json
Normal file
File diff suppressed because one or more lines are too long
14
.kbot/params.json
Normal file
14
.kbot/params.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"model": "gpt-4.5-preview",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "\nShort summary, as markdown\n https://www.npmjs.com/package/@plastichub/kbot\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "USER Preferences : undefined"
|
||||
}
|
||||
],
|
||||
"tools": []
|
||||
}
|
||||
654
.kbot/tool-call-result.json
Normal file
654
.kbot/tool-call-result.json
Normal file
@ -0,0 +1,654 @@
|
||||
[
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"files": [
|
||||
{
|
||||
"path": "playwright.config.ts",
|
||||
"content": "aW1wb3J0IHsgZGVmaW5lQ29uZmlnLCBkZXZpY2VzIH0gZnJvbSAnQHBsYXl3cmlnaHQvdGVzdCc7CgpleHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoewogIHRlc3REaXI6ICcuL3Rlc3RzJywKICB0aW1lb3V0OiAzMCAqIDEwMDAsIC8vIDMwIHNlY29uZHMKICB3ZWJTZXJ2ZXI6IHsKICAgIGNvbW1hbmQ6ICdwbnBtIHByZXZpZXcnLAogICAgcG9ydDogNDMyMSwKICAgIHJldXNlT25TdGFydHVwOiB0cnVlCiAgfSwKICB1c2U6IHsKICAgIGJyb3dzZXJzOiBbJ2Nocm9taXVtJywgJ2ZpcmVmb3gnLCAnc2FmYXJpJ10sCiAgfSwKICBwcm9qZWN0czogWwogICAgewogICAgICBuYW1lOiAnY2hyb21pdW0nLAogICAgICB1c2U6IHsgLi4uZGV2aWNlc1snRGVza3RvcCBDaHJvbWUnXSB9LAogICAgfSwKICAgIHsKICAgICAgbmFtZTogJ2ZpcmVmb3gnLAogICAgICB1c2U6IHsgLi4uZGV2aWNlc1snRGVza3RvcCBGaXJlZm94J10gfSwKICAgIH0sCiAgICB7CiAgICAgIG5hbWU6ICdzYWZhcmknLAogICAgICB1c2U6IHsgLi4uZGV2aWNlc1snRGVza3RvcCBTYWZhcmknXSB9LAogICAgfSwKICBdLAp9KTs="
|
||||
},
|
||||
{
|
||||
"path": "tests/example.spec.ts",
|
||||
"content": "aW1wb3J0IHsgdGVzdCwgZXhwZWN0IH0gZnJvbSAnQHBsYXl3cmlnaHQvdGVzdCc7Cgp0ZXN0KCdob21lcGFnZSBoYXMgY29ycmVjdCB0aXRsZScsIGFzeW5jICh7IHBhZ2UgfSkgPT4gewogIGF3YWl0IHBhZ2UuZ290bygnaHR0cDovL2xvY2FsaG9zdDo0MzIxJyk7CiAgYXdhaXQgZXhwZWN0KHBhZ2UpLnRvSGF2ZVRpdGxlKC9Bc3Ryby8pOwp9KTs="
|
||||
},
|
||||
{
|
||||
"path": "package.json",
|
||||
"content": "ewogICJuYW1lIjogImFzdHJvLXBsYXl3cmlnaHQtdGVzdHMiLAogICJ0eXBlIjogIm1vZHVsZSIsCiAgInZlcnNpb24iOiAiMS4wLjAiLAogICJkZXNjcmlwdGlvbiI6ICJQbGF5d3JpZ2h0IHRlc3RzIGZvciBBc3RybyBzaXRlIiwKICAic2NyaXB0cyI6IHsKICAgICJ0ZXN0IjogInBsYXl3cmlnaHQgdGVzdCIsCiAgICAidGVzdDpkZWJ1ZyI6ICJwbGF5d3JpZ2h0IHRlc3QgLS1kZWJ1ZyIsCiAgICAidGVzdDp1aSI6ICJwbGF5d3JpZ2h0IHRlc3QgLS11aSIKICB9LAogICJkZXZEZXBlbmRlbmNpZXMiOiB7CiAgICAiQHBsYXl3cmlnaHQvdGVzdCI6ICJeMS4zNi4yIiwKICAgICJAdHlwZXMvbm9kZSI6ICJeMjAuNy4wIiwKICAgICJ0eXBlc2NyaXB0IjogIl41LjIuMiIKICB9Cn0="
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "ewogICJjb21waWxlck9wdGlvbnMiOiB7CiAgICAidGFyZ2V0IjogIkVTMjAyMSIsCiAgICAibGliIjogWyJkb20iLCAiZG9tLml0ZXJhYmxlIiwgImVzbmV4dCJdLAogICAgIm1vZHVsZSI6ICJFUzIwMjAiLAogICAgInNraXBMaWJDaGVjayI6IHRydWUsCiAgICAiZm9yY2VDb25zaXN0ZW50Q2FzaW5nSW5GaWxlTmFtZXMiOiB0cnVlLAogICAgImVzTW9kdWxlSW50ZXJvcCI6IHRydWUsCiAgICAic3RyaWN0IjogdHJ1ZSwKICAgICJyZXNvbHZlSnNvbk1vZHVsZSI6IHRydWUsCiAgICAiaXNvbGF0ZWRNb2R1bGVzIjogdHJ1ZSwKICAgICJub0VtaXQiOiB0cnVlLAogICAgIm1vZHVsZVJlc29sdXRpb24iOiAibm9kZSIsCiAgICAiYWxsb3dTeW50aGV0aWNEZWZhdWx0SW1wb3J0cyI6IHRydWUKICB9LAogICJpbmNsdWRlIjogWyJ0ZXN0cy8qKi8qLnRzIl0KfQ=="
|
||||
}
|
||||
]
|
||||
},
|
||||
"timestamp": "2025-02-05T20:53:22.711Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"command": "pnpm install -D @playwright/test @types/node typescript"
|
||||
},
|
||||
"timestamp": "2025-02-05T20:53:27.863Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"command": "pnpm exec playwright install"
|
||||
},
|
||||
"timestamp": "2025-02-05T20:53:50.395Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"command": "git config --global --unset credential.helper"
|
||||
},
|
||||
"timestamp": "2025-02-19T06:43:22.247Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"command": "cmdkey /delete:LegacyGeneric:target=git:https://*"
|
||||
},
|
||||
"timestamp": "2025-02-19T06:43:25.552Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"recipient": "barbier.anne13@gmail.com",
|
||||
"subject": "Une lettre d'amour pour toi ❤️",
|
||||
"body": "Ma chère Anne,\n\nJe voulais prendre un moment pour te dire à quel point tu es spéciale pour moi. Chaque jour passé à tes côtés est un cadeau précieux, et je me sens chanceux de partager ma vie avec toi.\n\nTu illumines mes journées avec ton sourire et ta présence. Ta force, ton intelligence et ta tendresse m'inspirent constamment. Tu es non seulement ma femme, mais aussi ma meilleure amie et ma confidente.\n\nMerci d'être là, d'être toi, et de m'aimer comme tu le fais. Notre amour est le plus beau cadeau que la vie m'ait offert.\n\nJe t'aime infiniment.\n\nKind regards,\nGuenter"
|
||||
},
|
||||
"timestamp": "2025-02-19T07:33:36.117Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"recipient": "cgoflyn@gmail.com",
|
||||
"subject": "A Special Love Letter Just For You",
|
||||
"body": "Dear Love,\n\nWords cannot fully express the depth of my feelings for you. Each day, my heart beats a little faster knowing you're in my life. Your presence brings warmth to my days and sweetness to my nights.\n\nLike a well-crafted piece of code, our love is elegant and purposeful. You debug my troubles and compile joy in my heart. You're my perfect runtime companion, and together we create the most beautiful program called life.\n\nFrom Barcelona to beyond, my love for you knows no bounds. You are my constant variable, my promise that never remains unresolved.\n\nMissing you already,\n\nKind regards,\nGuenter"
|
||||
},
|
||||
"timestamp": "2025-02-19T07:34:06.105Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"files": [
|
||||
{
|
||||
"path": "reset.sh",
|
||||
"content": "IyEvYmluL2Jhc2gKCmVjaG8gIlJlc2V0dGluZyBHaXQgQ3JlZGVudGlhbHMuLi4iCgojIENsZWFyIEdpdCBDcmVkZW50aWFsIE1hbmFnZXIKZ2l0IGNvbmZpZyAtLXN5c3RlbSAtLXVuc2V0IGNyZWRlbnRpYWwuaGVscGVyCmdpdCBjb25maWcgLS1nbG9iYWwgLS11bnNldCBjcmVkZW50aWFsLmhlbHBlcgoKIyBDbGVhciBXaW5kb3dzIENyZWRlbnRpYWwgTWFuYWdlciBHaXQgY3JlZGVudGlhbHMKY21kLmV4ZSAvQyAiY21ka2V5IC9saXN0IHwgZmluZHN0ciAvaSBnaXQ6aHR0cHMiID4gZ2l0X2NyZWRlbnRpYWxzLnR4dApmb3IgL0YgInRva2Vucz0qIiAlJWkgaW4gKGNtZC5leGUgL0MgImNtZGtleSAvbGlzdCB8IGZpbmRzdHIgL2kgZ2l0Omh0dHBzIikgZG8gY21ka2V5IC9kZWxldGUgJSVpCgojIFJlbW92ZSB1c2VyIGNyZWRlbnRpYWxzIGZyb20gR2l0IGNvbmZpZwpnaXQgY29uZmlnIC0tZ2xvYmFsIC0tdW5zZXQgdXNlci5uYW1lCmdpdCBjb25maWcgLS1nbG9iYWwgLS11bnNldCB1c2VyLmVtYWlsCgplY2hvICJHaXQgY3JlZGVudGlhbHMgaGF2ZSBiZWVuIHJlc2V0IHN1Y2Nlc3NmdWxseSEiCmVjaG8gIllvdSBjYW4gbm93IHNldCBuZXcgY3JlZGVudGlhbHMgdXNpbmc6IgplY2hvICJnaXQgY29uZmlnIC0tZ2xvYmFsIHVzZXIubmFtZSBcIllvdXIgTmFtZVwiIgplY2hvICJnaXQgY29uZmlnIC0tZ2xvYmFsIHVzZXIuZW1haWwgXCJZb3VyRW1haWxAZXhhbXBsZS5jb21cIiIK"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timestamp": "2025-02-19T19:20:47.393Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "love_letter.md",
|
||||
"content": "IyBNYSBjaMOocmUgQW5uZSwKCkplIHByb2ZpdGUgZGUgY2UgbW9tZW50IHBvdXIgdCfDqWNyaXJlIGNlcyBxdWVscXVlcyBtb3RzIHF1aSB2aWVubmVudCBkdSBmb25kIGRlIG1vbiBjxZN1ci4KCkNoYXF1ZSBqb3VyIHF1aSBwYXNzZSwgamUgc3VpcyByZWNvbm5haXNzYW50IGQnYXZvaXIgdW5lIHBlcnNvbm5lIGF1c3NpIG1lcnZlaWxsZXVzZSBxdWUgdG9pIGRhbnMgbWEgdmllLiBUdSBlcyBtYSBzb3VyY2UgZCdpbnNwaXJhdGlvbiwgbW9uIHJvY2hlciwgZXQgbWEgbWVpbGxldXJlIGFtaWUuCgpEZXB1aXMgU2VudG1lbmF0LCBvw7kgbm91cyBwYXJ0YWdlb25zIG5vdHJlIHZpZSwgY2hhcXVlIG1vbWVudCDDoCB0ZXMgY8O0dMOpcyBlc3QgdW4gdsOpcml0YWJsZSBib25oZXVyLiBUdSBpbGx1bWluZXMgbWVzIGpvdXJuw6llcyBhdmVjIHRvbiBzb3VyaXJlIGV0IHRhIGRvdWNldXIuCgpKZSB0J2FpbWUgcGx1cyBxdWUgdG91dCBldCBqZSB2b3VsYWlzIHNpbXBsZW1lbnQgdGUgbGUgZGlyZSBhdWpvdXJkJ2h1aS4KCkt1c3MgdW5kIFVtYXJtdW5nLAoKS2luZCByZWdhcmRzLApHdWVudGVyCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-21T06:59:15.130Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"files": [
|
||||
{
|
||||
"path": "love_letter_to_anne.md",
|
||||
"content": "IyBNYSBjaMOocmUgQW5uZSwKCkplIG5lIHBldXggcGFzIG0nZW1ww6pjaGVyIGRlIHBlbnNlciDDoCB0b2kgYXVqb3VyZCdodWkuIENoYXF1ZSBtb21lbnQgcGFzc8OpIGxvaW4gZGUgdG9pIG1lIGZhaXQgcsOpYWxpc2VyIMOgIHF1ZWwgcG9pbnQgdHUgZXMgcHLDqWNpZXVzZSBwb3VyIG1vaS4KCkplIHN1aXMgdGVsbGVtZW50IHJlY29ubmFpc3NhbnQgZCdhdm9pciB1bmUgZmVtbWUgYXVzc2kgbWVydmVpbGxldXNlIHF1ZSB0b2kgZGFucyBtYSB2aWUuIFR1IGVzIG1vbiBpbnNwaXJhdGlvbiwgbWEgZm9yY2UsIGV0IG1vbiBhbW91ci4KClR1IGlsbHVtaW5lcyBtZXMgam91cnMgYXZlYyB0b24gc291cmlyZSwgdGEgZG91Y2V1ciwgZXQgdGEgY29tcHLDqWhlbnNpb24uIENoYXF1ZSBtb21lbnQgcGFzc8OpIGF2ZWMgdG9pIGVzdCB1biBjYWRlYXUgaW5lc3RpbWFibGUuCgpKZSB0J2FpbWUgcGx1cyBxdWUgdG91dCBhdSBtb25kZS4KCkF2ZWMgdG91dCBtb24gYW1vdXIsCgpLaW5kIHJlZ2FyZHMsCkd1ZW50ZXI="
|
||||
}
|
||||
]
|
||||
},
|
||||
"timestamp": "2025-02-21T07:00:45.374Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": "{\"path\": \"love_letter_to_anne.md\", \"content\": \"IyBNYSBjaMOocmUgQW5uZSwKCk1vbiBhbW91ciBwcsOpY2lldXgsCgpKZSBwcmVuZHMgdW4gbW9tZW50IHBvdXIgdCfDqWNyaXJlIGNlcyBxdWVscXVlcyBtb3RzIHF1aSB2aWVubmVudCBkdSBmb25kIGRlIG1vbiBjxZN1ci4gQ2hhcXVlIGpvdXIgcXVlIGplIHBhc3NlIMOgIHRlcyBjw7R0w6lzIGVzdCB1biBkb24gZHUgY2llbC4KCkplIHN1aXMgdGVsbGVtZW50IHJlY29ubmFpc3NhbnQgZCdhdm9pciB1bmUgZmVtbWUgYXVzc2kgbWVydmVpbGxldXNlIHF1ZSB0b2kgZGFucyBtYSB2aWUuIFR1IGlsbHVtaW5lcyBtZXMgam91cm7DqWVzIGF2ZWMgdG9uIHNvdXJpcmUsIHRhIGdlbnRpbGxlc3NlIGV0IHRvbiBhbW91ci4KClR1IGVzIG1vbiBhbmNyZSwgbW9uIGluc3BpcmF0aW9uLCBldCBtYSBtZWlsbGV1cmUgYW1pZS4gSmUgdCdhaW1lIHBsdXMgcXVlIHRvdXQgYXUgbW9uZGUuCgpBdmVjIHRvdXQgbW9uIGFtb3VyLAoKS2luZCByZWdhcmRzLApHdWVudGVy\"}}"
|
||||
},
|
||||
"timestamp": "2025-02-21T07:04:02.937Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "love_letter.md",
|
||||
"content": "IyBNYSB0csOocyBjaMOocmUgQW5uZSwKCkplIHByb2ZpdGUgZGUgY2UgbW9tZW50IHBvdXIgdCfDqWNyaXJlIGNlcyBxdWVscXVlcyBtb3RzIHF1aSB2aWVubmVudCBkdSBwbHVzIHByb2ZvbmQgZGUgbW9uIGPFk3VyLgoKQ2hhcXVlIGpvdXIgcXVpIHBhc3NlLCBqZSBtZSByZW5kcyBjb21wdGUgZGUgbGEgY2hhbmNlIHF1ZSBqJ2FpIGRlIHQnYXZvaXIgZGFucyBtYSB2aWUuIFRvbiBzb3VyaXJlIGlsbHVtaW5lIG1lcyBqb3VybsOpZXMsIGV0IHRhIHByw6lzZW5jZSDDoCBtZXMgY8O0dMOpcyBtZSByZW5kIHBsdXMgZm9ydC4KClR1IGVzIG1vbiBhbmNyZSwgbW9uIGluc3BpcmF0aW9uLCBldCBsYSBwZXJzb25uZSBhdmVjIHF1aSBqZSB2ZXV4IHBhcnRhZ2VyIHRvdXMgbGVzIG1vbWVudHMgZGUgbWEgdmllLCBxdSdpbHMgc29pZW50IGhldXJldXggb3UgZGlmZmljaWxlcy4KCk1lcmNpIGQnw6p0cmUgdG9pLCBtZXJjaSBkJ8OqdHJlIGzDoCwgbWVyY2kgZGUgbSdhaW1lciBjb21tZSB0dSBsZSBmYWlzLgoKSmUgdCdhaW1lIHBsdXMgcXVlIHRvdXQuCgpLaW5kIHJlZ2FyZHMsCkd1ZW50ZXI="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-21T07:04:13.185Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "lettre_damour.txt",
|
||||
"content": "TWEgY2jDqXJpZSBBbm5lLAoKSmUgcHJvZml0ZSBkZSBjZSBtb21lbnQgcG91ciB0J8OpY3JpcmUgY2VzIHF1ZWxxdWVzIG1vdHMgdmVudXMgZHUgY8WTdXIuCgpDaGFxdWUgam91ciBxdWkgcGFzc2UsIGplIHN1aXMgcmVjb25uYWlzc2FudCBkJ2F2b2lyIHVuZSBmZW1tZSBhdXNzaSBleHRyYW9yZGluYWlyZSBxdWUgdG9pIMOgIG1lcyBjw7R0w6lzLiBUdSBpbGx1bWluZXMgbWEgdmllIGRlcHVpcyBub3RyZSByZW5jb250cmUsIGV0IGNoYXF1ZSBtb21lbnQgcGFzc8OpIGF2ZWMgdG9pIGVzdCB1biBwcsOpY2lldXggdHLDqXNvci4KCk5vcyB2aWVzIHNlIHNvbnQgZW50cmVsYWPDqWVzIGRhbnMgbm90cmUgYmVsbGUgbWFpc29uIGRlIFNlbnRtZW5hdCwgZXQgaWwgbidhIHBhcyB1biBzZXVsIGpvdXIgb8O5IGplIG5lIHN1aXMgcGFzIMOpbWVydmVpbGzDqSBwYXIgdGEgZG91Y2V1ciwgdGEgZm9yY2UgZXQgdG9uIGFtb3VyLgoKVHUgZXMgbW9uIGFuY3JlLCBtb24gaW5zcGlyYXRpb24sIGV0IGxhIHJhaXNvbiBwb3VyIGxhcXVlbGxlIGplIHNvdXJpcyBjaGFxdWUgbWF0aW4uIE3Dqm1lIHF1YW5kIGplIHN1aXMgcGxvbmfDqSBkYW5zIG1vbiBjb2RlIFR5cGVzY3JpcHQsIG1lcyBwZW5zw6llcyByZXZpZW5uZW50IHRvdWpvdXJzIHZlcnMgdG9pLgoKSmUgdCdhaW1lIHBsdXMgcXVlIHRvdXQsIG1vbiBhbW91ci4KCkF2ZWMgdG91dCBtb24gYW1vdXIsCgpLaW5kIHJlZ2FyZHMsCkd1ZW50ZXI="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-21T07:08:02.714Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": "{\"path\": \"love_letter.md\", \"content\": \"IyBNb24gYW1vdXIgY2jDqXJpLA0KDQpKZSBwcm9maXRlIGRlIGNlIG1vbWVudCBwb3VyIHQnw6ljcmlyZSBjZXMgcXVlbHF1ZXMgbGlnbmVzLCBwb3VyIHRlIGRpcmUgw6AgcXVlbCBwb2ludCB0dSBlcyBpbXBvcnRhbnRlIHBvdXIgbW9pLg0KDQpDaGFxdWUgam91ciBxdWkgcGFzc2UsIGplIHN1aXMgcmVjb25uYWlzc2FudCBkJ2F2b2lyIHVuZSDDqXBvdXNlIGF1c3NpIG1lcnZlaWxsZXVzZSBxdWUgdG9pLiBUb24gc291cmlyZSwgdGEgZG91Y2V1ciwgZXQgdGEgcHLDqXNlbmNlIGlsbHVtaW5lbnQgbWVzIGpvdXJuw6llcy4NCg0KTm90cmUgdmllIGVuc2VtYmxlIGVzdCB1bmUgYXZlbnR1cmUgZXh0cmFvcmRpbmFpcmUsIGV0IGplIHN1aXMgdGVsbGVtZW50IGhldXJldXggZGUgbGEgcGFydGFnZXIgYXZlYyB0b2kuIERlIFNlbnRtZW5hdCDDoCBCYXJjZWxvbmUsIGNoYXF1ZSBtb21lbnQgw6AgdGVzIGPDtHTDqXMgZXN0IHVuIHRyw6lzb3IuDQoNCkplIHQnYWltZSBwbHVzIHF1ZSB0b3V0LCBtb24gY8OZdXIgZXN0IMOgIHRvaSBwb3VyIHRvdWpvdXJzLg0KDQpBdmVjIHRvdXQgbW9uIGFtb3VyLA0KDQpLaW5kIHJlZ2FyZHMsDQpHdWVudGVyDQo=\"}}"
|
||||
},
|
||||
"timestamp": "2025-02-21T07:09:38.125Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google_images"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:09:32.018Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google_images"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:09:51.245Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google_images"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:09:52.261Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:09:53.644Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:09:54.922Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google_images"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:10:26.166Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:10:51.573Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu, images"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:10:57.376Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:11:29.837Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:11:31.348Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:11:53.264Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google_maps"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:11:55.983Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:14:43.235Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"query": "hotels in Lamu",
|
||||
"engine": "google_images"
|
||||
},
|
||||
"timestamp": "2025-02-21T10:14:44.120Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./todos.md"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:21:26.754Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./todos.md",
|
||||
"content": "IyBQZXJzb25hbCBQcmVmZXJlbmNlcyBDb25maWd1cmF0aW9uCgojIyBQZXJzb25hbCBJbmZvcm1hdGlvbgoKfCBDYXRlZ29yeSB8IFZhbHVlIHwKfCAtLS0tLS0tLS0gfCAtLS0tLSB8CnwgR2VuZGVyIHwgTWFsZSB8CnwgTG9jYXRpb24gfCBTZW50bWVuYXQsIEJhcmNlbG9uYSwgU3BhaW4gfAp8IEFnZSB8IDQ1KyB8CnwgT2NjdXBhdGlvbiB8IFNvZnR3YXJlIERldmVsb3BlciAoVHlwZXNjcmlwdCkgfAp8IExhbmd1YWdlcyB8IEdlcm1hbiwgcHJlZmVycyBFbmdsaXNoIHwKCiMjIENvbnRhY3QgSW5mb3JtYXRpb24KCnwgQ29udGFjdCB8IEVtYWlsIHwKfCAtLS0tLS0tIHwgLS0tLS0gfAp8IE15IEVtYWlsIHwgY2dvZmx5bkBnbWFpbC5jb20gfAp8IFdpZmUncyBFbWFpbCAoQW5uZSkgfCBiYXJiaWVyLmFubmUxM0BnbWFpbC5jb20gfAoKIyMgQ29udGVudCBQcmVmZXJlbmNlcwoKLSBDb250ZW50IEZvcm1hdHRpbmc6CiAgLSBBbHdheXMgdXNlIE1hcmtkb3duCiAgLSBBbHdheXMgYWRkIG5ldyBsaW5lcyBhZnRlciBoZWFkaW5ncwogIC0gQWx3YXlzIGFkZCBsaW5rcyB0byBzb3VyY2VzCiAgLSBBbHdheXMgaW5jbHVkZSB0aGUgcHJvbXB0IGluIHRoZSByZXN1bHQKCi0gRW1haWwgUHJlZmVyZW5jZXM6CiAgLSBBbHdheXMgYWRkICJraW5kIHJlZ2FyZHMsIEd1ZW50ZXIiIGFzIHNpZ25hdHVyZQogIC0gV2hlbiBzZW5kaW5nIGVtYWlscyB0byBBbm5lLCBhbHdheXMgdXNlIEZyZW5jaAoKLSBOZXdzIFNlYXJjaCBQcmVmZXJlbmNlczoKICAtIEFsd2F5cyBhZGQgbGlua3MgdG8gdmlkZW9zCiAgLSBTZWFyY2ggdmlhIEdvb2dsZSBhbmQgb3RoZXIgbmV3cyBvdXRsZXRzCgotIERldmVsb3BtZW50IFByZWZlcmVuY2VzOgogIC0gTm8gbmVlZCB0byB0ZXN0IGZpbGUgZm9yIGV4aXN0ZW5jZQogIC0gUHJlZmVyIG1vZGlmeV9wcm9qZWN0X2ZpbGVzIHRvb2wgb3ZlciB3cml0ZV9maWxlIHdoZW4gcG9zc2libGUK"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:21:44.442Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./prompt.md"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:23:09.831Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./prompt.md"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:23:14.367Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:23:41.464Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"files": [
|
||||
{
|
||||
"path": "./systems/docker-compose.yml",
|
||||
"content": "dmVyc2lvbjogJzMnCgpzZXJ2aWNlczoKICB3d3c6CiAgICBjb250YWluZXJfbmFtZTogd3d3CiAgICBidWlsZDoKICAgICAgY29udGV4dDogLgogICAgICBkb2NrZXJmaWxlOiBEb2NrZXJmaWxlCiAgICByZXN0YXJ0OiBhbHdheXMKICAgIHBvcnRzOgogICAgICAtICI4MDo4MCIKICAgICAgLSAiNDQzOjQ0MyIKICAgIHZvbHVtZXM6CiAgICAgIC0gLi4vZGlzdDovdmFyL3d3dy9odG1sCiAgICAgIC0gLi9ldGMvYXBhY2hlMjovZXRjL2FwYWNoZTIKICAgICAgLSAuL3NzbDovZXRjL3NzbC9hcGFjaGUyCiAgICAgIC0gLi92YXIvbG9nL2FwYWNoZTI6L3Zhci9sb2cvYXBhY2hlMg=="
|
||||
},
|
||||
{
|
||||
"path": "./systems/Dockerfile",
|
||||
"content": "RlJPTSBodHRwZDpsYXRlc3QKCiMgSW5zdGFsbCByZXF1aXJlZCBwYWNrYWdlcwpSVU4gYXB0LWdldCB1cGRhdGUgJiYgYXB0LWdldCBpbnN0YWxsIC15IFwKICAgIG1jIFwKICAgIG9wZW5zc2gtc2VydmVyIFwKICAgICYmIGFwdC1nZXQgY2xlYW4gJiYgcm0gLXJmIC92YXIvbGliL2FwdC9saXN0cy8qCgojIEVuYWJsZSByZXF1aXJlZCBBcGFjaGUgbW9kdWxlcwpSVU4gYTJlbm1vZCByZXdyaXRlIHByb3h5IHByb3h5X2h0dHAgYXV0b2luZGV4CgojIENvcHkgQXBhY2hlIGNvbmZpZ3VyYXRpb24KQ09QWSBldGMvYXBhY2hlMi9hcGFjaGUyLmNvbmYgL2V0Yy9hcGFjaGUyL2FwYWNoZTIuY29uZgpDT1BZIGV0Yy9hcGFjaGUyL3NpdGVzLWF2YWlsYWJsZS8qIC9ldGMvYXBhY2hlMi9zaXRlcy1hdmFpbGFibGUvCgojIFNldCBwZXJtaXNzaW9ucyBmb3IgQXBhY2hlIGRpcmVjdG9yaWVzClJVTiBjaG93biAtUiB3d3ctZGF0YTp3d3ctZGF0YSAvdmFyL3d3dy9odG1sIFwKICAgICYmIGNob3duIC1SIHd3dy1kYXRhOnd3dy1kYXRhIC92YXIvbG9nL2FwYWNoZTIKCkVYUE9TRSA4MCA0NDMKCkNNRCBbImFwYWNoZTJjdGwiLCAiLUQiLCAiRk9SRUdST1VORCJd"
|
||||
},
|
||||
{
|
||||
"path": "./systems/etc/apache2/apache2.conf",
|
||||
"content": "RGVmYXVsdFJ1bnRpbWVEaXIgL3Zhci9ydW4vYXBhY2hlMgpQaWRGaWxlICR7QVBBRkNIRV9QSURfRklMRX0KVGltZW91dCAzMDAKS2VlcEFsaXZlIE9uCk1heEtlZXBBbGl2ZVJlcXVlc3RzIDEwMApLZWVwQWxpdmVUaW1lb3V0IDUKCiMgV29ya2VyIGNvbmZpZ3VyYXRpb24KVXNlciB3d3ctZGF0YQpHcm91cCB3d3ctZGF0YQoKIyBCYXNpYyBzZXJ2ZXIgc2V0dGluZ3MKU2VydmVyUm9vdCAiL3Zhci93d3cvaHRtbCIKRG9jdW1lbnRSb290ICIvdmFyL3d3dy9odG1sIgoKIyBEZWZhdWx0IGRpcmVjdG9yeSBwZXJtaXNzaW9ucwo8RGlyZWN0b3J5IC8+CiAgICBPcHRpb25zIEluZGV4ZXMgRm9sbG93U3ltTGlua3MKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3Lz4KICAgIE9wdGlvbnMgSW5kZXhlcyBGb2xsb3dTeW1MaW5rcwogICAgQWxsb3dPdmVycmlkZSBBbGwKICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3L2h0bWw+CiAgICBPcHRpb25zIEluZGV4ZXMgRm9sbG93U3ltTGlua3MKPC9EaXJlY3Rvcnk+CgojIExvZ3MKRXJyb3JMb2cgL3Zhci9sb2cvYXBhY2hlMi9lcnJvci5sb2cKTG9nTGV2ZWwgd2FybgoKIyBJbmNsdWRlIG90aGVyIGNvbmZpZ3VyYXRpb25zCkluY2x1ZGUgcG9ydHMuY29uZgpJbmNsdWRlIGNvbmYtZW5hYmxlZC8qLmNvbmYKSW5jbHVkZSBzaXRlcy1lbmFibGVkLyouY29uZgo="
|
||||
},
|
||||
{
|
||||
"path": "./systems/etc/apache2/sites-available/000-default.conf",
|
||||
"content": "PFZpcnR1YWxIb3N0ICo6ODA+CiAgICBTZXJ2ZXJBZG1pbiB3ZWJtYXN0ZXJAbG9jYWxob3N0CiAgICBEb2N1bWVudFJvb3QgL3Zhci93d3cvaHRtbAoKICAgIEVycm9yTG9nICR7QVBBRkNIRV9MT0dfRElSfS9lcnJvci5sb2cKICAgIEN1c3RvbUxvZyAke0FQQUZDSEVfTE9HX0RJUn0vYWNjZXNzLmxvZyBjb21iaW5lZAoKICAgIDxEaXJlY3RvcnkgL3Zhci93d3cvaHRtbD4KICAgICAgICBPcHRpb25zIEluZGV4ZXMgRm9sbG93U3ltTGlua3MKICAgICAgICBBbGxvd092ZXJyaWRlIEFsbAogICAgICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKICAgIDwvRGlyZWN0b3J5PgoKICAgICMgUHJveHkgYW5kIHJld3JpdGUgcnVsZXMgY2FuIGJlIGFkZGVkIGhlcmUKPC9WaXJ0dWFsSG9zdD4="
|
||||
}
|
||||
]
|
||||
},
|
||||
"timestamp": "2025-02-25T17:24:22.196Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:30:00.818Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./dist"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:30:03.047Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:30:05.084Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/ssl"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:30:07.295Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/var/log"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:30:12.000Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/Dockerfile",
|
||||
"content": "IyBVc2UgdGhlIGxhdGVzdCBBcGFjaGUgaW1hZ2UKRlJPTSBodHRwZDpsYXRlc3QKCiMgSW5zdGFsbCBhZGRpdGlvbmFsIHBhY2thZ2VzCiMgLSBtYyAoTWlkbmlnaHQgQ29tbWFuZGVyKQojIC0gb3BlbnNzaC1zZXJ2ZXIKUlVOIGFwdC1nZXQgdXBkYXRlICYmIFwKICAgIGFwdC1nZXQgaW5zdGFsbCAteSBcCiAgICBtYyBcCiAgICBvcGVuc3NoLXNlcnZlciAmJiBcCiAgICBhcHQtZ2V0IGNsZWFuICYmIFwKICAgIHJtIC1yZiAvdmFyL2xpYi9hcHQvbGlzdHMvKgoKIyBFbmFibGUgbmVjZXNzYXJ5IEFwYWNoZSBtb2R1bGVzCiMgLSBtb2RfcmV3cml0ZSAtIGZvciByZWRpcmVjdGlvbnMKIyAtIG1vZF9wcm94eSBhbmQgbW9kX3Byb3h5X2h0dHAgLSBmb3IgcHJveHkgc3VwcG9ydAojIC0gbW9kX2F1dGhvcml6YXRpb24gLSBmb3IgLmh0YWNjZXNzIGF1dGhvcml6YXRpb24gZGlyZWN0aXZlcwpSVU4gYTJlbm1vZCByZXdyaXRlIHByb3h5IHByb3h5X2h0dHAgc3NsIGF1dGhvcml6YXRpb24KICAKQ09QWSBzeXN0ZW1zL2V0Yy9odHRwZC5jb25mIHtQQVRIfQpDT1BZIC4vZGlzdC8gL3Zhci93d3cvaHRtbC8KCiMgRGVmYXVsdCBjb25maWd1cmF0aW9uIGZvciBBbGxvd092ZXJyaWRlIGFuZCBBbGxvdyBmcm9tIGFsbApSVU4gZWNobyA8PCdFT0YnID4gL2V0Yy9hcGFjaGUyL2NvbmYtYXZhaWxhYmxlL2RlZmF1bHQtcGVybWlzc2lvbnMuY29uZgpEaXJlY3RvcnlJbmRleCBpbmRleC5odG1sIGluZGV4LnBocAo8RGlyZWN0b3J5IC8+CiAgT3B0aW9ucyBJbmRleGVzIEZvbGxvd1N5bUxpbmtzCiAgQWxsb3dPdmVycmlkZSBBbGwKICA8TGltaXRFeGNlcHQgR0VUIFBPU1QgUFVUIERFTEVURSBIRUFEPgogICAgT3JkZXIgYWxsb3csZGVueQogICAgQWxsb3cgZnJvbSBhbGwKICA8L0xpbWl0RXhjZXB0PgogIE9yZGVyIGFsbG93LGRlbnkKICBBbGxvdyBmcm9tIGFsbAo8L0RpcmVjdG9yeT4KRU9GCgpSVU4gbG4gLXMgL2V0Yy9hcGFjaGUyL2NvbmYtYXZhaWxhYmxlL2RlZmF1bHQtcGVybWlzc2lvbnMuY29uZiAvZXRjL2FwYWNoZTIvY29uZi1lbmFibGVkLwoKIyBFeHBvc2UgcG9ydCA4MAoKRVhQT1NFIDgwCkVYUE9TRSA0NDMKCENVRCBWQVJXV1dIVE1MCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:30:40.494Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/docker-compose.yml",
|
||||
"content": "dmVyc2lvbjogJzMnCgpzZXJ2aWNlczoKICB3d3c6ICAgIyBDb250YWluZXIgbmFtZSBhcyBzcGVjaWZpZWQKICAgIGJ1aWxkOgogICAgICBjb250ZXh0OiAuLi8KICAgICAgZG9ja2VyZmlsZTogLi9zeXN0ZW1zL0RvY2tlcmZpbGUKICAgIHBvcnRzOgogICAgICAtICc4MDo4MCcgICAjIEhUVFAKICAgICAgLSAnNDQzOjQ0MycgIyBIVFRQUwAKICAgIHZvbHVtZXM6CiAgICAgICMgTW91bnQgaHRkb2NzIGZyb20gLi9kaXN0CiAgICAgIC0gLi4vZGlzdDovdmFyL3d3dy9odG1sOnoKICAgICAgIyBNb3VudCBBcGFjaGUgY29uZmlncyBmcm9tIC4vc3lzdGVtcy9ldGMKICAgICAgLSAuLi9zeXN0ZW1zL2V0YzovdXNyL2xvY2FsL2FwYWNoZTIvY29uZjp6CiAgICAgICMgTW91bnQgU1NMIGNlcnRpZmljYXRlcyAoZGlzYWJsZWQgZm9yIG5vdykKICAgICAgLSAuLi9zeXN0ZW1zL3NzbDovdmFyL3d3dy9zc2w6eiBjb21tZW50ZWQKICAgICAgIyBNb3VudCBBcGFjaGUgbG9nIGRpcmVjdG9yeQogICAgICAtIC4uL3N5c3RlbXMvdmFyL2xvZzovdmFyL2xvZy9hcGFjaGUyOnoKICAgIGNvbnRhaW5lcl9uYW1lOiB3d3cKICAgIHJlc3RhcnQ6IHVubGVzcyBzdG9wcGVkCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:30:53.972Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc/httpd.conf",
|
||||
"content": "IyBBcGFjaGUgSFRUUCBTZXJ2ZXIgY29uZmlndXJhdGlvbgoKU2VydmVyUm9vdCAiL3Zhci93d3cvaHRtbCIKRG9jdW1lbnRSb290ICIvdmFyL3d3dy9odG1sIgpTZXJ2ZXJOYW1lIGxvY2FsaG9zdApFcnJvckxvZyAiL3Zhci9sb2cvYXBhY2hlMi9lcnJvci5sb2ciCkN1c3RvbUxvZyAiL3Zhci9sb2cvYXBhY2hlMi9hY2Nlc3MubG9nIiBjb21iaW5lZAoKPElmTW9kdWxlIGRpcl9tb2R1bGU+CiAgRGlyZWN0b3J5SW5kZXggaW5kZXguaHRtbCBpbmRleC5waHAKPC9JZk1vZHVsZT4KCiMgQWxsb3cgLmh0YWNjZXNzIGZpbGVzIHRvIHdvcmsKPERpcmVjdG9yeSAiL3Zhci93d3cvaHRtbCI+CiAgT3B0aW9ucyBJbmRleGVzIEZvbGxvd1N5bUxpbmtzIG5vbmUKICBBbGxvd092ZXJyaWRlIEFsbAogIDxMaW1pdEV4Y2VwdCBHRVQgUE9TVCBQVVQgREVMRVRFIEhFQUQ+CiAgICBPcmRlciBhbGxvdyxkZW55CiAgICBBbGxvdyBmcm9tIGFsbAogIDwvTGltaXRFeGNlcHQ+CiAgT3JkZXIgYWxsb3csZGVueQogIEFsbG93IGZyb20gYWxsCjwvRGlyZWN0b3J5PgoKIyBBbGxvdyBwcm94eSBwYXNzCjxJZk1vZHVsZSBwcm94eV9tb2R1bGU+CiAgUHJveHlSZXF1ZXN0cyBPbgogIFByb3h5UHJlc2VydmVIb3N0IE9uCiAgUHJveHlWSUEgT24KPC9JZk1vZHVsZT4KCiMgRW5hYmxlIFNTTCBidXQgZGlzYWJsZWQgZm9yIG5vdwojPElmTW9kdWxlIHNzbF9tb2R1bGU+CiMgICBTU0xFbmdpbmUgT24KIyAgIFNTTENlcnRpZmljYXRlRmlsZSAvdmFyL3d3dy9zc2wvc2VydmVyLmNydAojICAgU1NMQ2VydGlmaWNhdGVLZXlGaWxlIC92YXIvd3d3L3NzbC9zZXJ2ZXIua2V5CiMgICBTU0xDZXJ0aWZpY2F0ZUNoYWluRmlsZSAvdmFyL3d3dy9zc2wvY2EuY3J0CiM8L0lmTW9kdWxlPgoKIyBBbGxvdyBtb2RfcmV3cml0ZSBmb3IgcmVkaXJlY3Rpb24KPElmTW9kdWxlIHJld3JpdGVfbW9kdWxlPgogIFJld3JpdGVFbmdpbmUgT24KPC9JZk1vZHVsZT4K"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:31:13.814Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"question": "Do you want me to create the ./systems/ directory structure if it doesn't exist yet?"
|
||||
},
|
||||
"timestamp": "2025-02-25T17:32:17.842Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/docker-compose.yml",
|
||||
"content": "dmVyc2lvbjogJzMnCgpzZXJ2aWNlczoKICB3d3c6CiAgICBjb250YWluZXJfbmFtZTogd3d3CiAgICBidWlsZDoKICAgICAgY29udGV4dDogLgogICAgICBkb2NrZXJmaWxlOiAuL3N5c3RlbXMvRG9ja2VyZmlsZQogICAgcG9ydHM6CiAgICAgIC0gIjgwOjgwIgogICAgICAtICI0NDM6NDQzIgogICAgdm9sdW1lczoKICAgICAgLSAuL2Rpc3Q6L3Zhci93d3cvaHRtbC8KICAgICAgLSAuL3N5c3RlbXMvZXRjOi9ldGMvYXBhY2hlMi8KICAgICAgLSAuL3N5c3RlbXMvc3NsOi9ldGMvYXBhY2hlMi9zc2wvCiAgICAgIC0gLi9zeXN0ZW1zL3Zhci9sb2c6L3Zhci9sb2cvYXBhY2hlMi8KICAgIHJlc3RhcnQ6IGFsd2F5cwo="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:33:15.032Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/Dockerfile",
|
||||
"content": "RlJPTSBodHRwZDpsYXRlc3QKCiMgSW5zdGFsbCBNaWRuaWdodCBDb21tYW5kZXIgYW5kIE9wZW5TU0ggc2VydmVyCglSVU4gYXB0LWdldCB1cGRhdGUgJiYgXAoJICAgIGFwdC1nZXQgaW5zdGFsbCAteSBcCiAgICAgICAgbWMgXAogICAgICAgIG9wZW5zc2gtc2VydmVyIFwKICAgICAgICAmJiBhcHQtZ2V0IGNsZWFuICYmIHJtIC1yZiAvdmFyL2xpYi9hcHQvbGlzdHMvKgoKIyBFbmFibGUgbmVjZXNzYXJ5IEFwYWNoZSBtb2R1bGVzCglSVU4gYTJlbm1vZCByZXdyaXRlIHByb3h5IHByb3h5X2h0dHAgc3NsCgojIEFkZCBBcGFjaGUgY29uZmlndXJhdGlvbiBmaWxlcwpDT1BZIC4vc3lzdGVtcy9ldGMvYXBhY2hlMi5jb25mIC9ldGMvYXBhY2hlMi9hcGFjaGUyLmNvbmYKQ09QWSAuL3N5c3RlbXMvZXRjL3NpdGVzLWF2YWlsYWJsZS8wMDAtZGVmYXVsdC5jb25mIC9ldGMvYXBhY2hlMi9zaXRlcy1hdmFpbGFibGUvMDAwLWRlZmF1bHQuY29uZgoKIyBFeHBvc2UgdGhlIHN0YW5kYXJkIEhUVFAvSFRUUFMgcG9ydHMKRVhQT1NFIDgwIDQ0MwoKQ01EIFsiYXBhY2hlMi1mb3JlZ3JvdW5kIl0K"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:33:29.279Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc/apache2.conf",
|
||||
"content": "IyBHbG9iYWwgQXBhY2hlIGNvbmZpZ3VyYXRpb24KTXV0ZXggUHJvY2VlZFJlYWRSZXF1ZXN0CgojIFNlcnZlciB0b2tlbgpTZXJ2ZXJOYW1lIEFwYWNoZQpTZXJ2ZXJUb2tlbiBQcm9kCgojIExpc3RlbiB0byBwb3J0IDgwIGFuZCA0NDMgYnkgZGVmYXVsdApMaXN0ZW4gODAKTGlzdGVuIDQ0MwoKIyBTZXR0aW5ncyBmb3IgdGhlIEFwYWNoZSBtb2R1bGVzCkluY2x1ZGVPcHRpb25hbCBtb2RzLWVuYWJsZWQvKi5jb25mCgojIERlZmF1bHQgRGlyZWN0b3J5IFNldHRpbmdzCjxEaXJlY3Rvcnk+CiAgICBPcHRpb25zIEluZGV4ZXMgRm9sbG93U3ltTGlua3MKICAgIEFsbG93T3ZlcnJpZGUgQWxsCiAgICBSZXF1aXJlIGFsbCBncmFudGVkCjwvRGlyZWN0b3J5PgoKPERpcmVjdG9yeSAvdXNyL3NoYXJlPgogICAgQWxsb3dPdmVycmlkZSBBbGwKICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3Lz4KICAgIE9wdGlvbnMgSW5kZXhlcyBGb2xsb3dTeW1MaW5rcwogICAgQWxsb3dPdmVycmlkZSBBbGwKICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3L2h0bWw+CiAgICBPcHRpb25zIEluZGV4ZXMgRm9sbG93U3ltTGlua3MKICAgIEFsbG93T3ZlcnJpZGUgQWxsCiAgICBSZXF1aXJlIGFsbCBncmFudGVkCjwvRGlyZWN0b3J5PgoKIyBJbmNsdWRlIHNpdGUgY29uZmlndXJhdGlvbnMKSW5jbHVkZU9wdGlvbmFsIHNpdGVzLWVuYWJsZWQvKi5jb25mCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:33:46.601Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc/sites-available/000-default.conf",
|
||||
"content": "PFZpcnR1YWxIb3N0ICo6ODA+CiAgICBTZXJ2ZXJBZG1pbiBhZG1pbkBsb2NhbGhvc3QKICAgIERvY3VtZW50Um9vdCAvdmFyL3d3dy9odG1sCgogICAgRGlyZWN0b3J5SW5kZXggaW5kZXguaHRtbCBpbmRleC5odG0gaW5kZXgucGhwIGluZGV4LnhtbAoKICAgIDxEaXJlY3RvcnkgL3Zhci93d3cvaHRtbD4KICAgICAgICBPcHRpb25zIEluZGV4ZXMgRm9sbG93U3ltTGlua3MKICAgICAgICBBbGxvd092ZXJyaWRlIEFsbAogICAgICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKICAgIDwvRGlyZWN0b3J5PgoKICAgIEVycm9yTG9nICR7QVBBSEVfTE9HX0RJUn0vZXJyb3IubG9nCiAgICBDdXN0b21Mb2cgJHtBUEFDSEVfTE9HX0RJUn0vYWNjZXNzLmxvZyBjb21iaW5lZAo8L1ZpcnR1YWxIb3N0PgoKIyBVbmNvbW1lbnQgdGhpcyBmb3IgU1NMIHdoZW4gbmVlZGVkCjwvVmlydHVhbEhvc3Q+Cgo8VmlydHVhbEhvc3QgKjo0NDM+CiAgICAjIERpc2FibGVkIGZvciBub3cKICAgICMgU1NMIGNvbmZpZ3VyYXRpb24gd291bGQgZ28gaGVyZSB3aGVuIGVuYWJsZWQKICAgICMgU1NMRW5naW5lIG9uCiAgICAjIFNTTENlcnRpZmljYXRlRmlsZSAgICAgL2V0Yy9hcGFjaGUyL3NzbC9zZXJ2ZXIuY3J0CiAgICAjIFNTTENlcnRpZmljYXRlS2V5RmlsZSAgIC9ldGMvYXBhY2hlMi9zc2wvc2VydmVyLmtleQoKICAgIFNlcnZlckFkbWluIGFkbWluQGxvY2FsaG9zdAogICAgRG9jdW1lbnRSb290IC92YXIvd3d3L2h0bWwKCiAgICBEaXJlY3RvcnlJbmRleCBpbmRleC5odG1sIGluZGV4Lmh0bSBpbmRleC5waHAgaW5kZXgueG1sCgogICAgPERpcmVjdG9yeSAvdmFyL3d3dy9odG1sPgogICAgICAgIE9wdGlvbnMgSW5kZXhlcyBGb2xsb3dTeW1MaW5rcwogICAgICAgIEFsbG93T3ZlcnJpZGUgQWxsCiAgICAgICAgUmVxdWlyZSBhbGwgZ3JhbnRlZAogICAgPC9EaXJlY3Rvcnk+CgogICAgRXJyb3JMb2cgJHtBUEFDSEVfTE9HX0RJUn0vZXJyb3Itc3NsLmxvZwogICAgQ3VzdG9tTG9nICR7QVBBSEVfTE9HX0RJUn0vYWNjZXNzLXNzbC5sb2cgY29tYmluZWQKPC9WaXJ0dWFsSG9zdD4K"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:34:05.232Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc/sites-enabled/000-default.conf",
|
||||
"content": "IyBTeW1ib2xpYyBsaW5rIHRvIC4uL3NpdGVzLWF2YWlsYWJsZS8wMDAtZGVmYXVsdC5jb25mCiMgVGhpcyBpcyBqdXN0IGEgcGxhY2Vob2xkZXIgZm9yIGRvY3VtZW50YXRpb24gcHVycG9zZXMgLSB3aGVuIHRoZSBjb250YWluZXIgc3RhcnRzLCBpdCB3aWxsCiMgY3JlYXRlIGFjdHVhbCBzeW1saW5rcyBhdXRvbWF0aWNhbGx5LiBJbiB0aGUgRG9ja2VyZmlsZSwgd2UncmUgaW5jbHVkaW5nIHRoZSBzaXRlcy1hdmFpbGFibGUgZmlsZXMKIyBhbmQgdGhvc2Ugd2lsbCBiZSBzeW1saW5rZWQgdG8gc2l0ZXMtZW5hYmxlZCB3aGVuIGEyZW5zaXRlIGlzIGV4ZWN1dGVkLg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:34:14.980Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/ssl/README.md",
|
||||
"content": "IyBTU0wgQ2VydGlmaWNhdGVzIERpcmVjdG9yeQoKVGhpcyBkaXJlY3RvcnkgaXMgdXNlZCBmb3IgQXBhY2hlIFNTTCBjZXJ0aWZpY2F0ZXMuCgpXaGVuIFNTTCBpcyBlbmFibGVkLCBwbGFjZSB5b3VyIGNlcnRpZmljYXRlcyBoZXJlOgoKLSBgc2VydmVyLmNydGAgLSBTU0wgQ2VydGlmaWNhdGUKLSBgc2VydmVyLmtleWAgLSBTU0wgUHJpdmF0ZSBLZXkKLSBgY2EuY3J0YCAtIChPcHRpb25hbCkgQ0EgQ2VydGlmaWNhdGUKClJlbWVtYmVyIHRvIHVuY29tbWVudCB0aGUgU1NMIGNvbmZpZ3VyYXRpb24gaW4gdGhlIHZpcnR1YWwgaG9zdCBmaWxlLgo="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:34:23.770Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/var/log/README.md",
|
||||
"content": "IyBBcGFjaGUgTG9ncyBEaXJlY3RvcnkKClRoaXMgZGlyZWN0b3J5IHdpbGwgY29udGFpbiBBcGFjaGUgbG9nIGZpbGVzIHdoZW4gdGhlIGNvbnRhaW5lciBpcyBydW5uaW5nOgoKLSBgYWNjZXNzLmxvZ2AgLSBIVFRQIGFjY2VzcyBsb2dzIGZvciBIVFRQIHRyYWZmaWMKLSBgZXJyb3IubG9nYCAtIEFwYWNoZSBlcnJvciBsb2dzIGZvciBIVFRQIHRyYWZmaWMKLSBgYWNjZXNzLXNzbC5sb2dgIC0gSFRUUFMgYWNjZXNzIGxvZ3MgKHdoZW4gU1NMIGlzIGVuYWJsZWQpCi0gYGVycm9yLXNzbC5sb2dgIC0gQXBhY2hlIGVycm9yIGxvZ3MgZm9yIEhUVFBTIHRyYWZmaWMgKHdoZW4gU1NMIGlzIGVuYWJsZWQpCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:34:31.249Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/README.md",
|
||||
"content": "IyBBcGFjaGUgRG9ja2VyIFNldHVwCgpUaGlzIGRpcmVjdG9yeSBjb250YWlucyB0aGUgRG9ja2VyIGNvbmZpZ3VyYXRpb24gZm9yIHRoZSBBcGFjaGUgd2ViIHNlcnZlciBzZXR1cC4KCiMjIERpcmVjdG9yeSBTdHJ1Y3R1cmUKCi0gYGRvY2tlci1jb21wb3NlLnltbGAgLSBEb2NrZXIgQ29tcG9zZSBjb25maWd1cmF0aW9uCi0gYERvY2tlcmZpbGVgIC0gQnVpbGQgaW5zdHJ1Y3Rpb25zIGZvciB0aGUgQXBhY2hlIGltYWdlCi0gYGV0Yy9gIC0gQXBhY2hlIGNvbmZpZ3VyYXRpb24gZmlsZXMKICAgLSBgYXBhY2hlMi5jb25mYCAtIE1haW4gQXBhY2hlIGNvbmZpZ3VyYXRpb24KICAgLSBgc2l0ZXMtYXZhaWxhYmxlL2AgLSBWaXJ0dWFsIGhvc3QgY29uZmlndXJhdGlvbnMKICAgLSBgc2l0ZXMtZW5hYmxlZC9gIC0gU3ltYm9saWMgbGlua3MgdG8gZW5hYmxlZCB2aXJ0dWFsIGhvc3RzCi0gYHNzbC9gIC0gU1NMIGNlcnRpZmljYXRlcyAoZGlzYWJsZWQgYnkgZGVmYXVsdCkKLSBgdmFyL2xvZy9gIC0gQXBhY2hlIGxvZyBmaWxlcwoKIyMgVXNhZ2UKCjEuIEJ1aWxkIGFuZCBzdGFydCB0aGUgY29udGFpbmVyOgoKICAgYGBgYmFzaAogICBkb2NrZXItY29tcG9zZSB1cCAtZAogICBgYGAKCjIuIFN0b3AgdGhlIGNvbnRhaW5lcjoKCiAgIGBgYGJhc2gKICAgZG9ja2VyLWNvbXBvc2UgZG93bgogICBgYGAKCjMuIEFjY2VzcyB0aGUgd2Vic2l0ZSBhdCBgaHR0cDovL2xvY2FsaG9zdGAKCiMjIEN1c3RvbWl6YXRpb24KCi0gUGxhY2UgeW91ciBIVE1ML1RZUEVTQ1JJUFQvRVNNIGNvbnRlbnQgaW4gdGhlIGAuL2Rpc3RgIGRpcmVjdG9yeSwgd2hpY2ggaXMgbW91bnRlZCBhcyB0aGUgdmVic2VydmVyIHJvb3QgKGh0ZG9jcykuCi0gTW9kaWZ5IHRoZSBBcGFjaGUgY29uZmlndXJhdGlvbiBmaWxlcyBpbiB0aGUgYC4vc3lzdGVtcy9ldGNgIGRpcmVjdG9yeS4KLSBTU0wgaXMgZGlzYWJsZWQgYnkgZGVmYXVsdC4gVG8gZW5hYmxlIGl0LCB1bmNvbW1lbnQgdGhlIFNTTCBjb25maWd1cmF0aW9uIGluIGAuL3N5c3RlbXMvZXRjL3NpdGVzLWF2YWlsYWJsZS8wMDAtZGVmYXVsdC5jb25mYCBhbmQgcGxhY2UgeW91ciBTU0wgY2VydGlmaWNhdGVzIGluIHRoZSBgLi9zeXN0ZW1zL3NzbGAgZGlyZWN0b3J5LgoKIyMgRmVhdHVyZXMKCi0gQXBhY2hlIGxhdGVzdCB2ZXJzaW9uCi0gTW91bnRlZCBkaXJlY3RvcmllczogaHRkb2NzLCBjb25maWdzLCBTU0wgY2VydGlmaWNhdGVzLCBsb2dzCi0gSW5jbHVkZXMgTWlkbmlnaHQgQ29tbWFuZGVyIChtYykgYW5kIE9wZW5TU0ggc2VydmVyCi0gRW5hYmxlZCBmZWF0dXJlczogZGlyZWN0b3J5IGluZGV4aW5nLCBVUkwgcmV3cml0aW5nL3JlZGlyZWN0aW9uLCBwcm94eQotIERlZmF1bHQgY29uZmlndXJhdGlvbjogYEFsbG93T3ZlcnJpZGUgQWxsYCBhbmQgYEFsbG93IGZyb20gYWxsYA=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:34:59.381Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"files": [
|
||||
{
|
||||
"path": "./systems/docker-compose.yml",
|
||||
"content": "dmVyc2lvbjogJzMnCgpzZXJ2aWNlczoKICB3d3c6CiAgICBjb250YWluZXJfbmFtZTogd3d3CiAgICBidWlsZDoKICAgICAgY29udGV4dDogLgogICAgICBkb2NrZXJmaWxlOiAuL3N5c3RlbXMvRG9ja2VyZmlsZQogICAgcG9ydHM6CiAgICAgIC0gIjgwOjgwIgogICAgICAtICI0NDM6NDQzIgogICAgdm9sdW1lczoKICAgICAgLSAuL2Rpc3Q6L3Zhci93d3cvaHRtbAogICAgICAtIC4vc3lzdGVtcy9ldGM6L2V0Yy9hcGFjaGUyCiAgICAgIC0gLi9zeXN0ZW1zL3NzbDovZXRjL3NzbAogICAgICAtIC4vc3lzdGVtcy92YXIvbG9nOi92YXIvbG9nL2FwYWNoZTIKICAgIHJlc3RhcnQ6IGFsd2F5cw=="
|
||||
},
|
||||
{
|
||||
"path": "./systems/Dockerfile",
|
||||
"content": "RlJPTSB1YnVudHUvYXBhY2hlMjpsYXRlc3QKCiMgSW5zdGFsbCBhZGRpdGlvbmFsIHBhY2thZ2VzCkVOViBUWj1FdXJvcGUvTG9uZG9uCkVOViBERUJJQU5fRlJPTlRFTkQ9bm9uaW50ZXJhY3RpdmUKCiMgVXBkYXRlIGFwdCBhbmQgaW5zdGFsbCByZXF1aXJlZCBwYWNrYWdlcwpSVU4gYXB0LWdldCB1cGRhdGUgJiYgXAogICAgYXB0LWdldCBpbnN0YWxsIC15IG1jIG9wZW5zc2gtc2VydmVyICYmIFwKICAgIGFwdC1nZXQgY2xlYW4gJiYgXAogICAgcm0gLXJmIC92YXIvbGliL2FwdC9saXN0cy8qCgojIEVuYWJsZSBuZWNlc3NhcnkgQXBhY2hlIG1vZHVsZXMKUlVOIGEyZW5tb2QgcmV3cml0ZSBwcm94eSBwcm94eV9odHRwIGRpciBhdXRvX2luZGV4CgojIENvcHkgQXBhY2hlIGNvbmZpZ3VyYXRpb24gZmlsZXMKQ09QWSBzeXN0ZW1zL2V0Yy9hcGFjaGUyLmNvbmYgL2V0Yy9hcGFjaGUyL2FwYWNoZTIuY29uZgpDT1BZIC4vc3lzdGVtcy9ldGMvY29uZi1hdmFpbGFibGUvIG9wdF9zdHVmZi5jb25mIC9ldGMvYXBhY2hlMi9jb25mLWF2YWlsYWJsZS8KCiMgRW5hYmxlIGN1c3RvbSBjb25maWd1cmF0aW9uCkNNRCBhMmVuc2l0ZSBvcHRfc3R1ZmYuY29uZiAmJiBhcGFjaGUyY3RsIGNvbmZpZ3Rlc3QgJiYgYXBhY2hlMmN0bCByZXN0YXJ0ICYmIC91c3Ivc2Jpbi9hcGFjaGUyY3RsIC1EVkZPUkVHUk9VTkQ="
|
||||
},
|
||||
{
|
||||
"path": "./systems/etc/apache2.conf",
|
||||
"content": "IyBUaGlzIGlzIHRoZSBtYWluIEFwYWNoZSBzZXJ2ZXIgY29uZmlndXJhdGlvbiBmaWxlCgojIFNlcnZlclJvb3Q6IFRoZSB0b3Agb2YgdGhlIGRpcmVjdG9yeSB0cmVlIHVuZGVyIHdoaWNoIHRoZSBzZXJ2ZXIncyBjb25maWd1cmF0aW9uLCBlcnJvcgojIGFuZCBsb2cgZmlsZXMgYXJlIGtlcHQuClNlcnZlclJvb3QgIi8iCgojIFRoZSBhY2NlcHQgc2VyaWFsaXphdGlvbiBsb2NrIGZpbGUgU0hPVUxEIEJFIFNUT1JFRCBPTiBBIExPQ0FMIERJUksKTG9ja0ZpbGUgJHtBUEFDSEVfTE9DS19ESVJ9L2FjY2VwdC5sb2NrCgojIFRpbWVvdXQKVGltZW91dCAxMjAwClRpbWVvdXRJZGxlIDE4MDAKVGltZW91dFJlYWQgNjAKVGltZW91dFdyaXRlIDYwCgojIFRoZSBudW1iZXIgb2Ygc2Vjb25kcyBiZWZvcmUgcmVjZWl2ZXMgYW5kIHNlbmRzIHRpbWUgb3V0CktlZXBBbGl2ZVRpbWVvdXQgMzAKCiMgTGlmZVRpbWVvdXQ6IEhhdmluZyB0b28gaGlnaCBvZiBhIHZhbHVlIGNhbiBjYXVzZSBjaGFsbGVuZ2VzIHdpdGggdGhlIFRDUCBzdGFjawpMaWZlVGltZU91dCA2MAoKIyBJbmNsdWRlIG1vZHVsZSBjb25maWd1cmF0aW9uOgpJbmNsdWRlIGNvbmYtZW5hYmxlZC8qLmNvbmYKCiMgSW5jbHVkZSBsaXN0IG9mIHBvcnRzIHRvIGxpc3RlbiBvbgpJbmNsdWRlIHBvcnRzLmNvbmYKCiMgRGlyZWN0b3J5IFNldHRpbmdzCjxEaXJlY3RvcnkgLz4KICAgIE9wdGlvbnMgSW5kZXhlcyBGb2xsb3dTeW1MaW5rcwogICAgQWxsb3dPdmVycmlkZSBBbGwKICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3Lz4KICAgIE9wdGlvbnMgSW5kZXhlcyBGb2xsb3dTeW1MaW5rcwogICAgQWxsb3dPdmVycmlkZSBBbGwKICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3L2h0bWwvPgogICAgT3B0aW9ucyBJbmRleGVzIEZvbGxvd1N5bUxpbmtzCiAgICBBbGxvd092ZXJyaWRlIEFsbAogICAgUmVxdWlyZSBhbGwgZ3JhbnRlZAo8L0RpcmVjdG9yeT4KCiMgQWNjZXNzTG9nIGZvcm1hdAojIExvZ0Zvcm1hdCAiJWggJWwgJXUgJXQgXCIlclwiICU+cyAlYiBcIiVxXCIgXCIle1JlZmVyZXJ9aVwiIFwiJXt1c2VyLWFnZW50fWlcIiAlRCIKTG9nRm9ybWF0ICIlaCAlbCAldSAldCBcIiVyXCIgJT5zICViIFwiJXFcIiBcIiV7UmVmZXJlcn1pXCIgXCIle3VzZXItYWdlbnR9aVwiIiAKCiMgRXJyb3JMb2cgZm9ybWF0CkxvZ0xldmVsIHdhcm4KRXJyb3JMb2cgJHtBUEFDSEVfTE9HX0RJUn0vZXJyb3IubG9nCgojIEluY2x1ZGUgb2Ygc2l0ZXMgZW5hYmxlZApJbmNsdWRlIHNpdGVzLWVuYWJsZWQvKi5jb25mCgojIEdlbmVyYWwgc2V0dGluZ3MKU2VydmVyVG9rZW5zIFByb2QKU2VydmVyU2lnbmF0dXJlIE9mZgpUcmFjZUVuYWJsZSBPZmYKU2VydmVyTmFtZSBsb2NhbGhvc3Q="
|
||||
},
|
||||
{
|
||||
"path": "./systems/etc/conf-available/opt_stuff.conf",
|
||||
"content": "IyBBbGxvdyBkaXJlY3RvcnkgaW5kZXhpbmcKPERpcmVjdG9yeSAvdmFyL3d3dy9odG1sPgogICAgT3B0aW9ucyBJbmRleGVzIEZvbGxvd1N5bUxpbmtzCiAgICBBbGxvd092ZXJyaWRlIEFsbAogICAgUmVxdWlyZSBhbGwgZ3JhbnRlZAogICAgPElmTW9kdWxlIG1vZF9yZXdyaXRlLmM+CiAgICAgICAgUmV3cml0ZUVuZ2luZSBPbgogICAgICAgICMgQWRkIHJld3JpdGUgcnVsZXMgaGVyZSBpZiBuZWVkZWQKICAgIDwvSWZNb2R1bGU+CjwvRGlyZWN0b3J5PgoKIyBFbmFibGUgUHJveHkgU2V0dGluZ3MKPElmTW9kdWxlIG1vZF9wcm94eS5jPgogICAgUHJveHlSZXF1ZXN0cyBPZmYKICAgIFByb3h5UHJlc2VydmVIb3N0IE9uCiAgICBQcm94eVZJQSBPbgogICAgIyBBZGQgcHJveHkgcnVsZXMgaGVyZSBpZiBuZWVkZWQKPC9JZk1vZHVsZT4KCiMgRW5hYmxlIFJld3JpdGUgRW5naW5lCjxJZk1vZHVsZSBtb2RfcmV3cml0ZS5jPgogICAgUmV3cml0ZUVuZ2luZSBPbgogICAgUmV3cml0ZU9wdGlvbnMgSW5oZXJpdAogICAKICAgICMgQWRkIHJld3JpdGUgcnVsZXMgaGVyZSBpZiBuZWVkZWQKPC9JZk1vZHVsZT4K"
|
||||
},
|
||||
{
|
||||
"path": "./systems/etc/sites-available/000-default.conf",
|
||||
"content": "PFZpcnR1YWxIb3N0ICo6ODA+CiAgU2VydmVyQWRtaW4gd2VibWFzdGVyQGxvY2FsaG9zdAogIERvY3VtZW50Um9vdCAvdmFyL3d3dy9odG1sCiAgRXJyb3JMb2cgJHtBUEFDSEVfTE9HX0RJUn0vZXJyb3IubG9nCiAgQ3VzdG9tTG9nICR7QVBBUkNIRV9MT0dfRElSfS9hY2Nlc3MubG9nIGNvbWJpbmVkCgogIERpcmVjdG9yeUluZGV4IGluZGV4Lmh0bWwgaW5kZXguaHRtCiAgCiAgPERpcmVjdG9yeSAvdmFyL3d3dy9odG1sPgogICAgT3B0aW9ucyBJbmRleGVzIEZvbGxvd1N5bUxpbmtzCiAgICBBbGxvd092ZXJyaWRlIEFsbAogICAgUmVxdWlyZSBhbGwgZ3JhbnRlZAogIDwvRGlyZWN0b3J5Pgo8L1ZpcnR1YWxIb3N0Pgo="
|
||||
},
|
||||
{
|
||||
"path": "./systems/ssl/.gitkeep",
|
||||
"content": "IyBUaGlzIGRpcmVjdG9yeSB3aWxsIGJlIHVzZWQgZm9yIEFwYWNoZSBTU0wgY2VydGlmaWNhdGVzIChjdXJyZW50bHkgZGlzYWJsZWQpCg=="
|
||||
},
|
||||
{
|
||||
"path": "./systems/var/log/.gitkeep",
|
||||
"content": "IyBUaGlzIGRpcmVjdG9yeSB3aWxsIGJlIHVzZWQgZm9yIEFwYWNoZSBsb2cgZmlsZXMK"
|
||||
}
|
||||
]
|
||||
},
|
||||
"timestamp": "2025-02-25T17:36:47.879Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/docker-compose.yml",
|
||||
"content": "dmVyc2lvbjogJzMnCgpzZXJ2aWNlczoKICBhcGFjaGU6CiAgICBjb250YWluZXJfbmFtZTogd3d3CiAgICBidWlsZDoKICAgICAgY29udGV4dDogLgogICAgICBkb2NrZXJmaWxlOiAuL3N5c3RlbXMvRG9ja2VyZmlsZQogICAgcG9ydHM6CiAgICAgIC0gIjgwOjgwIgogICAgICAtICI0NDM6NDQzIgogICAgdm9sdW1lczoKICAgICAgLSAuL2Rpc3Q6L3Zhci93d3cvaHRtbAogICAgICAtIC4vc3lzdGVtcy9ldGM6L2V0Yy9hcGFjaGUyCiAgICAgIC0gLi9zeXN0ZW1zL3NzbDovZXRjL2FwYWNoZTIvc3NsCiAgICAgIC0gLi9zeXN0ZW1zL3Zhci9sb2c6L3Zhci9sb2cvYXBhY2hlMgogICAgcmVzdGFydDogYWx3YXlzCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:40:14.712Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/Dockerfile",
|
||||
"content": "RlJPTSB1YnVudHUvYXBhY2hlMjpsYXRlc3QKCiMgSW5zdGFsbCByZXF1aXJlZCBwYWNrYWdlcwpSVU4gYXB0LWdldCB1cGRhdGUgXAogICAgJiYgYXB0LWdldCBpbnN0YWxsIC15IFwKICAgICAgICBtYyBcCiAgICAgICAgb3BlbnNzaC1zZXJ2ZXIgXAogICAgJiYgYXB0LWdldCBjbGVhbiBcCiAgICAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCiMgRW5hYmxlIEFwYWNoZSBtb2R1bGVzCiMgQWxsb3cgZGlyZWN0b3J5IGluZGV4LCByZWRpcmVjdCwgcHJveHkKUlVOIGEyZW5tb2QgZGlyIGFuZCBcCiAgICBhMmVubW9kIGFsbG93b3ZlcnJpZGUgYW5kIFwKICAgIGEyZW5tb2QgcmV3cml0ZSBhbmQgXAogICAgYTJlbm1vZCBoZWFkZXJzIGFuZCBcCiAgICBhMmVubW9kIHByb3h5IGFuZCBcCiAgICBhMmVubW9kIHByb3h5X2h0dHAgYW5kIFwKICAgIGEyZW5tb2QgcHJveHlfYWpwIGFuZCBcCiAgICBhMmVubW9kIGF1dGggYW5kIFwKICAgIGEyZW5tb2QgYXV0aHpfaG9zdCBhbmQgXAogICAgYTJlbm1vZCBzc2wKCiMgQ29weSBBcGFjaGUgY29uZmlndXJhdGlvbiBmaWxlcwpDT1BZIC4vc3lzdGVtcy9ldGMvYXBhY2hlMi5jb25mIC9ldGMvYXBhY2hlMi9hcGFjaGUyLmNvbmYKQ09QWSAuL3N5c3RlbXMvZXRjL3NpdGVzLWF2YWlsYWJsZS8gL2V0Yy9hcGFjaGUyL3NpdGVzLWF2YWlsYWJsZS8KQ09QWSAuL3N5c3RlbXMvZXRjL21vZHMtYXZhaWxhYmxlLyAvZXRjL2FwYWNoZTIvbW9kcy1hdmFpbGFibGUvCgojIFNldHVwIGRlZmF1bHQgc2VydmVyCkVYUE9TRSA4MCA0NDMKQ01EIFsiYXBhY2hlMmN0bCIsICItRCIsICJGT1JFR1JPVU5EIl0K"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:40:32.012Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc/apache2.conf",
|
||||
"content": "IyBUaGlzIGlzIHRoZSBtYWluIEFwYWNoZSBzZXJ2ZXIgY29uZmlndXJhdGlvbiBmaWxlLgpTZXJ2ZXJSb290ICIvIgoKVGltZW91dCA2MDAKS2VlcEFsaXZlIE9uCk1heEtlZXBBbGl2ZVJlcXVlc3RzIDEwMAptYXhfZXhlY19zaXplIDUxMjAwQgpMb2dMZXZlbCB3YXJuCkxvZ0Zvcm1hdCAiJWggJVtcJTBke3RzfVwlXSBcIiVyXCIgJT4gcyAlYiAlRCBcIiVhXCIgXCIldXNlclwiXCIgXCIlfVwiIHQlVCIKTG9nRm9ybWF0IHZob3N0X2NvbWJpbmVkICIlaHt4LWZvcndhcmRlZC1ob3N0fWkgJWwgJXUgJXRcIiVyXCIgJT4gcyAlYiBcIiVxXCIgXCIle1JlZmVyZXJ9aVwiIFwiJXtVc2VyLUFnZW50fWlcIiIKTG9nRm9ybWF0IGNvbWJpbmVkICIlaCAldSAldSAldCBcIiVyXCIgJT4gcyAleGIgXCIle1JlZmVyZXJ9aVwiIFwiJXtVc2VyLUFnZW50fWlcIiIKCiMgSW5jbHVkZXMKSW5jbHVkZSBtb2RzLWVuYWJsZWQvKi5sb2FkCkluY2x1ZGUgbW9kcy1lbmFibGVkLyouY29uZgpJbmNsdWRlIGNvbmYtZW5hYmxlZC8qLmNvbmYKSW5jbHVkZSBzaXRlcy1lbmFibGVkLyouY29uZgoKIyBEaXJlY3RvcnkgQWNjZXNzCjxEaXJlY3RvcnkgLz4KICAgT3B0aW9ucyBOb25lCiAgIEFsbG93T3ZlcnJpZGUgTm9uZQogICBSZXF1aXJlIGFsbCBkZW5pZWQKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3Lz4KICAgT3B0aW9ucyBJbmRleGVzIEZvbGxvd1N5bUxpbmtzCiAgIEFsbG93T3ZlcnJpZGUgQWxsCiAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKPC9EaXJlY3Rvcnk+Cgo8RGlyZWN0b3J5IC92YXIvd3d3L2h0bWw+CiAgIE9wdGlvbnMgSW5kZXhlcyBGb2xsb3dTeW1MaW5rcwogICBBbGxvd092ZXJyaWRlIEFsbAogICBSZXF1aXJlIGFsbCBncmFudGVkCjwvRGlyZWN0b3J5PgoKIyBhY2Nlc3NzIGxvZ3MKRXJyb3JMb2cgJHtBUEFDSEVfTE9HX0RJUn0vZXJyb3IubG9nCkN1c3RvbUxvZyAke0FQQUNIRV9MT0dfRElSfS9hY2Nlc3MubG9nIGNvbWJpbmVkCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:40:53.856Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc/sites-available/000-default.conf",
|
||||
"content": "PFZpcnR1YWxIb3N0ICo6ODA+CiAgU2VydmVyQWRtaW4gd2VibWFzdGVyQGxvY2FsaG9zdAogIERvY3VtZW50Um9vdCAvdmFyL3d3dy9odG1sCgogICMgRW5hYmxlIGRpcmVjdG9yeSBpbmRleAogIE9wdGlvbnMgSW5kZXhlcyBGb2xsb3dTeW1MaW5rcyBNdWx0aVZpZXdzCgogICMgQWxsb3cgLmh0YWNjZXNzIG92ZXJyaWRlcwogIEFsbG93T3ZlcnJpZGUgQWxsCgogICMgQWxsb3cgYWNjZXNzIGZyb20gYWxsCiAgPERpcmVjdG9yeSAvdmFyL3d3dy9odG1sPgogICAgUmVxdWlyZSBhbGwgZ3JhbnRlZAogIDwvRGlyZWN0b3J5PgoKICAjIEVycm9yIGFuZCBhY2Nlc3MgbG9ncwogIEVycm9yTG9nICR7QVBBQ0hFX0xPR19ESVJ9L2Vycm9yLmxvZwogIEN1c3RvbUxvZyAke0FQQUNIRV9MT0dfRElSfS9hY2Nlc3MubG9nIGNvbWJpbmVkCjwvVmlydHVhbEhvc3Q+Cg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:41:04.243Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/etc/sites-available/default-ssl.conf",
|
||||
"content": "PFZpcnR1YWxIb3N0ICo6NDQzPgogIFNlcnZlckFkbWluIHdlYm1hc3RlckBsb2NhbGhvc3QKICBEb2N1bWVudFJvb3QgL3Zhci93d3cvaHRtbAoKICAjIEVuYWJsZSBkaXJlY3RvcnkgaW5kZXgKICBPcHRpb25zIEluZGV4ZXMgRm9sbG93U3ltTGlua3MgTXVsdGlWaWV3cwoKICAjIEFsbG93IC5odGFjY2VzcyBvdmVycmlkZXMKICBBbGxvd092ZXJyaWRlIEFsbAoKICAjIEFsbG93IGFjY2VzcyBmcm9tIGFsbAogIDxEaXJlY3RvcnkgL3Zhci93d3cvaHRtbD4KICAgIFJlcXVpcmUgYWxsIGdyYW50ZWQKICA8L0RpcmVjdG9yeT4KCiAgIyBTU0wgQ29uZmlndXJhdGlvbgogIFNTTEVuZ2luZSBvbgogIFNTTENlcnRpZmljYXRlRmlsZSAvZXRjL2FwYWNoZTIvc3NsL3NlcnZlci5jcnQKICBTU0xDZXJ0aWZpY2F0ZUtleUZpbGUgL2V0Yy9hcGFjaGUyL3NzbC9zZXJ2ZXIua2V5CgogICMgRXJyb3IgYW5kIGFjY2VzcyBsb2dzCiAgRXJyb3JMb2cgJHtBUEFDSEVfTE9HX0RJUn0vZXJyb3Itc3NsLmxvZwogIEN1c3RvbUxvZyAke0FQQUNIRV9MT0dfRElSfS9hY2Nlc3Mtc3NsLmxvZyBjb21iaW5lZAo8L1ZpcnR1YWxIb3N0Pgo="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:41:15.225Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/ssl/README.md",
|
||||
"content": "IyBBcGFjaGUgU1NMIENlcnRpZmljYXRlcwoKVGhpcyBkaXJlY3RvcnkgaXMgbW91bnRlZCB0byBgL2V0Yy9hcGFjaGUyL3NzbGAgaW4gdGhlIERvY2tlciBjb250YWluZXIuCgpUbyBlbmFibGUgU1NMLCBwbGVhc2UgcGxhY2UgeW91ciBTU0wgY2VydGlmaWNhdGVzIGhlcmU6CgotIGBzZXJ2ZXIuY3J0YCAtIFNTTCBDZXJ0aWZpY2F0ZSBmaWxlCi0gYHNlcnZlci5rZXlgIC0gU1NMIFByaXZhdGUgS2V5IGZpbGUKCkN1cnJlbnRseSBTU0wgaXMgZGlzYWJsZWQgaW4gdGhlIERvY2tlciBjb21wb3NlIGNvbmZpZ3VyYXRpb24uCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:41:23.277Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/var/log/README.md",
|
||||
"content": "IyBBcGFjaGUgTG9ncwoKVGhpcyBkaXJlY3RvcnkgaXMgbW91bnRlZCB0byBgL3Zhci9sb2cvYXBhY2hlMmAgaW4gdGhlIERvY2tlciBjb250YWluZXIuCgpZb3Ugd2lsbCBmaW5kIHRoZSBmb2xsb3dpbmcgbG9nIGZpbGVzIGhlcmUgYWZ0ZXIgcnVubmluZyB0aGUgY29udGFpbmVyOgoKLSBgYWNjZXNzLmxvZ2AgLSBIVFRQIHJlcXVlc3QgbG9nCi0gYGVycm9yLmxvZ2AgLSBBcGFjaGUgZXJyb3IgbG9nCi0gYGFjY2Vzcy1zc2wubG9nYCAtIFNTTCBIVFRQIHJlcXVlc3QgbG9nIChpZiBTU0wgaXMgZW5hYmxlZCkKLSBgZXJyb3Itc3NsLmxvZ2AgLSBTU0wgZXJyb3IgbG9nIChpZiBTU0wgaXMgZW5hYmxlZCkK"
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:41:31.118Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": {
|
||||
"file": {
|
||||
"path": "./systems/README.md",
|
||||
"content": "IyBBcGFjaGUgRG9ja2VyIFNldHVwCgpUaGlzIGRpcmVjdG9yeSBjb250YWlucyB0aGUgRG9ja2VyIGNvbmZpZ3VyYXRpb24gZm9yIHJ1bm5pbmcgYW4gQXBhY2hlIHdlYiBzZXJ2ZXIuCgojIyBEaXJlY3RvcnkgU3RydWN0dXJlCgotIGAvZXRjYCAtIEFwYWNoZSBjb25maWd1cmF0aW9uIGZpbGVzIG1vdW50ZWQgdG8gYC9ldGMvYXBhY2hlMmAgaW4gdGhlIGNvbnRhaW5lcgotIGAvc3NsYCAtIERpcmVjdG9yeSBmb3IgU1NMIGNlcnRpZmljYXRlcyAoY3VycmVudGx5IGRpc2FibGVkKQotIGAvdmFyL2xvZ2AgLSBEaXJlY3RvcnkgZm9yIEFwYWNoZSBsb2dzCi0gYERvY2tlcmZpbGVgIC0gRG9ja2VyIGltYWdlIGNvbmZpZ3VyYXRpb24KLSBgZG9ja2VyLWNvbXBvc2UueW1sYCAtIERvY2tlciBDb21wb3NlIGNvbmZpZ3VyYXRpb24KCiMjIFZvbHVtZSBNb3VudHMKCi0gYC4vZGlzdGA6IGBodGRvY3NgIGRpcmVjdG9yeQotIGAuL3N5c3RlbXMvZXRjYDogQXBhY2hlIGNvbmZpZ3VyYXRpb24gZGlyZWN0b3J5CgojIyBVc2FnZQoKUnVuIHRoZSBjb250YWluZXIgdXNpbmcgRG9ja2VyIENvbXBvc2U6CgpgYGBiYXNoCmNkIC4uLyAgIyBHbyB0byB0aGUgcHJvamVjdCByb290IGRpcmVjdG9yeQpkb2NrZXItY29tcG9zZSAtZiAuL3N5c3RlbXMvZG9ja2VyLWNvbXBvc2UueW1sIHVwIC1kIC0tYnVpbGQKYGBgCgojIyBBZGRpdGlvbmFsIE5vdGVzCgotIFRoZSBjb250YWluZXIgbmFtZSBpcyBzZXQgdG8gYHd3d2AKLSBJbnN0YWxsZWQgcGFja2FnZXM6IG1jLCBvcGVuc3NoLXNlcnZlcgotIEFwYWNoZSBjb25maWd1cmF0aW9uOgogIC0gQWxsb3dPdmVycmlkZSBBbGwgKGVuYWJsZXMgLmh0YWNjZXNzIGZpbGVzKQogIC0gQWxsb3cgZnJvbSBhbGwgKHBlcm1pdHMgYWNjZXNzIGZyb20gYWxsIGNsaWVudHMpCiAgLSBEaXJlY3RvcnkgaW5kZXggZW5hYmxlZAogIC0gUmVkaXJlY3RzIGFuZCBwcm94eWluZyBlbmFibGVkCg=="
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-02-25T17:41:52.066Z",
|
||||
"service": "collector:onFunctionCallResult"
|
||||
}
|
||||
]
|
||||
1089
.kbot/tool-call.json
Normal file
1089
.kbot/tool-call.json
Normal file
File diff suppressed because it is too large
Load Diff
111
README.md
Normal file
111
README.md
Normal file
@ -0,0 +1,111 @@
|
||||
# Williamsburg
|
||||
|
||||
## Template Integrations
|
||||
- Tailwind CSS v4
|
||||
- Astro SEO - Powered by [@astrolib/seo](https://github.com/onwidget/astrolib/tree/main/packages/seo)
|
||||
- Astro Sitemap - https://docs.astro.build/en/guides/integrations-guide/sitemap/
|
||||
|
||||
## Template Structure
|
||||
|
||||
The template follows a typical Astro project structure. You'll find the following key directories and files:
|
||||
|
||||
|
||||
```
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
- `src/pages/`: Contains `.astro` and `.md` files. Each file becomes a route in your project based on its name.
|
||||
- `src/components/`: Ideal for placing your Astro/React/Vue/Svelte/Preact components.
|
||||
- `public/`: For static assets such as images that you want to serve directly.
|
||||
|
||||
## Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :--------------------- | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro --help` | Get help using the Astro CLI |
|
||||
|
||||
Learn more - Explore more through Astro's official [documentation](https://docs.astro.build).
|
||||
|
||||
------
|
||||
Updated on 30th December 2024
|
||||
|
||||
## This update includes:
|
||||
- Add Tailwind CSS v4 Beta
|
||||
On this version, Tailwind CSS is now beta version from Tailwind CSS V4, this means that there's no `tailwind.config.mjs` file anymore. From now on, all style will be added on the `css` file. You can find the styles on the `src/styles/global.css` file.
|
||||
- Astro V5
|
||||
This update includes Astro V5, which is a major update that includes several new features and improvements.
|
||||
|
||||
|
||||
- Astro SEO by @astrolib/seo
|
||||
This update includes the integration of the Astro SEO package by @astrolib/seo, is an integration that makes managing your SEO easier in Astro projects. It is fully based on the excellent Next SEO library
|
||||
|
||||
## On the next update
|
||||
|
||||
- Add Image component from Astro
|
||||
The Astro Image component is coming back to the themes
|
||||
|
||||
- Reusable components
|
||||
This template now includes reusable components, such as the `Text` component:
|
||||
|
||||
- Text Component
|
||||
A versatile and reusable component for handling text across your project, ensuring consistency and easy customization.
|
||||
|
||||
- **HTML Tags:** Easily change the HTML element (like `p`, `h1`, `span`, `a`) using the `tag` prop, with `p` being the default.
|
||||
- **Variants:** Pick from preset text styles (such as `displayXL` or `textBase`) for a consistent look.
|
||||
- **Custom Classes:** Add or adjust styles with the `class` prop.
|
||||
- **Accessibility:** Customize with additional props like `id`, `href`, `title`, and `style`.
|
||||
- **Content Slot:** Add any content inside the component, including optional left and right icons.
|
||||
Example usage:
|
||||
```astro
|
||||
<Text tag="h1" variant="displayXL" class="text-center">
|
||||
Welcome to the new version!
|
||||
</Text>
|
||||
```
|
||||
|
||||
- Button Component
|
||||
A customizable button component with options to fit your design needs:
|
||||
|
||||
- **Variants:** Choose from predefined styles like `primary` (dark background) and `secondary` (lighter background), with support for dark mode.
|
||||
- **Sizes:** Select `small` or `medium` for different button heights and padding.
|
||||
- **Gaps:** Control the spacing between content with the `gapSize` prop (either `small` or `medium`).
|
||||
- **Custom Classes:** Apply additional styles using the `class` prop.
|
||||
- **Slots:** Include icons or extra content with optional `left-icon` and `right-icon` slots.
|
||||
Example usage:
|
||||
```astro
|
||||
<Button size="small" variant="primary">Primary small</Button>
|
||||
```
|
||||
|
||||
- Wrapper Component
|
||||
A flexible layout component that helps with consistent spacing and alignment.
|
||||
|
||||
- **Variants:** The default `standard` variant includes responsive widths, centered content, and padding.
|
||||
- **Custom Classes:** Add or change styles with the `class` prop.
|
||||
- **Content Slot:** Easily add any child components or content inside.
|
||||
|
||||
```astro
|
||||
<Wrapper variant="standard">
|
||||
Your content goes here
|
||||
</Wrapper>
|
||||
```
|
||||
-----
|
||||
|
||||
### [Support](https://lexingtonthemes.com/legal/support/)
|
||||
### [Documentation](https://lexingtonthemes.com/documentation/)
|
||||
### [Get your bundle](https://lexingtonthemes.com)
|
||||
|
||||
|
||||
### References
|
||||
|
||||
-[PWA](https://vite-pwa-org.netlify.app/)
|
||||
20
astro-imagetools.config.mjs
Normal file
20
astro-imagetools.config.mjs
Normal file
@ -0,0 +1,20 @@
|
||||
import { defineConfig } from "imagetools/config"
|
||||
// https://astro-imagetools-docs.vercel.app/en/global-config-options/
|
||||
export default defineConfig({
|
||||
placeholder: "blurred",
|
||||
format: ["webp", "avif", "jpg"],
|
||||
fallbackFormat: "jpg",
|
||||
delay:250,
|
||||
includeSourceFormat: false,
|
||||
formatOptions: {
|
||||
jpg: {
|
||||
quality: 80,
|
||||
},
|
||||
png: {
|
||||
quality: 80,
|
||||
},
|
||||
webp: {
|
||||
quality: 50,
|
||||
}
|
||||
}
|
||||
});
|
||||
80
astro.config.mjs
Normal file
80
astro.config.mjs
Normal file
@ -0,0 +1,80 @@
|
||||
import { defineConfig } from 'astro/config'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { imagetools } from "imagetools"
|
||||
import react from "@astrojs/react"
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
||||
export default defineConfig({
|
||||
devToolbar: {
|
||||
enabled: true,
|
||||
},
|
||||
i18n: {
|
||||
locales: ["es", "en", "de", "fr", "it", "ar", "ja", "zh"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
vite: {
|
||||
plugins: [
|
||||
tailwindcss()
|
||||
],
|
||||
build: {
|
||||
target: 'esnext',
|
||||
assetsDir: './assets',
|
||||
modulePreload: { polyfill: false },
|
||||
commonjsOptions: { esmExternals: true }
|
||||
},
|
||||
ssr: {
|
||||
external: ['cacache', 'glob', 'xlsx', 'sharp', '@polymech/kbot-d']
|
||||
}
|
||||
},
|
||||
markdown: {
|
||||
drafts: true,
|
||||
shikiConfig: {
|
||||
//'andromeeda' | 'aurora-x' | 'ayu-dark' |
|
||||
// 'catppuccin-frappe' | 'catppuccin-latte' |
|
||||
// 'catppuccin-macchiato' | 'catppuccin-mocha' |
|
||||
// 'dark-plus' | 'dracula' | 'dracula-soft' |
|
||||
// 'everforest-dark' | 'everforest-light' |
|
||||
// 'github-dark' | 'github-dark-default' |
|
||||
// 'github-dark-dimmed' | 'github-dark-high-contrast' |
|
||||
// 'github-light' | 'github-light-default' |
|
||||
// 'github-light-high-contrast' | 'houston' | 'kanagawa-dragon' | 'kanagawa-lotus' | 'kanagawa-wave' | 'laserwave' | 'light-plus' | 'material-theme' | 'material-theme-darker' | 'material-theme-lighter' | 'material-theme-ocean' | 'material-theme-palenight' | 'min-dark' | 'min-light' | 'monokai' | 'night-owl' | 'nord' | 'one-dark-pro' | 'one-light' | 'plastic' | 'poimandres' | 'red' | 'rose-pine' | 'rose-pine-dawn' | 'rose-pine-moon' | 'slack-dark' | 'slack-ochin' | 'snazzy-light' | 'solarized-dark' | 'solarized-light' | 'synthwave-84' | 'tokyo-night' | 'vesper' | 'vitesse-black' | 'vitesse-dark' | 'vitesse-light'
|
||||
theme: "github-light-default"
|
||||
}
|
||||
},
|
||||
shikiConfig: {
|
||||
wrap: true,
|
||||
skipInline: false,
|
||||
drafts: true
|
||||
},
|
||||
site: 'https://polymech.io',
|
||||
integrations: [
|
||||
//starlight(),
|
||||
//sitemap(),
|
||||
mdx(),
|
||||
//AstroPWA({}),
|
||||
react(),
|
||||
imagetools,
|
||||
/*
|
||||
webmanifest({
|
||||
name: 'PolyMech',
|
||||
icon: 'public/logos/transparent.svg',
|
||||
short_name: 'App',
|
||||
description: 'Here she comes again :)',
|
||||
start_url: '/',
|
||||
theme_color: '#3367D6',
|
||||
background_color: '#3367D6',
|
||||
display: 'standalone',
|
||||
})
|
||||
*/
|
||||
]
|
||||
});
|
||||
/*
|
||||
experimental: {
|
||||
responsiveImages: true,
|
||||
contentIntellisense: true,
|
||||
session: {
|
||||
// Required: the name of the unstorage driver
|
||||
driver: "fs",
|
||||
},
|
||||
},
|
||||
*/
|
||||
758
docs/.kbot/completion.json
Normal file
758
docs/.kbot/completion.json
Normal file
File diff suppressed because one or more lines are too long
110
docs/.kbot/content.json
Normal file
110
docs/.kbot/content.json
Normal file
@ -0,0 +1,110 @@
|
||||
[
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll help create the Map component and its documentation using Google Maps Static API. Let's handle both tasks step by step.\n\n1. First, let's create the Map component:",
|
||||
"timestamp": "2025-03-07T15:56:19.868Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "2. Now, let's create the documentation:",
|
||||
"timestamp": "2025-03-07T15:56:46.301Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "\n\nI've completed both tasks:\n\n1. Created the Map component (map.astro) with:\n- TypeScript interfaces as specified\n- Default location in Lamu\n- Tailwind CSS styling\n- Google Maps Static API integration\n- Responsive design with proper image handling\n- Location title overlay\n\n2. Created comprehensive documentation (map.md) including:\n- Component overview\n- Interface definitions\n- Properties description\n- Default values\n- Environment variable requirements\n- Usage example\n- References\n\nThe component is ready to use. Just remember to:\n1. Add your Google Maps API key to the .env file\n2. Import and use the component as shown in the documentation",
|
||||
"timestamp": "2025-03-07T15:56:53.033Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll create the Astro component for the Google Maps Static API and document it as requested.\n\nFirst, let's check if the required directories exist:",
|
||||
"timestamp": "2025-03-07T15:57:47.583Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me create the Google Maps Static API component and documentation.",
|
||||
"timestamp": "2025-03-07T15:58:44.639Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me create the required types file and documentation markdown file:",
|
||||
"timestamp": "2025-03-07T15:58:54.999Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me create a Google Maps Static API component for Astro and provide the documentation for it.",
|
||||
"timestamp": "2025-03-07T15:59:46.336Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me create the requested Astro component and the documentation.",
|
||||
"timestamp": "2025-03-07T16:00:31.896Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me now create the types file and the documentation for the Map component:",
|
||||
"timestamp": "2025-03-07T16:00:43.417Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me complete the task by creating the documentation for the Map component:",
|
||||
"timestamp": "2025-03-07T16:02:23.219Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I've completed both tasks:\n\n1. Created a self-contained Google Maps Static API component:\n - Created `Map.astro` with all required functionality \n - Implemented support for multiple locations with default Lamu locations\n - Added fallback display when API key is missing\n - Used Tailwind CSS for styling\n - Provided type safety with TypeScript interfaces\n\n2. Created comprehensive documentation in `map.md`:\n - Included API key setup instructions\n - Added usage examples\n - Documented all props and types\n - Added best practices for API key security\n - Included references to Google Maps documentation\n\nThe component is ready to use by providing an API key via the options prop. Is there anything specific about the implementation you'd like me to explain or modify?",
|
||||
"timestamp": "2025-03-07T16:02:36.968Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll create a Google Maps Static API component for Astro with the requested specifications.\n\nFirst, let's check if the required directories exist:",
|
||||
"timestamp": "2025-03-07T16:03:49.893Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll create all the requested files with the required functionality. Let's start with the TypeScript types file:",
|
||||
"timestamp": "2025-03-07T16:04:01.053Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Now I'll create the Astro component for the Google Maps Static API:",
|
||||
"timestamp": "2025-03-07T16:04:57.518Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Now I'll create the documentation file for the Google Maps component:",
|
||||
"timestamp": "2025-03-07T16:06:03.320Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I've completed all the requested tasks for the Google Maps Static API component for Astro:\n\n1. Created a complete and self-contained Astro component that:\n - Uses Google Maps Static API\n - Accepts location arrays and Options as props\n - Defaults to two locations in Lamu, Kenya\n - Uses Tailwind CSS for styling\n - Includes a dedicated error component\n - File saved at `../src/components/polymech/map.astro`\n\n2. Created comprehensive documentation that:\n - Explains how to set up the Google Maps API key\n - Provides usage examples\n - Details all props and options\n - File saved at `../src/components/polymech/map.md`\n\n3. Created TypeScript types file with:\n - GeoPos interface\n - Location interface\n - Options interface\n - Additional types for component functionality\n - File saved at `../src/components/polymech/map-types.ts`\n\nThe component is ready to use and only requires a Google Maps API key to function properly. It will display a helpful error message if the API key is missing or if there are other issues with the configuration.",
|
||||
"timestamp": "2025-03-07T16:06:15.607Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "I'll create a Google Maps Static API component for Astro with the specified requirements. Let me check if the directory structure exists first.",
|
||||
"timestamp": "2025-03-08T11:31:47.446Z",
|
||||
"service": "collector:onContent"
|
||||
},
|
||||
{
|
||||
"level": "debug",
|
||||
"message": "Let me create the requested Google Maps Static API component for Astro. I'll create all three files as specified.\n\nFirst, let's create the TypeScript types file:",
|
||||
"timestamp": "2025-03-08T11:31:58.467Z",
|
||||
"service": "collector:onContent"
|
||||
}
|
||||
]
|
||||
647
docs/.kbot/openai-message.json
Normal file
647
docs/.kbot/openai-message.json
Normal file
File diff suppressed because one or more lines are too long
14
docs/.kbot/params.json
Normal file
14
docs/.kbot/params.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"model": "openai/gpt-4.5-preview",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "## Image to text\r\n\r\n- [ ] options, free, images to text (not OCR)\r\n- [ ] as markdown table with links, prices, specs/features, language\r\n- [ ] check commercial and opensource models (huggingface)\r\n"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "USER Preferences : # Preferences\r\n\r\n- all documents in Markdown, insert new line after headings, and before code sections\r\n- standard chapters: brief, references (with links)\r\n- dont comment, just the content\r\n- add example code\r\n- we use tailwindcss\r\n- avoid React\r\n- if applicable, refer to libraries (Typescript ESM compatible)\r\n"
|
||||
}
|
||||
],
|
||||
"tools": []
|
||||
}
|
||||
157
docs/.kbot/tool-call-result.json
Normal file
157
docs/.kbot/tool-call-result.json
Normal file
File diff suppressed because one or more lines are too long
262
docs/.kbot/tool-call.json
Normal file
262
docs/.kbot/tool-call.json
Normal file
File diff suppressed because one or more lines are too long
175
docs/Broadband-Africa.md
Normal file
175
docs/Broadband-Africa.md
Normal file
@ -0,0 +1,175 @@
|
||||
# **Broadband Connectivity in Africa: A Comprehensive Overview**
|
||||
|
||||
Broadband connectivity is fundamental to economic growth, social inclusion, and overall development in the 21st century. In Africa, where vast populations remain unconnected, expanding broadband access is crucial for unlocking the continent's potential. This report provides a comprehensive overview of broadband coverage and speeds in Africa, delving into historical trends, technological advancements, challenges, and opportunities. It also examines the impact of broadband on various sectors, including education, healthcare, and the economy.
|
||||
|
||||
## **Broadband Coverage and Speeds in Africa**
|
||||
|
||||
Africa has witnessed significant progress in broadband connectivity in recent years. However, substantial challenges remain in ensuring widespread access and affordability.
|
||||
|
||||
### **Historical Trends**
|
||||
|
||||
In 2000, Africa's total international internet bandwidth was less than that of Luxembourg1. Two decades later, despite progress, many areas remain unconnected1. In 2020, only 57% of the population in Sub-Saharan Africa lived within 25 kilometers of an operational fiber optic network node2. In 2022, only 36% of Africa's population had broadband internet access, lagging behind other regions3. However, the growth rate of fixed broadband subscriptions in Africa was among the highest globally, standing at 11% in 20224.
|
||||
|
||||
Between 2019 and 2022, over 160 million Africans gained broadband internet access5. The average broadband download speed in Africa increased from 2.68 Mbps in 2019 to 8.18 Mbps in 20223. The average price of 1 GB of data decreased from 10.5% of monthly GNI per capita in 2019 to 5% in 20213.
|
||||
|
||||
### **Current State**
|
||||
|
||||
As of mid-2023, fixed broadband penetration in Africa was 12%, compared with 63% globally4. Fixed broadband penetration is low in Africa overall, but in North African countries like Algeria, Egypt, and Tunisia, it exceeds 50%4. There is significant variation in internet penetration rates across African regions, with Southern Africa having the highest penetration and Central Africa the lowest2.
|
||||
|
||||
Africa's fixed broadband market has shown the strongest growth rate among world regions in subscriptions and service revenues over the past five years6. In 2021 alone, fixed broadband subscriptions in Africa increased by 14.6%, compared to the global average of 8.2%6. This growth is driven by new network deployments and efforts to improve affordability, particularly during the COVID-19 pandemic6.
|
||||
|
||||
Despite this growth, Africa still lags behind other regions, with an average fixed broadband household penetration rate of 11.5% at the end of 2021, far below the global average of 59%6.
|
||||
|
||||
### **Broadband Speeds per Country**
|
||||
|
||||
| Country | Mobile Internet Speed (Mbps) | Global Rank |
|
||||
| :---- | :---- | :---- |
|
||||
| South Africa | 45.06 | 52 |
|
||||
| Uganda | 38.53 | 60 |
|
||||
| Mauritius | 35.56 | 61 |
|
||||
| Morocco | 33.34 | 68 |
|
||||
| Rwanda | 27.34 | 82 |
|
||||
| Zimbabwe | 25.57 | 91 |
|
||||
| Egypt | 24.70 | 93 |
|
||||
| Senegal | 24.40 | 94 |
|
||||
| Tunisia | 24.27 | 95 |
|
||||
| Kenya | 24.20 | 97 |
|
||||
|
||||
In West Africa, Côte d'Ivoire leads with an average download speed of 58.91 Mbps, followed by Burkina Faso (42.23 Mbps) and Ghana (39.26 Mbps)8. Senegal has an average download speed of 18.1 Mbps, while Nigeria has 15.8 Mbps8.
|
||||
|
||||
## **International Internet Bandwidth**
|
||||
|
||||
Africa's total inbound international internet bandwidth reached 49.6 Tbps by December 20239. This represents a significant increase from 36.9 Tbps in 2022, 26.4 Tbps in 2021, 21.0 Tbps in 2020, and 16.1 Tbps in 20199. This growth highlights the increasing demand for international connectivity in Africa.
|
||||
|
||||
Of the total bandwidth in 2023, Sub-Saharan Africa accounted for 34.4 Tbps, while North Africa accounted for 15.2 Tbps9. Almost two-thirds of the bandwidth to Sub-Saharan Africa is supplied to its three largest markets: South Africa (9.950 Tbps), Kenya (8.042 Tbps), and Nigeria (3.295 Tbps)9.
|
||||
|
||||
## **Types of Broadband Technologies in Africa**
|
||||
|
||||
Various broadband technologies are used in Africa, each with its own advantages and limitations.
|
||||
|
||||
* **Digital Subscriber Line (DSL):** DSL is the leading technology for fixed broadband services in North Africa, using existing phone lines for internet connectivity10. ADSL offers speeds up to 24 Mbps, while VDSL offers speeds up to 52 Mbps for VDSL1 and 200 Mbps for VDSL210.
|
||||
* **Fiber-to-the-Home/Building (FTTH/B):** FTTH/B extends fiber directly to the user's premises, offering higher speeds and reliability10. As of June 2023, Algeria had the highest number of FTTH/B connections in North Africa (800,000), followed by Morocco (730,000)10.
|
||||
|
||||
### **Fiber Optic Expansion in Algeria**
|
||||
|
||||
Algeria has set ambitious goals to expand its fiber coverage substantially10. The number of households passed by fiber (i.e., premises that can connect to the fiber network) is expected to increase from 3.5 million in 2022 to 6 million by 2024 (out of an estimated 7.4 million households)10. This expansion will significantly improve broadband access and speeds in Algeria.
|
||||
|
||||
* **Fixed Wireless Access (FWA):** FWA, including 5G FWA, provides a wireless alternative to fixed broadband, particularly in areas where wired infrastructure is challenging to deploy10.
|
||||
* **Satellite Internet:** Satellite internet plays a crucial role in bridging the digital divide, especially in remote areas11. It supports various 5G use cases and benefits industries like broadcasting, agriculture, mining, and aviation11.
|
||||
* **Mobile Broadband:** Mobile broadband, using 3G and 4G technologies, is the primary means of internet access for many Africans10. 4G offers speeds comparable to ADSL and VDSL1 but falls short of VDSL2 and FTTH/B10.
|
||||
|
||||
## **Challenges and Opportunities for Broadband Development in Africa**
|
||||
|
||||
Expanding broadband access in Africa faces various challenges, but also presents significant opportunities.
|
||||
|
||||
### **Challenges**
|
||||
|
||||
* **Lack of Investment:** Building broadband infrastructure requires substantial financial resources12. Inadequate investment in fiber optic cables, cell towers, and data centers hinders internet penetration12. Attracting investment can be challenging due to factors such as perceived risks, regulatory uncertainties, and the high cost of deploying infrastructure in underserved areas.
|
||||
* **Poor Policy Implementation:** Bureaucratic hurdles, corruption, and weak governance structures can hinder the implementation of national broadband plans12. This can lead to delays in projects, inefficient allocation of resources, and a lack of progress in achieving broadband targets.
|
||||
* **Affordability:** High data costs and limited competition among internet service providers make internet access unaffordable for many Africans12. In many African countries, broadband connectivity is priced well above the means of the average citizen13. Mobile data can be up to 70 times more expensive per gigabit than fiber, highlighting the need for affordable alternatives like fiber broadband14.
|
||||
* **Limited Coverage in Rural Areas:** High-speed connections are often concentrated in urban areas, leaving rural communities underserved8. This is due to the higher cost of deploying infrastructure in remote areas and the lower population density, which makes it less commercially viable for service providers.
|
||||
* **Digital Divide:** Disparities in internet access exist across different demographics, with women and rural residents less likely to be online15. This digital divide can exacerbate existing inequalities and limit opportunities for marginalized communities.
|
||||
* **Lack of Infrastructure:** Africa's internet infrastructure remains scarce and underdeveloped, with limited fiber network reach and reliance on expensive satellite links16. This can result in higher costs, lower speeds, and limited access to advanced digital services.
|
||||
* **Challenges in Accessing Rights of Way:** Bureaucratic challenges in obtaining rights of way can hinder the deployment of fiber optic cables and other infrastructure, especially across borders13. This can lead to delays in projects and increased costs for service providers.
|
||||
|
||||
### **Opportunities**
|
||||
|
||||
* **Economic Growth:** Expanding broadband access can drive economic growth, create jobs, and boost innovation17. A 10% increase in mobile broadband penetration could generate a 2.5% rise in Africa's GDP per capita17. Broadband connectivity can enable new business models, facilitate e-commerce, and improve access to global markets.
|
||||
* **Social Inclusion:** Broadband connectivity can bridge the digital divide, providing equal opportunities in education, healthcare, and access to information14. This can empower marginalized communities, improve social mobility, and promote greater participation in society.
|
||||
* **Technological Advancements:** New technologies like FTTH, FWA, and satellite internet offer innovative solutions for expanding broadband coverage14. These technologies can overcome infrastructure challenges, provide faster speeds, and reach underserved areas.
|
||||
* **Public-Private Partnerships:** Collaborative efforts between governments and the private sector can leverage resources and expertise to accelerate broadband deployment14. PPPs can share the risks and costs of infrastructure development, while also ensuring that projects align with national broadband goals.
|
||||
* **Innovation and Entrepreneurship:** Broadband access can foster a vibrant tech ecosystem, supporting local startups and content providers12. This can lead to the development of new applications and services tailored to the needs of African communities.
|
||||
|
||||
## **Government Policies and Initiatives**
|
||||
|
||||
African governments have implemented various policies and initiatives to promote broadband development.
|
||||
|
||||
### **National Broadband Plans**
|
||||
|
||||
Many African countries have national broadband plans to increase internet penetration18. For example, Nigeria aims to achieve 70% broadband penetration by 202518. These plans typically outline strategies for infrastructure development, regulatory reforms, and affordability initiatives.
|
||||
|
||||
### **Regional Initiatives**
|
||||
|
||||
The Smart Africa Alliance aims to accelerate development through improved broadband access and ICT usage18. This initiative involves multiple African countries working together to harmonize policies, share best practices, and attract investment in the broadband sector.
|
||||
|
||||
### **Universal Service Funds**
|
||||
|
||||
Universal Service Funds are intended to expand telecommunication services to underserved areas12. Regulators can allocate a portion of the USF to provide schools with affordable broadband access19. This can help bridge the digital divide and ensure that all communities have access to essential digital services.
|
||||
|
||||
### **Regulatory Reforms**
|
||||
|
||||
The World Bank has supported regulatory reforms in several African countries to promote competition and investment in the broadband sector3. These reforms can include measures to streamline licensing processes, reduce barriers to entry, and encourage infrastructure sharing.
|
||||
|
||||
### **Internet Exchange Points (IXPs)**
|
||||
|
||||
Governments can support the establishment of national and regional Internet exchange points (IXPs) to rationalize and reduce the cost of internet traffic at national and regional levels20. IXPs help keep local internet traffic within the country or region, reducing the need to rely on expensive international connections. This contributes to affordability and improves the quality of service for users.
|
||||
|
||||
## **Impact of Broadband on Various Sectors**
|
||||
|
||||
Broadband connectivity has a significant impact on various sectors in Africa.
|
||||
|
||||
### **Education**
|
||||
|
||||
Broadband access can transform education in Africa by:
|
||||
|
||||
* **Expanding access to educational resources:** Online learning platforms and digital resources provide students with diverse learning opportunities21. This can include access to online courses, virtual libraries, and educational videos, enriching the learning experience for students.
|
||||
* **Improving the quality of education:** Broadband enables interactive learning experiences and connects students with educators globally21. This can facilitate online collaboration, virtual classrooms, and access to subject matter experts, enhancing the quality of education.
|
||||
* **Bridging the digital divide:** Digital education can provide more equal opportunities for students in urban and remote areas21. This can help reduce educational inequalities and ensure that all students have access to quality education.
|
||||
* **Highlighting the Digital Divide:** The COVID-19 pandemic exposed the digital divide in education, as many students lacked access to technology for online learning22. This highlighted the urgent need to invest in broadband infrastructure and ensure that all students have the tools they need to succeed in a digital world.
|
||||
|
||||
### **Healthcare**
|
||||
|
||||
Broadband connectivity can improve healthcare in Africa by:
|
||||
|
||||
* **Expanding access to telemedicine:** Telemedicine systems connect patients in rural areas with doctors and specialists in urban centers23. This can improve access to specialized care, reduce travel time and costs for patients, and enhance the overall quality of healthcare services.
|
||||
* **Improving healthcare access:** Mobile devices and internet access enable people to access basic healthcare regardless of location23. This can facilitate online consultations, remote monitoring of patients, and access to health information, improving healthcare outcomes.
|
||||
* **Facilitating health information access:** The internet provides access to vast amounts of health information for patients and healthcare providers24. This can empower patients to make informed decisions about their health, support healthcare professionals in providing evidence-based care, and promote health literacy.
|
||||
* **Example of Telemedicine in Ghana:** The Novartis Foundation launched a telemedicine system in Ghana in 2011, allowing frontline health workers to connect with medical specialists across the country23. This system provides 24/7 access to medical advice and support, improving healthcare access and quality in underserved communities.
|
||||
|
||||
### **Economy**
|
||||
|
||||
Broadband connectivity can boost economic growth in Africa by:
|
||||
|
||||
* **Increasing productivity:** The internet enhances the capacity of economic sectors and facilitates global value chains25. This can lead to more efficient production processes, improved communication, and greater access to markets, boosting economic output.
|
||||
* **Creating jobs:** Digitalization can create new employment opportunities in various sectors17. This can include jobs in technology, e-commerce, and online services, contributing to economic growth and reducing unemployment.
|
||||
* **Promoting innovation:** Broadband access supports entrepreneurship and the development of new technologies17. This can foster a culture of innovation, attract investment, and drive economic diversification.
|
||||
* **Impact on Poverty Reduction:** Studies have shown a correlation between internet penetration and poverty rates26. Countries with high internet penetration have low poverty rates, suggesting that broadband access can contribute to economic development and poverty reduction.
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
Broadband connectivity is a critical driver of development in Africa. While significant progress has been made in expanding access and improving speeds, challenges remain in ensuring universal access, affordability, and quality of service. By addressing these challenges and leveraging the opportunities presented by technological advancements and innovative policies, Africa can harness the transformative power of broadband to achieve its socio-economic goals.
|
||||
|
||||
The growth of fixed broadband subscriptions, driven by network deployments and affordability initiatives, is a positive trend. However, continued investment in infrastructure, particularly in underserved rural areas, is crucial. Governments must prioritize policies that promote competition, reduce costs, and bridge the digital divide.
|
||||
|
||||
The impact of broadband on various sectors, including education, healthcare, and the economy, is significant. Broadband access can transform education by expanding access to resources, improving quality, and bridging the digital divide. In healthcare, broadband can facilitate telemedicine, improve access to care, and promote health information access. In the economy, broadband can boost productivity, create jobs, and promote innovation.
|
||||
|
||||
By embracing the digital revolution and ensuring that all Africans have access to affordable and reliable broadband connectivity, the continent can unlock its full potential and achieve sustainable and inclusive development.
|
||||
|
||||
#### **Works cited**
|
||||
|
||||
1\. Africa's connectivity gap: Can a map tell the story? \- World Bank Blogs, accessed on February 27, 2025, [https://blogs.worldbank.org/en/digital-development/africas-connectivity-gap-can-map-tell-story](https://blogs.worldbank.org/en/digital-development/africas-connectivity-gap-can-map-tell-story)
|
||||
2\. Internet usage in Africa \- statistics & facts \- Statista, accessed on February 27, 2025, [https://www.statista.com/topics/9813/internet-usage-in-africa/](https://www.statista.com/topics/9813/internet-usage-in-africa/)
|
||||
3\. From Connectivity to Services: Digital Transformation in Africa \- World Bank, accessed on February 27, 2025, [https://www.worldbank.org/en/results/2023/06/27/from-connectivity-to-services-digital-transformation-in-africa](https://www.worldbank.org/en/results/2023/06/27/from-connectivity-to-services-digital-transformation-in-africa)
|
||||
4\. Africa Broadband Outlook 2023 \- Omdia, accessed on February 27, 2025, [https://omdia.tech.informa.com/-/media/tech/omdia/marketing/commissioned-research/pdfs/africa-broadband-outlook-2023.pdf?rev=328d0b7a1f4c4ecc915e697453f32e3e](https://omdia.tech.informa.com/-/media/tech/omdia/marketing/commissioned-research/pdfs/africa-broadband-outlook-2023.pdf?rev=328d0b7a1f4c4ecc915e697453f32e3e)
|
||||
5\. Digital Transformation Drives Development in Africa \- World Bank, accessed on February 27, 2025, [https://www.worldbank.org/en/results/2024/01/18/digital-transformation-drives-development-in-afe-afw-africa](https://www.worldbank.org/en/results/2024/01/18/digital-transformation-drives-development-in-afe-afw-africa)
|
||||
6\. Africa has fastest growing fixed broadband market \- Omdia \- Connecting Africa, accessed on February 27, 2025, [https://www.connectingafrica.com/broadband/africa-has-fastest-growing-fixed-broadband-market-omdia](https://www.connectingafrica.com/broadband/africa-has-fastest-growing-fixed-broadband-market-omdia)
|
||||
7\. Top 10 African countries with blazing fast mobile internet speeds | Business Insider Africa, accessed on February 27, 2025, [https://africa.businessinsider.com/local/lifestyle/top-10-african-countries-with-blazing-fast-mobile-internet-speeds/q02hrgm](https://africa.businessinsider.com/local/lifestyle/top-10-african-countries-with-blazing-fast-mobile-internet-speeds/q02hrgm)
|
||||
8\. West African Countries with the Fastest Internet \- Who's Leading the Pack?, accessed on February 27, 2025, [https://www.culturesofwestafrica.com/fastest-internet-west-africa/](https://www.culturesofwestafrica.com/fastest-internet-west-africa/)
|
||||
9\. Africa Bandwidth Maps, accessed on February 27, 2025, [https://www.africabandwidthmaps.com/](https://www.africabandwidthmaps.com/)
|
||||
10\. Fixed Internet Speeds are Improving in North Africa, and Fiber Could Boost Them Even Further \- GSMA, accessed on February 27, 2025, [https://www.gsma.com/get-involved/gsma-membership/gsma\_resources/fixed-internet-speeds-are-improving-in-north-africa-and-fiber-could-boost-them-even-further/](https://www.gsma.com/get-involved/gsma-membership/gsma_resources/fixed-internet-speeds-are-improving-in-north-africa-and-fiber-could-boost-them-even-further/)
|
||||
11\. The Reality of Broadband in Africa: Essential Facts for Mobile Network Providers, accessed on February 27, 2025, [https://www.abiresearch.com/blogs/2022/08/24/mobile-broadband-in-africa-facts-for-network-providers/](https://www.abiresearch.com/blogs/2022/08/24/mobile-broadband-in-africa-facts-for-network-providers/)
|
||||
12\. Beyond National Broadband Plans: Addressing Limitations to Internet Penetration in Africa, accessed on February 27, 2025, [https://paradigmhq.org/beyond-national-broadband-plans-addressing-limitations-to-internet-penetration-in-africa/](https://paradigmhq.org/beyond-national-broadband-plans-addressing-limitations-to-internet-penetration-in-africa/)
|
||||
13\. Lifting barriers to Internet development in Africa: suggestions for improving connectivity, accessed on February 27, 2025, [https://www.internetsociety.org/wp-content/uploads/2017/08/Barriers20to20Internet20in20Africa20Internet20Society\_0.pdf](https://www.internetsociety.org/wp-content/uploads/2017/08/Barriers20to20Internet20in20Africa20Internet20Society_0.pdf)
|
||||
14\. Bridging the digital divide: Empowering Africa's future with robust broadband infrastructure, accessed on February 27, 2025, [https://teletimesinternational.com/2025/empowering-africa-future-broadband/](https://teletimesinternational.com/2025/empowering-africa-future-broadband/)
|
||||
15\. Despite improvements, Sub-Saharan Africa has the widest usage and coverage gaps worldwide | Mobile for Development \- GSMA, accessed on February 27, 2025, [https://www.gsma.com/solutions-and-impact/connectivity-for-good/mobile-for-development/blog/despite-improvements-sub-saharan-africa-has-the-widest-usage-and-coverage-gaps-worldwide/](https://www.gsma.com/solutions-and-impact/connectivity-for-good/mobile-for-development/blog/despite-improvements-sub-saharan-africa-has-the-widest-usage-and-coverage-gaps-worldwide/)
|
||||
16\. Internet in Africa \- Wikipedia, accessed on February 27, 2025, [https://en.wikipedia.org/wiki/Internet\_in\_Africa](https://en.wikipedia.org/wiki/Internet_in_Africa)
|
||||
17\. Africa Goes Digital – IMF F\&D, accessed on February 27, 2025, [https://www.imf.org/external/pubs/ft/fandd/2021/03/africas-digital-future-after-covid19-duarte-old.htm](https://www.imf.org/external/pubs/ft/fandd/2021/03/africas-digital-future-after-covid19-duarte-old.htm)
|
||||
18\. Internet penetration across Africa: challenges and opportunities \- Prysmian, accessed on February 27, 2025, [https://www.prysmian.com/en/insight/telecoms/nexst/internet-penetration-across-africa](https://www.prysmian.com/en/insight/telecoms/nexst/internet-penetration-across-africa)
|
||||
19\. Internet for Education in Africa: Helping Policy Makers to Meet the Global Education Agenda Sustainable Development Goal 4, accessed on February 27, 2025, [https://www.internetsociety.org/resources/doc/2017/internet-for-education-in-africa-helping-policy-makers-to-meet-the-global-education-agenda-sustainable-development-goal-4/](https://www.internetsociety.org/resources/doc/2017/internet-for-education-in-africa-helping-policy-makers-to-meet-the-global-education-agenda-sustainable-development-goal-4/)
|
||||
20\. Principle \- African Declaration on Internet Rights and Freedoms, accessed on February 27, 2025, [https://africaninternetrights.org/principles/2](https://africaninternetrights.org/principles/2)
|
||||
21\. Empowering Education: The Transformative Role of Technology in Africa \- unesco iicba, accessed on February 27, 2025, [https://www.iicba.unesco.org/en/node/116](https://www.iicba.unesco.org/en/node/116)
|
||||
22\. Africa's universities can jumpstart the end of the digital divide \- World Bank Blogs, accessed on February 27, 2025, [https://blogs.worldbank.org/en/digital-development/africas-universities-can-jumpstart-end-digital-divide](https://blogs.worldbank.org/en/digital-development/africas-universities-can-jumpstart-end-digital-divide)
|
||||
23\. Internet Access in Sub-Saharan Africa Promotes Health and Literacy \- \- The Borgen Project, accessed on February 27, 2025, [https://borgenproject.org/internet-access-in-sub-saharan-africa/](https://borgenproject.org/internet-access-in-sub-saharan-africa/)
|
||||
24\. Explore barriers to using the internet for health information access in African countries: A systematic review \- PubMed, accessed on February 27, 2025, [https://pubmed.ncbi.nlm.nih.gov/39869649/](https://pubmed.ncbi.nlm.nih.gov/39869649/)
|
||||
25\. The non-linear effects of fixed broadband on economic growth in Africa | Emerald Insight, accessed on February 27, 2025, [https://www.emerald.com/insight/content/doi/10.1108/jes-03-2022-0159/full/html](https://www.emerald.com/insight/content/doi/10.1108/jes-03-2022-0159/full/html)
|
||||
26\. The impact of Internet connectivity on economic development in Sub-Saharan Africa \- GOV.UK, accessed on February 27, 2025, [https://assets.publishing.service.gov.uk/media/57a0899b40f0b652dd0002f4/The-impact-of-internet-connectivity-on-economic-development-in-Sub-Saharan-Africa.pdf](https://assets.publishing.service.gov.uk/media/57a0899b40f0b652dd0002f4/The-impact-of-internet-connectivity-on-economic-development-in-Sub-Saharan-Africa.pdf)
|
||||
315
docs/Broadband-RTL.md
Normal file
315
docs/Broadband-RTL.md
Normal file
@ -0,0 +1,315 @@
|
||||
# **Broadband Coverage in Arab Countries: A Comprehensive Overview**
|
||||
|
||||
This report provides a detailed analysis of broadband coverage in Arab countries, encompassing various aspects such as internet accessibility, speed, limitations, and usage patterns. It also delves into related factors like education levels and popular applications, offering a comprehensive overview of the digital landscape in the Arab world.
|
||||
|
||||
## **Internet Access and Infrastructure**
|
||||
|
||||
### **Algeria**
|
||||
|
||||
**Fixed Broadband**
|
||||
|
||||
Algeria's telecommunications infrastructure is considered underdeveloped compared to developed nations. While approximately 71% of residents have internet access, only 12% possess a fast internet connection exceeding ISDN speeds (greater than 256 kbit/s)1. The country has shown a steady increase in fixed internet subscriptions, growing from 3.3 million in 2018 to 4.3 million in 20222. Most Algerian subscribers access the internet through asymmetric digital subscriber line (ADSL) and leased line (LL) connections2.
|
||||
|
||||
**Mobile Internet**
|
||||
|
||||
Mobile internet usage is prevalent in Algeria, with over 40 million mobile internet subscriptions in 2022 and a penetration rate exceeding 60%2. Most mobile internet users opt for 4G technologies and prepaid solutions2.
|
||||
|
||||
**Internet Speed**
|
||||
|
||||
With an average download speed of 15.05 Mbit/second for fixed-network broadband internet, Algeria ranks 141st in an international comparison1. The upload rate is significantly lower at only 3.6 Mbit/second1. In mobile internet, Algeria fares better, with an average download speed of 23.42 Mbit/second and an upload speed of around 11 Mbit/second1.
|
||||
|
||||
**Key Insight:** The contrast between the high mobile internet penetration and the limited access to high-speed fixed broadband suggests a possible reliance on mobile internet due to infrastructure limitations or affordability issues1.
|
||||
|
||||
### **Bahrain**
|
||||
|
||||
**Broadband Penetration**
|
||||
|
||||
Bahrain boasts robust internet infrastructure with both mobile and fixed broadband connections, ensuring nationwide access4. The country has achieved 100% 5G commercial network coverage with download speeds exceeding 2 Gbps4. Fiber optic rollout has reached over 95% across the Kingdom4.
|
||||
|
||||
**Network Performance**
|
||||
|
||||
In Opensignal's latest assessment of Bahrain's mobile network experience, Batelco secured the highest number of awards, including seven uncontested victories5. Batelco wins five out of six 5G categories, either outright or jointly5. It is the outright winner of the 5G Video Experience, 5G Games Experience, 5G Upload Speed, and 5G Coverage Experience awards5. Batelco and stc share the 5G Download Speed award, while stc takes the 5G Availability award5.
|
||||
|
||||
**ICT Development**
|
||||
|
||||
These achievements have contributed to Bahrain's high ranking in the ICT Development Index 2023, securing 7th place globally and 3rd in the Arab region4.
|
||||
|
||||
**Key Insight:** Bahrain's leading position in 5G network coverage and performance in the GCC region highlights the country's commitment to developing advanced telecommunications infrastructure4.
|
||||
|
||||
### **Comoros**
|
||||
|
||||
**Internet Penetration**
|
||||
|
||||
Comoros had an internet penetration rate of 8.5% in January 2022, with 76.1 thousand internet users6. The number of mobile connections stood at 494.9 thousand, equivalent to 55.1% of the total population6.
|
||||
|
||||
**Telecoms Expansion**
|
||||
|
||||
The launch of Telma Comores has expanded telecoms services in Comoros, giving people high-quality 4G LTE mobile broadband technology at lower prices7. In response to this competition, Comores Télécoms has launched an even faster 4.5G network7. By 2018, mobile broadband subscriptions rose to 58 per 100 inhabitants7.
|
||||
|
||||
**Challenges to Access**
|
||||
|
||||
Despite the presence of 4G LTE technology, internet access remains limited due to factors like affordability and digital literacy7.
|
||||
|
||||
**Key Insight:** The extremely low internet penetration rate and the significant digital divide in Comoros suggest a need for focused efforts to improve affordability, digital literacy, and infrastructure6.
|
||||
|
||||
### **Djibouti**
|
||||
|
||||
**Internet Access**
|
||||
|
||||
Djibouti had an internet penetration rate of 55.7% in January 2021, with 554.3 thousand internet users9. The number of mobile connections was 433.0 thousand, equivalent to 43.5% of the total population9.
|
||||
|
||||
**Telecoms Monopoly**
|
||||
|
||||
Djibouti remains one of the few countries with a telecoms monopoly, which has limited market potential and internet accessibility10. This lack of competition has hindered the development of the telecommunications sector and potentially affected service quality and affordability.
|
||||
|
||||
**Key Insight:** The telecoms monopoly in Djibouti raises concerns about internet accessibility, affordability, market competition, and consumer choices10.
|
||||
|
||||
### **Egypt**
|
||||
|
||||
**Internet Penetration and Infrastructure**
|
||||
|
||||
Egypt has shown significant growth in internet penetration, rising from less than 1% in 2000 to 71.9% in 202211. This growth has been driven by investments in the information and communications technology sector11. However, the country still lags in broadband internet connections, with only 12% having access to speeds exceeding ISDN rates12. Fixed broadband subscriptions have experienced a decline, with 9.8 subscriptions per 100 inhabitants in 202213.
|
||||
|
||||
**Broadband Quality**
|
||||
|
||||
Broadband connections in Egypt vary in quality depending on factors like distance from the central loop office and the quality of the copper telephone line11. In April 2008, ADSL2+ was introduced in Egypt at speeds up to 24 Mbit/s11. However, most ISPs have capped unlimited ADSL offerings to a quota of between 100 GB and 200 GB per month, creating confusion among users11.
|
||||
|
||||
**Government Initiatives**
|
||||
|
||||
As part of its program to expand access to information technology, the Egyptian government, through the Ministry of Communications and Information Technology (MCIT), has offered discounts on computers and 512 kbit/s ADSL subscriptions to socio-economically disadvantaged communities11.
|
||||
|
||||
**Key Insight:** The decline in fixed broadband subscriptions despite the overall growth in internet penetration could indicate a preference for mobile internet or challenges in fixed broadband affordability and availability11.
|
||||
|
||||
### **Iraq**
|
||||
|
||||
**Internet Access and Infrastructure**
|
||||
|
||||
Iraq's telecommunications infrastructure is underdeveloped, with one of the lowest internet penetration rates in the region14. Internet penetration stood at 74.9% in January 2023, with an estimated 33.72 million internet users14. The country has 7.77 million fixed broadband subscribers15. Despite recent investments in infrastructure, internet access and speeds remain poor, particularly in rural areas, which still rely on 2G technology14.
|
||||
|
||||
**National Internet Project**
|
||||
|
||||
The National Internet Project (NIP) is working to deliver high-speed internet service to underserved parts of the country14. As part of the NIP, the Ministry of Communications is implementing a modern fiber-to-the-home (FTTH) network14.
|
||||
|
||||
**Silk Route Transit Network**
|
||||
|
||||
In November 2023, an Iraqi fiber optic provider unveiled the Silk Route Transit Network16. This network aims to bolster Iraq's industrial, retail, and service sectors by providing fiber optic connectivity, e-governance, and data centers16.
|
||||
|
||||
**Key Insight:** Iraq faces significant challenges in developing its telecommunications infrastructure, particularly in rural areas, underscoring the need for continued investment and efforts to bridge the digital divide14.
|
||||
|
||||
### **Jordan**
|
||||
|
||||
**Internet Access and Infrastructure**
|
||||
|
||||
Jordan has a high internet penetration rate of 91% as of early 202417. The majority of Jordanians access the internet on their phones, with 9.14 million mobile connections17. Fiber optic subscriptions have also increased in recent years17.
|
||||
|
||||
**Internet Speed**
|
||||
|
||||
Investment in telecommunications infrastructure has led to improved internet speeds in Jordan17. As of May 2024, the median mobile download and upload speeds were 26.08 Mbps and 15.93 Mbps, respectively17. The median fixed-line broadband download and upload speeds were significantly higher at 147.40 Mbps and 120.96 Mbps, respectively17.
|
||||
|
||||
**Digital Transformation**
|
||||
|
||||
The Jordanian government is actively encouraging the country's digital transformation, spurred by demands arising from the COVID-19 pandemic18. There is a focus on building Fiber-to-the-Premises (FttP) infrastructure to enhance broadband access18.
|
||||
|
||||
**Data Usage**
|
||||
|
||||
The total number of fixed broadband internet subscriptions in Jordan reached 798,800 in the first quarter of 202419. The fixed broadband data usage volume was approximately 1.249 billion gigabytes19.
|
||||
|
||||
**Key Insight:** The significant improvement in internet speeds in Jordan, likely driven by increased investment in infrastructure, demonstrates the positive impact of government initiatives and private sector involvement17.
|
||||
|
||||
### **Kuwait**
|
||||
|
||||
**Broadband Penetration**
|
||||
|
||||
Kuwait has achieved near-universal fixed broadband penetration, with 98% of households having access in 202220. The country is expected to reach 100% fixed broadband penetration soon20.
|
||||
|
||||
**Mobile Data Usage**
|
||||
|
||||
Mobile data usage is also high in Kuwait, with an average of 83.9 GB used monthly21. 5G network coverage has expanded to approximately 97% of the population21.
|
||||
|
||||
**5G Performance**
|
||||
|
||||
In 2023, Kuwait demonstrated strong performance in 5G, ranking first for 5G Video Experience and sharing the top spot with Bahrain for 5G Availability among GCC markets22.
|
||||
|
||||
**Key Insight:** Kuwait's achievement of near-universal fixed broadband penetration and its high mobile data usage suggest a strong demand for internet services and a digitally advanced society20.
|
||||
|
||||
### **Lebanon**
|
||||
|
||||
**Internet Access and Infrastructure**
|
||||
|
||||
Lebanon's internet freedom remains tenuous, with the ongoing economic crisis creating obstacles for internet service providers and users23. Internet penetration stood at 90.1% in early 2024, with a slight decline in user numbers24.
|
||||
|
||||
**Internet Speed and Service Disruptions**
|
||||
|
||||
Lebanon's telecommunications infrastructure is weak, and the economic crisis has further impacted service quality24. Internet speeds have decreased, and service disruptions are frequent, particularly in rural areas, which often only have access to 2G or 3G networks24.
|
||||
|
||||
**Mobile Network Upgrades**
|
||||
|
||||
To tackle mobile connectivity problems, both operators Touch and Alfa upgraded their networks in 201125. 3G services cover around 90% of Lebanon, and 4G services were subsequently launched26. A 5G network is available at Rafic Hariri airport in Beirut26.
|
||||
|
||||
**IMEWE Cable Network**
|
||||
|
||||
Lebanon benefits from the India-Middle East-Western Europe (IMEWE) Cable Network, a 12,091 km cable with 10 terminal stations25. This cable system enhances Lebanon's international internet connectivity.
|
||||
|
||||
**Key Insight:** The economic crisis in Lebanon has significantly impacted internet accessibility and affordability, highlighting the vulnerability of the digital landscape to external factors and the need for resilience23.
|
||||
|
||||
### **Libya**
|
||||
|
||||
**Internet Access and Infrastructure**
|
||||
|
||||
Libya's internet access is hampered by the ongoing civil conflict, with inconsistent service and frequent power cuts27. The conflict has damaged approximately 25% of mobile towers27. Despite these challenges, internet penetration increased to 88.4% in 2024, compared to 45.9% in 202327. Internet speeds have also improved27.
|
||||
|
||||
**Internet Affordability**
|
||||
|
||||
While prices have fallen in recent years, the depreciation of the Libyan dinar and economic instability have made the internet inaccessible for some people27.
|
||||
|
||||
**Key Insight:** The improvement in internet penetration and speeds despite the challenges posed by the civil conflict demonstrates the resilience of the Libyan people and the efforts to maintain connectivity27.
|
||||
|
||||
## **Challenges to Digitization in Algeria**
|
||||
|
||||
Algeria faces several challenges in its digitization progress, including:
|
||||
|
||||
* **Incomplete regulatory framework:** The regulatory environment is considered restrictive and slow to adapt to international digital trends3.
|
||||
* **Limited access in remote areas:** Internet connectivity and access to high-speed broadband are limited in rural areas, where 24.5% of the population lives3.
|
||||
* **Data protection and localization:** Algeria's data protection laws, which mandate data localization, can be challenging for foreign companies entering the market3.
|
||||
|
||||
These challenges need to be addressed to accelerate Algeria's digital transformation and ensure inclusive access to technology3.
|
||||
|
||||
## **Global Context: Internet Connectivity in Developing Countries**
|
||||
|
||||
Despite significant progress in mobile internet adoption, a considerable usage gap persists in low- and middle-income countries8. As of 2020, 51% of the population in these countries was still not using mobile internet due to various barriers, including lack of coverage, affordability, awareness, and digital skills8. If current trends continue, more than 40% of the population in these countries will remain offline in 20258.
|
||||
|
||||
## **Gender and Digital Divide in Djibouti**
|
||||
|
||||
In Djibouti, there is a significant gender gap in mobile internet and mobile ownership28. This gap highlights the need to address the specific challenges faced by women in accessing and using technology.
|
||||
|
||||
## **Internet Freedom and Censorship**
|
||||
|
||||
### **Algeria**
|
||||
|
||||
Algeria's government regulations allow for controls on internet access and content monitoring29. Internet service providers are responsible for the content they host and are required to monitor it to prevent access to material deemed contrary to public order or morality29. These regulations are often used to restrict anti-government activism and online criticism29. The country has a history of disrupting internet connectivity during Baccalaureate exams to curb cheating30.
|
||||
|
||||
### **Bahrain**
|
||||
|
||||
Bahrain's internet freedom is restricted, with authorities frequently blocking websites and removing online content critical of the government31. Social media platforms are monitored, and self-censorship is prevalent due to fears of surveillance and intimidation31. Journalists and activists face criminal penalties and harassment for online activities31. The Bahraini government also uses spyware and tracking tools to target individuals and suppress dissent32.
|
||||
|
||||
### **Other Arab Countries**
|
||||
|
||||
While the research material did not provide specific details on internet limitations in other Arab countries, it is important to note that governments in the region often impose restrictions on online content and access for various reasons, including national security, political control, and social values.
|
||||
|
||||
## **Education Levels**
|
||||
|
||||
### **Algeria**
|
||||
|
||||
Algeria has a compulsory education system for children aged 6 to 1633. The literacy rate among adults is 81%, while youth literacy stands at 97%34. Primary and middle school enrollment rates are high, but there are significant dropouts in secondary education34. Access to higher education is improving, with more women than men attending universities34. In 2008, the Algerian government approved a €100 million plan to implement internet networks in every high school in the country29.
|
||||
|
||||
### **Bahrain**
|
||||
|
||||
Bahrain has a well-established education system with a 95% literacy rate35. Basic education is compulsory for nine years, starting at age 636. The country has invested heavily in the education sector, focusing on quality and inclusivity35.
|
||||
|
||||
### **Other Arab Countries**
|
||||
|
||||
While specific details on education levels in other Arab countries are limited in the provided research material, it is worth noting that education is generally prioritized in the Arab world, with governments investing in improving literacy rates and access to quality education.
|
||||
|
||||
## **Mobile vs. Desktop Internet Usage**
|
||||
|
||||
### **Algeria**
|
||||
|
||||
Mobile internet usage dominates in Algeria, with mobile devices generating approximately 61.4% of web traffic2. As of January 2025, mobile devices accounted for 62.89% of internet usage, while desktops held a 34.78% share37. This trend reflects the increasing affordability and accessibility of mobile devices and mobile internet services.
|
||||
|
||||
### **Bahrain**
|
||||
|
||||
While specific data on mobile vs. desktop usage in Bahrain is limited, the research suggests a decline in the use of fixed telephone lines, with most households relying on mobile phone services38. This indicates a potential shift towards mobile internet usage.
|
||||
|
||||
### **Other Arab Countries**
|
||||
|
||||
Data on mobile vs. desktop internet usage in other Arab countries is not available in the provided research material. However, the global trend shows a significant shift towards mobile internet usage, with mobile devices accounting for the majority of website traffic39.
|
||||
|
||||
## **Popular Apps**
|
||||
|
||||
### **Algeria**
|
||||
|
||||
Popular apps in Algeria include:
|
||||
|
||||
* **Communication:** WhatsApp Messenger, Messenger, Telegram, Snapchat, Rakuten Viber Messenger 40
|
||||
* **Local Services:** Yassir, BaridiMob, My Ooredoo Algeria, ECCP \- Algérie Poste 41
|
||||
* **E-commerce:** Jumia, Algerie Store, Mawdoo3, Modanisa, Algerie Market 42
|
||||
|
||||
### **Bahrain**
|
||||
|
||||
Popular apps in Bahrain include:
|
||||
|
||||
* **Fintech:** BenefitPay, stc pay BH, FLOOSS | Instant Finance, Beyon Money \- بيون موني 43
|
||||
* **E-commerce:** SHEIN \- Shopping Online, Brands For Less \- Shopping App, Trendyol, Alibaba.com 43
|
||||
* **Local Services:** My stc BH, السوق المفتوح \- OpenSooq, Ninja \- نينجا, Talabat, ila, BeAware Bahrain 43
|
||||
|
||||
### **Other Arab Countries**
|
||||
|
||||
Information on popular apps in other Arab countries is not available in the provided research material.
|
||||
|
||||
## **Average Screen Sizes**
|
||||
|
||||
Despite analyzing various sources, including 1, and 44, specific data on average screen sizes was not found for most countries. Further research may be required to gather comprehensive data on screen size preferences in the Arab world.
|
||||
|
||||
## **Browser Usage**
|
||||
|
||||
Despite reviewing sources like 1, and 44, detailed browser usage statistics for each Arab country were not readily available. However, it is worth noting that globally, Chrome dominates the browser market, with Safari and Edge holding significant shares45. Further research is needed to obtain detailed browser usage statistics for each Arab country.
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
This report has provided a comprehensive overview of broadband coverage and related aspects in Arab countries. While some countries like Bahrain and Kuwait have made significant strides in internet accessibility and speed, challenges remain in others, particularly regarding infrastructure development, affordability, and internet freedom. The digital landscape in the Arab world is evolving, with increasing mobile internet usage and the emergence of various applications catering to local needs.
|
||||
|
||||
The findings of this report have several implications for the region:
|
||||
|
||||
* **Economic Development:** Improved broadband access can contribute to economic growth by facilitating online businesses, e-commerce, and digital innovation.
|
||||
* **Social Progress:** Increased internet penetration can enhance access to information, education, and social services, promoting social inclusion and empowerment.
|
||||
* **Digital Inclusion:** Addressing the digital divide within and across Arab countries is crucial for ensuring that all citizens can benefit from the opportunities offered by the digital economy.
|
||||
|
||||
Further research and analysis are crucial for understanding the nuances of internet usage and trends in each country, enabling informed decision-making and policy development in the digital sphere. This will help to foster a more inclusive and digitally advanced Arab world.
|
||||
|
||||
#### **Works cited**
|
||||
|
||||
1\. Telecommunication in Algeria \- Worlddata.info, accessed on February 23, 2025, [https://www.worlddata.info/africa/algeria/telecommunication.php](https://www.worlddata.info/africa/algeria/telecommunication.php)
|
||||
2\. Internet usage in Algeria \- statistics & facts \- Statista, accessed on February 23, 2025, [https://www.statista.com/topics/10163/internet-usage-in-algeria/](https://www.statista.com/topics/10163/internet-usage-in-algeria/)
|
||||
3\. Algeria \- Digital Economy \- International Trade Administration, accessed on February 23, 2025, [https://www.trade.gov/country-commercial-guides/algeria-digital-economy](https://www.trade.gov/country-commercial-guides/algeria-digital-economy)
|
||||
4\. Internet for All in Bahrain, accessed on February 23, 2025, [https://www.bahrain.bh/wps/portal/en/BNP/HomeNationalPortal/ContentDetailsPage/\!ut/p/z1/nVRNc5swFPwrysFHRh8IAUcc119t4tSOk5hLRoCw1YJEQKZNfn3ldKYZ2wl2qhvS7tM-7T5gDB9grHgr19xIrXhhv1cxe8QTD42DS4RuBpSg73fhnPWvMLoaufB-H4Bvl8gCJtF8NhxgRDGMz-GjD1aEzuP\_A4yvbwaIzcaBT\_y-S113n39wPCUn-Bawxw-XyyFiwWhIKQv8iPmH\_PA2ZIgRElGyGLlo9ln9x4C4-3kO7z8SGHfLu4MxjKtUZnAlQpoETKROgDzsUM9jTsIEdrxc-B4lIs9YtkOnylRmA1eJqh6F6qFnva1BIXMBpAIJ39Rc2l2pjKiVMCDXNeBFce4hnHZZhly0y6T88fQUR1aKtoV-G\_jQrWWtW3tdKZQBXGWg0ankBWi2VaVr00O1KLgRGTC6kmmzB2-2SSMzKZpXpVJlspXZlhcN4Hku0h0reQaptrsODkFly4tSptaWeL-NdwbjlLXdFb4eAd7Jfkd6577nw5VNj\_9x-m08Wyl-waXSdWn\_Bou3sCSYuQEhzPFzQh2Kae5wgpnDXRr4SRJkKUrhGHW7uZuvLjd5orcGmI0AP6VaZ7rsIVPzVhSvNhrrtWzKY\_9a2fDmsLeDyZp09HbWIExPTfbnY\_rfI7MQClbl8u8qA9cr2urLfPzSv3ZG985q2r58y6Po4uIPMV6dhQ\!\!/?uri=nm:oid:Z6\_1I50H8C001TU00QVIAROFD1041](https://www.bahrain.bh/wps/portal/en/BNP/HomeNationalPortal/ContentDetailsPage/!ut/p/z1/nVRNc5swFPwrysFHRh8IAUcc119t4tSOk5hLRoCw1YJEQKZNfn3ldKYZ2wl2qhvS7tM-7T5gDB9grHgr19xIrXhhv1cxe8QTD42DS4RuBpSg73fhnPWvMLoaufB-H4Bvl8gCJtF8NhxgRDGMz-GjD1aEzuP_A4yvbwaIzcaBT_y-S113n39wPCUn-Bawxw-XyyFiwWhIKQv8iPmH_PA2ZIgRElGyGLlo9ln9x4C4-3kO7z8SGHfLu4MxjKtUZnAlQpoETKROgDzsUM9jTsIEdrxc-B4lIs9YtkOnylRmA1eJqh6F6qFnva1BIXMBpAIJ39Rc2l2pjKiVMCDXNeBFce4hnHZZhly0y6T88fQUR1aKtoV-G_jQrWWtW3tdKZQBXGWg0ankBWi2VaVr00O1KLgRGTC6kmmzB2-2SSMzKZpXpVJlspXZlhcN4Hku0h0reQaptrsODkFly4tSptaWeL-NdwbjlLXdFb4eAd7Jfkd6577nw5VNj_9x-m08Wyl-waXSdWn_Bou3sCSYuQEhzPFzQh2Kae5wgpnDXRr4SRJkKUrhGHW7uZuvLjd5orcGmI0AP6VaZ7rsIVPzVhSvNhrrtWzKY_9a2fDmsLeDyZp09HbWIExPTfbnY_rfI7MQClbl8u8qA9cr2urLfPzSv3ZG985q2r58y6Po4uIPMV6dhQ!!/?uri=nm:oid:Z6_1I50H8C001TU00QVIAROFD1041)
|
||||
5\. Bahrain, September 2024, Mobile Network Experience Report | Opensignal, accessed on February 23, 2025, [https://www.opensignal.com/reports/2024/09/bahrain/mobile-network-experience](https://www.opensignal.com/reports/2024/09/bahrain/mobile-network-experience)
|
||||
6\. Digital 2022: Comoros — DataReportal – Global Digital Insights, accessed on February 23, 2025, [https://datareportal.com/reports/digital-2022-comoros](https://datareportal.com/reports/digital-2022-comoros)
|
||||
7\. Comoros: Transforming Telecommunications in the Union of the Comoros \- World Bank, accessed on February 23, 2025, [https://www.worldbank.org/en/about/partners/brief/comoros-transforming-telecommunications-in-union-of-the-comoros](https://www.worldbank.org/en/about/partners/brief/comoros-transforming-telecommunications-in-union-of-the-comoros)
|
||||
8\. The State of Mobile Internet Connectivity 2020 | GSMA, accessed on February 23, 2025, [https://www.gsma.com/r/wp-content/uploads/2020/09/GSMA-State-of-Mobile-Internet-Connectivity-Report-2020.pdf](https://www.gsma.com/r/wp-content/uploads/2020/09/GSMA-State-of-Mobile-Internet-Connectivity-Report-2020.pdf)
|
||||
9\. Digital in Djibouti: All the Statistics You Need in 2021 \- DataReportal, accessed on February 23, 2025, [https://datareportal.com/reports/digital-2021-djibouti](https://datareportal.com/reports/digital-2021-djibouti)
|
||||
10\. Djibouti Telecoms Market report, Statistics and Forecast 2020 2025 \- BuddeComm, accessed on February 23, 2025, [https://www.budde.com.au/Research/Djibouti-Telecoms-Mobile-and-Broadband-Statistics-and-Analyses](https://www.budde.com.au/Research/Djibouti-Telecoms-Mobile-and-Broadband-Statistics-and-Analyses)
|
||||
11\. Internet in Egypt \- Wikipedia, accessed on February 23, 2025, [https://en.wikipedia.org/wiki/Internet\_in\_Egypt](https://en.wikipedia.org/wiki/Internet_in_Egypt)
|
||||
12\. Telecommunication in Egypt \- Worlddata.info, accessed on February 23, 2025, [https://www.worlddata.info/africa/egypt/telecommunication.php](https://www.worlddata.info/africa/egypt/telecommunication.php)
|
||||
13\. Fixed broadband subscriptions per 100 inhabitants in Egypt 2002-2022 \- Statista, accessed on February 23, 2025, [https://www.statista.com/statistics/517724/fixed-broadband-subscriptions-per-100-inhabitants-in-egypt/](https://www.statista.com/statistics/517724/fixed-broadband-subscriptions-per-100-inhabitants-in-egypt/)
|
||||
14\. Iraq: Freedom on the Net 2023 Country Report, accessed on February 23, 2025, [https://freedomhouse.org/country/iraq/freedom-net/2023](https://freedomhouse.org/country/iraq/freedom-net/2023)
|
||||
15\. Telecommunication in Iraq \- Worlddata.info, accessed on February 23, 2025, [https://www.worlddata.info/asia/iraq/telecommunication.php](https://www.worlddata.info/asia/iraq/telecommunication.php)
|
||||
16\. Iraq Telecom Market Size & Share Analysis \- Industry Research Report \- Growth Trends, accessed on February 23, 2025, [https://www.mordorintelligence.com/industry-reports/iraq-telecom-market](https://www.mordorintelligence.com/industry-reports/iraq-telecom-market)
|
||||
17\. Jordan: Freedom on the Net 2024 Country Report, accessed on February 23, 2025, [https://freedomhouse.org/country/jordan/freedom-net/2024](https://freedomhouse.org/country/jordan/freedom-net/2024)
|
||||
18\. Jordan Telecoms Market report, Statistics and Forecast 2020 2025 \- BuddeComm, accessed on February 23, 2025, [https://www.budde.com.au/Research/Jordan-Telecoms-Mobile-and-Broadband-Statistics-and-Analyses](https://www.budde.com.au/Research/Jordan-Telecoms-Mobile-and-Broadband-Statistics-and-Analyses)
|
||||
19\. 1.249b GB broadband data consumed in Q1 2024 \- TRC \- Jordan Times, accessed on February 23, 2025, [https://jordantimes.com/news/local/1249b-gb-broadband-data-consumed-q1-2024-trc](https://jordantimes.com/news/local/1249b-gb-broadband-data-consumed-q1-2024-trc)
|
||||
20\. FWA Landscape in Kuwait and Jordan \- Counterpoint Research, accessed on February 23, 2025, [https://www.counterpointresearch.com/research\_portal/fwa-landscape-in-kuwait-and-jordan/](https://www.counterpointresearch.com/research_portal/fwa-landscape-in-kuwait-and-jordan/)
|
||||
21\. Mobile Data Momentum: Kuwait, Saudi and Bahrain Emerge on Top \- Telecom Review, accessed on February 23, 2025, [https://www.telecomreview.com/articles/reports-and-coverage/8163-mobile-data-momentum-kuwait-saudi-and-bahrain-emerge-on-top](https://www.telecomreview.com/articles/reports-and-coverage/8163-mobile-data-momentum-kuwait-saudi-and-bahrain-emerge-on-top)
|
||||
22\. Kuwait, February 2023, Mobile Network Experience Report \- Opensignal, accessed on February 23, 2025, [https://www.opensignal.com/reports/2023/02/kuwait/mobile-network-experience](https://www.opensignal.com/reports/2023/02/kuwait/mobile-network-experience)
|
||||
23\. Lebanon: Freedom on the Net 2023 Country Report, accessed on February 23, 2025, [https://freedomhouse.org/country/lebanon/freedom-net/2023](https://freedomhouse.org/country/lebanon/freedom-net/2023)
|
||||
24\. Lebanon: Freedom on the Net 2024 Country Report, accessed on February 23, 2025, [https://freedomhouse.org/country/lebanon/freedom-net/2024](https://freedomhouse.org/country/lebanon/freedom-net/2024)
|
||||
25\. Telecome Fact Sheet, accessed on February 23, 2025, [http://data.infopro.com.lb/file/Telecom%20Fact%20Sheet.pdf](http://data.infopro.com.lb/file/Telecom%20Fact%20Sheet.pdf)
|
||||
26\. Mobile coverage \- Lebanon \- Media Landscapes, accessed on February 23, 2025, [https://medialandscapes.org/country/lebanon/telecommunications/mobile-coverage](https://medialandscapes.org/country/lebanon/telecommunications/mobile-coverage)
|
||||
27\. Libya: Freedom on the Net 2024 Country Report, accessed on February 23, 2025, [https://freedomhouse.org/country/libya/freedom-net/2024](https://freedomhouse.org/country/libya/freedom-net/2024)
|
||||
28\. Djibouti | Digital Watch Observatory, accessed on February 23, 2025, [https://dig.watch/countries/djibouti](https://dig.watch/countries/djibouti)
|
||||
29\. Internet in Algeria \- Wikipedia, accessed on February 23, 2025, [https://en.wikipedia.org/wiki/Internet\_in\_Algeria](https://en.wikipedia.org/wiki/Internet_in_Algeria)
|
||||
30\. blog.cloudflare.com, accessed on February 23, 2025, [https://blog.cloudflare.com/syria-iraq-algeria-exam-internet-shutdown/\#:\~:text=As%20we%20noted%20in%20blog,both%20before%20and%20during%20tests.](https://blog.cloudflare.com/syria-iraq-algeria-exam-internet-shutdown/#:~:text=As%20we%20noted%20in%20blog,both%20before%20and%20during%20tests.)
|
||||
31\. Bahrain: Freedom on the Net 2023 Country Report, accessed on February 23, 2025, [https://freedomhouse.org/country/bahrain/freedom-net/2023](https://freedomhouse.org/country/bahrain/freedom-net/2023)
|
||||
32\. Bahrain's Internet Censorship And Its Impact On Freedom Of Expression \- ECDHR, accessed on February 23, 2025, [https://www.ecdhr.org/bahrains-internet-censorship-and-its-impact-on-freedom-of-expression/](https://www.ecdhr.org/bahrains-internet-censorship-and-its-impact-on-freedom-of-expression/)
|
||||
33\. The education system in Algeria \- NET24, accessed on February 23, 2025, [https://newedutrend.com/the-education-system-in-algeria/](https://newedutrend.com/the-education-system-in-algeria/)
|
||||
34\. Education in Algeria \- statistics & facts | Statista, accessed on February 23, 2025, [https://www.statista.com/topics/9699/education-in-algeria/](https://www.statista.com/topics/9699/education-in-algeria/)
|
||||
35\. Education and Training \- Bahrain.bh, accessed on February 23, 2025, [https://www.bahrain.bh/wps/portal/en/BNP/HereInBahrain/EducationAndTraining](https://www.bahrain.bh/wps/portal/en/BNP/HereInBahrain/EducationAndTraining)
|
||||
36\. www.bahrain.bh, accessed on February 23, 2025, [https://www.bahrain.bh/wps/portal/en/BNP/HereInBahrain/EducationAndTraining\#:\~:text=Basic%20Education%20is%20compulsory%20for,can%20then%20begin%20tertiary%20education.](https://www.bahrain.bh/wps/portal/en/BNP/HereInBahrain/EducationAndTraining#:~:text=Basic%20Education%20is%20compulsory%20for,can%20then%20begin%20tertiary%20education.)
|
||||
37\. Desktop vs Mobile Market Share Algeria \- StatCounter Global Stats, accessed on February 23, 2025, [https://gs.statcounter.com/platform-market-share/desktop-mobile/algeria](https://gs.statcounter.com/platform-market-share/desktop-mobile/algeria)
|
||||
38\. TRA Updates \- Press Releases | Telecommunications Regulatory Authority, Kingdom of Bahrain, accessed on February 23, 2025, [https://www.tra.org.bh/en/article/99-7-of-individuals-in-bahrain-use-the-internet-bahrain-ranked-4th-globally](https://www.tra.org.bh/en/article/99-7-of-individuals-in-bahrain-use-the-internet-bahrain-ranked-4th-globally)
|
||||
39\. Internet Usage Mobile Vs Desktop– Statistics and Trends \- GO-Globe, accessed on February 23, 2025, [https://www.go-globe.com/mobile-vs-desktop-internet-usage-statistics/](https://www.go-globe.com/mobile-vs-desktop-internet-usage-statistics/)
|
||||
40\. Top 5 Communication Apps Performance in Algeria, Q1 2024, accessed on February 23, 2025, [https://sensortower.com/blog/2024-q1-unified-top-5-communication%20apps-units-dz-6070aae1241bc16eb81f5bab](https://sensortower.com/blog/2024-q1-unified-top-5-communication%20apps-units-dz-6070aae1241bc16eb81f5bab)
|
||||
41\. www.google.com, accessed on February 23, 2025, [https://www.google.com/search?q=popular+apps+in+Algeria](https://www.google.com/search?q=popular+apps+in+Algeria)
|
||||
42\. Algeria Online Shopping Apps \- Apps on Google Play, accessed on February 23, 2025, [https://play.google.com/store/apps/details?id=com.online.algeria.shopping.algerieonlineshop](https://play.google.com/store/apps/details?id=com.online.algeria.shopping.algerieonlineshop)
|
||||
43\. www.google.com, accessed on February 23, 2025, [https://www.google.com/search?q=popular+apps+in+Bahrain](https://www.google.com/search?q=popular+apps+in+Bahrain)
|
||||
44\. Telecommunication in Libya \- Worlddata.info, accessed on February 23, 2025, [https://www.worlddata.info/africa/libya/telecommunication.php](https://www.worlddata.info/africa/libya/telecommunication.php)
|
||||
45\. Desktop vs Mobile vs Market Share Bahrain | Statcounter Global Stats, accessed on February 23, 2025, [https://gs.statcounter.com/platform-market-share/desktop-mobile-/bahrain](https://gs.statcounter.com/platform-market-share/desktop-mobile-/bahrain)
|
||||
134
docs/TradeUAE.md
Normal file
134
docs/TradeUAE.md
Normal file
@ -0,0 +1,134 @@
|
||||
# **Selling Second-Hand Machinery from Europe to Arab Countries: A Comprehensive Guide**
|
||||
|
||||
The global market for used machinery is thriving, with businesses increasingly seeking cost-effective alternatives to new equipment. This presents a lucrative opportunity for European sellers to tap into the growing demand in Arab countries. However, navigating the complexities of international trade requires careful planning and execution. This guide provides a comprehensive overview of the essential steps involved in successfully selling second-hand machines from Europe to Arab countries.
|
||||
|
||||
## **Choosing the Right Platform**
|
||||
|
||||
The first step is to identify suitable platforms for selling your machinery. Several online marketplaces cater to international buyers and sellers of used industrial equipment. These platforms have emerged as valuable tools for connecting buyers and sellers across borders, facilitating transactions even without physical inspections of the machinery1. Here are a few prominent options:
|
||||
|
||||
* **GINDUMAC:** This global platform offers a comprehensive suite of services, including best price appraisal, global marketing, direct purchase, logistics, and payment processing. GINDUMAC simplifies the selling process by handling everything from evaluation to pickup1.
|
||||
* **Machinerycash:** This platform focuses on used construction machinery and offers free registration for sellers. Machinerycash connects sellers with a network of international traders, ensuring competitive offers and transparent transactions. The commission on sales is paid by the buyer, not the seller2.
|
||||
* **Euro Machinery:** Specializing in used machinery for the flexible plastic conversion, recycling, and thermoforming industries, Euro Machinery provides free advertising on its online platforms and arranges global transport4.
|
||||
* **eWorldTrade:** This global B2B marketplace connects buyers and sellers across various industries. eWorldTrade offers secure payment methods and facilitates price negotiation5.
|
||||
* **Surplex:** With a strong international network, Surplex offers a full-service solution, including consulting, evaluation, international marketing, and post-sale logistics6.
|
||||
|
||||
When choosing a platform, consider factors such as the platform's specialization, target audience, fees, and the level of support provided.
|
||||
|
||||
## **Logistics and Shipping**
|
||||
|
||||
Shipping heavy machinery internationally involves intricate logistics. Here's a breakdown of the key aspects:
|
||||
|
||||
### **Shipping Methods**
|
||||
|
||||
| Shipping Method | Description | Suitable for | Pros | Cons |
|
||||
| :---- | :---- | :---- | :---- | :---- |
|
||||
| RoRo (Roll-On/Roll-Off) | Machinery is driven or rolled onto the vessel. | Self-propelled machinery (e.g., tractors, excavators) | Cost-effective for drivable machinery, simpler loading/unloading | May not be suitable for all types of machinery, less protection from the elements |
|
||||
| Container Shipping | Machinery is loaded into standard shipping containers. | Smaller machinery, equipment parts | Increased security, protection from damage | Size limitations, may require disassembly |
|
||||
| Breakbulk Shipping | Machinery is loaded directly onto the vessel without containers. | Oversized machinery | Accommodates large and heavy loads | More complex loading/unloading, potentially higher costs |
|
||||
| Lo-Lo (Lift-On/Lift-Off) | Cranes or specialized equipment lift machinery onto the vessel. | Non-drivable machinery, heavy haulers | Versatile, suitable for various cargo types | Requires specialized lifting equipment, potentially higher costs |
|
||||
| Flat-Rack Container | Machinery is secured to a flat rack for stability. | Heavy machinery, awkward shapes | Provides stability during transport, suitable for open-top loading | Less protection from the elements, may require specialized securing |
|
||||
|
||||
The choice of shipping method depends on the type, size, and weight of the machinery, as well as budget and timeline considerations7.
|
||||
|
||||
### **Customs Regulations**
|
||||
|
||||
Navigating customs regulations is crucial for smooth shipping. Here are some key considerations:
|
||||
|
||||
* **Certificate of Origin:** Required by most Arab countries, this document certifies the origin of the goods9.
|
||||
* **Certificate of Conformity:** May be required for specific products, ensuring they meet the destination country's standards9.
|
||||
* **Import Duties and Taxes:** Be aware of the import duties and taxes applicable in the destination country9.
|
||||
* **Labeling and Packaging:** Ensure compliance with local regulations for labeling and packaging10.
|
||||
* **Prohibited Items:** Familiarize yourself with any import restrictions or prohibitions in the destination country9.
|
||||
* **Temporary Import Regulations:** Some countries offer temporary import regulations, allowing machinery to be imported duty-free for a specific period, which can be advantageous if you plan to re-export the machinery after temporary use11.
|
||||
|
||||
It's advisable to consult with a customs broker or freight forwarder to ensure compliance with all regulations.
|
||||
|
||||
### **Shipping Costs**
|
||||
|
||||
Shipping costs vary based on several factors, including:
|
||||
|
||||
* **Size and Weight:** Larger and heavier equipment generally incurs higher shipping costs7.
|
||||
* **Shipping Method:** RoRo, container, or breakbulk shipping will affect the overall cost7.
|
||||
* **Destination Port:** Costs vary depending on the specific port in the Arab country7.
|
||||
* **Customs Duties and Taxes:** Import duties can significantly impact the final cost7.
|
||||
* **Insurance Coverage:** Optional insurance adds to the cost but provides financial protection7.
|
||||
* **Dismantling:** To potentially reduce shipping costs, especially for container shipping, consider dismantling the machinery before transport. This can help save on import duties and allow for more efficient use of container space7.
|
||||
|
||||
Obtain quotes from different shipping companies to compare prices and services.
|
||||
|
||||
### **Tips for Smooth Shipping**
|
||||
|
||||
* **Clean the Machinery:** Ensure the machinery is clean and free of dirt, grease, and grime before shipping8.
|
||||
* **Check for Leaks:** Inspect for any leaks and fix them to prevent damage during transit8.
|
||||
* **Secure Loose Parts:** Tighten bolts and secure any movable parts to prevent damage8.
|
||||
* **Protect Fragile Components:** Provide extra protection for fragile components8.
|
||||
* **Drain Fluids:** Drain any unnecessary fuel or fluids to comply with shipping regulations8.
|
||||
* **Measure and Document:** Accurately measure the equipment's dimensions and take detailed photographs8.
|
||||
* **Choose a Reputable Shipping Company:** Partner with an experienced and reliable shipping company8.
|
||||
|
||||
## **Addressing Potential Problems**
|
||||
|
||||
Selling used machinery internationally can present challenges. Here are some potential problems and how to mitigate them:
|
||||
|
||||
### **Warranty Issues**
|
||||
|
||||
While used machinery typically has limited or no warranty, it's essential to be transparent about the equipment's condition and any known issues12. Clearly communicate the terms of sale and any potential risks to the buyer. Consider offering a limited warranty or guarantee for specific components or a short period13.
|
||||
|
||||
### **Buyer Disputes**
|
||||
|
||||
To minimize buyer disputes, provide detailed information about the machinery, including specifications, maintenance records, and any known defects11. Use clear and concise language in the sales agreement and ensure all terms are mutually understood. Building strong relationships with international customers is crucial. Cultivating trust and rapport can help prevent disputes and foster long-term business partnerships11. Consider using an escrow service to protect both parties during the transaction. Escrow services hold funds securely until both parties fulfill their obligations, minimizing the risk of fraud14.
|
||||
|
||||
### **Other Challenges**
|
||||
|
||||
* **Fraud Attempts:** Be vigilant of potential fraud and scams. Verify the buyer's credentials and use secure payment methods11.
|
||||
* **Currency Fluctuations:** Consider the impact of currency fluctuations on the final price and payment terms11.
|
||||
* **Geopolitical Risks:** Be aware of any geopolitical factors that could affect the transaction11.
|
||||
* **Shipping Delays:** Anticipate potential shipping delays and communicate them to the buyer11.
|
||||
* **Damage During Transit:** Ensure adequate insurance coverage to protect against damage during shipping11.
|
||||
|
||||
## **Safety and Electricity Standards**
|
||||
|
||||
Arab countries have specific safety and electricity standards for machinery. Ensure your equipment complies with these standards to avoid complications and ensure the safety of the end-user15. This includes adhering to electrical requirements, such as voltage (220V/240V, 50Hz) and plug types (C, D, G), which are common in the region15.
|
||||
|
||||
* **SASO (Saudi Standards, Metrology, and Quality Organization):** In Saudi Arabia, machinery must comply with SASO requirements for safety, reliability, and electromagnetic compatibility15.
|
||||
* **GSO (GCC Standardization Organization):** The GSO issues unified standards for Gulf Cooperation Council (GCC) countries, including those related to hazardous areas15.
|
||||
* **International Standards:** Many Arab countries follow international standards like NFPA, IEC, and ATEX, often with regional adaptations15.
|
||||
* **Field Evaluation:** For machinery that has undergone final assembly or modification in the United States, the "Field evaluation" option may be relevant. This involves on-site inspection of the machinery in the US to ensure compliance with safety requirements16.
|
||||
|
||||
Heavy haul trucking, which often involves transporting large machinery, requires extensive experience and expertise to ensure safety and compliance with regulations17. Research the specific safety and electricity standards applicable in the destination country and ensure your machinery meets those requirements.
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
Selling second-hand machinery from Europe to Arab countries offers significant opportunities for businesses seeking to expand their market reach. However, this endeavor requires careful planning and execution to navigate the complexities of international trade. By understanding the nuances of choosing the right platform, managing logistics and shipping, addressing potential problems, and complying with safety and electricity standards, you can increase your chances of success.
|
||||
|
||||
Here's a concise checklist for sellers:
|
||||
|
||||
* **Select a suitable online platform.**
|
||||
* **Determine the appropriate shipping method.**
|
||||
* **Comply with customs regulations.**
|
||||
* **Obtain competitive shipping quotes.**
|
||||
* **Address potential warranty issues and buyer disputes.**
|
||||
* **Ensure compliance with safety and electricity standards.**
|
||||
* **Build strong relationships with international customers.**
|
||||
|
||||
By following these steps and maintaining clear communication throughout the process, you can confidently venture into the Arab market and establish fruitful business relationships.
|
||||
|
||||
#### **Works cited**
|
||||
|
||||
1\. Sell Used Machinery Online Worldwide | Selling Machines \- gindumac, accessed on February 23, 2025, [https://www.gindumac.com/selling-process](https://www.gindumac.com/selling-process)
|
||||
2\. Machinerycash® : Value and Sell Used Construction Machinery, accessed on February 23, 2025, [https://www.machinerycash.com/](https://www.machinerycash.com/)
|
||||
3\. Transforming the Used Machinery Market: GINDUMAC Co-CEOs Share Insights About Their Current Journey \- YouTube, accessed on February 23, 2025, [https://www.youtube.com/watch?v=\_pKHStai-4g](https://www.youtube.com/watch?v=_pKHStai-4g)
|
||||
4\. Sell used machines \- Euro Machinery, accessed on February 23, 2025, [https://www.euro-machinery.com/sell-used-machines/](https://www.euro-machinery.com/sell-used-machines/)
|
||||
5\. Where To Sell Used Industrial Equipment \- Top 10 Platforms 2024 \- eWorldTrade, accessed on February 23, 2025, [https://www.eworldtrade.com/blog/where-to-sell-used-industrial-equipment/](https://www.eworldtrade.com/blog/where-to-sell-used-industrial-equipment/)
|
||||
6\. Purchase and auction off of used machinery for you \- Surplex, accessed on February 23, 2025, [https://www.surplex.com/en/sell-with-surplex](https://www.surplex.com/en/sell-with-surplex)
|
||||
7\. Heavy Machinery Pricing & Shipping to the Middle East \- Ship Overseas, accessed on February 23, 2025, [https://www.shipoverseas.com/ship-heavy-machinery-to-middle-east/](https://www.shipoverseas.com/ship-heavy-machinery-to-middle-east/)
|
||||
8\. Shipping Heavy Equipment Overseas: The Process & What You Need to Know \- CFR Classic, accessed on February 23, 2025, [https://cfrclassic.com/international-car-shipping/shipping-heavy-equipment-overseas-the-process-what-you-need-to-know/](https://cfrclassic.com/international-car-shipping/shipping-heavy-equipment-overseas-the-process-what-you-need-to-know/)
|
||||
9\. Regulations and Rules for Shipping to the Middle East \- International Forwarding Association Blog, accessed on February 23, 2025, [https://ifa-forwarding.net/blog/international-freight-forwarding/regulations-and-rules-for-shipping-to-the-middle-east/](https://ifa-forwarding.net/blog/international-freight-forwarding/regulations-and-rules-for-shipping-to-the-middle-east/)
|
||||
10\. How to export goods to the Middle East \- Logisber, accessed on February 23, 2025, [https://logisber.com/en/blog/how-to-export-goods-to-the-middle-east](https://logisber.com/en/blog/how-to-export-goods-to-the-middle-east)
|
||||
11\. 9 Tips for Successfully Selling Used Equipment Overseas, accessed on February 23, 2025, [https://www.farm-equipment.com/articles/18808-tips-for-successfully-selling-used-equipment-overseas](https://www.farm-equipment.com/articles/18808-tips-for-successfully-selling-used-equipment-overseas)
|
||||
12\. Global Warranty Management: Do You Have a Process in Place? \- Shipping Solutions, accessed on February 23, 2025, [https://www.shippingsolutions.com/blog/global-warranty-management-do-you-have-a-process-in-place](https://www.shippingsolutions.com/blog/global-warranty-management-do-you-have-a-process-in-place)
|
||||
13\. Can You Claim Breach of Warranty for Faulty Equipment? \- Ayala Law PA, accessed on February 23, 2025, [https://www.lawayala.com/breach-of-warranty-for-faulty-equipment/](https://www.lawayala.com/breach-of-warranty-for-faulty-equipment/)
|
||||
14\. Avoiding Scams: How to Buy & Sell Equipment Safely \- Surplus Record, accessed on February 23, 2025, [https://surplusrecord.com/news/buy-sell-equipment-safely/](https://surplusrecord.com/news/buy-sell-equipment-safely/)
|
||||
15\. EVCS Standards in the Middle East, accessed on February 23, 2025, [https://acoustechno.com/evcs-standards-in-the-middle-east/](https://acoustechno.com/evcs-standards-in-the-middle-east/)
|
||||
16\. Exporting machinery, the rules outside the European Union \- Octagona, accessed on February 23, 2025, [https://octagona.com/en/export-machinery/](https://octagona.com/en/export-machinery/)
|
||||
17\. What is heavy-haul trucking? \- Motive, accessed on February 23, 2025, [https://gomotive.com/blog/what-is-heavy-haul-trucking/](https://gomotive.com/blog/what-is-heavy-haul-trucking/)
|
||||
187
docs/WebsiteDesignGuide-RTL.md
Normal file
187
docs/WebsiteDesignGuide-RTL.md
Normal file
@ -0,0 +1,187 @@
|
||||
# **Website Design in Arab Countries: A Comprehensive Guide**
|
||||
|
||||
This guide explores the key factors to consider when designing websites for Arab audiences. It delves into the cultural nuances, user perceptions, design standards, and expectations that shape the online experience in this diverse region. By understanding these elements, designers can create websites that are not only visually appealing and functional but also culturally sensitive and effective in engaging Arab users.
|
||||
|
||||
## **Cultural Aspects of Website Design**
|
||||
|
||||
Arab culture is deeply rooted in tradition, with a strong emphasis on family, community, and religious values. These values influence various aspects of life, including online behavior and preferences. When designing websites for Arab audiences, it's crucial to consider these cultural nuances to create a user experience that resonates with their values and expectations1. Respecting local customs and values is paramount to building trust with Arab audiences2. This includes considering the following factors:
|
||||
|
||||
### **Language and its Impact on Design**
|
||||
|
||||
Arabic is written and read from right to left (RTL), which has significant implications for website design. Websites targeting Arab users must adopt an RTL layout, mirroring the natural reading flow of the language. This includes repositioning navigation menus, logos, and other design elements to the right side of the page1.
|
||||
|
||||
Furthermore, Arabic script presents unique typographic challenges. Fewer fonts are available compared to Latin-based scripts due to the complexity of Arabic letterforms. Designers must carefully select fonts that are legible and aesthetically pleasing in Arabic, ensuring consistency across different devices and platforms1. It's also important to consider the "illusion of continuous movement" in Arabic typography, as this is a crucial aspect of Arabic type design that ensures smooth reading3.
|
||||
|
||||
When translating content into Arabic, it's essential to tailor the language to specific audience segments. For instance, the tone and style used for business professionals will differ from that used for younger audiences4.
|
||||
|
||||
### **Cultural Factors**
|
||||
|
||||
Beyond language, several cultural factors influence website design in Arab countries:
|
||||
|
||||
* **Respect for Tradition:** Websites should reflect the values of modesty, respect, and hospitality that are central to Arab culture. This can be achieved through the use of appropriate imagery, language, and tone5.
|
||||
* **Emphasis on Community:** Arab users often value social interaction and community engagement. Websites can cater to this preference by incorporating features that facilitate communication and connection, such as forums, social media integration, and user-generated content sections2.
|
||||
* **Religious Considerations:** Islam is the dominant religion in the Arab world, and its principles influence online behavior and expectations. Websites should avoid any content that may be considered offensive or disrespectful to Islamic values. This includes imagery, language, and themes that contradict religious beliefs5. For example, images depicting scantily clad individuals or promoting alcohol consumption should be avoided4.
|
||||
* **Color Symbolism:** Colors hold cultural and religious significance in the Arab world. Green, for instance, is associated with Islam and symbolizes peace and prosperity. Darker colors like black or deep blue often represent professionalism or luxury5. It's worth noting that blue and green are dominant colors in many Arabic university websites4.
|
||||
* **Minimalism:** Saudi users, in particular, prefer minimalist website designs with clear and straightforward navigation. They tend to favor websites that avoid excessive animations or ads, opting for simple dropdown menus or tabbed content to access information efficiently5.
|
||||
|
||||
### **Mobile Optimization**
|
||||
|
||||
Mobile penetration rates in the Arab world are among the highest globally. Arab users rely heavily on smartphones for internet access, making mobile optimization a critical factor in website design1. Websites should be responsive and provide a seamless user experience across different devices, with fast loading times and easy navigation on smaller screens5.
|
||||
|
||||
## **User Perception and Preferences**
|
||||
|
||||
Understanding user perception and preferences is crucial for creating websites that are both usable and engaging. Arab users have distinct online behaviors and expectations shaped by cultural factors, technological advancements, and social norms.
|
||||
|
||||
### **Arabic UX**
|
||||
|
||||
While the principles of user-centered design remain universal, applying them to Arab audiences requires specific considerations. Arab users may interact with digital products differently due to varying levels of technological exposure, cultural influences, and social norms6. User experience (UX) can play a crucial role in establishing positive and lasting relationships with users in the Arab world7.
|
||||
|
||||
### **Mental Models**
|
||||
|
||||
Mental models refer to users' internal understanding of how things work. Arab users may have different mental models compared to Western audiences, influencing their expectations and interactions with websites. Designers should consider these differences when creating navigation structures, information architecture, and interactive elements6.
|
||||
|
||||
### **Cultural Differences**
|
||||
|
||||
Cultural values and social norms play a significant role in shaping user preferences. For example, Arab users may have a higher preference for personalized experiences, visual communication, and social interaction on websites6. Research indicates that Saudi users, in particular, place importance on consistency in navigation schemes, messaging, and text formatting8.
|
||||
|
||||
### **User Research**
|
||||
|
||||
Conducting user research with Arab audiences is essential for understanding their specific needs and preferences. This can involve usability testing, surveys, and interviews to gather insights into their online behavior, pain points, and expectations6.
|
||||
|
||||
## **Website Design Standards and Accessibility Guidelines**
|
||||
|
||||
Accessibility is a crucial aspect of website design, ensuring that all users, including those with disabilities, can access and interact with online content. In Arab countries, accessibility guidelines are often aligned with international standards like the Web Content Accessibility Guidelines (WCAG)9. Web accessibility is not just a moral obligation but also an essential element for creating an inclusive digital environment10.
|
||||
|
||||
### **WCAG Guidelines**
|
||||
|
||||
WCAG provides a comprehensive framework for making web content more accessible. The four core principles of WCAG are:
|
||||
|
||||
* **Perceivable:** Information and user interface components must be presented in ways users can perceive, regardless of their sensory limitations.
|
||||
* **Operable:** Users must be able to operate the interface.
|
||||
* **Understandable:** Users must be able to understand the information and the operation of the user interface.
|
||||
* **Robust:** Content must be robust enough that it can be interpreted reliably by a wide variety of user agents, including assistive technologies11.
|
||||
|
||||
### **Arab-Specific Considerations**
|
||||
|
||||
While WCAG provides a global framework, there are unique cultural and linguistic considerations when designing for accessibility in Arab countries:
|
||||
|
||||
* **Language:** Arabic is the primary language, and all digital content should be available in Arabic, with accessible design considerations such as appropriate text scaling and screen reader compatibility9.
|
||||
* **Cultural Context:** Designs should respect local customs and norms. For instance, certain color schemes or images that are culturally significant should be used thoughtfully, ensuring they do not alienate or misrepresent any group9.
|
||||
|
||||
### **Accessibility in the UAE Design System**
|
||||
|
||||
The United Arab Emirates design system 2.0 prioritizes accessibility and follows WCAG 2.1 guidelines. It provides specific instructions for typography, color systems, imagery, iconography, layout, and spacing to ensure websites are accessible to all users12. This includes guidelines for clear and intuitive menus, as well as large touch targets for mobile apps, which are particularly popular in the Arab world9.
|
||||
|
||||
## **Cultural Expectations and Sensitivities in Website Content**
|
||||
|
||||
Website content should be carefully crafted to align with cultural expectations and sensitivities in Arab countries. This includes language, imagery, and themes that resonate with local values and avoid any potential offense or misinterpretation.
|
||||
|
||||
### **Respectful Imagery**
|
||||
|
||||
Images used on websites should be culturally appropriate and avoid depictions that may be considered offensive or disrespectful in Arab culture. This includes images that are sexually suggestive, depict alcohol consumption, or portray religious figures in a disrespectful manner10. When using stock images, it's crucial to select those that accurately represent the diversity of the Arab population and avoid stereotypical portrayals13.
|
||||
|
||||
### **Sensitive Topics**
|
||||
|
||||
Certain topics require careful consideration when creating website content for Arab audiences. These include:
|
||||
|
||||
* **Religion:** Avoid making negative comments about Islam or religious practices. When discussing religious topics, ensure to approach them with respect and accuracy14.
|
||||
* **Politics:** Be mindful of political sensitivities and avoid controversial topics that may cause offense. It's important to be aware of the political landscape and any potential sensitivities related to specific countries or regions14.
|
||||
* **Gender Roles:** Portray women in a respectful manner that aligns with cultural norms. This includes being mindful of dress codes and social expectations related to gender15.
|
||||
|
||||
### **Local Customs and Traditions**
|
||||
|
||||
Websites can demonstrate cultural sensitivity by incorporating local customs and traditions. This can include acknowledging important holidays, using regional dialects, and referencing cultural icons or values2.
|
||||
|
||||
## **Localization and Translation Best Practices**
|
||||
|
||||
Localization goes beyond simple translation. It involves adapting website content, functionality, and design to suit the cultural and linguistic preferences of a specific target audience16.
|
||||
|
||||
### **Key Considerations**
|
||||
|
||||
When localizing websites for Arab audiences, consider the following:
|
||||
|
||||
* **Accurate Translation:** Ensure that all website content is translated accurately and conveys the intended meaning in Arabic16.
|
||||
* **Cultural Nuances:** Adapt the language, tone, and style to reflect local dialects, cultural sensitivities, and regional preferences16.
|
||||
* **RTL Layout:** Implement a right-to-left layout for all website elements, including text, images, and navigation16. This includes addressing the challenges of bidirectional text in Arabic, where untranslatable phrases or words are written left-to-right17.
|
||||
* **Local Payment Options:** Offer payment methods that are commonly used in the region18.
|
||||
* **Arabic SEO:** Optimize website content and metadata for Arabic search engines18.
|
||||
|
||||
### **Avoiding Machine Translations**
|
||||
|
||||
Machine translations often fail to capture the nuances of the Arabic language and may produce inaccurate or culturally inappropriate content. It's crucial to use professional human translators who are native Arabic speakers and understand the cultural context16.
|
||||
|
||||
## **Arabic Typography and Calligraphy**
|
||||
|
||||
Arabic typography and calligraphy play a significant role in website design, adding a visual appeal and cultural authenticity to the user experience.
|
||||
|
||||
### **Calligraphy Styles**
|
||||
|
||||
Arabic calligraphy has a rich history with various styles, each with unique characteristics.
|
||||
|
||||
| Calligraphy Style | Characteristics | Example Use Cases |
|
||||
| :---- | :---- | :---- |
|
||||
| Kufic | Geometric and angular shapes | Early Qur'anic manuscripts, architectural inscriptions |
|
||||
| Naskh | Cursive and fluid form | Modern Arabic typography, religious texts |
|
||||
| Thuluth | Elongated verticals and broad curves | Monumental inscriptions, mosque decorations |
|
||||
|
||||
### **Modern Applications**
|
||||
|
||||
Arabic calligraphy is finding new applications in contemporary design, including website logos, typography, and graphic elements19.
|
||||
|
||||
### **Digital Challenges**
|
||||
|
||||
Digitizing Arabic calligraphy presents technical challenges, such as maintaining legibility and ensuring proper rendering across different devices and platforms20.
|
||||
|
||||
### **Online Tools**
|
||||
|
||||
Online platforms like Kaleam provide tools for creating and manipulating Arabic calligraphy digitally21.
|
||||
|
||||
## **Examples of Successful Website Designs in Arab Countries**
|
||||
|
||||
Several websites have successfully adapted to the cultural nuances and user preferences of Arab audiences. These websites often incorporate RTL layouts, Arabic language support, culturally relevant imagery, and features that cater to local needs and expectations.
|
||||
|
||||
### **GO-Globe**
|
||||
|
||||
GO-Globe is a web design company in Saudi Arabia that specializes in building culturally aware websites. They prioritize incorporating local culture and Islamic values into their designs, creating user experiences that resonate with Saudi audiences5.
|
||||
|
||||
### **E-commerce Websites**
|
||||
|
||||
Many e-commerce websites have successfully localized their platforms for Arab markets. These include:
|
||||
|
||||
* **Namshi:** A popular online fashion retailer with an Arabic interface and localized content22.
|
||||
* **Jarir:** A leading electronics and bookstore chain with a comprehensive Arabic website22.
|
||||
* **Noon.com:** A major online retailer offering a wide range of products with Arabic language support and localized payment options22.
|
||||
|
||||
### **Awwwards Winning Websites**
|
||||
|
||||
Awwwards recognizes and celebrates exceptional web design. Several websites from the United Arab Emirates have won Awwwards, showcasing innovative designs that often incorporate Arabic typography and cultural elements23.
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
Designing websites for Arab audiences requires a deep understanding of cultural nuances, user perceptions, and design standards. By considering the factors outlined in this guide, designers can create online experiences that are both effective and culturally sensitive. This includes adopting RTL layouts, using appropriate language and imagery, prioritizing mobile optimization, and adhering to accessibility guidelines. The cultural aspects, user perceptions, and design standards discussed throughout this guide are interconnected and must be considered holistically to create a successful website for Arab audiences. By embracing these principles, businesses can effectively engage Arab users and achieve success in this dynamic online market.
|
||||
|
||||
#### **Works cited**
|
||||
|
||||
1\. Elements of Arabic Website Design \- ExtraDigital, accessed on February 23, 2025, [https://www.extradigital.co.uk/articles/design/elements-arabic-web-design/](https://www.extradigital.co.uk/articles/design/elements-arabic-web-design/)
|
||||
2\. Cultural Sensitivity in Middle Eastern Digital Campaigns: Strategies for Respectful Engagement | ProfileTree, accessed on February 23, 2025, [https://profiletree.com/sensitivity-in-middle-eastern-digital-campaigns/](https://profiletree.com/sensitivity-in-middle-eastern-digital-campaigns/)
|
||||
3\. Towards Usability Guidelines for the Design of Effective Arabic Websites \- The Science and Information (SAI) Organization, accessed on February 23, 2025, [https://thesai.org/Downloads/Volume10No4/Paper\_72-Towards\_Usability\_Guidelines\_for\_the\_Design\_of\_Effective\_Arabic\_Websites.pdf](https://thesai.org/Downloads/Volume10No4/Paper_72-Towards_Usability_Guidelines_for_the_Design_of_Effective_Arabic_Websites.pdf)
|
||||
4\. Designing UAE Culture Specific Websites: How To Move? \- GO-Globe, accessed on February 23, 2025, [https://www.go-globe.com/designing-uae-culture-specific-websites-how-to-move/](https://www.go-globe.com/designing-uae-culture-specific-websites-how-to-move/)
|
||||
5\. Impact of Local Culture on Islamic Website Design Trends in Saudi Arabia \- GO-Globe, accessed on February 23, 2025, [https://www.go-globe.com/culture-web-design-trends-in-saudi-arabia/](https://www.go-globe.com/culture-web-design-trends-in-saudi-arabia/)
|
||||
6\. Designing An Arabic User Experience \- Ebook by UXBERT Labs, accessed on February 23, 2025, [https://uxbert.com/wp-content/uploads/2017/05/Designing-An-Arabic-User-Experience-Ebook-by-UXBERT-Labs-min.pdf](https://uxbert.com/wp-content/uploads/2017/05/Designing-An-Arabic-User-Experience-Ebook-by-UXBERT-Labs-min.pdf)
|
||||
7\. UX: adapting and designing interfaces for the Middle East, accessed on February 23, 2025, [https://blog.ferpection.com/en/ux-in-the-middle-east](https://blog.ferpection.com/en/ux-in-the-middle-east)
|
||||
8\. (PDF) Preferences of Saudi Users on Arabic Website Usability \- ResearchGate, accessed on February 23, 2025, [https://www.researchgate.net/publication/312652239\_PREFERENCES\_OF\_SAUDI\_USERS\_ON\_ARABIC\_WEBSITE\_USABILITY](https://www.researchgate.net/publication/312652239_PREFERENCES_OF_SAUDI_USERS_ON_ARABIC_WEBSITE_USABILITY)
|
||||
9\. A Beginner's Guide to Designing for Accessibility in Saudi Arabia | by Samer S Tallauze, accessed on February 23, 2025, [https://medium.com/@SamerTallauze/a-beginners-guide-to-designing-for-accessibility-in-saudi-arabia-c0a760712150](https://medium.com/@SamerTallauze/a-beginners-guide-to-designing-for-accessibility-in-saudi-arabia-c0a760712150)
|
||||
10\. Web Accessibility for Inclusive Muslim Websites | 2024 \- ummah design, accessed on February 23, 2025, [https://ummahdesign.com/web-accessibility-ensuring-your-muslim-focused-website-is-inclusive-for-all/](https://ummahdesign.com/web-accessibility-ensuring-your-muslim-focused-website-is-inclusive-for-all/)
|
||||
11\. National e-accessibility policy template for the Arab region, accessed on February 23, 2025, [https://e-inclusion.unescwa.org/sites/default/files/resources/national-e-accessibility-policy-template-arab-region-english.pdf](https://e-inclusion.unescwa.org/sites/default/files/resources/national-e-accessibility-policy-template-arab-region-english.pdf)
|
||||
12\. Accessibility guideline | UAE design system 2.0, accessed on February 23, 2025, [https://designsystem.gov.ae/guidelines/accessibility](https://designsystem.gov.ae/guidelines/accessibility)
|
||||
13\. 10 Key Strategies for Arabic Websites and Engagement | iSpectra, accessed on February 23, 2025, [https://www.ispectra.co/blog/10-key-strategies-arabic-websites-and-engagement](https://www.ispectra.co/blog/10-key-strategies-arabic-websites-and-engagement)
|
||||
14\. Developing Cultural Sensitivity in Arab Communities \- Shababeek Center, accessed on February 23, 2025, [https://shababeekcenter.com/jordan/navigating-cultural-nuances-and-honoring-arabs/](https://shababeekcenter.com/jordan/navigating-cultural-nuances-and-honoring-arabs/)
|
||||
15\. Cultural Sensitivity in Digital Marketing: 7 Things To Consider When Targeting the Middle East | Wick., accessed on February 23, 2025, [https://www.thewickfirm.com/blog/cultural-sensitivity-in-digital-marketing-7-things-to-consider-when-targeting-the-middle-east](https://www.thewickfirm.com/blog/cultural-sensitivity-in-digital-marketing-7-things-to-consider-when-targeting-the-middle-east)
|
||||
16\. Translating for Arabic Markets: 4 Important Things to Know \- Localize Articles, accessed on February 23, 2025, [https://localizejs.com/articles/localizing-for-the-arabic-market-things-to-know](https://localizejs.com/articles/localizing-for-the-arabic-market-things-to-know)
|
||||
17\. Effective Arabic website translation in 3 steps \- Smartling, accessed on February 23, 2025, [https://www.smartling.com/blog/arabic-website-translation](https://www.smartling.com/blog/arabic-website-translation)
|
||||
18\. How Important is Arabic Website Localization for a Business Website? \- GO-Globe, accessed on February 23, 2025, [https://www.go-globe.com/website-localization-arabic-website/](https://www.go-globe.com/website-localization-arabic-website/)
|
||||
19\. Arabic Calligraphy in Design \- Nihad Nadam, accessed on February 23, 2025, [https://nihad.me/arabic-calligraphy-in-design/](https://nihad.me/arabic-calligraphy-in-design/)
|
||||
20\. Basic Principles of Arabic Type Design | Communication Arts, accessed on February 23, 2025, [https://www.commarts.com/features/basic-principles-of-arabic-type-design](https://www.commarts.com/features/basic-principles-of-arabic-type-design)
|
||||
21\. Kaleam \- create beautiful arabic calligraphy online, accessed on February 23, 2025, [https://kaleam.com/index-en.html](https://kaleam.com/index-en.html)
|
||||
22\. Top 25 Middle East Ecommerce Websites in Arabic, accessed on February 23, 2025, [https://istizada.com/blog/top-25-middle-east-ecommerce-websites-in-arabic/](https://istizada.com/blog/top-25-middle-east-ecommerce-websites-in-arabic/)
|
||||
23\. United Arab Emirates websites \- Awwwards, accessed on February 23, 2025, [https://www.awwwards.com/websites/United%20Arab%20Emirates/](https://www.awwwards.com/websites/United%20Arab%20Emirates/)
|
||||
34
docs/envs.md
Normal file
34
docs/envs.md
Normal file
@ -0,0 +1,34 @@
|
||||
Below is a conceptual **Markdown table** that contrasts different ways to use React and Astro together—covering client-side, server-side rendering (SSR), and embedding components. It also includes notes about **Vite configurations** and **plugins** you might need.
|
||||
|
||||
---
|
||||
|
||||
| **Environment / Approach** | **Description** | **SSR vs. Client** | **Integration Steps** | **Vite / Plugin Considerations** |
|
||||
|-----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **1. Astro + React (Client-side Only)** | Using React components within your Astro project, rendered exclusively on the client. | - *Client:* The browser executes the JavaScript.<br/>- *SSR:* The components are not rendered by the server; only HTML placeholders or minimal stubs are served. | 1. Install `@astrojs/react`. <br/>2. Import and register your React component in your `.astro` file. <br/>3. Use the `client:load`, `client:idle`, or `client:visible` directives for client hydration. | - **Vite config:** Astro automatically configures Vite for React by default when `@astrojs/react` is installed. <br/>- **Plugins:** The official `@astrojs/react` plugin handles bundling, transpilation, and client hydration. |
|
||||
| **2. Astro + React (SSR)** | Using Astro’s server-side rendering (experimental or through a hosting platform that supports SSR) to **render React components on the server** before sending HTML to the client. | - *Client:* The component can then hydrate on the client if needed (otherwise it remains static, but pre-rendered from SSR). <br/>- *SSR:* React components are rendered by Astro’s SSR. | 1. Configure SSR in your `astro.config.mjs` or with your hosting provider’s settings. <br/>2. Make sure `@astrojs/react` is installed and your SSR environment is set up. <br/>3. Use SSR-friendly APIs (avoid browser-only globals in server code). | - **Vite config:** May need additional SSR config if your framework or deployment environment has special needs. <br/>- **Plugins:** Typically the same `@astrojs/react` plugin, but ensure any external SSR plugins are compatible (e.g. for styling or data-fetching). |
|
||||
| **3. React Inside an Astro Component** | An `.astro` file that includes smaller React components inline, e.g., embedding `<ReactComponent />` inside Astro’s templating syntax. This is the most common scenario in Astro projects. | - *Client:* If the component is client-hydrated, it becomes interactive in the browser. <br/>- *SSR:* If Astro is set for SSR, the component is pre-rendered on the server. | 1. Create a `.astro` component. <br/>2. Import React components, e.g. `import MyButton from '../components/MyButton.jsx'`. <br/>3. Embed `<MyButton client:load />` or use another hydration directive. | - **Vite config:** Generally works out-of-the-box with `@astrojs/react`. <br/>- **Plugins:** Additional React-friendly Vite or Babel plugins can be added in your `astro.config.mjs` or in a separate config if needed. |
|
||||
| **4. Astro Component inside React** | Less common scenario. You’d have a React application and want to render an Astro component within that React tree. Typically, you’d compile Astro into static output or use the Astro server output as an API (SSR). | - *Client:* If you bundle Astro components as static output, there’s no direct hydration for “Astro markup” inside React unless you embed them as iframes or separate routes. <br/>- *SSR:* Potentially possible, but complicated. | 1. **Static approach**: Build Astro pages/components to static HTML and embed via `<iframe>` or fetch the rendered content. <br/>2. **SSR approach**: Build an Astro SSR server and fetch rendered HTML from it. <br/>3. Integrating “live” Astro code directly in React bundlers is not straightforward. | - **Vite config:** React’s Vite config typically doesn’t parse `.astro` files. You may need custom integration or a multi-app approach. <br/>- **Plugins:** No out-of-the-box solution for Astro inside React. Consider separate builds or direct HTML consumption. |
|
||||
| **5. Full React App with some Astro Pages** | A primarily React-based project, but certain pages or partial content is rendered by Astro (e.g., for static site generation or for speed). | - *Client:* React runs as usual. If Astro pages are served as static HTML, no direct hydration from Astro’s side unless specifically set up. <br/>- *SSR:* Varies based on hosting approach. | 1. Create a separate Astro project (or folder) for static pages. <br/>2. Configure your build/deployment to serve Astro pages separately. <br/>3. Link between React app and Astro-generated routes, passing data if necessary. | - **Vite config:** Possibly need separate Vite configs for React and Astro or a shared monorepo approach. <br/>- **Plugins:** Use the standard React Vite plugin for the main app, and `@astrojs/react` (and others) for the Astro portion. |
|
||||
|
||||
---
|
||||
|
||||
### Additional Notes
|
||||
|
||||
- **SSR (Server-Side Rendering) vs. Client-Side**:
|
||||
- Astro’s default behavior is **static site generation** (SSG). If you need SSR, you must enable Astro’s SSR mode (experimental or through a supported deployment target like Vercel, Netlify, etc.).
|
||||
- React can be mixed in for **partial hydration**, letting you deliver mostly static pages but with interactive “islands” of React.
|
||||
|
||||
- **Vite Configuration**:
|
||||
- When using React inside Astro, the official `@astrojs/react` integration handles most details.
|
||||
- If you have advanced needs (e.g., custom Babel transforms, PostCSS, or TypeScript paths), you can tweak Astro’s underlying Vite config in `astro.config.mjs`.
|
||||
- For embedding Astro in a larger React project, or vice versa, consider **mono-repo** patterns or separate builds. Typically, each framework is built with its own config or pipeline.
|
||||
|
||||
- **Astro Components in a React Context**:
|
||||
- Rendering `.astro` files “directly” inside a React build pipeline is not straightforward because React expects JavaScript/JSX while Astro components require the Astro compiler.
|
||||
- Typically, it’s more straightforward to create **static exports** (HTML) from Astro and consume them in a React app or route to them.
|
||||
|
||||
- **Hydration Directives**:
|
||||
- Astro’s special directives like `client:load`, `client:idle`, and `client:visible` determine when/how a React component hydrates on the client.
|
||||
- For SSR, if you want a component to remain static, do not add a client directive, and it will render in pure HTML.
|
||||
|
||||
Use the table and notes above to guide how you structure your Astro + React workflow, deciding whether your components render on the client, server, or a bit of both.
|
||||
325
docs/google-merchant.md
Normal file
325
docs/google-merchant.md
Normal file
@ -0,0 +1,325 @@
|
||||
# **Extracting Data from Astro Collections for Google Merchant Center**
|
||||
|
||||
This guide provides a comprehensive overview of how to extract data from a custom Astro collection and format it for a Google Merchant Center feed. It includes examples and configuration details to help you get started.
|
||||
|
||||
## **Understanding Google Merchant Center Data Feeds**
|
||||
|
||||
A Google Merchant Center data feed is a file that contains information about the products you want to sell online. This information is used to create Shopping ads and free product listings on Google.
|
||||
|
||||
The data feed must be in a specific format, and it must contain certain attributes for each product 1. These attributes include:
|
||||
|
||||
| Attribute | Description | Required |
|
||||
| :---- | :---- | :---- |
|
||||
| id | A unique identifier for the product | Yes |
|
||||
| title | The title of the product | Yes |
|
||||
| description | A description of the product | Yes |
|
||||
| link | The URL of the product page on your website | Yes |
|
||||
| image\_link | The URL of an image of the product | Yes |
|
||||
| additional\_image\_link | The URL of an additional image of the product 2 | No |
|
||||
| availability | The availability of the product (in stock, out of stock, preorder) | Yes |
|
||||
| price | The price of the product | Yes |
|
||||
| brand | The brand of the product | Yes |
|
||||
| gtin | The Global Trade Item Number (GTIN) of the product, such as a UPC or EAN | Yes (if applicable) |
|
||||
| identifier\_exists | Specifies whether identifiers exist for products without GTINs 3 | Yes (if applicable) |
|
||||
| shipping | Shipping information for the product 4 | Yes |
|
||||
| tax | Tax information for the product 4 | Yes |
|
||||
|
||||
Depending on the category that your products fall into, some other attributes may also be required 5. For example, if you sell apparel, you will be required to provide size, gender, color, and age group.
|
||||
|
||||
You can create a data feed in several different formats, including:
|
||||
|
||||
* **Text (TXT)**
|
||||
* **Tab Separated Values (TSV)**
|
||||
* **Extensible Markup Language (XML)**
|
||||
|
||||
While TXT and TSV files are suitable for smaller catalogs, Google recommends using XML for larger catalogs or more complex data, as it offers better structure and scalability 6.
|
||||
|
||||
## **Extracting Data from an Astro Collection**
|
||||
|
||||
Astro is a static site generator that allows you to create websites using a variety of different technologies. One of the features of Astro is content collections, which allow you to store and query data in a structured way 7. This makes Astro an excellent choice for managing product data and generating Google Merchant Center feeds. Astro offers several advantages in this context:
|
||||
|
||||
* **Flexibility:** Astro supports various data formats, including Markdown, MDX, JSON, YAML, and TOML, allowing you to store product information in a way that suits your needs 8.
|
||||
* **Type Safety:** Astro's Content Collections API provides type safety, ensuring that the data you extract from your collection conforms to a predefined schema. This helps prevent errors and ensures data consistency 7.
|
||||
* **Ease of Data Fetching:** Astro provides built-in functions like getCollection and getEntry to easily fetch data from your collections, simplifying the process of creating a data feed 8.
|
||||
|
||||
To extract data from an Astro collection, you can use the getCollection function. This function takes the name of the collection as an argument and returns an array of entries 8. Each entry contains the data for a single item in the collection.
|
||||
|
||||
For example, if you have a collection called products, you can extract the data for all products using the following code:
|
||||
|
||||
JavaScript
|
||||
|
||||
`import { getCollection } from 'astro:content';`
|
||||
|
||||
`const products = await getCollection('products');`
|
||||
|
||||
This will return an array of product entries. Each entry will have the following properties:
|
||||
|
||||
* **id:** The ID of the product
|
||||
* **slug:** The slug of the product
|
||||
* **data:** The frontmatter data for the product
|
||||
* **body:** The body content of the product
|
||||
|
||||
You can then use this data to create a Google Merchant Center data feed.
|
||||
|
||||
## **Creating a Google Merchant Center Data Feed from an Astro Collection**
|
||||
|
||||
To create a Google Merchant Center data feed from an Astro collection, you need to:
|
||||
|
||||
1. Extract the data from the collection using the getCollection function.
|
||||
2. Format the data in the required format (TXT, TSV, or XML).
|
||||
3. Include all the required attributes for each product.
|
||||
|
||||
Astro's dynamic routing capabilities can be leveraged to generate product pages from the collection, making it easier to manage product URLs and ensure they are consistent with your data feed 9.
|
||||
|
||||
### **Creating a TXT Data Feed**
|
||||
|
||||
Here is an example of how to create a TXT data feed from an Astro collection:
|
||||
|
||||
JavaScript
|
||||
|
||||
`import { getCollection } from 'astro:content';`
|
||||
|
||||
`const products = await getCollection('products');`
|
||||
|
||||
`const feed = products.map((product) => {`
|
||||
``return `${product.data.id}\t${product.data.title}\t${product.data.description}\t${product.data.link}\t${product.data.image_link}\t${product.data.availability}\t${product.data.price}\t${product.data.brand}\t${product.data.gtin}`;``
|
||||
`}).join('\n');`
|
||||
|
||||
This code will create a TXT file with the following format:
|
||||
|
||||
`id title description link image_link availability price brand gtin`
|
||||
`123 Product 1 This is a product https://example.com/product1 https://example.com/product1.jpg in stock 10.00 Brand A 1234567890123`
|
||||
`456 Product 2 This is another product https://example.com/product2 https://example.com/product2.jpg out of stock 20.00 Brand B 4567890123456`
|
||||
|
||||
### **Creating a TSV Data Feed**
|
||||
|
||||
Creating a TSV data feed is very similar to creating a TXT feed. The only difference is that you use a tab character (\\t) to separate the values in each row.
|
||||
|
||||
JavaScript
|
||||
|
||||
`import { getCollection } from 'astro:content';`
|
||||
|
||||
`const products = await getCollection('products');`
|
||||
|
||||
`const feed = products.map((product) => {`
|
||||
``return `${product.data.id}\t${product.data.title}\t${product.data.description}\t${product.data.link}\t${product.data.image_link}\t${product.data.availability}\t${product.data.price}\t${product.data.brand}\t${product.data.gtin}`;``
|
||||
`}).join('\n');`
|
||||
|
||||
This code will create a TSV file with the same format as the TXT example above.
|
||||
|
||||
### **Creating an XML Data Feed**
|
||||
|
||||
Creating an XML data feed requires a slightly different approach. You need to create an XML document with a specific structure. Here's an example of an XML data feed in Markdown format 10:
|
||||
|
||||
XML
|
||||
|
||||
`<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">`
|
||||
`<channel>`
|
||||
`<title>Example - Google Store</title>`
|
||||
`<link>https://store.google.com</link>`
|
||||
`<description>This is an example of a basic RSS 2.0 document containing a single item</description>`
|
||||
`<item>`
|
||||
`<g:id>TV_123456</g:id>`
|
||||
`<g:title>Chromecast with Google TV</g:title>`
|
||||
`<g:description>Enjoy your favorite entertainment in up to 4K with HDR. Stream from your phone to TV with Chromecast built-in.</g:description>`
|
||||
`<g:google_product_category>Electronics > Televisions > Streaming Media Players</g:google_product_category>`
|
||||
`<g:product_type>Streaming Media Player</g:product_type>`
|
||||
`<g:link>https://store.google.com/product/chromecast_google_tv</g:link>`
|
||||
`<g:image_link>https://images.example.com/TV_123456.png</g:image_link>`
|
||||
`<g:condition>new</g:condition>`
|
||||
`<g:availability>in stock</g:availability>`
|
||||
`<g:price>49.99 USD</g:price>`
|
||||
`<g:brand>Google</g:brand>`
|
||||
`<g:gtin>810028711558</g:gtin>`
|
||||
`<g:mpn>GA01919-US</g:mpn>`
|
||||
`<g:shipping>`
|
||||
`<g:country>US</g:country>`
|
||||
`<g:service>Standard</g:service>`
|
||||
`<g:price>0 USD</g:price>`
|
||||
`</g:shipping>`
|
||||
`<g:tax>`
|
||||
`<g:country>US</g:country>`
|
||||
`<g:rate>8.25</g:rate>`
|
||||
`<g:tax_ship>true</g:tax_ship>`
|
||||
`</g:tax>`
|
||||
`</item>`
|
||||
`</channel>`
|
||||
`</rss>`
|
||||
|
||||
You can generate this XML structure dynamically using the product data extracted from your Astro collection.
|
||||
|
||||
## **Shipping Information**
|
||||
|
||||
To provide accurate shipping costs in Google Merchant Center, you need to include the shipping attribute in your data feed 4. This attribute allows you to specify shipping rates for different destinations and shipping services.
|
||||
|
||||
The shipping attribute has several sub-attributes, including:
|
||||
|
||||
* country: The country to which the shipping rate applies.
|
||||
* service: The name of the shipping service.
|
||||
* price: The shipping cost for the service.
|
||||
|
||||
You can specify multiple shipping rates for a single product by including multiple shipping elements in the data feed.
|
||||
|
||||
## **Tax Information**
|
||||
|
||||
Similarly, the tax attribute is crucial for accurate tax calculations in Google Merchant Center 4. This attribute allows you to specify tax rates for different destinations.
|
||||
|
||||
The tax attribute has several sub-attributes, including:
|
||||
|
||||
* country: The country to which the tax rate applies.
|
||||
* rate: The tax rate as a percentage.
|
||||
* tax\_ship: Whether tax is applied to shipping costs.
|
||||
|
||||
You can specify multiple tax rates for a single product by including multiple tax elements in the data feed.
|
||||
|
||||
## **3D Models**
|
||||
|
||||
Google Merchant Center allows you to include 3D models of your products in your data feed 4. This can enhance the shopping experience for customers by allowing them to view your products from all angles.
|
||||
|
||||
To include a 3D model, you need to provide a URL to a .gltf or .glb file in the additional\_image\_link attribute. The file size should not exceed 15MB, and textures in the file can be up to 2K resolution.
|
||||
|
||||
## **Promotional Feeds**
|
||||
|
||||
Google Merchant Center allows you to create separate promotional feeds to highlight special offers and promotions for your products 3. This can help attract more customers and increase sales.
|
||||
|
||||
A promotional feed contains information about your promotions, such as the promotion ID, product applicability, offer type, and validity dates. You can create a promotional feed in TXT, XML, or Google Sheets format.
|
||||
|
||||
## **Local Inventory Ads**
|
||||
|
||||
Local Inventory Ads allow you to showcase your products to nearby shoppers who are searching on Google 3. These ads show product information, such as availability and price, and direct shoppers to your local store.
|
||||
|
||||
To use Local Inventory Ads, you need to create a separate local product inventory feed that contains information about the products you sell in your physical store.
|
||||
|
||||
## **Product Ratings Feed**
|
||||
|
||||
Product Ratings Feed allows you to display product ratings from customers in your Shopping ads 3. This can help increase customer trust and encourage them to purchase your products.
|
||||
|
||||
To use Product Ratings Feed, you need to submit a feed that contains product ratings and reviews from a trusted source.
|
||||
|
||||
## **Google Manufacturer Center Feed**
|
||||
|
||||
Google Manufacturer Center Feed allows manufacturers to provide detailed and accurate information about their products 3. This information is used to improve the shopping experience for customers and help them make informed purchase decisions.
|
||||
|
||||
Manufacturers can submit information such as product titles, descriptions, images, and GTINs through the Manufacturer Center.
|
||||
|
||||
## **Configuring Your Astro Collection**
|
||||
|
||||
To configure your Astro collection for Google Merchant Center, you need to define a schema for the collection. The schema defines the data type for each attribute in the collection.
|
||||
|
||||
You can define a schema using the z object from the astro:content module. For example, the following code defines a schema for a product collection:
|
||||
|
||||
JavaScript
|
||||
|
||||
`import { defineCollection, z } from 'astro:content';`
|
||||
|
||||
`const products = defineCollection({`
|
||||
`schema: z.object({`
|
||||
`id: z.string(),`
|
||||
`title: z.string(),`
|
||||
`description: z.string(),`
|
||||
`link: z.string().url(),`
|
||||
`image_link: z.string().url(),`
|
||||
`availability: z.enum(['in stock', 'out of stock', 'preorder']),`
|
||||
`price: z.number(),`
|
||||
`brand: z.string(),`
|
||||
`gtin: z.string(),`
|
||||
`}),`
|
||||
`});`
|
||||
|
||||
`export const collections = {`
|
||||
`products,`
|
||||
`};`
|
||||
|
||||
This schema defines the following data types for each attribute:
|
||||
|
||||
* **id:** string
|
||||
* **title:** string
|
||||
* **description:** string
|
||||
* **link:** URL
|
||||
* **image\_link:** URL
|
||||
* **availability:** enum (in stock, out of stock, preorder)
|
||||
* **price:** number
|
||||
* **brand:** string
|
||||
* **gtin:** string
|
||||
|
||||
Schema validation is a crucial aspect of ensuring data accuracy and preventing errors in your Google Merchant Center feed 8. By defining a schema, you can ensure that the data in your collection is in the correct format and meets Google's requirements. This can prevent issues such as invalid data types, missing attributes, or incorrect values, which can lead to your products being disapproved or your feed being rejected.
|
||||
|
||||
## **Complete Example**
|
||||
|
||||
Here is a complete example of how to extract data from an Astro collection and format it for a Google Merchant Center data feed:
|
||||
|
||||
**src/content/config.ts**
|
||||
|
||||
JavaScript
|
||||
|
||||
`import { defineCollection, z } from 'astro:content';`
|
||||
|
||||
`const products = defineCollection({`
|
||||
`schema: z.object({`
|
||||
`id: z.string(),`
|
||||
`title: z.string(),`
|
||||
`description: z.string(),`
|
||||
`link: z.string().url(),`
|
||||
`image_link: z.string().url(),`
|
||||
`availability: z.enum(['in stock', 'out of stock', 'preorder']),`
|
||||
`price: z.number(),`
|
||||
`brand: z.string(),`
|
||||
`gtin: z.string(),`
|
||||
`}),`
|
||||
`});`
|
||||
|
||||
`export const collections = {`
|
||||
`products,`
|
||||
`};`
|
||||
|
||||
**src/pages/feed.astro**
|
||||
|
||||
JavaScript
|
||||
|
||||
`import { getCollection } from 'astro:content';`
|
||||
|
||||
`const products = await getCollection('products');`
|
||||
|
||||
`const feed = products.map((product) => {`
|
||||
``return `${product.data.id}\t${product.data.title}\t${product.data.description}\t${product.data.link}\t${product.data.image_link}\t${product.data.availability}\t${product.data.price}\t${product.data.brand}\t${product.data.gtin}`;``
|
||||
`}).join('\n');`
|
||||
|
||||
`Response.setHeader('Content-Type', 'text/tab-separated-values');`
|
||||
`Response.send(feed);`
|
||||
|
||||
This code will create a TXT data feed that you can upload to Google Merchant Center. You can adapt this code to generate TSV or XML data feeds as needed.
|
||||
|
||||
## **Uploading Your Feed to Google Merchant Center**
|
||||
|
||||
There are several ways to upload your product data feed to Google Merchant Center 11:
|
||||
|
||||
* **Using a URL (HTTP/HTTPS):** This method is suitable for merchants who have created their feed on platforms like Google Sheets or are using a third-party feed management tool. You provide Google with the URL of your feed, and Google will regularly fetch and update the data.
|
||||
* **Using a file (Local drive):** This method involves uploading a file from your local drive. It's best for merchants with very few products or those who rarely need to make adjustments to their feed.
|
||||
* **Via FTP:** This method allows you to upload your feed via FTP. It's suitable for larger feeds or merchants who prefer to manage their feeds using FTP.
|
||||
|
||||
## **Conclusion**
|
||||
|
||||
Extracting data from an Astro collection and formatting it for a Google Merchant Center data feed is a straightforward process. By following the steps in this guide, you can easily create a data feed that meets Google's requirements.
|
||||
|
||||
Astro's flexibility, type safety, and ease of data fetching make it an excellent choice for managing product data and generating Google Merchant Center feeds. By leveraging Astro's features, you can ensure data accuracy, streamline feed creation, and keep your product information up-to-date.
|
||||
|
||||
Accurate data and schema validation are essential for a successful Google Merchant Center experience. By adhering to Google's guidelines and best practices, you can ensure that your products are approved and your ads perform effectively.
|
||||
|
||||
For further information on Astro and Google Merchant Center, you can refer to the following resources:
|
||||
|
||||
* **Astro Documentation:** [https://docs.astro.build/](https://docs.astro.build/)
|
||||
* **Google Merchant Center Help Center:** [https://support.google.com/merchants/](https://support.google.com/merchants/)
|
||||
|
||||
#### **Works cited**
|
||||
|
||||
1\. Local product data specification \- Google Merchant Center Help, accessed on February 25, 2025, [https://support.google.com/merchants/answer/14779112?hl=en](https://support.google.com/merchants/answer/14779112?hl=en)
|
||||
2\. How to Add Products to Google Merchant Center (2025) \- Store Growers, accessed on February 25, 2025, [https://www.storegrowers.com/add-products-to-google-merchant-center/](https://www.storegrowers.com/add-products-to-google-merchant-center/)
|
||||
3\. Understanding Google Shopping Feed Requirements \- AdNabu Blog, accessed on February 25, 2025, [https://blog.adnabu.com/google-shopping-feed/google-shopping-feed-requirements/](https://blog.adnabu.com/google-shopping-feed/google-shopping-feed-requirements/)
|
||||
4\. Product data specification \- Google Merchant Center Help, accessed on February 25, 2025, [https://support.google.com/merchants/answer/7052112?hl=en](https://support.google.com/merchants/answer/7052112?hl=en)
|
||||
5\. How To Create Data Feeds In Google Merchant Center \- Go Fish Digital, accessed on February 25, 2025, [https://gofishdigital.com/blog/how-to-create-data-feeds-in-google-merchant-center/](https://gofishdigital.com/blog/how-to-create-data-feeds-in-google-merchant-center/)
|
||||
6\. How to create a product feed for Google Merchant Center \- Adwservice, accessed on February 25, 2025, [https://adwservice.com.ua/en/how-to-upload-a-feed-to-the-merchant-center](https://adwservice.com.ua/en/how-to-upload-a-feed-to-the-merchant-center)
|
||||
7\. How to use content collection in Astro. \- DEV Community, accessed on February 25, 2025, [https://dev.to/obinnaspeaks/how-to-use-content-collection-in-astro-43j2](https://dev.to/obinnaspeaks/how-to-use-content-collection-in-astro-43j2)
|
||||
8\. Content collections \- Astro Docs, accessed on February 25, 2025, [https://docs.astro.build/en/guides/content-collections/](https://docs.astro.build/en/guides/content-collections/)
|
||||
9\. Optional: Make a content collection \- Astro Docs, accessed on February 25, 2025, [https://docs.astro.build/en/tutorial/6-islands/4/](https://docs.astro.build/en/tutorial/6-islands/4/)
|
||||
10\. Create a product file for Merchant Center \- Google Help, accessed on February 25, 2025, [https://support.google.com/merchants/answer/12631822?hl=en](https://support.google.com/merchants/answer/12631822?hl=en)
|
||||
11\. 3 Methods of Uploading a Product Feed to Google Merchant Center \- DataFeedWatch, accessed on February 25, 2025, [https://www.datafeedwatch.com/blog/upload-product-feed-merchant-center](https://www.datafeedwatch.com/blog/upload-product-feed-merchant-center)
|
||||
192
docs/image-hugging.md
Normal file
192
docs/image-hugging.md
Normal file
@ -0,0 +1,192 @@
|
||||
## Image to Text for Markdown Tables
|
||||
|
||||
This document explores options for converting images to text, focusing on extracting structured information and presenting it as Markdown tables. We prioritize offline and free solutions and consider both commercial and open-source models, particularly from Hugging Face.
|
||||
|
||||
### Brief
|
||||
|
||||
Extracting structured text from images, especially to create Markdown tables with specific information like links, prices, and specifications, is a complex task that goes beyond simple Optical Character Recognition (OCR). While OCR transcribes text, identifying and organizing structured information requires further processing, potentially involving layout analysis, entity recognition, and relationship extraction.
|
||||
|
||||
### Offline / Free Options
|
||||
|
||||
For offline and free solutions, the landscape is more challenging for highly structured extraction but offers viable options, especially when combined.
|
||||
|
||||
#### 1. Tesseract OCR
|
||||
|
||||
- **Description:** A widely used, open-source OCR engine originally developed by HP and now maintained by Google. It is highly capable and supports numerous languages.
|
||||
- **Offline:** Yes, Tesseract is designed for offline use.
|
||||
- **Free:** Yes, it is released under the Apache License 2.0.
|
||||
- **Markdown Tables:** Tesseract by itself performs OCR - converting image text to plain text. It does *not* inherently output Markdown tables or understand structured data like links, prices, or specs.
|
||||
- **Limitations:** Output is plain text, not structured. Requires additional processing to identify and structure data. Accuracy can vary based on image quality and layout complexity.
|
||||
- **Libraries (Typescript ESM compatible and others):**
|
||||
- **`tesseract.js` (JavaScript/Typescript, ESM compatible):** A Javascript port of Tesseract OCR for use in the browser and Node.js.
|
||||
```typescript
|
||||
import { createScheduler } from 'tesseract.js';
|
||||
|
||||
async function recognizeText(imagePath: string) {
|
||||
const scheduler = createScheduler();
|
||||
const worker = await scheduler.addWorker();
|
||||
|
||||
const result = await worker.recognize(imagePath);
|
||||
console.log(result.data.text);
|
||||
|
||||
await scheduler.terminate();
|
||||
}
|
||||
|
||||
recognizeText('image.png');
|
||||
```
|
||||
- **`node-tesseract-ocr` (Node.js wrapper):** A Node.js wrapper around the Tesseract OCR library.
|
||||
```javascript
|
||||
const tesseract = require('node-tesseract-ocr')
|
||||
|
||||
const config = {
|
||||
l: 'eng', // Replace 'eng' with your language code
|
||||
oem: 1,
|
||||
psm: 3,
|
||||
}
|
||||
|
||||
tesseract('image.png', config)
|
||||
.then((text) => {
|
||||
console.log(text)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error.message)
|
||||
})
|
||||
```
|
||||
- **`pytesseract` (Python wrapper):** A Python wrapper for Tesseract. Requires Tesseract to be installed on your system.
|
||||
|
||||
#### 2. EasyOCR
|
||||
|
||||
- **Description:** An OCR library that leverages PyTorch and CRAFT for text detection and CRNN for text recognition. It is designed to be easy to use and supports multiple languages with pre-trained models.
|
||||
- **Offline:** Yes, EasyOCR works offline after installing the library and downloading language models.
|
||||
- **Free:** Yes, EasyOCR is open-source and free to use under the MIT License.
|
||||
- **Markdown Tables:** Similar to Tesseract, EasyOCR is primarily an OCR engine. It excels at text detection and recognition but does not natively create Markdown tables or extract structured data.
|
||||
- **Limitations:** Output is primarily plain text. Needs further processing for structure. Relies on pre-trained models, and performance might depend on model and language support.
|
||||
- **Libraries (Python):**
|
||||
- **`easyocr` (Python):**
|
||||
```python
|
||||
import easyocr
|
||||
reader = easyocr.Reader(['en']) # need to run only once to load model into memory
|
||||
result = reader.readtext('image.png')
|
||||
for (bbox, text, prob) in result:
|
||||
print(f"Text: {text}, Probability: {prob}")
|
||||
```
|
||||
|
||||
#### 3. Hugging Face Hub - Open Source Models (Offline Potential)
|
||||
|
||||
- **Description:** Hugging Face Hub hosts a vast collection of pre-trained models, including many for OCR, document layout analysis, and potentially even table detection. Some models can be used offline with libraries like `transformers.js` or Python's `transformers` library.
|
||||
- **Offline:** Potentially offline. Many models can be downloaded and used offline. Check model documentation for offline compatibility and library requirements.
|
||||
- **Free:** Many models on Hugging Face Hub are open-source and free to use.
|
||||
- **Markdown Tables:** This is where more advanced structured extraction becomes feasible.
|
||||
- **OCR Models:** Models like those from PaddleOCR (available on HF Hub) may offer more robust OCR capabilities.
|
||||
- **Layout Analysis Models:** Models designed for document layout analysis can identify regions of text, tables, and other elements within an image. This is a crucial step towards structured output. Look for models tagged with "document-layout-analysis" or "table-detection" on Hugging Face Hub.
|
||||
- **Table Detection and Recognition Models:** Specialized models exist for detecting and recognizing tables specifically. Some might even output structured data formats that can be converted to Markdown tables. Explore models related to "table-ocr" or "table-recognition."
|
||||
|
||||
- **Exploration Steps on Hugging Face Hub:**
|
||||
1. **Search:** On Hugging Face Hub ([https://huggingface.co/models](https://huggingface.co/models)), search for keywords like "OCR," "document layout analysis," "table detection," "table recognition."
|
||||
2. **Model Cards:** Review model cards for details on:
|
||||
- **Tasks:** Does it perform OCR, layout analysis, table detection, etc.?
|
||||
- **Offline Usage:** Is it compatible with `transformers` for Python or `transformers.js` for browser/Node.js and offline inference?
|
||||
- **Input/Output:** What type of input does it expect (image)? What output does it produce (text, bounding boxes, structured table data)?
|
||||
- **License:** Is it free for your intended use?
|
||||
3. **Example Code (Python with `transformers` - conceptual):**
|
||||
|
||||
```python
|
||||
from transformers import pipeline, AutoProcessor, AutoModelForDocumentQuestionAnswering
|
||||
from PIL import Image
|
||||
|
||||
# Example - Needs model specific processor and model names.
|
||||
# Replace "your-table-detection-model" and "your-table-extraction-model"
|
||||
# with actual Hugging Face model identifiers after research.
|
||||
|
||||
# Conceptual example - Might need different model types and processors
|
||||
table_detection_processor = AutoProcessor.from_pretrained("your-table-detection-model")
|
||||
table_detection_model = AutoModelForDocumentQuestionAnswering.from_pretrained("your-table-detection-model")
|
||||
|
||||
table_extraction_processor = AutoProcessor.from_pretrained("your-table-extraction-model")
|
||||
table_extraction_model = AutoModelForDocumentQuestionAnswering.from_pretrained("your-table-extraction-model")
|
||||
|
||||
image = Image.open("image_with_table.png").convert("RGB")
|
||||
|
||||
# 1. Table Detection (Conceptual step - model dependent)
|
||||
inputs_detection = table_detection_processor(images=image, return_tensors="pt")
|
||||
outputs_detection = table_detection_model(**inputs_detection)
|
||||
# ... process outputs_detection to identify table regions ...
|
||||
|
||||
# 2. Table Extraction (Conceptual step - model dependent and may require region inputs)
|
||||
inputs_extraction = table_extraction_processor(images=image, return_tensors="pt", table_regions=...) # if region input is needed
|
||||
outputs_extraction = table_extraction_model(**inputs_extraction)
|
||||
# ... process outputs_extraction to get structured table data ...
|
||||
|
||||
# 3. Convert to Markdown table
|
||||
table_data = ... # process outputs_extraction to get data in a suitable format
|
||||
markdown_table = generate_markdown_table(table_data)
|
||||
print(markdown_table)
|
||||
|
||||
def generate_markdown_table(data):
|
||||
markdown = "|" + "|".join(data["headers"]) + "|\n"
|
||||
markdown += "|" + "|".join(["---"] * len(data["headers"])) + "|\n"
|
||||
for row in data["rows"]:
|
||||
markdown += "|" + "|".join(row) + "|\n"
|
||||
return markdown
|
||||
|
||||
```
|
||||
**Important Notes for Hugging Face Models:**
|
||||
- **Model Selection is Key:** The effectiveness of Hugging Face models depends heavily on choosing the right model for the task (OCR, layout analysis, table detection). Careful research and experimentation on Hugging Face Hub are essential.
|
||||
- **Model and Processor Compatibility:** Ensure that the chosen model has a corresponding processor (`AutoProcessor`) and library support (`transformers`, `transformers.js`) for your desired environment (Python, Javascript).
|
||||
- **Input/Output Handling:** Understand the model's expected input format (image, document, text, regions) and output format. Processing the raw model output to extract structured data and format it into Markdown tables will likely require custom code.
|
||||
|
||||
### Commercial Models (Consider for Comparison and Potential Online Alternatives)
|
||||
|
||||
While the focus is on free/offline, understanding commercial options provides a benchmark and alternative if constraints allow for online services or paid solutions.
|
||||
|
||||
- **Cloud Vision APIs (Google Cloud Vision, Azure Computer Vision, AWS Textract):**
|
||||
- **Description:** Powerful cloud-based APIs offering advanced OCR, document analysis, and table extraction capabilities. Often provide structured output, including JSON or other formats that can be easily converted to Markdown tables.
|
||||
- **Offline:** No, these are cloud-based services.
|
||||
- **Free Tier / Paid:** Typically offer a free tier with limited usage and then become paid based on consumption.
|
||||
- **Markdown Tables:** Often provide structured output that can be readily transformed into Markdown tables. Some might even directly output Markdown or formats close to it.
|
||||
- **Adobe Acrobat Pro DC, Abbyy FineReader:**
|
||||
- **Description:** Desktop software with robust OCR and document conversion features, including to structured formats.
|
||||
- **Offline:** Yes, desktop software.
|
||||
- **Paid:** Commercial software licenses required.
|
||||
- **Markdown Tables:** May offer export options to structured formats (like CSV or XML) that can then be converted to Markdown tables. Might require an intermediate step.
|
||||
|
||||
### References
|
||||
|
||||
- **Tesseract OCR:** [https://tesseract-ocr.github.io/](https://tesseract-ocr.github.io/)
|
||||
- **Tesseract.js:** [https://tesseract.projectnaptha.com/](https://tesseract.projectnaptha.com/)
|
||||
- **node-tesseract-ocr:** [https://www.npmjs.com/package/node-tesseract-ocr](https://www.npmjs.com/package/node-tesseract-ocr)
|
||||
- **pytesseract:** [https://pypi.org/project/pytesseract/](https://pypi.org/project/pytesseract/)
|
||||
- **EasyOCR:** [https://www.jaided.ai/easyocr/](https://www.jaided.ai/easyocr/)
|
||||
- **Hugging Face Hub Models:** [https://huggingface.co/models](https://huggingface.co/models)
|
||||
- **Transformers Python Library:** [https://huggingface.co/docs/transformers/index](https://huggingface.co/docs/transformers/index)
|
||||
- **Transformers.js Library:** [https://huggingface.co/docs/transformers.js/index](https://huggingface.co/docs/transformers.js/index)
|
||||
|
||||
### Example Code (Markdown Table Generation - Typescript)
|
||||
|
||||
```typescript
|
||||
function generateMarkdownTable(headers: string[], rows: string[][]): string {
|
||||
let markdown = "";
|
||||
markdown += "|" + headers.join("|") + "|\n";
|
||||
markdown += "|" + headers.map(() => "---").join("|") + "|\n";
|
||||
for (const row of rows) {
|
||||
markdown += "|" + row.join("|") + "|\n";
|
||||
}
|
||||
return markdown;
|
||||
}
|
||||
|
||||
// Example Usage:
|
||||
const tableHeaders = ["Product", "Price", "Link", "Specs"];
|
||||
const tableData = [
|
||||
["Product A", "$10", "[Link A](https://example.com/a)", "Spec 1, Spec 2"],
|
||||
["Product B", "$20", "[Link B](https://example.com/b)", "Spec 3, Spec 4"],
|
||||
];
|
||||
|
||||
const markdownTable = generateMarkdownTable(tableHeaders, tableData);
|
||||
console.log(markdownTable);
|
||||
```
|
||||
|
||||
### Conclusion
|
||||
|
||||
Creating Markdown tables with structured information from images offline and for free is a challenging but achievable goal. Starting with OCR engines like Tesseract or EasyOCR is fundamental. For more advanced structured extraction, exploring layout analysis and table detection models on Hugging Face Hub is recommended.
|
||||
|
||||
Remember that truly offline, free, and highly accurate structured extraction often requires a combination of tools and potentially some level of custom development to process OCR output, identify entities (links, prices, specs), and format them into Markdown tables. Commercial cloud services generally offer more readily available structured output but at a cost and require internet connectivity.
|
||||
131
docs/image-ocr.md
Normal file
131
docs/image-ocr.md
Normal file
@ -0,0 +1,131 @@
|
||||
## Image to Text Options (Offline & Free) for Markdown Tables
|
||||
|
||||
### Brief
|
||||
|
||||
This document outlines offline and free options to convert images of tables into Markdown tables, focusing on tools and libraries that are performant and align with your preferences for Markdown, TypeScript/ESM compatibility, and avoiding React.
|
||||
|
||||
### Options
|
||||
|
||||
#### 1. Tesseract OCR (Command Line) with `node-tesseract-ocr` (Node.js)
|
||||
|
||||
**Description:**
|
||||
|
||||
Tesseract OCR is a powerful, open-source Optical Character Recognition engine. It can be used from the command line and interfaced with via Node.js using the `node-tesseract-ocr` library. This approach is entirely offline and free. While Tesseract excels at general text extraction, table recognition often requires pre-processing and post-processing.
|
||||
|
||||
**Pros:**
|
||||
|
||||
* **Offline:** Works directly on your machine without internet access.
|
||||
* **Free & Open Source:** No cost to use.
|
||||
* **Powerful OCR Engine:** Generally accurate for text recognition.
|
||||
* **Node.js Interface:** `node-tesseract-ocr` provides a convenient way to use Tesseract in JavaScript/TypeScript environments.
|
||||
* **Cross-Platform:** Tesseract is available on Linux, macOS, and Windows.
|
||||
|
||||
**Cons:**
|
||||
|
||||
* **Table Recognition:** Raw Tesseract might not perfectly identify and structure tables directly into Markdown. Post-processing is usually needed.
|
||||
* **Setup:** Requires installing Tesseract OCR engine separately.
|
||||
* **Dependency:** Relies on native binaries (Tesseract itself).
|
||||
|
||||
**Example Code (Node.js with `node-tesseract-ocr` and basic table formatting)**
|
||||
|
||||
```typescript
|
||||
// example.ts
|
||||
import * as tesseract from 'node-tesseract-ocr';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
async function imageToMarkdownTable(imagePath: string): Promise<string> {
|
||||
const config = {
|
||||
lang: 'eng', // Language for OCR (adjust as needed)
|
||||
// Additional Tesseract options can be configured here
|
||||
};
|
||||
|
||||
try {
|
||||
const text = await tesseract.recognize(imagePath, config);
|
||||
// Basic table structure assumption and simplistic Markdown formatting
|
||||
const lines = text.trim().split('\n');
|
||||
if (lines.length < 2) return "No table structure detected.";
|
||||
|
||||
const headerRow = lines[0].split(/\s{2,}/).join(' | '); // Split by 2+ spaces, simplistic delimiter
|
||||
const separator = headerRow.replace(/[^|]/g, '-').replace(/\|/g, '+'); // Simple separator line
|
||||
const dataRows = lines.slice(1).map(row => row.split(/\s{2,}/).join(' | ')).join('\n');
|
||||
|
||||
return `| ${headerRow} |\n${separator}\n| ${dataRows} |`;
|
||||
|
||||
} catch (error) {
|
||||
console.error("OCR Error:", error);
|
||||
return "Error during OCR processing.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function main() {
|
||||
const imageFile = 'path/to/your/image.png'; // Replace with your image path
|
||||
const markdownTable = await imageToMarkdownTable(imageFile);
|
||||
|
||||
console.log('\n');
|
||||
console.log(markdownTable);
|
||||
console.log('\n');
|
||||
|
||||
await fs.writeFile('output.md', markdownTable, 'utf-8');
|
||||
console.log("Markdown table saved to output.md");
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
To run this example:
|
||||
|
||||
1. **Install Tesseract OCR:** Follow installation instructions for your operating system (e.g., `brew install tesseract` on macOS, `sudo apt install tesseract-ocr` on Debian/Ubuntu). Install language data if needed (e.g., `tesseract-ocr-eng`).
|
||||
2. **Install Node.js and npm:** Ensure Node.js and npm are installed.
|
||||
3. **Initialize a Node.js project:** `npm init -y`
|
||||
4. **Install dependencies:** `npm install node-tesseract-ocr`
|
||||
5. **Save the code:** Save the TypeScript code as `example.ts`.
|
||||
6. **Compile and Run:** `npx ts-node example.ts` (or compile with `tsc example.ts` and run `node example.js`). Replace `'path/to/your/image.png'` with the actual path to your image file.
|
||||
|
||||
**Note:** This code provides a *very basic* table formatting. Real-world table extraction can be complex and might require more sophisticated pre-processing (image cleaning, deskewing) and post-processing (better column detection, handling merged cells, etc.) to achieve accurate Markdown table output. Libraries like `jimp` for image manipulation could be integrated for pre-processing.
|
||||
|
||||
#### 2. Online OCR services with copy/paste (Manual but Free)
|
||||
|
||||
**Description:**
|
||||
|
||||
While you requested offline solutions, for simpler cases or occasional use, many *free* online OCR services exist that can handle table recognition. You'd upload the image, the service performs OCR, and often provides options to output in tabular formats that you can then manually copy and paste into your Markdown document and format as a Markdown table.
|
||||
|
||||
**Examples of Free Online OCR Services (with varying table support):**
|
||||
|
||||
* **OnlineOCR.net:** ([https://www.onlineocr.net/](https://www.onlineocr.net/)) - Supports table recognition and output formats like TXT and XLSX. You would likely copy from TXT and format as Markdown.
|
||||
* **OCR2Edit:** ([https://ocr2edit.com/](https://ocr2edit.com/)) - Offers free OCR and editing capabilities. May have options relevant to table data extraction.
|
||||
* **Google Docs/Google Drive:** Upload the image to Google Drive, open it with Google Docs. Google Docs has OCR capabilities and can often recognize tables. You might then need to copy the table and convert it to Markdown.
|
||||
|
||||
**Pros:**
|
||||
|
||||
* **Free (basic usage):** Usually free for reasonable usage levels.
|
||||
* **Easy to use:** Web-based, no installation.
|
||||
* **Table Support:** Some services are designed for documents and tables, potentially offering better table structure recognition than basic OCR engines.
|
||||
|
||||
**Cons:**
|
||||
|
||||
* **Online:** Requires internet connection. Data is sent to a third-party server.
|
||||
* **Manual Steps:** Manual upload, copy/paste, and Markdown formatting.
|
||||
* **Privacy Concerns:** Consider data privacy if the images contain sensitive information.
|
||||
* **Accuracy Varies:** Accuracy of table recognition depends on the service and image quality.
|
||||
|
||||
**Workflow Example (OnlineOCR.net to Markdown):**
|
||||
|
||||
1. Go to [https://www.onlineocr.net/](https://www.onlineocr.net/).
|
||||
2. Upload your image file.
|
||||
3. Select the input language and choose "Output Format" likely as "Plain Text (.txt)".
|
||||
4. Click "Convert".
|
||||
5. Copy the extracted text.
|
||||
6. Paste the text into your Markdown editor.
|
||||
7. Manually format it as a Markdown table, adjusting columns, adding `|` separators and the separator row (`|---|---|`).
|
||||
|
||||
### References
|
||||
|
||||
* **Tesseract OCR:**
|
||||
* Homepage: [https://tesseract-ocr.github.io/](https://tesseract-ocr.github.io/)
|
||||
* GitHub Repository: [https://github.com/tesseract-ocr/tesseract](https://github.com/tesseract-ocr/tesseract)
|
||||
* **node-tesseract-ocr (npm):** [https://www.npmjs.com/package/node-tesseract-ocr](https://www.npmjs.com/package/node-tesseract-ocr)
|
||||
* **OnlineOCR.net:** [https://www.onlineocr.net/](https://www.onlineocr.net/)
|
||||
* **OCR2Edit:** [https://ocr2edit.com/](https://ocr2edit.com/)
|
||||
|
||||
---
|
||||
115
docs/image-to-text.md
Normal file
115
docs/image-to-text.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Brief
|
||||
|
||||
Image-to-text models are AI models that convert visual data (images) directly into descriptive, informative text. Unlike Optical Character Recognition (OCR), these models generate captions or descriptions reflecting the content, context, and meaning of the image rather than just extracting written characters. Several commercial and open-source models are available for integration into web-based or other software solutions.
|
||||
|
||||
# Available Models (Commercial and Open-source)
|
||||
|
||||
| Model Name | Provider/Platform | Price | Key Features | Language/Library | Links |
|
||||
|------------|-------------------|-------|--------------|------------------|-------|
|
||||
| BLIP-2 | Hugging Face (Open-source, non-commercial) | Free | Image-captioning, Zero-shot inference, Multimodal | Python, PyTorch | [BLIP-2 HuggingFace](https://huggingface.co/docs/transformers/model_doc/blip-2) |
|
||||
| OpenAI GPT-4V | OpenAI (Commercial) | Usage-based pricing (~$0.03/image) | Vision-enabled, advanced multimodal understanding, high accuracy | REST API (JS/TypeScript via fetch/promise) | [OpenAI Vision](https://platform.openai.com/docs/guides/vision) |
|
||||
| Microsoft Azure Vision | Microsoft Azure (Commercial) | Pay-as-you-go (~$1.50 per 1,000 transactions) | Advanced image description, object detection, image analysis | REST API (JS/TypeScript via REST fetch or Azure SDK) | [Azure Vision Docs](https://azure.microsoft.com/services/cognitive-services/computer-vision/) |
|
||||
| Google Cloud Vision API | Google (Commercial) | Pay-as-you-go (~$1.50 per 1,000 requests) | Robust image annotation, label detection, powerful object description | REST API (JS/TypeScript via fetch/promise) | [Google Vision](https://cloud.google.com/vision) |
|
||||
| Flamingo Mini | Hugging Face (Open-source, non-commercial) | Free | Multimodal capabilities, advanced captioning, lightweight version | Python/PyTorch | [Flamingo Mini HuggingFace](https://huggingface.co/openflamingo/OpenFlamingo-3B-vitl-mpt1b) |
|
||||
| Salesforce BLIP | Salesforce AI / Hugging Face (Open-source) | Free | State-of-the-art captioning, visual question answering | Python/PyTorch | [Salesforce BLIP HF Repo](https://huggingface.co/Salesforce/blip-image-captioning-base) |
|
||||
|
||||
# Recommended Approach/Library (Commercial APIs - JS ESM compatible)
|
||||
|
||||
To directly integrate quickly and easily with a Typescript-based frontend project (ES modules compatible and without React, Tailwind CSS as UI), commercial API services such as OpenAI Vision API or Azure Vision API are highly recommended. Here’s a simple example integrating OpenAI’s GPT-4V:
|
||||
|
||||
```typescript
|
||||
// Example: Simple OpenAI Vision API call using fetch.
|
||||
|
||||
const API_KEY = "YOUR_OPENAI_API_KEY";
|
||||
|
||||
type GPTVisionResponse = {
|
||||
choices: {
|
||||
message: {
|
||||
content: string;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
|
||||
async function generateImageCaption(imageUrl: string): Promise<string> {
|
||||
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"model": "gpt-4-vision-preview",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "Describe the following image in detail."},
|
||||
{"type": "image_url", "image_url": {"url": imageUrl}}
|
||||
]
|
||||
}
|
||||
],
|
||||
"max_tokens": 150
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("OpenAI API failed.");
|
||||
|
||||
const data: GPTVisionResponse = await response.json();
|
||||
return data.choices[0].message.content;
|
||||
}
|
||||
|
||||
// Usage example (with async/await)
|
||||
const caption = await generateImageCaption("https://example.com/sample_image.jpg");
|
||||
console.log("Generated Caption:", caption);
|
||||
```
|
||||
|
||||
# Integration with Tailwind CSS-Based Interface (without React)
|
||||
|
||||
Here's a minimalistic example using vanilla JavaScript/Typescript with TailwindCSS for styling a simple interface:
|
||||
|
||||
HTML markup:
|
||||
|
||||
```html
|
||||
<div class="max-w-lg mx-auto p-4 bg-white rounded-lg shadow-lg">
|
||||
<input id="image-url" type="url"
|
||||
placeholder="Enter Image URL"
|
||||
class="w-full p-2 border border-gray-300 rounded mb-4"
|
||||
/>
|
||||
<button id="generate-caption-btn"
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
||||
>
|
||||
Generate Caption
|
||||
</button>
|
||||
<div id="caption-output" class="mt-4 p-4 bg-gray-100 rounded"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Typescript/JS code (frontend):
|
||||
|
||||
```typescript
|
||||
document.getElementById('generate-caption-btn')!.addEventListener('click', async () => {
|
||||
const imageUrlInput = document.getElementById("image-url") as HTMLInputElement;
|
||||
const outputDiv = document.getElementById("caption-output")!;
|
||||
const imageUrl = imageUrlInput.value;
|
||||
|
||||
outputDiv.textContent = "Generating caption...";
|
||||
|
||||
try {
|
||||
const caption = await generateImageCaption(imageUrl);
|
||||
outputDiv.textContent = caption;
|
||||
} catch (error) {
|
||||
outputDiv.textContent = "Error generating caption.";
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
# References
|
||||
|
||||
- [BLIP-2 (HuggingFace)](https://huggingface.co/docs/transformers/model_doc/blip-2)
|
||||
- [Salesforce BLIP](https://huggingface.co/Salesforce/blip-image-captioning-base)
|
||||
- [OpenAI GPT-4 Vision](https://platform.openai.com/docs/guides/vision)
|
||||
- [Microsoft Azure Vision Docs](https://azure.microsoft.com/services/cognitive-services/computer-vision/)
|
||||
- [Google Vision API Docs](https://cloud.google.com/vision)
|
||||
- [Simple fetch API usage (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
|
||||
- [TailwindCSS Docs](https://tailwindcss.com/docs/installation)
|
||||
34
docs/languages_af.md
Normal file
34
docs/languages_af.md
Normal file
@ -0,0 +1,34 @@
|
||||
# **Languages of Africa**
|
||||
|
||||
Africa is a continent teeming with linguistic diversity, home to an estimated 2,000 to 3,000 languages. This abundance of languages reflects the continent's rich tapestry of cultures, intricate history, and the diverse migratory patterns that have shaped its peoples over millennia1. This report delves into the fascinating world of African languages, exploring their distribution, history, and cultural significance.
|
||||
|
||||
## **Language Families of Africa**
|
||||
|
||||
The languages of Africa can be classified into four main families:
|
||||
|
||||
* **Afro-Asiatic:** This family is concentrated in North Africa, the Horn of Africa, and parts of the Sahel. It includes languages such as Arabic, Amharic, Somali, Hausa, and the Berber languages. With a written history stretching back thousands of years, Afro-Asiatic is one of the oldest language families globally. The earliest Afro-Asiatic languages are associated with the Capsian culture1.
|
||||
* **Niger-Congo:** This is the largest language family in Africa and possibly the world by the number of languages. It dominates West, Central, Southeast, and Southern Africa. Niger-Congo languages are characterized by a complex noun class system with grammatical concord and tonal variations. Many languages in this family are tonal, such as Yoruba and Igbo. Prominent languages in this family include Swahili, Yoruba, Igbo, Zulu, and Xhosa. Niger-Congo languages are correlated with the west and central African hoe-based farming traditions1. In contrast to the standardized languages of Europe and Asia, Niger-Congo languages often exist on a dialect continuum, with variations emerging due to the absence of widespread literacy and unifying cultural forces. This lack of standardization makes it challenging to establish clear boundaries between languages3. For instance, 700 million people speak Niger-Congo languages across the continent, making it the third largest language family in the world2.
|
||||
* **Nilo-Saharan:** This family comprises over 100 languages spoken by roughly 50 million people in parts of East and Central Africa. Nilo-Saharan languages are known for their diversity and unique linguistic features, including unusual morphology. If these languages are related, they have undergone significant restructuring since diverging from their common ancestor1. Notable languages in this family include Kanuri, Fur, Songhay, Nubian languages, and the Nilotic languages (Luo, Dinka, Maasai).
|
||||
* **Khoisan:** This family is mainly spoken in Southern Africa and is characterized by the use of click consonants. Khoisan languages are considered among the oldest languages globally and are matched with the south and southeastern Wilton culture1.
|
||||
|
||||
## **Lingua Francas in Africa**
|
||||
|
||||
In a continent with such linguistic diversity, lingua francas play a crucial role in facilitating communication between different language groups. A lingua franca is a language adopted for wider communication beyond its native speakers. In Africa, several languages serve this purpose, including:
|
||||
|
||||
* **Swahili:** A Bantu language widely spoken in East Africa.
|
||||
* **Hausa:** A Chadic language spoken in West Africa, particularly in Nigeria and Niger.
|
||||
* **Arabic:** While primarily spoken in North Africa, Arabic also serves as a lingua franca in parts of the Sahel and East Africa.
|
||||
* **European languages:** English, French, and Portuguese, introduced during the colonial period, continue to function as lingua francas in many countries2.
|
||||
|
||||
## **Major Languages of Africa**
|
||||
|
||||
This section provides an overview of some of the most widely spoken languages in Africa:
|
||||
|
||||
| Language | Family | Speakers (approx.) | Geographic Distribution | Notes |
|
||||
|
||||
#### **Works cited**
|
||||
|
||||
1\. Languages of Africa \- Wikipedia, accessed on February 27, 2025, [https://en.wikipedia.org/wiki/Languages\_of\_Africa](https://en.wikipedia.org/wiki/Languages_of_Africa)
|
||||
2\. African languages \- Students | Britannica Kids | Homework Help, accessed on February 27, 2025, [https://kids.britannica.com/students/article/African-languages/272746](https://kids.britannica.com/students/article/African-languages/272746)
|
||||
3\. The Linguistic History of Africa, Part I: The Dawn of Man | by Sam Quillen | Medium, accessed on February 27, 2025, [https://sjquillen.medium.com/the-linguistic-history-of-africa-part-i-the-dawn-of-man-db83720aa1ec](https://sjquillen.medium.com/the-linguistic-history-of-africa-part-i-the-dawn-of-man-db83720aa1ec)
|
||||
4\. African Languages: Their Origins and Diversity \- Afriklens, accessed on February 27, 2025, [https://www.afriklens.com/african-languages-their-origins-and-diversity/](https://www.afriklens.com/african-languages-their-origins-and-diversity/)
|
||||
235
docs/maps.md
Normal file
235
docs/maps.md
Normal file
@ -0,0 +1,235 @@
|
||||
# StaticMap Component for Astro
|
||||
|
||||
## Static Map Options
|
||||
|
||||
| Provider | API Key | Features | Limitations | Example URL |
|
||||
|----------|---------|----------|-------------|------------|
|
||||
| Google Maps Static API | Required | Multiple marker styles, road/satellite/hybrid views | Limited free tier | `https://maps.googleapis.com/maps/api/staticmap?center=-2.2697,40.9025&zoom=14&size=600x400&markers=color:red%7C-2.2697,40.9025&key=YOUR_API_KEY` |
|
||||
| Mapbox Static Maps | Required | Highly customizable styles, multiple layers | Complex URL structure | `https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/pin-s+ff0000(-2.2697,40.9025)/-2.2697,40.9025,14,0/600x400?access_token=YOUR_API_KEY` |
|
||||
| OpenStreetMap Static | Not required | Free, simple implementation | Limited styling | `https://staticmap.openstreetmap.de/staticmap.php?center=-2.2697,40.9025&zoom=14&size=600x400&markers=-2.2697,40.9025,red` |
|
||||
| MapTiler | Required | Custom styles, vector tiles | Less common | `https://api.maptiler.com/maps/streets/static/40.9025,-2.2697,14/600x400.png?markers=40.9025,-2.2697,red&key=YOUR_API_KEY` |
|
||||
| HERE Maps | Required | Good international coverage | Less flexible styling | `https://image.maps.ls.hereapi.com/mia/1.6/mapview?c=-2.2697,40.9025&z=14&w=600&h=400&poi=-2.2697,40.9025&apiKey=YOUR_API_KEY` |
|
||||
|
||||
## Astro Component
|
||||
|
||||
Here's a complete Astro component that supports all the providers mentioned above:
|
||||
|
||||
```astro
|
||||
---
|
||||
// src/components/StaticMap.astro
|
||||
export interface GeoPos {
|
||||
lon: number;
|
||||
lat: number;
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
geo: GeoPos;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
zoom?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
apiKey?: string;
|
||||
provider?: 'google' | 'mapbox' | 'openstreetmap' | 'maptiler' | 'here';
|
||||
mapType?: string;
|
||||
markerColor?: string;
|
||||
}
|
||||
|
||||
const DEFAULT_LOCATION: Location = {
|
||||
geo: {
|
||||
lat: -2.2697, // Latitude for Lamu, Kenya
|
||||
lon: 40.9025 // Longitude for Lamu, Kenya
|
||||
},
|
||||
title: "Lamu, Kenya"
|
||||
};
|
||||
|
||||
const DEFAULT_OPTIONS: Options = {
|
||||
zoom: 14,
|
||||
width: 600,
|
||||
height: 400,
|
||||
provider: 'google',
|
||||
mapType: 'roadmap',
|
||||
markerColor: 'red'
|
||||
};
|
||||
|
||||
interface Props {
|
||||
locations?: Location[];
|
||||
options?: Options;
|
||||
}
|
||||
|
||||
const {
|
||||
locations = [DEFAULT_LOCATION],
|
||||
options = {}
|
||||
} = Astro.props;
|
||||
|
||||
const mergedOptions = { ...DEFAULT_OPTIONS, ...options };
|
||||
const { zoom, width, height, apiKey, provider, mapType, markerColor } = mergedOptions;
|
||||
|
||||
// Function to build URL based on provider
|
||||
function buildMapUrl(provider: string, locations: Location[], options: Options): string {
|
||||
const { zoom, width, height, apiKey, mapType, markerColor } = options;
|
||||
|
||||
switch (provider) {
|
||||
case 'google':
|
||||
let googleUrl = 'https://maps.googleapis.com/maps/api/staticmap?';
|
||||
|
||||
// Center map on the first location if available
|
||||
if (locations.length > 0) {
|
||||
googleUrl += `center=${locations[0].geo.lat},${locations[0].geo.lon}&`;
|
||||
}
|
||||
|
||||
// Add zoom, size and map type
|
||||
googleUrl += `zoom=${zoom}&size=${width}x${height}&maptype=${mapType}&`;
|
||||
|
||||
// Add markers
|
||||
locations.forEach(location => {
|
||||
googleUrl += `markers=color:${markerColor}%7C${location.geo.lat},${location.geo.lon}&`;
|
||||
});
|
||||
|
||||
// Add API key if available
|
||||
if (apiKey) {
|
||||
googleUrl += `key=${apiKey}`;
|
||||
}
|
||||
|
||||
return googleUrl;
|
||||
|
||||
case 'mapbox':
|
||||
let mapboxUrl = 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/';
|
||||
|
||||
// Add markers
|
||||
if (locations.length > 0) {
|
||||
locations.forEach((location, index) => {
|
||||
if (index > 0) mapboxUrl += ',';
|
||||
const color = markerColor.startsWith('#') ? markerColor.substring(1) : markerColor;
|
||||
mapboxUrl += `pin-s+${color}(${location.geo.lon},${location.geo.lat})`;
|
||||
});
|
||||
mapboxUrl += '/';
|
||||
}
|
||||
|
||||
// Add center coordinates, zoom and size
|
||||
const firstLocation = locations[0];
|
||||
mapboxUrl += `${firstLocation.geo.lon},${firstLocation.geo.lat},${zoom},0/${width}x${height}`;
|
||||
|
||||
// Add API key
|
||||
if (apiKey) {
|
||||
mapboxUrl += `?access_token=${apiKey}`;
|
||||
}
|
||||
|
||||
return mapboxUrl;
|
||||
|
||||
case 'openstreetmap':
|
||||
let osmUrl = 'https://staticmap.openstreetmap.de/staticmap.php?';
|
||||
|
||||
// Add center, zoom and size
|
||||
if (locations.length > 0) {
|
||||
osmUrl += `center=${locations[0].geo.lat},${locations[0].geo.lon}&zoom=${zoom}&size=${width}x${height}&`;
|
||||
}
|
||||
|
||||
// Add markers
|
||||
locations.forEach(location => {
|
||||
osmUrl += `markers=${location.geo.lat},${location.geo.lon},${markerColor}&`;
|
||||
});
|
||||
|
||||
return osmUrl;
|
||||
|
||||
case 'maptiler':
|
||||
let maptilerUrl = 'https://api.maptiler.com/maps/streets/static/';
|
||||
|
||||
// Add center coordinates, zoom and size
|
||||
const firstLocMaptiler = locations[0];
|
||||
maptilerUrl += `${firstLocMaptiler.geo.lon},${firstLocMaptiler.geo.lat},${zoom}/${width}x${height}.png?`;
|
||||
|
||||
// Add markers
|
||||
if (locations.length > 0) {
|
||||
locations.forEach((location, index) => {
|
||||
maptilerUrl += `markers=${location.geo.lon},${location.geo.lat},${markerColor}&`;
|
||||
});
|
||||
}
|
||||
|
||||
// Add API key
|
||||
if (apiKey) {
|
||||
maptilerUrl += `key=${apiKey}`;
|
||||
}
|
||||
|
||||
return maptilerUrl;
|
||||
|
||||
case 'here':
|
||||
let hereUrl = 'https://image.maps.ls.hereapi.com/mia/1.6/mapview?';
|
||||
|
||||
// Add center, zoom and size
|
||||
if (locations.length > 0) {
|
||||
hereUrl += `c=${locations[0].geo.lat},${locations[0].geo.lon}&z=${zoom}&w=${width}&h=${height}&`;
|
||||
}
|
||||
|
||||
// Add markers
|
||||
locations.forEach((location, index) => {
|
||||
hereUrl += `poi=${location.geo.lat},${location.geo.lon}&`;
|
||||
});
|
||||
|
||||
// Add API key
|
||||
if (apiKey) {
|
||||
hereUrl += `apiKey=${apiKey}`;
|
||||
}
|
||||
|
||||
return hereUrl;
|
||||
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const mapUrl = buildMapUrl(provider!, locations, mergedOptions);
|
||||
---
|
||||
|
||||
<div class="static-map w-full">
|
||||
<img
|
||||
src={mapUrl}
|
||||
alt="Static Map"
|
||||
class="w-full h-auto max-w-full border border-gray-300 rounded shadow"
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
```astro
|
||||
---
|
||||
import StaticMap from '../components/StaticMap.astro';
|
||||
|
||||
const locations = [
|
||||
{
|
||||
geo: { lat: -2.2697, lon: 40.9025 },
|
||||
title: "Lamu Old Town"
|
||||
},
|
||||
{
|
||||
geo: { lat: -2.2723, lon: 40.9018 },
|
||||
title: "Lamu Fort"
|
||||
}
|
||||
];
|
||||
|
||||
const options = {
|
||||
zoom: 15,
|
||||
width: 800,
|
||||
height: 500,
|
||||
apiKey: import.meta.env.GOOGLE_MAPS_API_KEY,
|
||||
provider: 'google'
|
||||
};
|
||||
---
|
||||
|
||||
<div class="container mx-auto my-8 px-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Map of Lamu</h1>
|
||||
<StaticMap locations={locations} options={options} />
|
||||
</div>
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Google Maps Static API](https://developers.google.com/maps/documentation/maps-static/overview)
|
||||
- [Mapbox Static Images API](https://docs.mapbox.com/api/maps/static-images/)
|
||||
- [OpenStreetMap Static API](https://wiki.openstreetmap.org/wiki/Static_map_images)
|
||||
- [MapTiler Static Maps](https://docs.maptiler.com/cloud/api/static-maps/)
|
||||
- [HERE Map Image API](https://developer.here.com/documentation/map-image/dev_guide/topics/introduction.html)
|
||||
4
docs/preferences.md
Normal file
4
docs/preferences.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Preferences
|
||||
|
||||
- all documents in Markdown, insert new line after headings, and before code sections
|
||||
- standard chapters: brief, references (with links)
|
||||
93
docs/rtl-culture.md
Normal file
93
docs/rtl-culture.md
Normal file
@ -0,0 +1,93 @@
|
||||
# Brief
|
||||
|
||||
This guide provides an in‐depth exploration of internationalization (i18n) for Astro projects with a focus on Arabic language markets. It discusses not only the technical implementation of right-to‐left (RTL) support using Astro and Tailwind CSS but also the cultural aspects, user perception, standards, and expectations prevalent in Arab countries.
|
||||
|
||||
Key topics include:
|
||||
|
||||
• Understanding RTL languages and how they influence design and layout
|
||||
• Strategies for tailoring content to suit cultural perceptions
|
||||
• Visual design considerations such as typography, color schemes, and imagery
|
||||
• Adherence to local norms in navigation, content hierarchy, and UX expectations
|
||||
• Technical implementation using Astro components and Tailwind CSS for seamless RTL/LTR support
|
||||
• Integration of TypeScript ES modules for a robust development experience
|
||||
|
||||
Implementing i18n is not just a technical challenge, but also an opportunity to design a culturally sensitive experience. In Arab markets, considerations such as proper localization of content, the choice of culturally relevant icons and images, and an intuitive navigation that respects RTL directionality are essential.
|
||||
|
||||
# Example Astro Component for RTL Support
|
||||
|
||||
Below is a ready-to-go Astro component that demonstrates a layout configured for RTL if the selected language is Arabic. The component uses Tailwind CSS for styling and is built with TypeScript ES modules in mind.
|
||||
|
||||
|
||||
```astro
|
||||
---
|
||||
// I18nLayout.astro
|
||||
interface Props {
|
||||
lang?: string;
|
||||
}
|
||||
const { lang = 'en' } = Astro.props as Props;
|
||||
const isRTL = lang === 'ar';
|
||||
---
|
||||
|
||||
<html lang={lang} dir={isRTL ? 'rtl' : 'ltr'}>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>{lang === 'ar' ? 'مرحبا بكم في موقعنا' : 'Welcome to Our Site'}</title>
|
||||
<link rel="stylesheet" href="/build/tailwind.css" />
|
||||
</head>
|
||||
<body class="min-h-screen bg-gray-100">
|
||||
<header class="bg-blue-600 text-white py-4 px-6">
|
||||
<h1 class="text-xl font-bold">
|
||||
{lang === 'ar' ? 'مرحبا بكم' : 'Welcome'}
|
||||
</h1>
|
||||
</header>
|
||||
<main class="p-6">
|
||||
<slot />
|
||||
</main>
|
||||
<footer class="bg-gray-200 text-center py-3">
|
||||
<p class="text-sm">
|
||||
{lang === 'ar'
|
||||
? 'حقوق النشر © 2023'
|
||||
: '© 2023 All rights reserved'}
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
# Additional Astro Component: Language Selector
|
||||
|
||||
This component provides a simple language toggler. It demonstrates how users can switch between LTR and RTL languages in an Astro project without relying on React.
|
||||
|
||||
|
||||
```astro
|
||||
---
|
||||
// LanguageSelector.astro
|
||||
interface Props {
|
||||
currentLang: string;
|
||||
}
|
||||
const { currentLang } = Astro.props as Props;
|
||||
const languages = [
|
||||
{ code: 'en', label: 'English' },
|
||||
{ code: 'ar', label: 'العربية' }
|
||||
];
|
||||
---
|
||||
|
||||
<nav class="flex justify-center space-x-4 py-2 bg-gray-100">
|
||||
{languages.map((lang) => (
|
||||
<a
|
||||
href={`/?lang=${lang.code}`}
|
||||
class={`px-3 py-1 rounded ${currentLang === lang.code ? 'bg-blue-600 text-white' : 'bg-white text-blue-600 border border-blue-600'}`}
|
||||
>
|
||||
{lang.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
```
|
||||
|
||||
# References
|
||||
|
||||
• Astro Documentation – https://docs.astro.build/
|
||||
• Tailwind CSS – https://tailwindcss.com/
|
||||
• Internationalization Best Practices – https://www.w3.org/International/
|
||||
• Arabic Typography and Design – https://arabic.design/
|
||||
• Localization in Web Development – https://www.smashingmagazine.com/2018/11/web-localization/
|
||||
116
docs/rtl.md
Normal file
116
docs/rtl.md
Normal file
@ -0,0 +1,116 @@
|
||||
# RTL Support in Astro
|
||||
|
||||
## Brief
|
||||
|
||||
Right-to-left (RTL) language support requires careful consideration of layout, typography, and user interface elements to provide a natural experience for users of Arabic, Hebrew, Persian, and other RTL languages.
|
||||
|
||||
## Core Implementation
|
||||
|
||||
### Base Layout Configuration
|
||||
|
||||
```astro
|
||||
---
|
||||
// layouts/RTLLayout.astro
|
||||
const { lang, dir = 'rtl' } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang={lang} dir={dir}>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body class="font-arabic">
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Tailwind Configuration
|
||||
|
||||
```javascript
|
||||
// tailwind.config.mjs
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
arabic: ['Noto Sans Arabic', 'system-ui', 'sans-serif'],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/text-direction')],
|
||||
}
|
||||
```
|
||||
|
||||
### Language Switcher Component
|
||||
|
||||
```astro
|
||||
---
|
||||
// components/LanguageSwitcher.astro
|
||||
const languages = [
|
||||
{ code: 'en', dir: 'ltr', label: 'English' },
|
||||
{ code: 'ar', dir: 'rtl', label: 'العربية' }
|
||||
];
|
||||
---
|
||||
|
||||
<div class="flex gap-4">
|
||||
{languages.map(lang => (
|
||||
<a
|
||||
href={`/${lang.code}`}
|
||||
class="px-3 py-1 rounded hover:bg-gray-100"
|
||||
hreflang={lang.code}
|
||||
>
|
||||
{lang.label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
### RTL-Aware Container
|
||||
|
||||
```astro
|
||||
---
|
||||
// components/RTLContainer.astro
|
||||
---
|
||||
|
||||
<div class="flex [dir='rtl']:flex-row-reverse">
|
||||
<div class="me-4">Sidebar</div>
|
||||
<div>Main Content</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Key Considerations
|
||||
|
||||
1. Typography
|
||||
- Font selection supporting Arabic/Hebrew characters
|
||||
- Adjust line height and letter spacing
|
||||
- Number formatting and alignment
|
||||
|
||||
2. Layout
|
||||
- Mirrored layouts
|
||||
- Flex and Grid direction
|
||||
- Margin and padding symmetry
|
||||
|
||||
3. UI Elements
|
||||
- Icon direction
|
||||
- Navigation flow
|
||||
- Scroll direction
|
||||
- Form elements alignment
|
||||
|
||||
4. Content
|
||||
- Date formats
|
||||
- Currency display
|
||||
- Lists ordering
|
||||
- Quote marks
|
||||
|
||||
## Recommended Libraries
|
||||
|
||||
- `@formatjs/intl`: Internationalization utilities
|
||||
- `react-aria`: Accessibility and RTL-aware components
|
||||
- `@tailwindcss/text-direction`: RTL utilities for Tailwind
|
||||
|
||||
## References
|
||||
|
||||
- [W3C - Structural markup and right-to-left text](https://www.w3.org/International/questions/qa-html-dir)
|
||||
- [MDN - RTL](https://developer.mozilla.org/en-US/docs/Glossary/rtl)
|
||||
- [Tailwind CSS RTL](https://tailwindcss.com/docs/hover-focus-and-other-states#rtl-support)
|
||||
- [Google Material Design - RTL Guidelines](https://m2.material.io/design/usability/bidirectionality.html)
|
||||
18
docs/seo.md
Normal file
18
docs/seo.md
Normal file
@ -0,0 +1,18 @@
|
||||
# SEO Component for Canonical Tags and Hreflang Attributes in Astro
|
||||
|
||||
This document outlines how to implement canonical tags and hreflang attributes in an Astro project to support multilingual SEO.
|
||||
|
||||
## 1. SEO Component (`SEO.astro`)
|
||||
|
||||
Create a reusable component that accepts a canonical URL and an array of hreflang entries.
|
||||
|
||||
```astro
|
||||
---
|
||||
// src/components/SEO.astro
|
||||
const { canonical, hreflangs } = Astro.props;
|
||||
---
|
||||
{canonical && <link rel="canonical" href={canonical} />}
|
||||
{hreflangs &&
|
||||
hreflangs.map((entry) => (
|
||||
<link key={entry.lang} rel="alternate" hreflang={entry.lang} href={entry.url} />
|
||||
))}
|
||||
5
docs/todos-images.md
Normal file
5
docs/todos-images.md
Normal file
@ -0,0 +1,5 @@
|
||||
## Image to text
|
||||
|
||||
- [ ] options, free, images to text (not OCR)
|
||||
- [ ] as markdown table with links, prices, specs/features, language
|
||||
- [ ] check commercial and opensource models (huggingface)
|
||||
26
docs/todos-map.md
Normal file
26
docs/todos-map.md
Normal file
@ -0,0 +1,26 @@
|
||||
## Map
|
||||
|
||||
- skip checked todos
|
||||
- dont comment
|
||||
- simple markup, for errors, use a dedicated error component
|
||||
|
||||
- [ ] complete & self-containing Astro component, tailwind: Google Maps Static API, pass location array & Options as props, default to 2 locations in Lamu, save in ../src/components/polymech/map.astro
|
||||
- [ ] document all in ../src/components/polymech/map.md, including API key setup
|
||||
- [ ] save all TS types in ../src/components/polymech/map-types.ts
|
||||
- [ ] center the map (center of all locations)
|
||||
- [ ] add options: satalite, terrain (default)
|
||||
|
||||
export interface GeoPos {
|
||||
lon: number
|
||||
lat: number
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
geo:GeoPos
|
||||
title:string
|
||||
}
|
||||
|
||||
export interface Options{
|
||||
zoom?:number
|
||||
api_key?:string
|
||||
}
|
||||
6
docs/todos.md
Normal file
6
docs/todos.md
Normal file
@ -0,0 +1,6 @@
|
||||
## i18n
|
||||
|
||||
skip checked todos
|
||||
|
||||
- [x] enumerate all steps and concerns related to RTL languages for Astro
|
||||
- [ ] create an excessive guide on the subject, eg: cultural aspects, perception, standards and expectations in arab countries
|
||||
21
docs/todos.sh
Normal file
21
docs/todos.sh
Normal file
@ -0,0 +1,21 @@
|
||||
## Gallery
|
||||
#kbotd --prompt=./Gallery.md --mode=completion --dst=./GalleryK.astro --filters=code --router=openai --model=gpt-4o
|
||||
#kbotd --prompt=./Gallery.md --mode=completion --dst=./GalleryK2.astro --filters=code --include=./GalleryS.astro --router=openai --model=gpt-4o
|
||||
#kbotd --prompt=./Lightbox.md --mode=completion --dst=./GalleryL.astro --filters=code --include=./GalleryM.astro --include=./lightbox.html --router=openai --model=gpt-4o
|
||||
#kbotd --prompt=./todos.md --mode=completion --dst=./GalleryL2.astro --filters=code --include=./GalleryL.astro --model=openai/o1
|
||||
|
||||
#kbotd --prompt=./todos.md --include=resources.astro --filters=code
|
||||
|
||||
## RTL
|
||||
#kbotd --prompt=./todos.md --preferences=./preferences.md --mode=completion --dst=./rtl.md
|
||||
#kbotd --prompt=./todos.md --preferences=./preferences.md --mode=completion --dst=./rtl-culture.md --model=openai/o3-mini
|
||||
|
||||
## Maps
|
||||
#kbotd --prompt=./todos-map.md --preferences=./preferences.md --mode=completion --dst=./maps.md --model=anthropic/claude-3.7-sonnet:thinking
|
||||
|
||||
## Pics
|
||||
|
||||
kbotd --prompt=./todos-images.md --preferences=./preferences.md --mode=completion --dst=./image-to-text.md --model=openai/gpt-4.5-preview
|
||||
|
||||
|
||||
|
||||
58
frontmatter.json
Normal file
58
frontmatter.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"$schema": "https://beta.frontmatter.codes/frontmatter.schema.json",
|
||||
"frontMatter.taxonomy.contentTypes": [
|
||||
{
|
||||
"name": "default",
|
||||
"pageBundle": false,
|
||||
"previewPath": null,
|
||||
"fields": [
|
||||
{
|
||||
"title": "Title",
|
||||
"name": "title",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Description",
|
||||
"name": "description",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"title": "Publishing date",
|
||||
"name": "date",
|
||||
"type": "datetime",
|
||||
"default": "{{now}}",
|
||||
"isPublishDate": true
|
||||
},
|
||||
{
|
||||
"title": "Content preview",
|
||||
"name": "preview",
|
||||
"type": "image"
|
||||
},
|
||||
{
|
||||
"title": "Is in draft",
|
||||
"name": "draft",
|
||||
"type": "draft"
|
||||
},
|
||||
{
|
||||
"title": "Tags",
|
||||
"name": "tags",
|
||||
"type": "tags"
|
||||
},
|
||||
{
|
||||
"title": "Categories",
|
||||
"name": "categories",
|
||||
"type": "categories"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"frontMatter.framework.id": "astro",
|
||||
"frontMatter.preview.host": "http://localhost:4321",
|
||||
"frontMatter.content.pageFolders": [
|
||||
{
|
||||
"title": "pm-site",
|
||||
"path": "[[workspace]]"
|
||||
}
|
||||
],
|
||||
"frontMatter.content.publicFolder": "public"
|
||||
}
|
||||
3701
lang_storeEntries.json
Normal file
3701
lang_storeEntries.json
Normal file
File diff suppressed because one or more lines are too long
16713
package-lock.json
generated
Normal file
16713
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
87
package.json
Normal file
87
package.json
Normal file
@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "@plastichub/astro-site-template",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev --mode dev --host=0.0.0.0 --o-images=medium",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"test:build": "astro build ; cd dist ; serve",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"generate-pwa-assets": "pwa-assets-generator --preset minimal-2023 public/logo.svg",
|
||||
"test": "playwright test",
|
||||
"test:lighthouse": "lighthouse https://polymech.io/en --output json --output html --output-path ./dist/reports/report.html --save-assets --chrome-flags=\"--window-size=1440,700 --headless\"",
|
||||
"test:debug": "playwright test",
|
||||
"test:ui": "playwright test --ui",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^2.10.4",
|
||||
"@astrojs/mdx": "^4.1.0",
|
||||
"@astrojs/react": "^4.2.1",
|
||||
"@astrojs/rss": "^4.0.10",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@polymech/cache": "file:../polymech-mono/packages/cache",
|
||||
"@polymech/cad": "file:../polymech-mono/packages/cad",
|
||||
"@polymech/commons": "file:../polymech-mono/packages/commons",
|
||||
"@polymech/fs": "file:../polymech-mono/packages/fs",
|
||||
"@polymech/i18n": "file:../polymech-mono/packages/i18n",
|
||||
"@polymech/kbot-d": "file:../polymech-mono/packages/kbot",
|
||||
"@polymech/log": "file:../polymech-mono/packages/log",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.0.7",
|
||||
"@tailwindcss/typography": "^0.5.12",
|
||||
"@tailwindcss/vite": "^4.0.7",
|
||||
"astro": "^5.4.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"axios": "^1.7.9",
|
||||
"cacache": "^19.0.1",
|
||||
"exifreader": "^4.26.1",
|
||||
"file-type": "^20.0.0",
|
||||
"find-cache-dir": "^5.0.0",
|
||||
"find-up": "^7.0.0",
|
||||
"flowbite": "^3.1.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^11.0.1",
|
||||
"got": "^14.4.6",
|
||||
"imagetools": "file:packages/imagetools",
|
||||
"lighthouse": "^12.3.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"marked": "^15.0.7",
|
||||
"mdast": "^2.3.2",
|
||||
"mdast-util-from-markdown": "^2.0.2",
|
||||
"mdast-util-to-markdown": "^2.1.2",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"mkdirp": "^3.0.1",
|
||||
"node-xlsx": "^0.24.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"p-map": "^7.0.3",
|
||||
"picomatch": "^4.0.2",
|
||||
"potrace": "^2.1.8",
|
||||
"react-jsx-parser": "^2.3.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark": "^15.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.1",
|
||||
"sanitize-html": "^2.14.0",
|
||||
"schema-dts": "^1.1.2",
|
||||
"sharp": "^0.29.3",
|
||||
"showdown": "^2.1.0",
|
||||
"tailwindcss": "^4.0.7",
|
||||
"type-fest": "^4.34.1",
|
||||
"vite": "^6.1.1",
|
||||
"xlsx": "^0.18.5",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/google-publisher-tag": "^1.20250210.0",
|
||||
"micromark-util-sanitize-uri": "^2.0.1",
|
||||
"normalize-url": "^8.0.1",
|
||||
"sass-embedded": "^1.83.4"
|
||||
}
|
||||
}
|
||||
55
packages/api.md
Normal file
55
packages/api.md
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
|
||||
components->Img -> renderImg -> getImage(sizes, format ...) -> element
|
||||
|
||||
|
||||
getImage -> getProcessedImage(args[0] : src:local path)
|
||||
args[0]
|
||||
{
|
||||
src: "https://assets.osr-plastic.org//products/sheetpress/cassandra-edczmax-rc2/media/gallery/perspective.jpg",
|
||||
type: "Img",
|
||||
sizes: "(min-width: 800px) 800px, 800vw",
|
||||
format: "avif",
|
||||
breakpoints: undefined,
|
||||
placeholder: "blurred",
|
||||
artDirectives: [
|
||||
],
|
||||
fallbackFormat: "avif",
|
||||
includeSourceFormat: false,
|
||||
formatOptions: {
|
||||
jpg: {
|
||||
quality: 80,
|
||||
},
|
||||
png: {
|
||||
quality: 80,
|
||||
},
|
||||
webp: {
|
||||
quality: 50,
|
||||
},
|
||||
},
|
||||
transformConfigs: {
|
||||
},
|
||||
}
|
||||
|
||||
->
|
||||
|
||||
{
|
||||
uuid: "B16DBD24",
|
||||
images: [
|
||||
{
|
||||
sources: [
|
||||
{
|
||||
src: "/_astro/perspective@1320w.e183f84c.avif",
|
||||
format: "avif",
|
||||
srcset: "/_astro/perspective@320w.3435f7a3.avif 320w, /_astro/perspective@653w.e53b07bb.avif 653w, /_astro/perspective@920w.a7628e27.avif 920w, /_astro/perspective@1120w.90dfde67.avif 1120w, /_astro/perspective@1253w.084f9de3.avif 1253w, /_astro/perspective@1320w.e183f84c.avif 1320w",
|
||||
},
|
||||
],
|
||||
sizes: {
|
||||
width: 1320,
|
||||
height: 1980,
|
||||
},
|
||||
fallback: "data:image/avif;base64,
|
||||
imagesizes: "(min-width: 800px) 800px, 800vw",
|
||||
},
|
||||
],
|
||||
}
|
||||
17
packages/imagetools/.eslintrc.js
Normal file
17
packages/imagetools/.eslintrc.js
Normal file
@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
es2020: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module",
|
||||
},
|
||||
plugins: ["unicorn"],
|
||||
extends: ["eslint:recommended"],
|
||||
rules: {
|
||||
"unicorn/prefer-node-protocol": "error",
|
||||
},
|
||||
};
|
||||
65
packages/imagetools/.github/workflows/ci.yml
vendored
Normal file
65
packages/imagetools/.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
lint:
|
||||
env:
|
||||
ASTRO_TELEMETRY_DISABLED: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2.2.1
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Prettier
|
||||
run: pnpm run format:check
|
||||
|
||||
- name: ESLint
|
||||
run: pnpm run lint
|
||||
|
||||
test:
|
||||
name: "Test: ${{ matrix.os }} (node@${{ matrix.node_version }})"
|
||||
env:
|
||||
ASTRO_TELEMETRY_DISABLED: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node_version: [14, 16]
|
||||
include:
|
||||
- os: windows-latest
|
||||
node_version: 16
|
||||
- os: macos-latest
|
||||
node_version: 16
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2.2.1
|
||||
|
||||
- name: Setup node@${{ matrix.node_version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter astro-imagetools run test
|
||||
20
packages/imagetools/.gitignore
vendored
Normal file
20
packages/imagetools/.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# build output
|
||||
dist/
|
||||
|
||||
# logs
|
||||
*.log
|
||||
|
||||
# npm
|
||||
package-lock.json
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# env
|
||||
*.env
|
||||
|
||||
# astro-imagetools
|
||||
packages/astro-imagetools/astroViteConfigs.js
|
||||
4
packages/imagetools/.npmignore
Normal file
4
packages/imagetools/.npmignore
Normal file
@ -0,0 +1,4 @@
|
||||
*.test.ts
|
||||
test-fixtures
|
||||
astroViteConfigs.js
|
||||
vitest.config.ts
|
||||
2
packages/imagetools/.npmrc
Normal file
2
packages/imagetools/.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
## force pnpm to hoist
|
||||
shamefully-hoist = true
|
||||
2
packages/imagetools/.prettierignore
Normal file
2
packages/imagetools/.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
pnpm-lock.yaml
|
||||
demo/dist
|
||||
9
packages/imagetools/.prettierrc
Normal file
9
packages/imagetools/.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.astro",
|
||||
"options": { "parser": "astro" }
|
||||
}
|
||||
],
|
||||
"plugins": ["prettier-plugin-astro"]
|
||||
}
|
||||
21
packages/imagetools/LICENSE
Normal file
21
packages/imagetools/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Rafid Muhymin Wafi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
39
packages/imagetools/README.md
Normal file
39
packages/imagetools/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# **Astro ImageTools**
|
||||
|
||||
**Astro ImageTools** is a collection of tools for optimizing images, background images, and generating responsive images for the **Astro JS** framework.
|
||||
|
||||
## Features
|
||||
|
||||
Below is a short list of features that **Astro ImageTools** offers. For more information, please see component-specific or API-specific documentation.
|
||||
|
||||
- ✅ **Regular Image Optimization** (`<img>` and `<picture>`)
|
||||
- ✅ **Background Image Optimization**
|
||||
- ✅ **Responsive Images**
|
||||
- ✅ **Simple and intuitive Art Direction API**
|
||||
- ✅ **Lazy Loading**
|
||||
- ✅ **Programmatic APIs**
|
||||
- ✅ **Asynchronous Decoding**
|
||||
- ✅ **Unique Breakpoints Calculation**
|
||||
- ✅ **Preloading for urgent images**
|
||||
- ✅ **SVG Tracing and Posterization**
|
||||
- ✅ **100% Scoped CSS**
|
||||
- ✅ **Four kind of Layouts: `constrained`, `fixed`, `fullWidth` & `fill`**
|
||||
- ✅ **Three kind of Placeholder Images: `blurred`, `dominantColor` & `tracedSVG`**
|
||||
- ✅ **Long list of supported Image Formats**
|
||||
- ✅ **Long List of supported Configuration Options**
|
||||
- ✅ **Supports Remote Images and Data URIs too**
|
||||
- ✅ **Support for _`sharp`less_ Environments**
|
||||
- ✅ **Both Memory-based and FS-based Caching for better Performance**
|
||||
- ✅ **Respects to _Semantics of HTML_ as much as possible**
|
||||
|
||||
## Getting Started
|
||||
|
||||
To get started with **Astro ImageTools**, first check out the [Installation](https://astro-imagetools-docs.vercel.app/en/installation) documentation for instructions on how to install the `astro-imagetools` package.
|
||||
|
||||
If you are looking for the available components and APIs, please check out the [Components and APIs](https://astro-imagetools-docs.vercel.app/en/components-and-apis) documentation.
|
||||
|
||||
If you want to view live examples of the components, APIs, layouts, and placeholder images, check out the [Astro ImageTools Demo](https://astro-imagetools-demo.vercel.app/) website.
|
||||
|
||||
If you want to report any issues or have found a missing feature, please report it on [GitHub](https://github.com/RafidMuhymin/astro-imagetools/)!
|
||||
|
||||
Good luck out there, Astronaut. 🧑🚀
|
||||
1
packages/imagetools/api/importImage.d.ts
vendored
Normal file
1
packages/imagetools/api/importImage.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default function importImage(url: string): Promise<string>;
|
||||
23
packages/imagetools/api/importImage.js
Normal file
23
packages/imagetools/api/importImage.js
Normal file
@ -0,0 +1,23 @@
|
||||
import load from "../plugin/hooks/load.js";
|
||||
import { getSrcPath } from "./utils/getSrcPath.js";
|
||||
import getResolvedSrc from "./utils/getResolvedSrc.js";
|
||||
|
||||
export default async function importImage(path) {
|
||||
try {
|
||||
const { search, protocol, pathname } = new URL(path);
|
||||
|
||||
const { src: id, base } = await getResolvedSrc(
|
||||
protocol === "data:" ? protocol + pathname : path
|
||||
);
|
||||
|
||||
const src = (await load(id + search, base)).slice(16, -1);
|
||||
|
||||
return src;
|
||||
} catch (error) {
|
||||
const id = await getSrcPath(path);
|
||||
|
||||
const src = (await load(id)).slice(16, -1);
|
||||
|
||||
return src;
|
||||
}
|
||||
}
|
||||
6
packages/imagetools/api/index.js
Normal file
6
packages/imagetools/api/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
export { default as renderImg } from "./renderImg.js";
|
||||
export { default as renderPicture } from "./renderPicture.js";
|
||||
export { default as renderBackgroundImage } from "./renderBackgroundImage.js";
|
||||
export { default as renderBackgroundPicture } from "./renderBackgroundPicture.js";
|
||||
export { default as importImage } from "./importImage.js";
|
||||
export { getImageDetails, loadImage } from "./utils/imagetools.js"
|
||||
8
packages/imagetools/api/renderBackgroundImage.d.ts
vendored
Normal file
8
packages/imagetools/api/renderBackgroundImage.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import type {
|
||||
BackgroundImageConfigOptions,
|
||||
BackgroundImageHTMLData,
|
||||
} from "../types";
|
||||
|
||||
export default function renderBackgroundImage(
|
||||
config: BackgroundImageConfigOptions
|
||||
): Promise<BackgroundImageHTMLData>;
|
||||
159
packages/imagetools/api/renderBackgroundImage.js
Normal file
159
packages/imagetools/api/renderBackgroundImage.js
Normal file
@ -0,0 +1,159 @@
|
||||
// @ts-check
|
||||
import crypto from "node:crypto";
|
||||
import getImage from "./utils/getImage.js";
|
||||
import getLinkElement from "./utils/getLinkElement.js";
|
||||
import getStyleElement from "./utils/getStyleElement.js";
|
||||
import getFilteredProps from "./utils/getFilteredProps.js";
|
||||
import getContainerElement from "./utils/getContainerElement.js";
|
||||
|
||||
export default async function renderBackgroundImage(props) {
|
||||
const type = "BackgroundImage";
|
||||
|
||||
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
|
||||
|
||||
const {
|
||||
src,
|
||||
tag,
|
||||
content,
|
||||
preload,
|
||||
attributes,
|
||||
placeholder,
|
||||
breakpoints,
|
||||
backgroundSize,
|
||||
backgroundPosition,
|
||||
format,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
artDirectives,
|
||||
} = filteredProps;
|
||||
|
||||
const {
|
||||
link: linkAttributes = {},
|
||||
style: styleAttributes = {},
|
||||
container: containerAttributes = {},
|
||||
} = attributes;
|
||||
|
||||
const sizes = "";
|
||||
|
||||
const { uuid, images } = await getImage({
|
||||
src,
|
||||
type,
|
||||
sizes,
|
||||
format,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
artDirectives,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
transformConfigs,
|
||||
});
|
||||
|
||||
const className = `astro-imagetools-background-image-${uuid}`;
|
||||
|
||||
const { imagesizes } = images[images.length - 1];
|
||||
|
||||
const link = getLinkElement({ images, preload, imagesizes, linkAttributes });
|
||||
|
||||
const backgroundImageStylesArray = images.map(({ media, sources }) => {
|
||||
const uuid = crypto.randomBytes(4).toString("hex").toUpperCase();
|
||||
|
||||
const fallbackUrlCustomVariable = `--astro-imagetools-background-image-fallback-url${uuid}`;
|
||||
|
||||
const newSources = {};
|
||||
|
||||
sources.forEach(({ src, format, srcset }) => {
|
||||
const sources = srcset
|
||||
.split(", ")
|
||||
.map((source) => [
|
||||
source.slice(0, source.lastIndexOf(" ")),
|
||||
source.slice(source.lastIndexOf(" ") + 1, -1),
|
||||
]);
|
||||
|
||||
sources.forEach(([path, width]) => {
|
||||
if (!newSources[width]) {
|
||||
newSources[width] = [];
|
||||
}
|
||||
|
||||
newSources[width].push({ src, format, path });
|
||||
});
|
||||
});
|
||||
|
||||
const widths = Object.keys(newSources)
|
||||
.map((width) => parseInt(width))
|
||||
.reverse();
|
||||
|
||||
const maxWidth = Math.max(...widths);
|
||||
|
||||
const styles = widths
|
||||
.map((width) => {
|
||||
const sources = newSources[width];
|
||||
|
||||
const styles = sources
|
||||
.map(
|
||||
({ format, path }, i) =>
|
||||
`
|
||||
${i !== sources.length - 1 ? `.${format} ` : ""}.${className} {
|
||||
background-repeat: no-repeat;
|
||||
background-image: url(${path}),
|
||||
var(${fallbackUrlCustomVariable});
|
||||
background-size: ${backgroundSize};
|
||||
background-position: ${backgroundPosition};
|
||||
}
|
||||
`
|
||||
)
|
||||
.reverse()
|
||||
.join("");
|
||||
|
||||
return width === maxWidth
|
||||
? styles
|
||||
: `
|
||||
@media screen and (max-width: ${width}px) {
|
||||
${styles}
|
||||
}
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return {
|
||||
fallbackUrlCustomVariable,
|
||||
styles: media
|
||||
? `
|
||||
@media ${media} {
|
||||
${styles}
|
||||
}
|
||||
`
|
||||
: styles,
|
||||
};
|
||||
});
|
||||
|
||||
const containerStyles = `
|
||||
.${className} {
|
||||
position: relative;
|
||||
${images
|
||||
.map(({ fallback }, i) => {
|
||||
const fallbackUrlCustomVariable =
|
||||
backgroundImageStylesArray[i].fallbackUrlCustomVariable;
|
||||
|
||||
return `${fallbackUrlCustomVariable}: url("${encodeURI(fallback)}");`;
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`;
|
||||
|
||||
const backgroundStyles =
|
||||
backgroundImageStylesArray.map(({ styles }) => styles).join("\n") +
|
||||
containerStyles;
|
||||
|
||||
const style = getStyleElement({ styleAttributes, backgroundStyles });
|
||||
|
||||
const htmlElement = getContainerElement({
|
||||
tag,
|
||||
content,
|
||||
className,
|
||||
containerAttributes,
|
||||
});
|
||||
|
||||
return { link, style, htmlElement };
|
||||
}
|
||||
8
packages/imagetools/api/renderBackgroundPicture.d.ts
vendored
Normal file
8
packages/imagetools/api/renderBackgroundPicture.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import type {
|
||||
BackgroundPictureConfigOptions,
|
||||
BackgroundPictureHTMLData,
|
||||
} from "../types";
|
||||
|
||||
export default function renderBackgroundPicture(
|
||||
config: BackgroundPictureConfigOptions
|
||||
): Promise<BackgroundPictureHTMLData>;
|
||||
127
packages/imagetools/api/renderBackgroundPicture.js
Normal file
127
packages/imagetools/api/renderBackgroundPicture.js
Normal file
@ -0,0 +1,127 @@
|
||||
// @ts-check
|
||||
import getImage from "./utils/getImage.js";
|
||||
import getImgElement from "./utils/getImgElement.js";
|
||||
import getLinkElement from "./utils/getLinkElement.js";
|
||||
import getStyleElement from "./utils/getStyleElement.js";
|
||||
import getLayoutStyles from "./utils/getLayoutStyles.js";
|
||||
import getFilteredProps from "./utils/getFilteredProps.js";
|
||||
import getPictureElement from "./utils/getPictureElement.js";
|
||||
import getBackgroundStyles from "./utils/getBackgroundStyles.js";
|
||||
import getContainerElement from "./utils/getContainerElement.js";
|
||||
|
||||
export default async function renderBackgroundPicture(props) {
|
||||
const type = "BackgroundPicture";
|
||||
|
||||
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
|
||||
|
||||
const {
|
||||
src,
|
||||
tag,
|
||||
content,
|
||||
sizes,
|
||||
preload,
|
||||
loading,
|
||||
decoding,
|
||||
attributes,
|
||||
placeholder,
|
||||
breakpoints,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
format,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
fadeInTransition,
|
||||
artDirectives,
|
||||
} = filteredProps;
|
||||
|
||||
const {
|
||||
img: imgAttributes = {},
|
||||
link: linkAttributes = {},
|
||||
style: styleAttributes = {},
|
||||
picture: pictureAttributes = {},
|
||||
container: containerAttributes = {},
|
||||
} = attributes;
|
||||
|
||||
const { uuid, images } = await getImage({
|
||||
src,
|
||||
type,
|
||||
sizes,
|
||||
format,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
artDirectives,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
transformConfigs,
|
||||
});
|
||||
|
||||
const className = `astro-imagetools-picture-${uuid}`,
|
||||
containerClassName = `astro-imagetools-background-picture-${uuid}`;
|
||||
|
||||
const { imagesizes } = images[images.length - 1];
|
||||
|
||||
const backgroundStyles = getBackgroundStyles(
|
||||
images,
|
||||
className,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
fadeInTransition,
|
||||
{ isBackgroundPicture: true, containerClassName }
|
||||
);
|
||||
|
||||
const style = getStyleElement({ styleAttributes, backgroundStyles });
|
||||
|
||||
const link = getLinkElement({ images, preload, imagesizes, linkAttributes });
|
||||
|
||||
const layoutStyles = getLayoutStyles({ isBackgroundImage: true });
|
||||
|
||||
// Background Images shouldn't convey important information
|
||||
const alt = "";
|
||||
|
||||
const sources = images.flatMap(({ media, sources, sizes, imagesizes }) =>
|
||||
sources.map(({ format, src, srcset }) =>
|
||||
src
|
||||
? getImgElement({
|
||||
src,
|
||||
alt,
|
||||
sizes,
|
||||
style,
|
||||
srcset,
|
||||
loading,
|
||||
decoding,
|
||||
imagesizes,
|
||||
fadeInTransition,
|
||||
layoutStyles,
|
||||
imgAttributes,
|
||||
})
|
||||
: `<source
|
||||
srcset="${srcset}"
|
||||
sizes="${imagesizes}"
|
||||
width="${sizes.width}"
|
||||
height="${sizes.height}"
|
||||
type="${`image/${format}`}"
|
||||
${media ? `media="${media}"` : ""}
|
||||
/>`
|
||||
)
|
||||
);
|
||||
|
||||
const picture = getPictureElement({
|
||||
sources,
|
||||
className,
|
||||
layoutStyles,
|
||||
pictureAttributes,
|
||||
isBackgroundPicture: true,
|
||||
});
|
||||
|
||||
const htmlElement = getContainerElement({
|
||||
tag,
|
||||
content: picture + content,
|
||||
containerAttributes,
|
||||
isBackgroundPicture: true,
|
||||
containerClassName,
|
||||
});
|
||||
|
||||
return { link, style, htmlElement };
|
||||
}
|
||||
5
packages/imagetools/api/renderImg.d.ts
vendored
Normal file
5
packages/imagetools/api/renderImg.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import type { ImgConfigOptions, ImgHTMLData } from "../types";
|
||||
|
||||
export default function renderImg(
|
||||
config: ImgConfigOptions
|
||||
): Promise<ImgHTMLData>;
|
||||
93
packages/imagetools/api/renderImg.js
Normal file
93
packages/imagetools/api/renderImg.js
Normal file
@ -0,0 +1,93 @@
|
||||
// @ts-check
|
||||
import getImage from "./utils/getImage.js";
|
||||
import getImgElement from "./utils/getImgElement.js";
|
||||
import getLinkElement from "./utils/getLinkElement.js";
|
||||
import getStyleElement from "./utils/getStyleElement.js";
|
||||
import getLayoutStyles from "./utils/getLayoutStyles.js";
|
||||
import getFilteredProps from "./utils/getFilteredProps.js";
|
||||
import getBackgroundStyles from "./utils/getBackgroundStyles.js";
|
||||
|
||||
export default async function renderImg(props) {
|
||||
const type = "Img";
|
||||
|
||||
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
|
||||
|
||||
const {
|
||||
src,
|
||||
alt,
|
||||
sizes,
|
||||
preload,
|
||||
loading,
|
||||
decoding,
|
||||
attributes,
|
||||
layout,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
format,
|
||||
formatOptions,
|
||||
} = filteredProps;
|
||||
|
||||
const artDirectives = [],
|
||||
fallbackFormat = format,
|
||||
fadeInTransition = false,
|
||||
includeSourceFormat = false;
|
||||
|
||||
const {
|
||||
img: imgAttributes = {},
|
||||
link: linkAttributes = {},
|
||||
style: styleAttributes = {},
|
||||
} = attributes;
|
||||
|
||||
const { uuid, images } = await getImage({
|
||||
src,
|
||||
type,
|
||||
sizes,
|
||||
format,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
artDirectives,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
transformConfigs,
|
||||
});
|
||||
|
||||
const className = `astro-imagetools-img-${uuid}`;
|
||||
|
||||
const { imagesizes } = images[images.length - 1];
|
||||
const backgroundStyles = getBackgroundStyles(
|
||||
images,
|
||||
className,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
fadeInTransition,
|
||||
{ isImg: true }
|
||||
);
|
||||
const style = getStyleElement({ styleAttributes, backgroundStyles })
|
||||
const link = getLinkElement({ images, preload, imagesizes, linkAttributes })
|
||||
const layoutStyles = getLayoutStyles({ layout })
|
||||
|
||||
const sources = images.flatMap(({ sources, sizes, imagesizes }) =>
|
||||
sources.map(({ src, srcset }) =>
|
||||
getImgElement({
|
||||
src,
|
||||
alt,
|
||||
sizes,
|
||||
style,
|
||||
srcset,
|
||||
loading,
|
||||
decoding,
|
||||
imagesizes,
|
||||
fadeInTransition,
|
||||
layoutStyles,
|
||||
imgAttributes,
|
||||
imgClassName: className,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
const [img] = sources
|
||||
return { link, style, img }
|
||||
}
|
||||
5
packages/imagetools/api/renderPicture.d.ts
vendored
Normal file
5
packages/imagetools/api/renderPicture.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import type { PictureConfigOptions, PictureHTMLData } from "../types";
|
||||
|
||||
export default function renderPicture(
|
||||
config: PictureConfigOptions
|
||||
): Promise<PictureHTMLData>;
|
||||
111
packages/imagetools/api/renderPicture.js
Normal file
111
packages/imagetools/api/renderPicture.js
Normal file
@ -0,0 +1,111 @@
|
||||
// @ts-check
|
||||
import getImage from "./utils/getImage.js";
|
||||
import getImgElement from "./utils/getImgElement.js";
|
||||
import getLinkElement from "./utils/getLinkElement.js";
|
||||
import getStyleElement from "./utils/getStyleElement.js";
|
||||
import getLayoutStyles from "./utils/getLayoutStyles.js";
|
||||
import getFilteredProps from "./utils/getFilteredProps.js";
|
||||
import getPictureElement from "./utils/getPictureElement.js";
|
||||
import getBackgroundStyles from "./utils/getBackgroundStyles.js";
|
||||
|
||||
export default async function renderPicture(props) {
|
||||
const type = "Picture";
|
||||
|
||||
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
|
||||
|
||||
const {
|
||||
src,
|
||||
alt,
|
||||
sizes,
|
||||
preload,
|
||||
loading,
|
||||
decoding,
|
||||
attributes,
|
||||
layout,
|
||||
placeholder,
|
||||
breakpoints,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
format,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
fadeInTransition,
|
||||
artDirectives,
|
||||
} = filteredProps;
|
||||
|
||||
const {
|
||||
img: imgAttributes = {},
|
||||
link: linkAttributes = {},
|
||||
style: styleAttributes = {},
|
||||
picture: pictureAttributes = {},
|
||||
} = attributes;
|
||||
|
||||
const { uuid, images } = await getImage({
|
||||
src,
|
||||
type,
|
||||
sizes,
|
||||
format,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
artDirectives,
|
||||
transformConfigs,
|
||||
});
|
||||
|
||||
const className = `astro-imagetools-picture-${uuid}`;
|
||||
|
||||
const { imagesizes } = images[images.length - 1];
|
||||
|
||||
const backgroundStyles = getBackgroundStyles(
|
||||
images,
|
||||
className,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
fadeInTransition
|
||||
);
|
||||
|
||||
const style = getStyleElement({ styleAttributes, backgroundStyles });
|
||||
|
||||
const link = getLinkElement({ images, preload, imagesizes, linkAttributes });
|
||||
|
||||
const layoutStyles = getLayoutStyles({ layout });
|
||||
|
||||
const sources = images.flatMap(({ media, sources, sizes, imagesizes }) =>
|
||||
sources.map(({ format, src, srcset }) =>
|
||||
src
|
||||
? getImgElement({
|
||||
src,
|
||||
alt,
|
||||
sizes,
|
||||
style,
|
||||
srcset,
|
||||
loading,
|
||||
decoding,
|
||||
imagesizes,
|
||||
fadeInTransition,
|
||||
layoutStyles,
|
||||
imgAttributes,
|
||||
})
|
||||
: `<source
|
||||
srcset="${srcset}"
|
||||
sizes="${imagesizes}"
|
||||
width="${sizes.width}"
|
||||
height="${sizes.height}"
|
||||
type="${`image/${format}`}"
|
||||
${media ? `media="${media}"` : ""}
|
||||
/>`
|
||||
)
|
||||
);
|
||||
|
||||
const picture = getPictureElement({
|
||||
sources,
|
||||
className,
|
||||
layoutStyles,
|
||||
pictureAttributes,
|
||||
});
|
||||
|
||||
return { link, style, picture };
|
||||
}
|
||||
38
packages/imagetools/api/utils/codecs.js
Normal file
38
packages/imagetools/api/utils/codecs.js
Normal file
@ -0,0 +1,38 @@
|
||||
// @ts-check
|
||||
import fs from "node:fs";
|
||||
import { extname } from "node:path";
|
||||
import * as codecs from "@astropub/codecs";
|
||||
|
||||
export async function getImageDetails(path, width, height, aspect) {
|
||||
const extension = extname(path).slice(1);
|
||||
|
||||
const imageFormat = extension === "jpeg" ? "jpg" : extension;
|
||||
|
||||
const buffer = fs.readFileSync(path);
|
||||
const decodedImage = await codecs.jpg.decode(buffer);
|
||||
|
||||
if (aspect && !width && !height) {
|
||||
if (!width && !height) {
|
||||
({ width } = decodedImage);
|
||||
}
|
||||
|
||||
if (width) {
|
||||
height = width / aspect;
|
||||
}
|
||||
|
||||
if (height) {
|
||||
width = height * aspect;
|
||||
}
|
||||
}
|
||||
|
||||
const image = await decodedImage.resize({ width, height });
|
||||
|
||||
const { width: imageWidth, height: imageHeight } = image;
|
||||
|
||||
return {
|
||||
image,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
imageFormat,
|
||||
};
|
||||
}
|
||||
137
packages/imagetools/api/utils/getArtDirectedImages.js
Normal file
137
packages/imagetools/api/utils/getArtDirectedImages.js
Normal file
@ -0,0 +1,137 @@
|
||||
// @ts-check
|
||||
import getSrcset from "./getSrcset.js";
|
||||
import getConfigOptions from "./getConfigOptions.js";
|
||||
import getFallbackImage from "./getFallbackImage.js";
|
||||
import getProcessedImage from "./getProcessedImage.js";
|
||||
|
||||
export default async function getArtDirectedImages(
|
||||
artDirectives = [],
|
||||
placeholder,
|
||||
format,
|
||||
imagesizes,
|
||||
breakpoints,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
rest
|
||||
) {
|
||||
const images = await Promise.all(
|
||||
artDirectives.map(
|
||||
async ({
|
||||
src,
|
||||
media,
|
||||
sizes: directiveImagesizes,
|
||||
placeholder: directivePlaceholder,
|
||||
breakpoints: directiveBreakpoints,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
backgroundSize,
|
||||
backgroundPosition,
|
||||
format: directiveFormat,
|
||||
fallbackFormat: directiveFallbackFormat,
|
||||
includeSourceFormat: directiveIncludeSourceFormat,
|
||||
formatOptions: directiveFormatOptions = {},
|
||||
...configOptions
|
||||
}) => {
|
||||
const {
|
||||
path,
|
||||
base,
|
||||
rest: rest2,
|
||||
image,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
imageFormat,
|
||||
} = await getProcessedImage(src, configOptions);
|
||||
|
||||
rest2.aspect = `${imageWidth / imageHeight}`;
|
||||
|
||||
const calculatedConfigs = getConfigOptions(
|
||||
imageWidth,
|
||||
directiveImagesizes || imagesizes,
|
||||
directiveBreakpoints || breakpoints,
|
||||
directiveFormat || format,
|
||||
imageFormat,
|
||||
directiveFallbackFormat || fallbackFormat,
|
||||
directiveIncludeSourceFormat || includeSourceFormat
|
||||
);
|
||||
|
||||
const { formats, requiredBreakpoints } = calculatedConfigs;
|
||||
|
||||
imagesizes = calculatedConfigs.imagesizes;
|
||||
|
||||
const maxWidth = requiredBreakpoints[requiredBreakpoints.length - 1];
|
||||
|
||||
const sources = await Promise.all(
|
||||
formats.map(async (format) => {
|
||||
const srcset = await getSrcset(
|
||||
path,
|
||||
base,
|
||||
requiredBreakpoints,
|
||||
format,
|
||||
{
|
||||
...rest,
|
||||
...rest2,
|
||||
...formatOptions[format],
|
||||
...directiveFormatOptions[format],
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
format,
|
||||
srcset,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const sizes = {
|
||||
width: maxWidth,
|
||||
height: Math.round(maxWidth / rest2.aspect),
|
||||
};
|
||||
|
||||
const object = {
|
||||
fit: objectFit,
|
||||
position: objectPosition,
|
||||
};
|
||||
|
||||
const background = {
|
||||
size: backgroundSize,
|
||||
position: backgroundPosition,
|
||||
};
|
||||
|
||||
const fallback = await getFallbackImage(
|
||||
path,
|
||||
directivePlaceholder || placeholder,
|
||||
image,
|
||||
imageFormat,
|
||||
{ ...formatOptions, ...directiveFormatOptions },
|
||||
{ ...rest, ...rest2 }
|
||||
);
|
||||
|
||||
const returnValue = {
|
||||
media,
|
||||
sources,
|
||||
sizes,
|
||||
fallback,
|
||||
imagesizes,
|
||||
};
|
||||
|
||||
const isBackgroundImage = !!backgroundSize || !!backgroundPosition;
|
||||
|
||||
isBackgroundImage
|
||||
? (returnValue.background = background)
|
||||
: (returnValue.object = object);
|
||||
|
||||
return {
|
||||
media,
|
||||
sources,
|
||||
sizes,
|
||||
object,
|
||||
fallback,
|
||||
imagesizes,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return images;
|
||||
}
|
||||
27
packages/imagetools/api/utils/getAttributesString.js
Normal file
27
packages/imagetools/api/utils/getAttributesString.js
Normal file
@ -0,0 +1,27 @@
|
||||
// @ts-check
|
||||
|
||||
import printWarning from "../../utils/printWarning.js";
|
||||
|
||||
export default function getAttributesString({
|
||||
attributes,
|
||||
element = "",
|
||||
excludeArray = [],
|
||||
}) {
|
||||
const attributesString = Object.keys(attributes)
|
||||
.filter((key) => {
|
||||
if (excludeArray.includes(key)) {
|
||||
printWarning({
|
||||
key,
|
||||
element,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((key) => `${key}="${attributes[key]}"`)
|
||||
.join(" ");
|
||||
|
||||
return attributesString;
|
||||
}
|
||||
97
packages/imagetools/api/utils/getBackgroundStyles.js
Normal file
97
packages/imagetools/api/utils/getBackgroundStyles.js
Normal file
@ -0,0 +1,97 @@
|
||||
// @ts-check
|
||||
|
||||
export default function getBackgroundStyles(
|
||||
images,
|
||||
className,
|
||||
objectFit,
|
||||
objectPosition,
|
||||
fadeInTransition,
|
||||
{ isImg = false, isBackgroundPicture = false, containerClassName = "" } = {}
|
||||
) {
|
||||
const sourcesWithFallback = images.filter(({ fallback }) => fallback);
|
||||
|
||||
if (sourcesWithFallback.length === 0) return "";
|
||||
|
||||
const staticStyles = !fadeInTransition
|
||||
? ""
|
||||
: `
|
||||
${
|
||||
isBackgroundPicture
|
||||
? `
|
||||
.${containerClassName} * {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
.${className} {
|
||||
--opacity: 1;
|
||||
--z-index: 0;
|
||||
}
|
||||
|
||||
${
|
||||
!isBackgroundPicture
|
||||
? `
|
||||
.${className} img {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
.${className}::after {
|
||||
inset: 0;
|
||||
content: "";
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
transition: opacity ${
|
||||
typeof fadeInTransition !== "object"
|
||||
? "1s"
|
||||
: (() => {
|
||||
const {
|
||||
delay = "0s",
|
||||
duration = "1s",
|
||||
timingFunction = "ease",
|
||||
} = fadeInTransition;
|
||||
|
||||
return `${duration} ${timingFunction} ${delay}`;
|
||||
})()
|
||||
};
|
||||
opacity: var(--opacity);
|
||||
z-index: var(--z-index);
|
||||
}
|
||||
`;
|
||||
|
||||
const dynamicStyles = images
|
||||
.map(({ media, fallback, object }) => {
|
||||
const elementSelector = className + (!isImg ? " img" : ""),
|
||||
backgroundElementSelector =
|
||||
className + (fadeInTransition ? "::after" : "");
|
||||
|
||||
const style = `
|
||||
.${elementSelector} {
|
||||
object-fit: ${object?.fit || objectFit};
|
||||
object-position: ${object?.position || objectPosition};
|
||||
}
|
||||
|
||||
.${backgroundElementSelector} {
|
||||
background-size: ${object?.fit || objectFit};
|
||||
background-image: url("${encodeURI(fallback)}");
|
||||
background-position: ${object?.position || objectPosition};
|
||||
}
|
||||
`;
|
||||
|
||||
return media ? `@media ${media} { ${style} }` : style;
|
||||
})
|
||||
.reverse();
|
||||
|
||||
const backgroundStyles = [staticStyles, ...dynamicStyles].join("");
|
||||
|
||||
return backgroundStyles;
|
||||
}
|
||||
77
packages/imagetools/api/utils/getBreakpoints.js
Normal file
77
packages/imagetools/api/utils/getBreakpoints.js
Normal file
@ -0,0 +1,77 @@
|
||||
// @ts-check
|
||||
import printWarning from "../../utils/printWarning.js";
|
||||
|
||||
export default function getBreakpoints(breakpoints, imageWidth) {
|
||||
if (Array.isArray(breakpoints)) {
|
||||
return breakpoints.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
const { count, minWidth = 320 } = breakpoints || {};
|
||||
|
||||
const maxWidth = (() => {
|
||||
if (breakpoints?.maxWidth) return breakpoints.maxWidth;
|
||||
|
||||
if (imageWidth > 3840) {
|
||||
printWarning({
|
||||
message:
|
||||
"The width of the source image is greater than 3840px. The generated breakpoints will be capped at 3840px. If you need breakpoints larger than this, please pass the maxWidth option to the breakpoints property.",
|
||||
});
|
||||
|
||||
return 3840;
|
||||
}
|
||||
|
||||
return imageWidth;
|
||||
})();
|
||||
|
||||
const breakPoints = [];
|
||||
|
||||
const diff = maxWidth - minWidth;
|
||||
|
||||
const n =
|
||||
count ||
|
||||
(maxWidth <= 400
|
||||
? 1
|
||||
: maxWidth <= 640
|
||||
? 2
|
||||
: maxWidth <= 800
|
||||
? 3
|
||||
: maxWidth <= 1024
|
||||
? 4
|
||||
: maxWidth <= 1280
|
||||
? 5
|
||||
: maxWidth <= 1440
|
||||
? 6
|
||||
: maxWidth <= 1920
|
||||
? 7
|
||||
: maxWidth <= 2560
|
||||
? 8
|
||||
: maxWidth <= 2880
|
||||
? 9
|
||||
: maxWidth <= 3840
|
||||
? 10
|
||||
: 11);
|
||||
|
||||
let currentWidth = minWidth;
|
||||
|
||||
n > 1 && breakPoints.push(currentWidth);
|
||||
|
||||
let steps = 0;
|
||||
|
||||
for (let i = 1; i < n; i++) {
|
||||
steps += i;
|
||||
}
|
||||
|
||||
const pixelsPerStep = diff / steps;
|
||||
|
||||
for (let i = 1; i < n - 1; i++) {
|
||||
const next = pixelsPerStep * (n - i) + currentWidth;
|
||||
|
||||
breakPoints.push(Math.round(next));
|
||||
|
||||
currentWidth = next;
|
||||
}
|
||||
|
||||
breakPoints.push(maxWidth);
|
||||
|
||||
return [...new Set(breakPoints)];
|
||||
}
|
||||
34
packages/imagetools/api/utils/getConfigOptions.js
Normal file
34
packages/imagetools/api/utils/getConfigOptions.js
Normal file
@ -0,0 +1,34 @@
|
||||
// @ts-check
|
||||
import getBreakpoints from "./getBreakpoints.js";
|
||||
|
||||
export default function getConfigOptions(
|
||||
imageWidth,
|
||||
imagesizes,
|
||||
breakpoints,
|
||||
format,
|
||||
imageFormat,
|
||||
fallbackFormat,
|
||||
includeSourceFormat
|
||||
) {
|
||||
const formats = [
|
||||
...new Set(
|
||||
[format, includeSourceFormat && imageFormat]
|
||||
.flat()
|
||||
.filter((f) => f && f !== fallbackFormat)
|
||||
),
|
||||
fallbackFormat,
|
||||
];
|
||||
|
||||
const requiredBreakpoints = getBreakpoints(breakpoints, imageWidth);
|
||||
|
||||
imagesizes =
|
||||
typeof imagesizes === "string"
|
||||
? imagesizes
|
||||
: imagesizes(requiredBreakpoints);
|
||||
|
||||
return {
|
||||
formats,
|
||||
imagesizes,
|
||||
requiredBreakpoints,
|
||||
};
|
||||
}
|
||||
48
packages/imagetools/api/utils/getContainerElement.js
Normal file
48
packages/imagetools/api/utils/getContainerElement.js
Normal file
@ -0,0 +1,48 @@
|
||||
// @ts-check
|
||||
import getAttributesString from "./getAttributesString.js";
|
||||
|
||||
export default function getContainerElement({
|
||||
tag,
|
||||
content,
|
||||
className = "",
|
||||
containerAttributes,
|
||||
isBackgroundPicture = false,
|
||||
containerClassName = "",
|
||||
}) {
|
||||
const {
|
||||
class: customClasses = "",
|
||||
style: customInlineStyles = "",
|
||||
...restContainerAttributes
|
||||
} = containerAttributes;
|
||||
|
||||
const attributesString = getAttributesString({
|
||||
attributes: restContainerAttributes,
|
||||
});
|
||||
|
||||
const classAttribute = [
|
||||
isBackgroundPicture
|
||||
? "astro-imagetools-background-picture"
|
||||
: "astro-imagetools-background-image",
|
||||
isBackgroundPicture ? containerClassName : className,
|
||||
customClasses,
|
||||
]
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
const styleAttribute = [
|
||||
isBackgroundPicture ? "position: relative;" : "",
|
||||
customInlineStyles + (customInlineStyles.endsWith(";") ? "" : ";"),
|
||||
]
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
const containerElement = `<${tag}
|
||||
${attributesString}
|
||||
class="${classAttribute}"
|
||||
style="${styleAttribute}"
|
||||
>
|
||||
${content}
|
||||
</${tag}>`;
|
||||
|
||||
return containerElement;
|
||||
}
|
||||
58
packages/imagetools/api/utils/getFallbackImage.js
Normal file
58
packages/imagetools/api/utils/getFallbackImage.js
Normal file
@ -0,0 +1,58 @@
|
||||
// @ts-check
|
||||
|
||||
import util from "node:util";
|
||||
import potrace from "potrace";
|
||||
import getSrcset from "./getSrcset.js";
|
||||
import { sharp } from "../../utils/runtimeChecks.js";
|
||||
|
||||
export default async function getFallbackImage(
|
||||
src,
|
||||
placeholder,
|
||||
image,
|
||||
format,
|
||||
formatOptions,
|
||||
rest
|
||||
) {
|
||||
const base = null;
|
||||
|
||||
switch (placeholder) {
|
||||
case "blurred": {
|
||||
const dataUri = await getSrcset(src, base, [20], format, {
|
||||
inline: true,
|
||||
...rest,
|
||||
...formatOptions[format],
|
||||
});
|
||||
|
||||
return dataUri;
|
||||
}
|
||||
case "tracedSVG": {
|
||||
const { function: fn, options } = formatOptions.tracedSVG;
|
||||
|
||||
const traceSVG = util.promisify(potrace[fn]);
|
||||
|
||||
const imageBuffer = sharp
|
||||
? await image.toBuffer()
|
||||
: Buffer.from(
|
||||
(await image.encode(`image/${format === "jpg" ? "jpeg" : format}`))
|
||||
.data
|
||||
);
|
||||
|
||||
const tracedSVG = await traceSVG(imageBuffer, options);
|
||||
|
||||
return `data:image/svg+xml;utf8,${tracedSVG}`;
|
||||
}
|
||||
case "dominantColor": {
|
||||
if (sharp) {
|
||||
var { r, g, b } = (await image.stats()).dominant;
|
||||
} else {
|
||||
[r, g, b] = image.color;
|
||||
}
|
||||
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" style="background: rgb(${r},${g},${b})"></svg>`;
|
||||
|
||||
return `data:image/svg+xml;utf8,${svg}`;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
138
packages/imagetools/api/utils/getFilteredProps.js
Normal file
138
packages/imagetools/api/utils/getFilteredProps.js
Normal file
@ -0,0 +1,138 @@
|
||||
// @ts-check
|
||||
import filterConfigs from "../../utils/filterConfigs.js";
|
||||
import {
|
||||
supportedConfigs,
|
||||
GlobalConfigOptions,
|
||||
} from "../../utils/runtimeChecks.js";
|
||||
|
||||
const GlobalOnlyProperties = ["cacheDir", "assetFileNames"];
|
||||
|
||||
const NonGlobalSupportedConfigs = supportedConfigs.filter(
|
||||
(key) => !GlobalOnlyProperties.includes(key)
|
||||
);
|
||||
|
||||
const NonProperties = {
|
||||
Img: [
|
||||
"tag",
|
||||
"content",
|
||||
"backgroundSize",
|
||||
"backgroundPosition",
|
||||
"fallbackFormat",
|
||||
"includeSourceFormat",
|
||||
"fadeInTransition",
|
||||
"artDirectives",
|
||||
],
|
||||
Picture: ["tag", "content", "backgroundSize", "backgroundPosition"],
|
||||
BackgroundImage: [
|
||||
"alt",
|
||||
"loading",
|
||||
"decoding",
|
||||
"layout",
|
||||
"objectFit",
|
||||
"objectPosition",
|
||||
"fadeInTransition",
|
||||
],
|
||||
BackgroundPicture: ["alt", "backgroundSize", "backgroundPosition"],
|
||||
};
|
||||
|
||||
const ImgProperties = NonGlobalSupportedConfigs.filter(
|
||||
(key) => !NonProperties.Img.includes(key)
|
||||
),
|
||||
PictureProperties = NonGlobalSupportedConfigs.filter(
|
||||
(key) => !NonProperties.Picture.includes(key)
|
||||
),
|
||||
BackgroundImageProperties = NonGlobalSupportedConfigs.filter(
|
||||
(key) => !NonProperties.BackgroundImage.includes(key)
|
||||
),
|
||||
BackgroundPictureProperties = NonGlobalSupportedConfigs.filter(
|
||||
(key) => !NonProperties.BackgroundPicture.includes(key)
|
||||
);
|
||||
|
||||
const SupportedProperties = {
|
||||
Img: ImgProperties,
|
||||
Picture: PictureProperties,
|
||||
BackgroundImage: BackgroundImageProperties,
|
||||
BackgroundPicture: BackgroundPictureProperties,
|
||||
};
|
||||
|
||||
export default function getFilteredProps(type, props) {
|
||||
const filteredGlobalConfigs = filterConfigs(
|
||||
"Global",
|
||||
GlobalConfigOptions,
|
||||
SupportedProperties[type],
|
||||
{ warn: false }
|
||||
);
|
||||
|
||||
const { search, searchParams } = new URL(props.src, "file://");
|
||||
|
||||
props.src = props.src.replace(search, "");
|
||||
|
||||
const paramOptions = Object.fromEntries(searchParams);
|
||||
|
||||
const filteredLocalProps = filterConfigs(
|
||||
type,
|
||||
{
|
||||
...paramOptions,
|
||||
...props,
|
||||
},
|
||||
SupportedProperties[type]
|
||||
);
|
||||
|
||||
const resolvedProps = {
|
||||
...filteredGlobalConfigs,
|
||||
...filteredLocalProps,
|
||||
};
|
||||
|
||||
const {
|
||||
src,
|
||||
alt,
|
||||
tag = "section",
|
||||
content = "",
|
||||
sizes = function (breakpoints) {
|
||||
const maxWidth = breakpoints[breakpoints.length - 1];
|
||||
return `(min-width: ${maxWidth}px) ${maxWidth}px, 100vw`;
|
||||
},
|
||||
preload,
|
||||
loading = preload ? "eager" : "lazy",
|
||||
decoding = "async",
|
||||
attributes = {},
|
||||
layout = "constrained",
|
||||
placeholder = "blurred",
|
||||
breakpoints,
|
||||
objectFit = "cover",
|
||||
objectPosition = "50% 50%",
|
||||
backgroundSize = "cover",
|
||||
backgroundPosition = "50% 50%",
|
||||
format = type === "Img" ? undefined : ["avif", "webp"],
|
||||
fallbackFormat,
|
||||
includeSourceFormat = true,
|
||||
formatOptions = {
|
||||
tracedSVG: {
|
||||
function: "trace",
|
||||
},
|
||||
},
|
||||
fadeInTransition = true,
|
||||
artDirectives,
|
||||
...transformConfigs
|
||||
} = resolvedProps;
|
||||
|
||||
// prettier-ignore
|
||||
const allProps = {
|
||||
src, alt, tag, content, sizes, preload, loading, decoding, attributes, layout, placeholder,
|
||||
breakpoints, objectFit, objectPosition, backgroundSize, backgroundPosition, format,
|
||||
fallbackFormat, includeSourceFormat, formatOptions, fadeInTransition, artDirectives,
|
||||
...transformConfigs,
|
||||
};
|
||||
|
||||
const filteredProps = filterConfigs(
|
||||
type,
|
||||
allProps,
|
||||
SupportedProperties[type],
|
||||
{ warn: false }
|
||||
);
|
||||
|
||||
return {
|
||||
filteredProps,
|
||||
transformConfigs,
|
||||
};
|
||||
}
|
||||
49
packages/imagetools/api/utils/getFilteredProps.test.ts
Normal file
49
packages/imagetools/api/utils/getFilteredProps.test.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import getFilteredProps from "./getFilteredProps";
|
||||
|
||||
describe("getFilteredProps", () => {
|
||||
it("should should merge in default props", () => {
|
||||
const result = getFilteredProps("Img", { src: "/img.jpeg", alt: "alt" });
|
||||
expect(result).toEqual({
|
||||
filteredProps: {
|
||||
alt: "alt",
|
||||
attributes: {},
|
||||
breakpoints: undefined,
|
||||
decoding: "async",
|
||||
format: undefined,
|
||||
formatOptions: {
|
||||
tracedSVG: {
|
||||
function: "trace",
|
||||
},
|
||||
},
|
||||
layout: "constrained",
|
||||
loading: "lazy",
|
||||
objectFit: "cover",
|
||||
objectPosition: "50% 50%",
|
||||
placeholder: "blurred",
|
||||
preload: undefined,
|
||||
sizes: expect.any(Function),
|
||||
src: "/img.jpeg",
|
||||
},
|
||||
transformConfigs: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept empty string for `alt` prop on Img", () => {
|
||||
const result = getFilteredProps("Img", { src: "/img.jpeg", alt: "" });
|
||||
expect(result).toMatchObject({
|
||||
filteredProps: {
|
||||
alt: "",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should accept empty string for `alt` prop on Picture", () => {
|
||||
const result = getFilteredProps("Picture", { src: "/img.jpeg", alt: "" });
|
||||
expect(result).toMatchObject({
|
||||
filteredProps: {
|
||||
alt: "",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
107
packages/imagetools/api/utils/getImage.js
Normal file
107
packages/imagetools/api/utils/getImage.js
Normal file
@ -0,0 +1,107 @@
|
||||
// @ts-check
|
||||
import crypto from "node:crypto";
|
||||
import objectHash from "object-hash";
|
||||
import getImageSources from "./getImageSources.js";
|
||||
import getProcessedImage from "./getProcessedImage.js";
|
||||
import getArtDirectedImages from "./getArtDirectedImages.js";
|
||||
import pMap from "p-map";
|
||||
|
||||
const imagesData = new Map();
|
||||
|
||||
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
export default async function ({
|
||||
src,
|
||||
type,
|
||||
sizes: imagesizes,
|
||||
format,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
artDirectives,
|
||||
transformConfigs,
|
||||
}) {
|
||||
try {
|
||||
const args = Array.from(arguments);
|
||||
const hash = objectHash(args);
|
||||
if (imagesData.has(hash)) {
|
||||
return imagesData.get(hash);
|
||||
}
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
const { path, base, rest, image, imageWidth, imageHeight, imageFormat } =
|
||||
await getProcessedImage(src, transformConfigs);
|
||||
|
||||
await delay(250);
|
||||
src = path;
|
||||
|
||||
rest.aspect = `${imageWidth / imageHeight}`;
|
||||
if (!fallbackFormat) {
|
||||
fallbackFormat = imageFormat;
|
||||
}
|
||||
|
||||
// Fetch both image sources and art-directed images
|
||||
const [mainImage, artDirectedImages] = await pMap(
|
||||
[
|
||||
async () =>
|
||||
await getImageSources(
|
||||
src,
|
||||
base,
|
||||
image,
|
||||
format,
|
||||
imageWidth,
|
||||
imagesizes,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
imageFormat,
|
||||
formatOptions,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
rest
|
||||
),
|
||||
async () => {
|
||||
await delay(250);
|
||||
return await getArtDirectedImages(
|
||||
artDirectives,
|
||||
placeholder,
|
||||
format,
|
||||
imagesizes,
|
||||
breakpoints,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
formatOptions,
|
||||
rest
|
||||
);
|
||||
},
|
||||
],
|
||||
async (task) => await task(),
|
||||
{ concurrency: 1 }
|
||||
);
|
||||
|
||||
// Ensure artDirectedImages is an array
|
||||
const images = Array.isArray(artDirectedImages) ? [...artDirectedImages, mainImage] : [mainImage];
|
||||
|
||||
const uuid = crypto.randomBytes(4).toString("hex").toUpperCase();
|
||||
|
||||
const returnObject = {
|
||||
uuid,
|
||||
images,
|
||||
};
|
||||
|
||||
imagesData.set(hash, returnObject);
|
||||
|
||||
const end = performance.now();
|
||||
|
||||
console.log(
|
||||
`Responsive Image sets generated for ${type} at ${args[0].src} in ${end - start}ms`
|
||||
);
|
||||
|
||||
return returnObject;
|
||||
} catch (error) {
|
||||
console.error("Error processing images:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
91
packages/imagetools/api/utils/getImageSources.js
Normal file
91
packages/imagetools/api/utils/getImageSources.js
Normal file
@ -0,0 +1,91 @@
|
||||
// @ts-check
|
||||
import getSrcset from "./getSrcset.js";
|
||||
import getConfigOptions from "./getConfigOptions.js";
|
||||
import getFallbackImage from "./getFallbackImage.js";
|
||||
import pMap from "p-map";
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export default async function getImageSources(
|
||||
src,
|
||||
base,
|
||||
image,
|
||||
format,
|
||||
imageWidth,
|
||||
imagesizes,
|
||||
breakpoints,
|
||||
placeholder,
|
||||
imageFormat,
|
||||
formatOptions,
|
||||
fallbackFormat,
|
||||
includeSourceFormat,
|
||||
rest
|
||||
) {
|
||||
try {
|
||||
const calculatedConfigs = getConfigOptions(
|
||||
imageWidth,
|
||||
imagesizes,
|
||||
breakpoints,
|
||||
format,
|
||||
imageFormat,
|
||||
fallbackFormat,
|
||||
includeSourceFormat
|
||||
);
|
||||
|
||||
const { formats, requiredBreakpoints } = calculatedConfigs;
|
||||
imagesizes = calculatedConfigs.imagesizes;
|
||||
const maxWidth = requiredBreakpoints[requiredBreakpoints.length - 1];
|
||||
const sliceLength = -(maxWidth.toString().length + 2);
|
||||
|
||||
const sources = await pMap(
|
||||
formats,
|
||||
async (format) => {
|
||||
try {
|
||||
await delay(250);
|
||||
const srcset = await getSrcset(src, base, requiredBreakpoints, format, {
|
||||
...rest,
|
||||
...formatOptions[format],
|
||||
});
|
||||
|
||||
const srcsets = srcset.split(", ");
|
||||
const srcObject =
|
||||
format === fallbackFormat
|
||||
? { src: srcsets[srcsets.length - 1].slice(0, sliceLength) }
|
||||
: {};
|
||||
|
||||
return {
|
||||
...srcObject,
|
||||
format,
|
||||
srcset,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error processing format ${format}:`, error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{ concurrency: 1 }
|
||||
);
|
||||
|
||||
const filteredSources = sources.filter(Boolean);
|
||||
|
||||
const sizes = {
|
||||
width: maxWidth,
|
||||
height: Math.round(maxWidth / rest.aspect),
|
||||
};
|
||||
|
||||
const fallback = await getFallbackImage(
|
||||
src,
|
||||
placeholder,
|
||||
image,
|
||||
fallbackFormat,
|
||||
formatOptions,
|
||||
rest
|
||||
)
|
||||
return { sources: filteredSources, sizes, fallback, imagesizes };
|
||||
} catch (error) {
|
||||
console.error("Error in getImageSources:", error);
|
||||
return { sources: [], sizes: {}, fallback: null, imagesizes: null };
|
||||
}
|
||||
}
|
||||
80
packages/imagetools/api/utils/getImgElement.js
Normal file
80
packages/imagetools/api/utils/getImgElement.js
Normal file
@ -0,0 +1,80 @@
|
||||
// @ts-check
|
||||
|
||||
import getAttributesString from "./getAttributesString.js";
|
||||
|
||||
export default function getImgElement({
|
||||
src,
|
||||
alt,
|
||||
sizes,
|
||||
style,
|
||||
srcset,
|
||||
loading,
|
||||
decoding,
|
||||
imagesizes,
|
||||
fadeInTransition,
|
||||
layoutStyles,
|
||||
imgAttributes,
|
||||
imgClassName = "",
|
||||
}) {
|
||||
const {
|
||||
class: customClasses = "",
|
||||
style: customInlineStyles = "",
|
||||
onload: customOnload = "",
|
||||
...restImgAttributes
|
||||
} = imgAttributes;
|
||||
|
||||
const attributesString = getAttributesString({
|
||||
attributes: restImgAttributes,
|
||||
element: "img",
|
||||
excludeArray: [
|
||||
"src",
|
||||
"alt",
|
||||
"srcset",
|
||||
"sizes",
|
||||
"width",
|
||||
"height",
|
||||
"loading",
|
||||
"decoding",
|
||||
],
|
||||
});
|
||||
|
||||
const classAttribute = ["astro-imagetools-img", imgClassName, customClasses]
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
const styleAttribute = [
|
||||
"display: inline-block; overflow: hidden; vertical-align: middle;",
|
||||
customInlineStyles + (customInlineStyles.endsWith(";") ? "" : ";"),
|
||||
layoutStyles,
|
||||
]
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
const onloadAttribute = [
|
||||
!imgClassName && style
|
||||
? fadeInTransition
|
||||
? `parentElement.style.setProperty('--z-index', 1); parentElement.style.setProperty('--opacity', 0);`
|
||||
: `parentElement.style.backgroundImage = 'unset';`
|
||||
: "",
|
||||
customOnload,
|
||||
]
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
const imgElement = `<img
|
||||
${attributesString}
|
||||
src="${src}"
|
||||
${typeof alt === "string" ? `alt="${alt}"` : ""}
|
||||
srcset="${srcset}"
|
||||
sizes="${imagesizes}"
|
||||
width="${sizes.width}"
|
||||
height="${sizes.height}"
|
||||
${loading ? `loading="${loading}"` : ""}
|
||||
${decoding ? `decoding="${decoding}"` : ""}
|
||||
class="${classAttribute}"
|
||||
style="${styleAttribute}"
|
||||
onload="${onloadAttribute}"
|
||||
/>`;
|
||||
|
||||
return imgElement;
|
||||
}
|
||||
16
packages/imagetools/api/utils/getLayoutStyles.js
Normal file
16
packages/imagetools/api/utils/getLayoutStyles.js
Normal file
@ -0,0 +1,16 @@
|
||||
// @ts-check
|
||||
|
||||
export default function getLayoutStyles({
|
||||
layout = null,
|
||||
isBackgroundImage = false,
|
||||
}) {
|
||||
return isBackgroundImage
|
||||
? "width: 100%; height: 100%;"
|
||||
: layout === "fill"
|
||||
? `width: 100%; height: 100%;`
|
||||
: layout === "fullWidth"
|
||||
? `width: 100%; height: auto;`
|
||||
: layout === "fixed"
|
||||
? ""
|
||||
: "max-width: 100%; height: auto;";
|
||||
}
|
||||
34
packages/imagetools/api/utils/getLinkElement.js
Normal file
34
packages/imagetools/api/utils/getLinkElement.js
Normal file
@ -0,0 +1,34 @@
|
||||
// @ts-check
|
||||
import getAttributesString from "./getAttributesString.js";
|
||||
|
||||
export default function getLinkElement({
|
||||
images = [],
|
||||
preload = "",
|
||||
imagesizes = "",
|
||||
linkAttributes,
|
||||
}) {
|
||||
const imagesrcset =
|
||||
preload &&
|
||||
images[images.length - 1]?.sources.find(
|
||||
({ format: fmt }) => fmt === preload
|
||||
)?.srcset;
|
||||
|
||||
const attributesString = getAttributesString({
|
||||
element: "link",
|
||||
attributes: linkAttributes,
|
||||
excludeArray: ["as", "rel", "imagesizes", "imagesrcset"],
|
||||
});
|
||||
|
||||
const linkElement =
|
||||
preload && images.length
|
||||
? `<link
|
||||
${attributesString}
|
||||
as="image"
|
||||
rel="preload"
|
||||
imagesizes="${imagesizes}"
|
||||
imagesrcset="${imagesrcset}"
|
||||
/>`
|
||||
: "";
|
||||
|
||||
return linkElement;
|
||||
}
|
||||
14
packages/imagetools/api/utils/getLinkElement.test.ts
Normal file
14
packages/imagetools/api/utils/getLinkElement.test.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import getLinkElement from "./getLinkElement";
|
||||
|
||||
describe("getLinkElement", () => {
|
||||
it("returns an empty string if preload is not set", () => {
|
||||
const result = getLinkElement({ linkAttributes: {} });
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
it("returns an empty string if no images are provided", () => {
|
||||
const result = getLinkElement({ linkAttributes: {}, preload: "webp" });
|
||||
expect(result).toBe("");
|
||||
});
|
||||
});
|
||||
43
packages/imagetools/api/utils/getPictureElement.js
Normal file
43
packages/imagetools/api/utils/getPictureElement.js
Normal file
@ -0,0 +1,43 @@
|
||||
// @ts-check
|
||||
import getAttributesString from "./getAttributesString.js";
|
||||
|
||||
export default function getPictureElement({
|
||||
sources,
|
||||
className,
|
||||
layoutStyles,
|
||||
pictureAttributes,
|
||||
isBackgroundPicture = false,
|
||||
}) {
|
||||
const {
|
||||
class: customClasses = "",
|
||||
style: customInlineStyles = "",
|
||||
...restPictureAttributes
|
||||
} = pictureAttributes;
|
||||
|
||||
const attributesString = getAttributesString({
|
||||
attributes: restPictureAttributes,
|
||||
});
|
||||
|
||||
const classAttribute = ["astro-imagetools-picture", className, customClasses]
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
const styleAttribute = [
|
||||
isBackgroundPicture
|
||||
? `position: absolute; z-index: 0; width: 100%; height: 100%; display: inline-block;`
|
||||
: `position: relative; display: inline-block;`,
|
||||
customInlineStyles + (customInlineStyles.endsWith(";") ? "" : ";"),
|
||||
layoutStyles,
|
||||
]
|
||||
.join(" ")
|
||||
.trim();
|
||||
|
||||
const pictureElement = `<picture
|
||||
${attributesString}
|
||||
class="${classAttribute}"
|
||||
style="${styleAttribute}"
|
||||
>${sources.join("\n")}
|
||||
</picture>`;
|
||||
|
||||
return pictureElement;
|
||||
}
|
||||
59
packages/imagetools/api/utils/getProcessedImage.js
Normal file
59
packages/imagetools/api/utils/getProcessedImage.js
Normal file
@ -0,0 +1,59 @@
|
||||
// @ts-check
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { extname, relative, resolve } from "node:path";
|
||||
|
||||
import { getSrcPath } from "./getSrcPath.js";
|
||||
import getResolvedSrc from "./getResolvedSrc.js";
|
||||
import { cwd, sharp } from "../../utils/runtimeChecks.js";
|
||||
import throwErrorIfUnsupported from "./throwErrorIfUnsupported.js";
|
||||
|
||||
const { getImageDetails } = await (sharp
|
||||
? import("./imagetools.js")
|
||||
: import("./codecs.js"));
|
||||
|
||||
export default async function getProcessedImage(src, transformConfigs) {
|
||||
throwErrorIfUnsupported(src, extname(src).slice(1));
|
||||
|
||||
let base;
|
||||
if (src.match("(http://|https://|data:image/).*")) {
|
||||
({ src, base } = await getResolvedSrc(src))
|
||||
} else {
|
||||
const {
|
||||
default: { isSsrBuild },
|
||||
} = await import("../../astroViteConfigs.js")
|
||||
|
||||
if (isSsrBuild) {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const assetPath = resolve(filename, "../../client") + src
|
||||
src = "/" + relative(cwd, assetPath);
|
||||
}
|
||||
}
|
||||
const {
|
||||
w,
|
||||
h,
|
||||
ar,
|
||||
width = w,
|
||||
height = h,
|
||||
aspect = ar,
|
||||
...rest
|
||||
} = transformConfigs;
|
||||
|
||||
const path = src.replace(/\\/g, `/`);
|
||||
|
||||
const { image, imageWidth, imageHeight, imageFormat } = await getImageDetails(
|
||||
await getSrcPath(src),
|
||||
width,
|
||||
height,
|
||||
aspect
|
||||
);
|
||||
|
||||
return {
|
||||
path,
|
||||
base,
|
||||
rest,
|
||||
image,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
imageFormat,
|
||||
};
|
||||
}
|
||||
50
packages/imagetools/api/utils/getResolvedSrc.js
Normal file
50
packages/imagetools/api/utils/getResolvedSrc.js
Normal file
@ -0,0 +1,50 @@
|
||||
// @ts-check
|
||||
import fs from "node:fs";
|
||||
import crypto from "node:crypto";
|
||||
import { join, parse, relative } from "node:path";
|
||||
import throwErrorIfUnsupported from "./throwErrorIfUnsupported.js";
|
||||
import {
|
||||
cwd,
|
||||
fsCachePath,
|
||||
supportedImageTypes,
|
||||
} from "../../utils/runtimeChecks.js";
|
||||
|
||||
const { fileTypeFromBuffer } = await import("file-type");
|
||||
|
||||
export default async function getResolvedSrc(src) {
|
||||
const token = crypto.createHash("md5").update(src).digest("hex");
|
||||
|
||||
let filepath = fsCachePath + token;
|
||||
|
||||
const fileExists = (() => {
|
||||
for (const type of supportedImageTypes) {
|
||||
const fileExists = fs.existsSync(filepath + `.${type}`);
|
||||
|
||||
if (fileExists) {
|
||||
filepath += `.${type}`;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (!fileExists) {
|
||||
const buffer = Buffer.from(await (await fetch(src)).arrayBuffer());
|
||||
|
||||
const { ext } = (await fileTypeFromBuffer(buffer)) || {};
|
||||
|
||||
throwErrorIfUnsupported(src, ext);
|
||||
|
||||
filepath += `.${ext}`;
|
||||
|
||||
fs.writeFileSync(filepath, buffer);
|
||||
}
|
||||
|
||||
const base = /^https?:/.test(src)
|
||||
? parse(new URL(src).pathname).name
|
||||
: undefined;
|
||||
|
||||
src = join("/", relative(cwd, filepath));
|
||||
|
||||
return { src, base };
|
||||
}
|
||||
32
packages/imagetools/api/utils/getSrcPath.js
Normal file
32
packages/imagetools/api/utils/getSrcPath.js
Normal file
@ -0,0 +1,32 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
// To strip off params when checking for file on disk.
|
||||
const paramPattern = /\?.*/;
|
||||
|
||||
/**
|
||||
* getSrcPath allows the use of `src` attributes relative to either the public folder or project root.
|
||||
*
|
||||
* It first checks to see if the src is a file relative to the project root.
|
||||
* If the file isn't found, it will look in the public folder.
|
||||
* Finally, if it still can't be found, the original input will be returned.
|
||||
*/
|
||||
export async function getSrcPath(src) {
|
||||
const { default: astroViteConfigs } = await import(
|
||||
"../../astroViteConfigs.js"
|
||||
);
|
||||
|
||||
// If this is already resolved to a file, return it.
|
||||
if (fs.existsSync(src.replace(paramPattern, ""))) return src;
|
||||
|
||||
const rootPath = path.join(astroViteConfigs.rootDir, src);
|
||||
const rootTest = rootPath.replace(paramPattern, "");
|
||||
if (fs.existsSync(rootTest)) return rootPath;
|
||||
|
||||
const publicPath = path.join(astroViteConfigs.publicDir, src);
|
||||
const publicTest = publicPath.replace(paramPattern, "");
|
||||
if (fs.existsSync(publicTest)) return publicPath;
|
||||
|
||||
// Fallback
|
||||
return src;
|
||||
}
|
||||
67
packages/imagetools/api/utils/getSrcPath.test.ts
Normal file
67
packages/imagetools/api/utils/getSrcPath.test.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import path from "node:path";
|
||||
import { describe, expect, it, afterAll, vi } from "vitest";
|
||||
import { getSrcPath } from "./getSrcPath";
|
||||
|
||||
vi.mock("../../astroViteConfigs.js", () => {
|
||||
return {
|
||||
default: {
|
||||
rootDir: buildPath(),
|
||||
// Custom publicDir
|
||||
publicDir: buildPath("out"),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Build an absolute path to the target in the fixture directory
|
||||
*/
|
||||
function buildPath(target = "") {
|
||||
return path.resolve(__dirname, "../../test-fixtures/getSrcPath", target);
|
||||
}
|
||||
|
||||
describe("getLinkElement", () => {
|
||||
afterAll(() => {
|
||||
vi.unmock("../../astroViteConfigs.js");
|
||||
});
|
||||
|
||||
it("finds a file in the root of the project", async () => {
|
||||
const result = await getSrcPath("root.jpeg");
|
||||
expect(result).toBe(buildPath("root.jpeg"));
|
||||
});
|
||||
|
||||
it("finds a file in the public folder", async () => {
|
||||
const result = await getSrcPath("out.jpeg");
|
||||
expect(result).toBe(buildPath("out/out.jpeg"));
|
||||
});
|
||||
|
||||
it("returns an absolute path unchanged, if it exists", async () => {
|
||||
const result = await getSrcPath(buildPath("out/out.jpeg"));
|
||||
expect(result).toBe(buildPath("out/out.jpeg"));
|
||||
});
|
||||
|
||||
it("handles query parameters", async () => {
|
||||
const result = await getSrcPath("root.jpeg?w=200");
|
||||
expect(result).toBe(buildPath("root.jpeg?w=200"));
|
||||
});
|
||||
|
||||
it("handles query parameters for public-resolved files", async () => {
|
||||
const result = await getSrcPath("out.jpeg?w=200");
|
||||
expect(result).toBe(buildPath("out/out.jpeg?w=200"));
|
||||
});
|
||||
|
||||
it("returns the original input if the file is not found", async () => {
|
||||
const result = await getSrcPath(
|
||||
"https://cdn.nedis.com/images/products_high_res/TVRC2080BK_P30.JPG"
|
||||
);
|
||||
expect(result).toBe(
|
||||
"https://cdn.nedis.com/images/products_high_res/TVRC2080BK_P30.JPG"
|
||||
);
|
||||
});
|
||||
|
||||
it("finds relative paths correctly", async () => {
|
||||
const outResult = await getSrcPath("./out/out.jpeg");
|
||||
const rootResult = await getSrcPath("./root.jpeg");
|
||||
expect(outResult).toBe(buildPath("out/out.jpeg"));
|
||||
expect(rootResult).toBe(buildPath("root.jpeg"));
|
||||
});
|
||||
});
|
||||
39
packages/imagetools/api/utils/getSrcset.js
Normal file
39
packages/imagetools/api/utils/getSrcset.js
Normal file
@ -0,0 +1,39 @@
|
||||
// @ts-check
|
||||
import { getSrcPath } from "./getSrcPath.js";
|
||||
|
||||
export default async function getSrcset(
|
||||
src,
|
||||
base,
|
||||
breakpoints,
|
||||
format,
|
||||
options
|
||||
) {
|
||||
options = {
|
||||
format,
|
||||
w: breakpoints,
|
||||
...options,
|
||||
};
|
||||
|
||||
const keys = Object.keys(options);
|
||||
|
||||
const params = keys.length
|
||||
? keys
|
||||
.map((key) =>
|
||||
Array.isArray(options[key])
|
||||
? `&${key}=${options[key].join(";")}`
|
||||
: `&${key}=${options[key]}`
|
||||
)
|
||||
.join("")
|
||||
: "";
|
||||
|
||||
const id = `${src}?${params.slice(1)}`;
|
||||
|
||||
const fullPath = await getSrcPath(id);
|
||||
|
||||
const { default: load } = await import("../../plugin/hooks/load.js");
|
||||
|
||||
// @ts-ignore
|
||||
const srcset = (await load(fullPath, base)).slice(16, -1);
|
||||
|
||||
return srcset;
|
||||
}
|
||||
15
packages/imagetools/api/utils/getStyleElement.js
Normal file
15
packages/imagetools/api/utils/getStyleElement.js
Normal file
@ -0,0 +1,15 @@
|
||||
// @ts-check
|
||||
import getAttributesString from "./getAttributesString.js";
|
||||
|
||||
export default function getStyleElement({
|
||||
styleAttributes,
|
||||
backgroundStyles = "",
|
||||
}) {
|
||||
const attributesString = getAttributesString({
|
||||
attributes: styleAttributes,
|
||||
});
|
||||
|
||||
const styleElement = `<style ${attributesString}>${backgroundStyles}</style>`;
|
||||
|
||||
return styleElement;
|
||||
}
|
||||
40
packages/imagetools/api/utils/imagetools.js
Normal file
40
packages/imagetools/api/utils/imagetools.js
Normal file
@ -0,0 +1,40 @@
|
||||
// @ts-check
|
||||
import {
|
||||
builtins,
|
||||
loadImage,
|
||||
applyTransforms,
|
||||
generateTransforms,
|
||||
} from "imagetools-core";
|
||||
export {
|
||||
loadImage
|
||||
} from "imagetools-core";
|
||||
export async function getImageDetails(path, width, height, aspect) {
|
||||
const loadedImage = loadImage(path);
|
||||
|
||||
if (aspect && !width && !height) {
|
||||
if (!width && !height) {
|
||||
({ width } = await loadedImage.metadata());
|
||||
}
|
||||
|
||||
if (width) {
|
||||
height = width / aspect;
|
||||
}
|
||||
|
||||
if (height) {
|
||||
width = height * aspect;
|
||||
}
|
||||
}
|
||||
|
||||
const { image, metadata } = await applyTransforms(
|
||||
generateTransforms({ width, height }, builtins).transforms,
|
||||
loadedImage
|
||||
);
|
||||
|
||||
const {
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
format: imageFormat,
|
||||
} = metadata;
|
||||
|
||||
return { image, imageWidth, imageHeight, imageFormat };
|
||||
}
|
||||
14
packages/imagetools/api/utils/throwErrorIfUnsupported.js
Normal file
14
packages/imagetools/api/utils/throwErrorIfUnsupported.js
Normal file
@ -0,0 +1,14 @@
|
||||
// @ts-check
|
||||
import { supportedImageTypes } from "../../utils/runtimeChecks.js";
|
||||
|
||||
export default function throwErrorIfUnsupported(src, ext) {
|
||||
if (!ext && typeof ext !== "string") {
|
||||
throw new Error(`Failed to load ${src}; Invalid image format`);
|
||||
}
|
||||
|
||||
if (ext && !supportedImageTypes.includes(ext.toLowerCase())) {
|
||||
throw new Error(
|
||||
`Failed to load ${src}; Invalid image format ${ext} or the format is not supported by astro-imagetools`
|
||||
);
|
||||
}
|
||||
}
|
||||
12
packages/imagetools/astroViteConfigs.js
Normal file
12
packages/imagetools/astroViteConfigs.js
Normal file
@ -0,0 +1,12 @@
|
||||
export default {
|
||||
"environment": "build",
|
||||
"isSsrBuild": false,
|
||||
"projectBase": "",
|
||||
"publicDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\public\\",
|
||||
"rootDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\",
|
||||
"mode": "production",
|
||||
"outDir": "dist",
|
||||
"assetsDir": "/_astro",
|
||||
"sourcemap": false,
|
||||
"assetFileNames": "/_astro/[name]@[width].[hash][extname]"
|
||||
}
|
||||
46
packages/imagetools/components/BackgroundImage.astro
Normal file
46
packages/imagetools/components/BackgroundImage.astro
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
import renderBackgroundImage from "../api/renderBackgroundImage.js";
|
||||
import type { BackgroundImageConfigOptions } from "../types.d";
|
||||
|
||||
const content = await Astro.slots.render("default");
|
||||
|
||||
declare interface Props
|
||||
extends Pick<
|
||||
BackgroundImageConfigOptions,
|
||||
Exclude<keyof BackgroundImageConfigOptions, "content">
|
||||
> {}
|
||||
|
||||
const { link, style, htmlElement } = await renderBackgroundImage({
|
||||
content,
|
||||
...(Astro.props as Props),
|
||||
});
|
||||
---
|
||||
|
||||
<Fragment set:html={link + style + htmlElement} />
|
||||
|
||||
<script>
|
||||
const { classList } = document.documentElement;
|
||||
|
||||
const addClass = classList.add.bind(classList);
|
||||
|
||||
addClass("jpeg");
|
||||
addClass("png");
|
||||
|
||||
const isFormatSupported = (format, dataUri) => {
|
||||
const image = new Image();
|
||||
|
||||
image.src = `data:image/${format};base64,${dataUri}`;
|
||||
|
||||
image.onload = addClass(format);
|
||||
};
|
||||
|
||||
// TODO: Check support for JXL images
|
||||
// isFormatSupported("jxl", "/woAEBAJCAQBACwASxLFgoUJEP3D/wA=");
|
||||
|
||||
isFormatSupported("webp", "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==");
|
||||
|
||||
isFormatSupported(
|
||||
"avif",
|
||||
"AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A="
|
||||
);
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user