migrate from AmeVox

This commit is contained in:
FullGreaM 2026-04-27 02:16:02 +03:00
parent 8246bf3492
commit 73171e316a
10 changed files with 555 additions and 13 deletions

316
README.md
View File

@ -1,2 +1,316 @@
# Gefest
# Gefest Framework
Gefest is a lightweight, TypeScript-based framework for building and managing HTML elements programmatically. It provides a class-based approach to create reusable UI components with built-in support for attributes, classes, styles, and event handling.
## Features
- **Class-based Element Creation**: Define HTML elements as TypeScript classes
- **Attribute Management**: Easy setting, getting, and removing of HTML attributes
- **CSS Class Handling**: Add and remove classes dynamically
- **Style System**: Extensible styling with custom style classes
- **Event Handling**: Built-in click event management with automatic re-rendering
- **Element Registration**: Automatic ID generation and DOM integration
- **Primitive Components**: Pre-built HTML element wrappers
- **Dynamic Updates**: Re-render elements on the fly
## Installation
Gefest is included as a module in this project. To use it in your TypeScript files:
```typescript
import { GefestEngine, GefestElement } from './engine';
import { GefestButton } from './primitives/button';
// ... other imports as needed
```
## Core Concepts
### GefestElement
The base abstract class for all Gefest elements. It provides:
- Content management (strings or nested elements)
- Attribute handling
- Class management
- Style application
- Event binding
- Automatic re-rendering on updates
### GefestEngine
Manages the lifecycle of Gefest elements:
- Registers elements with unique IDs
- Activates event handlers on DOM elements
- Provides the main entry point for applications
- Handles re-rendering of updated elements
### GefestStyle
Abstract base class for styling systems. Implement custom styles by extending this class.
## Basic Usage
### Creating Elements
```typescript
import { GefestButton } from './primitives/button';
// Create a simple button
const button = new GefestButton("Click me!");
console.log(button.build()); // <button data-gefest-id="gefest-abc123">Click me!</button>
```
### Setting Attributes
```typescript
const button = new GefestButton("Submit");
button.setAttribute('type', 'submit');
button.setAttribute('disabled', 'true');
```
### Managing Classes
```typescript
const button = new GefestButton("Button");
button.addClass('btn');
button.addClass('btn-primary');
button.removeClass('btn');
console.log(button.getClassList()); // ['btn-primary']
```
### Hiding Elements
```typescript
const button = new GefestButton("Hidden Button");
button.isHidden = true; // Sets the 'hidden' attribute on the element
```
### Event Handling
```typescript
const button = new GefestButton("Click me!");
button.onClick = () => {
console.log('Button clicked!');
// The framework automatically re-renders after click
};
```
### Updating Elements
```typescript
const button = new GefestButton("Initial");
button.update(); // Re-renders the element in the DOM
```
### Running the Application
```typescript
import { GefestEngine } from './engine';
async function main() {
// Create and configure your elements here
const button = new GefestButton("Hello World");
button.onClick = () => alert('Hello!');
// Insert into DOM
document.body.innerHTML = button.build();
}
GefestEngine.main(main);
```
## Primitives
Gefest includes several pre-built primitive elements:
### GefestButton
Wraps HTML `<button>` elements.
```typescript
const button = new GefestButton("Click me");
button.setAttribute('type', 'submit');
```
### GefestA
Wraps HTML `<a>` elements.
```typescript
const link = new GefestA("Visit site", "https://example.com");
link.setAttribute('target', '_blank');
```
### GefestImg
Wraps HTML `<img>` elements.
```typescript
const image = new GefestImg("", "path/to/image.jpg");
image.setAttribute('alt', 'Description');
```
### Other Primitives
- `GefestP`: Paragraph (`<p>`)
- `GefestSpan`: Span (`<span>`)
- `GefestHeader`: Header (`<h1>`)
- `GefestI`: Italic (`<i>`)
- `GefestSmall`: Small text (`<small>`)
- `GefestCenter`: Centered content (`<center>`)
## Advanced Features
### Nested Elements
```typescript
const link = new GefestA("Read more", "#");
const button = new GefestButton([link, " about this"]);
```
### Custom Elements
Create your own elements by extending `GefestElement`:
```typescript
import { GefestElement } from './element';
export class GefestDiv extends GefestElement {
protected wrapHTML(content: string): string {
const attrString = this.attributesToString();
return `<div ${attrString}>${content}</div>`;
}
}
```
### Custom Styles
Implement custom styling by extending `GefestStyle`:
```typescript
import { GefestStyle, GefestElement } from './style';
export class BootstrapStyle extends GefestStyle {
applyStyle(html: string, element?: GefestElement): string {
// Apply Bootstrap classes or inline styles
if (element?.getClassList().includes('btn')) {
return html.replace('<button', '<button class="btn btn-primary"');
}
return html;
}
}
```
Apply styles to elements:
```typescript
const button = new GefestButton("Styled Button");
button.style = new BootstrapStyle();
```
### Parsing HTML
Convert existing HTML strings to Gefest elements:
```typescript
const element = GefestElement.fromHTML('<button class="btn">Click</button>');
```
## API Reference
### GefestElement
#### Properties
- `gefestId: string` - Unique identifier for the element (readonly)
- `style: GefestStyle | null` - Style to apply to the element
- `onClick: (() => void) | null` - Click event handler (getter/setter)
- `isHidden: boolean` - Whether the element should be hidden (sets the 'hidden' attribute when true)
#### Methods
- `constructor(content: (GefestElement | string)[] | string)`
- `build(isFromStyle?: boolean): string`
- `update(): void` - Re-renders the element in the DOM
- `setAttribute(key: string, value: string): void`
- `getAttribute(key: string): string | undefined`
- `removeAttribute(key: string): void`
- `addClass(className: string): void`
- `removeClass(className: string): void`
- `getClassList(): string[]`
- `setPersonalStyle(style: string | null): void`
#### Static Methods
- `fromHTML(html: string): GefestElement`
### GefestEngine
#### Static Methods
- `register(element: GefestElement): string`
- `activateOnClick(): Promise<void>`
- `main(main: () => Promise<void>): Promise<void>`
- `reRenderByGI(gefestId: string): void`
### GefestStyle
#### Methods
- `applyStyle(html: string, element?: GefestElement): string`
## Examples
### Complete Application
```typescript
import { GefestEngine } from './engine';
import { GefestButton, GefestP } from './primitives';
async function main() {
const title = new GefestP("Welcome to Gefest!");
title.addClass('title');
const button = new GefestButton("Get Started");
button.addClass('start-btn');
button.onClick = () => {
alert('Welcome!');
};
// For custom elements, define them as shown above
const container = new (class extends GefestElement {
protected wrapHTML(content: string): string {
const attrString = this.attributesToString();
return `<div ${attrString}>${content}</div>`;
}
})([title, button]);
container.addClass('container');
document.body.innerHTML = container.build();
}
GefestEngine.main(main);
```
### Form Creation
```typescript
import { GefestButton } from './primitives/button';
const submitBtn = new GefestButton("Submit");
submitBtn.setAttribute('type', 'submit');
submitBtn.addClass('btn-submit');
const form = document.createElement('form');
form.innerHTML = submitBtn.build();
document.body.appendChild(form);
```
## Contributing
To add new primitives or extend the framework:
1. Create new classes extending `GefestElement`
2. Implement the `wrapHTML` method to return the appropriate HTML tag
3. Add event handling if needed
4. Test with `GefestEngine.main()`
## License
This framework is part of the AmeVox project. See project license for details.

