generated from polymech/site-template
quassel strippe 1/2
This commit is contained in:
parent
11ba538de4
commit
ec4c0e85a2
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-12-30T08:55:09.232Z"
|
||||
"default": "2025-12-30T13:48:44.716Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
@ -1,26 +1,26 @@
|
||||
|
||||
export default new Map([
|
||||
["src/content/resources/astro-development.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fastro-development.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/collections.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcollections.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/Masonry.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2FMasonry.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/sidebar.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fsidebar.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/FileTree.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2FFileTree.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/collections.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcollections.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/sidebar.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fsidebar.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/test.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Ftest.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/workflow.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fworkflow.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/changelog.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fchangelog.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/modbus.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fmodbus.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/home.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fhome.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/profiles.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fprofiles.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/process.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fprocess.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/modbus.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fmodbus.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/operator.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Foperator.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/motion.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fmotion.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/profiles.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fprofiles.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/process.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fprocess.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/signalplots.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fsignalplots.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/update.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fupdate.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/troubleshooting.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Ftroubleshooting.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/info/about.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Finfo%2Fabout.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/update.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fupdate.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/info/contact.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Finfo%2Fcontact.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/help/Folding-Guide.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fhelp%2FFolding-Guide.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/help/hydraulics.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fhelp%2Fhydraulics.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/info/about.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Finfo%2Fabout.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/help/Load-Cells-Europe.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fhelp%2FLoad-Cells-Europe.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/help/Oil-Heating.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fhelp%2FOil-Heating.mdx&astroContentModuleFlag=true")],
|
||||
["src/content/resources/cassandra/help/Pneumatic-Valves-Europe.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fresources%2Fcassandra%2Fhelp%2FPneumatic-Valves-Europe.mdx&astroContentModuleFlag=true")]]);
|
||||
|
||||
File diff suppressed because one or more lines are too long
14
app.log
14
app.log
@ -277,3 +277,17 @@
|
||||
{"level":"INFO","time":"2025-12-30T08:55:07.464Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T08:55:08.575Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T08:55:08.586Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T12:39:49.723Z","pid":19968,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T12:39:50.644Z","pid":19968,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T12:40:12.063Z","pid":26292,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T12:40:16.220Z","pid":26292,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T12:40:18.146Z","pid":26292,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T12:40:18.161Z","pid":26292,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:48:33.136Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:48:35.961Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:48:36.501Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:48:36.511Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:48:43.976Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:48:44.035Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:48:44.048Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from ./app-config.json for locale en"}
|
||||
{"level":"INFO","time":"2025-12-30T13:51:14.169Z","pid":25324,"hostname":"DESKTOP-OL563U1","msg":"Loading library config from C:\\Users\\zx\\Desktop\\polymech\\library.polymech\\app-config.json for locale en"}
|
||||
|
||||
217
docs/extensions.md
Normal file
217
docs/extensions.md
Normal file
@ -0,0 +1,217 @@
|
||||
# StoreLayout Extension System Plan
|
||||
|
||||
## Overview
|
||||
This document outlines the plan to enable external content completion in `StoreLayout` using a **Registry-based Widget System**, aligned with the HMI `LayoutContainer` and `widgetRegistry` pattern. This allows products to "snap" extensions into predefined slots using a JSON configuration.
|
||||
|
||||
## Goals
|
||||
1. **Registry Pattern**: Extensions are registered in a central `ExtensionRegistry`, similar to `src/lib/widgetRegistry.ts`.
|
||||
2. **JSON Configuration**: Layout composition is defined via JSON, specifying which extensions go into which slots.
|
||||
3. **Opt-Out/Opt-In**: The system is flexible; products can opt-out or override default configurations.
|
||||
4. **Extensibility**: Support adding tabs, extending content panes, and enhancing galleries.
|
||||
|
||||
## Proposed Architecture
|
||||
|
||||
### 1. Extension Registry (`ExtensionRegistry`)
|
||||
We will create a registry to manage available extensions (widgets).
|
||||
|
||||
```typescript
|
||||
// model/extension/registry.ts (Draft)
|
||||
|
||||
export interface ExtensionMetadata {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
category?: 'tab' | 'panel' | 'gallery' | 'general';
|
||||
}
|
||||
|
||||
export interface ExtensionDefinition {
|
||||
component: any; // Astro Component
|
||||
metadata: ExtensionMetadata;
|
||||
}
|
||||
|
||||
class ExtensionRegistry {
|
||||
private extensions = new Map<string, ExtensionDefinition>();
|
||||
|
||||
register(definition: ExtensionDefinition) {
|
||||
this.extensions.set(definition.metadata.id, definition);
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
return this.extensions.get(id);
|
||||
}
|
||||
}
|
||||
|
||||
export const extensionRegistry = new ExtensionRegistry();
|
||||
```
|
||||
|
||||
### 2. JSON Configuration Schema
|
||||
The configuration defines the "slots" and the extensions within them. This mimics `LayoutContainer` where a container holds widgets.
|
||||
|
||||
```typescript
|
||||
// JSON Structure
|
||||
interface ExtensionConfig {
|
||||
enabled: boolean; // Opt-out mechanism
|
||||
slots: {
|
||||
[slotName: string]: ExtensionInstance[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ExtensionInstance {
|
||||
id: string; // Unique instance ID
|
||||
extensionId: string; // ID from registry
|
||||
props?: Record<string, any>; // Props to pass to the component
|
||||
order?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Slots in `StoreLayout`
|
||||
`StoreLayout.astro` will define specific "Hosting Slots" where the registry engine will render content.
|
||||
|
||||
| Slot Name | Context | usage |
|
||||
| :--- | :--- | :--- |
|
||||
| `tabs` | Tab Navigation Bar | Inject new `TabButton` components |
|
||||
| `tab-panels` | Tab Content Area | Inject new `TabContent` panels corresponding to new tabs |
|
||||
| `description` | Main Description Column | Append/Prepend widgets to product description |
|
||||
| `gallery` | Media Area | Inject additional gallery items or widgets |
|
||||
|
||||
### 4. Data Passing & Context
|
||||
Extensions need access to the product data. We will pass the full product model to all extensions via props.
|
||||
|
||||
* **Context Interface**: The extension component will receive a standard set of props.
|
||||
```typescript
|
||||
// model/extension/context.ts
|
||||
import type { IStoreItem } from '@/polymech/model/component'; // or appropriate path
|
||||
|
||||
export interface ExtensionContext {
|
||||
item: IStoreItem; // The full product data
|
||||
slot: string; // The slot where this extension is rendered
|
||||
config?: any; // Extension-specific configuration
|
||||
}
|
||||
```
|
||||
|
||||
* **Prop injection**: The `ExtensionHost` will automatically inject these props.
|
||||
```astro
|
||||
// ExtensionHost.astro
|
||||
const { slotName, config, item } = Astro.props;
|
||||
// ...
|
||||
<Component {...instance.props} item={item} slot={slotName} />
|
||||
```
|
||||
|
||||
### 5. Implementation Details
|
||||
|
||||
#### A. Data Loading
|
||||
* **Default Config**: A global config defines default extensions for all products.
|
||||
* **Product Config**: Products can provide a specific config (e.g., via `store.extensions.json` or frontmatter) to override or extend defaults.
|
||||
* **Merging**: Logic to merge default and product-specific configurations.
|
||||
|
||||
#### B. Component Rendering (`ExtensionHost.astro`)
|
||||
A helper component to render extensions for a given slot.
|
||||
|
||||
```astro
|
||||
---
|
||||
// components/ExtensionHost.astro
|
||||
import { extensionRegistry } from '../model/extension/registry';
|
||||
const { slotName, config, item } = Astro.props;
|
||||
const extensions = config.slots[slotName] || [];
|
||||
---
|
||||
{extensions.map(instance => {
|
||||
const customExt = extensionRegistry.get(instance.extensionId);
|
||||
const Component = customExt?.component;
|
||||
return Component ? <Component {...instance.props} item={item} /> : null;
|
||||
})}
|
||||
```
|
||||
|
||||
#### C. Updates to `StoreLayout.astro`
|
||||
* Import `ExtensionHost`.
|
||||
* Load `ExtensionConfig`.
|
||||
* Replace hardcoded sections with or append `ExtensionHost` calls, passing the `item` (which corresponds to `data` props in `StoreLayout`).
|
||||
|
||||
**Example:**
|
||||
```astro
|
||||
<!-- In StoreLayout.astro -->
|
||||
<div id="tabs">
|
||||
<!-- Existing Tabs -->
|
||||
<TabButton title="Overview" />
|
||||
...
|
||||
<!-- Extension Tabs -->
|
||||
<ExtensionHost slotName="tabs" config={extConfig} item={data} />
|
||||
</div>
|
||||
```
|
||||
|
||||
## Integration Steps
|
||||
1. **Create Registry**: Implement `src/model/extension/registry.ts`.
|
||||
2. **Define Config Schema**: Create TypeScript interfaces.
|
||||
3. **Implement `ExtensionHost`**: Create the renderer component.
|
||||
4. **Register Core Extensions**: Convert some existing optional parts (like "3D Preview") into registered extensions as a proof of concept.
|
||||
5. **Update `StoreLayout`**: Integrate `ExtensionHost` into the identified slots.
|
||||
|
||||
### 6. Plugin Lifecycle & Authoring (Hybrid Mode)
|
||||
This system supports a hybrid lifecycle where extensions can be static (baked at build time) or dynamic (loaded at runtime for authoring/personalization).
|
||||
|
||||
#### A. Architecture: The "Overlay" Model
|
||||
The "Authoring Plugin" acts as an overlay on top of the static Astro page.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Build Time
|
||||
A[Astro Build] -->|Generates| B(Static HTML)
|
||||
B -->|Contains| C["<ExtensionHost> Slots"]
|
||||
end
|
||||
|
||||
subgraph Client Runtime
|
||||
C -->|Hydrates| D[Client Boot]
|
||||
D -->|Loads| E[Authoring Plugin]
|
||||
E -->|Fetches| F["External Data (Supabase)"]
|
||||
F -->|Injects| G[Dynamic Widgets]
|
||||
end
|
||||
|
||||
subgraph "Baking"
|
||||
H[Build Process] -->|Reads| F
|
||||
H -->|Generates| I[Static Config JSON]
|
||||
I -->|Input to| A
|
||||
end
|
||||
```
|
||||
|
||||
#### B. Lifecycle Stages
|
||||
|
||||
1. **Static Render (Server-Side)**:
|
||||
* Astro renders the page using `IStoreItem` data.
|
||||
* `ExtensionHost` renders any *statically registered* extensions defined in `store.extensions.json`.
|
||||
* Slots are marked with `data-slot-id` attributes for client-side targeting.
|
||||
|
||||
2. **Client Boot & Hooking**:
|
||||
* The page loads.
|
||||
* The **Authoring User** logs in (or the plugin auto-loads).
|
||||
* The plugin script initializes and "hooks" into the global registry.
|
||||
|
||||
3. **Data Pre-Processing (Client-Side)**:
|
||||
* The plugin intercepts the product data state.
|
||||
* **Hook**: `onProductDataLoaded(item: IStoreItem) => ModifiedItem`
|
||||
* *Example*: The plugin fetches "Draft Review Notes" from Supabase and appends them to `item.notes`.
|
||||
|
||||
4. **Dynamic Widget Injection**:
|
||||
* The plugin registers *new* widgets at runtime or overrides existing ones.
|
||||
* It uses React Portals (or similar) to mount components into the `data-slot-id` DOM nodes.
|
||||
* *Example*: A "Comment/Annotate" widget is injected into the `description` slot.
|
||||
|
||||
5. **Post-Processing & Authoring**:
|
||||
* User interacts with widgets (e.g., adds a new "Draft Tab").
|
||||
* **Save**: Changes are persisted to the external store (Supabase).
|
||||
* **Local State**: The UI updates immediately to reflect the new authoring state.
|
||||
|
||||
6. **"Baking" (Reification)**:
|
||||
* Trigger: A build pipeline runs.
|
||||
* Action: It fetches the finalized state from Supabase.
|
||||
* Transform: Converts the dynamic state into a static `store.extensions.json` file.
|
||||
* Result: The next Astro build includes these changes statically, improving performance and removing the runtime dependency for end-users.
|
||||
### 7. Risks & Trade-offs (Critique)
|
||||
While powerful, this "Hybrid/Overlay" architecture introduces significant complexity.
|
||||
|
||||
| Risk | Description | Mitigation |
|
||||
| :--- | :--- | :--- |
|
||||
| **Hydration Mismatches** | Injecting React components dynamically into server-rendered DOM nodes can cause hydration errors (`Text content does not match server-rendered HTML`). | Use `client:only` for extension hosts or strict boundary isolation (Shadow DOM or specific React Roots). |
|
||||
| **Performance (CLS)** | Client-side extensions load *after* the initial page, causing Content Layout Shift (CLS) as widgets pop in. | Reserve space with skeletons/placeholders of fixed dimensions. |
|
||||
| **Performance (LCP)** | Fetching configuration and data from Supabase blocks the "interactive" state, degrading Core Web Vitals. | Cache configurations on the Edge; use aggressive `stale-while-revalidate` strategies. |
|
||||
| **Vendor Lock-in/Drift** | The "Authoring Plugin" becomes a separate, monolithic app that drifts from the main codebase. | Monorepo integration; share types (`IStoreItem`) strictly between the main app and plugin. |
|
||||
| **Security (XSS)** | Loading remote configurations that define component props acts as a vector for Cross-Site Scripting if not sanitized. | Strict schema validation (Zod) on all loaded configs; avoid `dangerouslySetInnerHTML`. |
|
||||
| **Baking Complexity** | The "Baking" pipeline is engineering-heavy: it requires a headless browser or a complex build script to "replay" the dynamic state into static files. | Keep the data model simple (JSON) so baking is just "merging JSONs" rather than "scraping DOM". |
|
||||
@ -11,6 +11,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../osr/products/products"
|
||||
},
|
||||
{
|
||||
"path": "../pm-pics/src"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
---
|
||||
import "flowbite";
|
||||
|
||||
import { createMarkdownComponent } from "@/base/index.js";
|
||||
import { translate } from "@polymech/astro-base/base/i18n.js";
|
||||
import Translate from "@polymech/astro-base/components/i18n.astro";
|
||||
|
||||
15
src/plugins/reading-time.ts
Normal file
15
src/plugins/reading-time.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { IPlugin } from "@polymech/astro-base/model/extension/types.js";
|
||||
import getReadingTime from 'reading-time';
|
||||
|
||||
export const ReadingTimePlugin: IPlugin = {
|
||||
name: 'reading-time',
|
||||
hooks: {
|
||||
onData: (item) => {
|
||||
if (item.data.content) {
|
||||
const stats = getReadingTime(item.data.content);
|
||||
item.data['readingTime'] = stats;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user