View File

@ -1,33 +1,149 @@
import { GefestEngine } from './engine';
import { GefestStyle } from './style';
// The general class of Gefest
export abstract class GefestElement {
style: GefestStyle | null = null;
isHidden: boolean = false;
//onClick: (() => void) | null = null;
readonly gefestId: string;
protected content: (GefestElement | string)[] | string;
protected attributes: Record<string, string> = {};
protected classList: Set<string> = new Set();
protected clickHandler: (() => void) | null = null;
set onClick(handler: (() => void) | null) {
if (handler) {
this.clickHandler = handler;
} else {
this.clickHandler = null;
}
}
get onClick(): (() => void) | null {
if (this.clickHandler === null)
return null;
return () => {
this.clickHandler!();
GefestEngine.activateOnClick();
};
}
/**
* Creates a new instance of the element from an HTML string.
* @param html The HTML string to parse.
* @returns The created GefestElement.
*/
static fromHTML(html: string): GefestElement {
const htmlElement = new DOMParser().parseFromString(html, 'text/html').body.firstChild as HTMLElement;
const element = new (class extends GefestElement {
protected wrapHTML(content: string): string {
return content;
}
})(htmlElement.innerHTML);
for (const attr of htmlElement.attributes) {
try {
element.setAttribute(attr.name, attr.value);
} catch (_) {}
}
for (const className of htmlElement.classList) {
element.addClass(className);
}
return element;
}
/**
* Creates a new instance of the element.
* @param content The content for the element. Can be a string, an array of strings, an array of GefestElements or a combination of both.
*/
constructor (content : (GefestElement | string)[] | string = []) {
this.gefestId = GefestEngine.register(this);
this.content = content;
}
/**
* The build method is used to build the element and return it as a string. Dont't redefine this method in the child class, if you want to add some styles to the element, use the setStyle method and then call the render method in the build method of the child class.
* @return {string} The built element as a string.
*/
build (): string {
return this.applyStyle(this.render());
* Call to GefestEngine for rerender element
*/
update (): void {
GefestEngine.reRenderByGI(this.gefestId);
}
/**
* Builds the element and returns it as a string. If the element has a style, it applies the style to the rendered HTML.
* @param isFromStyle Indicates if the build is being called from a style application. This prevents infinite recursion when applying styles.
* @returns The built element as a string.
*/
build (isFromStyle: boolean = false): string {
if (this.isHidden)
this.setAttribute('hidden', '');
else
this.removeAttribute('hidden');
if (!isFromStyle)
return this.applyStyle(this.render(), this);
return this.render();
};
/**
* Checks if an attribute key is reserved.
* @param key The attribute key.
*/
checkReservedAttribute(key: string): void {
if (({ "style": true, "class": true })[key])
throw new Error(`The attribute "${
key
}" is reserved. Use the setPersonalStyle method for styles and setClass method for classes.`);
}
/**
* Sets a personal style for the element. This will be applied on top of any style that is set for the element.
* @param style The personal style to set. If null, the personal style will be removed.
*/
setPersonalStyle(style: string | null): void {
if (style === null) {
delete this.attributes.style;
return;
}
this.attributes.style = style;
}
/**
* Adds a class to the element's class list and updates the "class" attribute accordingly.
* @param className The class name to add.
*/
addClass(className: string): void {
this.classList.add(className);
this.attributes.class = Array.from(this.classList).join(' ');
}
/**
* Removes a class from the element's class list and updates the "class" attribute accordingly.
* @param className The class name to remove.
*/
removeClass(className: string): void {
this.classList.delete(className);
this.attributes.class = Array.from(this.classList).join(' ');
}
/**
* Gets the list of classes for the element.
* @returns The array of class names.
*/
getClassList(): string[] {
return Array.from(this.classList);
}
/**
* Sets an attribute for the element.
* @param key The attribute key.
* @param value The attribute value.
*/
setAttribute (key: string, value: string): void {
this.checkReservedAttribute(key);
this.attributes[key] = value;
}
@ -45,6 +161,7 @@ export abstract class GefestElement {
* @param key The attribute key.
*/
removeAttribute (key: string): void {
this.checkReservedAttribute(key);
delete this.attributes[key];
}
@ -53,9 +170,9 @@ export abstract class GefestElement {
* @param html The HTML to apply the style to.
* @returns The styled HTML.
*/
protected applyStyle(html: string): string {
protected applyStyle(html: string, element?: GefestElement): string {
if (this.style)
return this.style.applyStyle(html);
return this.style.applyStyle(html, element);
return html;
}
@ -76,7 +193,7 @@ export abstract class GefestElement {
*/
protected wrapHTML (content: string): string {
const attrString = this.attributesToString();
return `<div class="gefest-element" ${attrString}>${content}</div>`;
return `<div ${attrString}>${content}</div>`;
}
/**

43
src/engine.ts Normal file
View File

@ -0,0 +1,43 @@
import { GefestElement } from "./element";
// For protecting the set of elements from direct access
const elements: Set<GefestElement> = new Set();
const elementsMapping: Map<string, GefestElement> = new Map<string, GefestElement> ();
export class GefestEngine {
static register (element: GefestElement) : string {
elements.add(element);
// Generate GefestID for the element
const gefestId = `gefest-${Math.random().toString(36).substr(2, 9)}`;
element.setAttribute('data-gefest-id', gefestId);
elementsMapping.set(gefestId, element);
return gefestId;
}
static async activateOnClick(): Promise<void> {
for (let element of elements) {
const elementHTML = document.querySelectorAll(`[data-gefest-id="${element.gefestId}"]`);
if (elementHTML.length > 0) {
const htmlElement = elementHTML[0] as HTMLElement;
htmlElement.onclick = element.onClick;
}
}
}
static async main(main : () => Promise<void>): Promise<void> {
await main();
await GefestEngine.activateOnClick();
}
static reRenderByGI (gefestId: string) {
const gefestElement = elementsMapping.get(gefestId);
if (!gefestElement) return;
const elementHTML = document.querySelectorAll(`[data-gefest-id="${gefestId}"]`);
if (elementHTML.length > 0) {
const htmlElement = elementHTML[0] as HTMLElement;
htmlElement.outerHTML = gefestElement.build();
GefestEngine.activateOnClick();
}
}
}

View File

@ -1,9 +1,10 @@
import { GefestElement } from "../element";
export class GefestA extends GefestElement {
constructor(content : (GefestElement | string)[] | string, href: string) {
constructor(content : (GefestElement | string)[] | string, href: string | null = null) {
super(content);
this.setAttribute('href', href);
if (href !== null)
this.setAttribute('href', href);
}
protected wrapHTML (content: string): string {

16
src/primitives/header.ts Normal file
View File

@ -0,0 +1,16 @@
import { GefestElement } from "../element";
type HeaderLevel = 1 | 2 | 3 | 4 | 5 | 6;
export class GefestHeader extends GefestElement {
protected level: HeaderLevel;
constructor(content : (GefestElement | string)[] | string, level : HeaderLevel = 1) {
super(content);
this.level = level;
}
protected wrapHTML (content: string): string {
const attrString = this.attributesToString();
return `<h${this.level} ${attrString}>${content}</h${this.level}>`;
}
}

12
src/primitives/i.ts Normal file
View File

@ -0,0 +1,12 @@
import { GefestElement } from "../element";
export class GefestI extends GefestElement {
constructor(content : (GefestElement | string)[] | string) {
super(content);
}
protected wrapHTML (content: string): string {
const attrString = this.attributesToString();
return `<i ${attrString}>${content}</i>`;
}
}

13
src/primitives/img.ts Normal file
View File

@ -0,0 +1,13 @@
import { GefestElement } from "../element";
export class GefestImg extends GefestElement {
constructor(content : (GefestElement | string)[] | string, src: string) {
super(content);
this.setAttribute('src', src);
}
protected wrapHTML (content: string): string {
const attrString = this.attributesToString();
return `<img ${attrString}>${content}</img>`;
}
}

12
src/primitives/small.ts Normal file
View File

@ -0,0 +1,12 @@
import { GefestElement } from "../element";
export class GefestSmall extends GefestElement {
constructor(content : (GefestElement | string)[] | string) {
super(content);
}
protected wrapHTML (content: string): string {
const attrString = this.attributesToString();
return `<small ${attrString}>${content}</small>`;
}
}

12
src/primitives/span.ts Normal file
View File

@ -0,0 +1,12 @@
import { GefestElement } from "../element";
export class GefestSpan extends GefestElement {
constructor(content : (GefestElement | string)[] | string) {
super(content);
}
protected wrapHTML (content: string): string {
const attrString = this.attributesToString();
return `<span ${attrString}>${content}</span>`;
}
}

View File

@ -1,5 +1,7 @@
import { GefestElement } from "./element";
export abstract class GefestStyle {
applyStyle (html: string): string {
applyStyle (html: string, element ?: GefestElement): string {
throw new Error('You need to implement the applyStyle method in the child class');
}
}