howtos, basic shit ;

This commit is contained in:
lovebird 2025-03-23 14:02:44 +01:00
parent 511f9c9add
commit 930d5c207c
7 changed files with 304 additions and 629 deletions

File diff suppressed because one or more lines are too long

177
src/model/howto-model.ts Normal file
View File

@ -0,0 +1,177 @@
export const ITEM_TYPE = 'howto'
////////////////////////////////
//
// Interfaces - Old
export interface IHowto {
_createdBy: string
mentions: any[]
_deleted: boolean
fileLink: string
slug: string
_modified: string
previousSlugs: string[]
_created: string
description: string
votedUsefulBy: string[]
creatorCountry: string
total_downloads: number
title: string
time: string
files: any[]
category: IOACategory
difficulty_level: string
_id: string
tags?: IOATag[]
total_views: number
_contentModifiedTimestamp: string
cover_image: Image
comments: any[]
moderatorFeedback: string
steps: Step[]
moderation: string
user?: User
}
export interface Tags {
[key: string]: boolean
}
export interface CoverImage {
name: string
downloadUrl: string
type: string
fullPath: string
updated: string
size: number
timeCreated: string
contentType: string
}
export interface Step {
title: string
text: string
images: Image[]
_animationKey: string
}
export interface Image {
updated: string
size: number
fullPath: string
timeCreated: string
name: string
downloadUrl: string
contentType: string
type: string
src: string,
alt: string
}
/// Taxonomy
export interface IOACategory {
_created: string
_id: string
_deleted: boolean
label: string
_modified: string
}
export interface IOATag {
categories: string[]
image: string
_created: string
_deleted: boolean
label: string
_createdBy: string
_modified: string
_id: string
}
export interface User {
_modified: string
_id: string
subType: string
moderation: string
_deleted: boolean
verified: boolean
type: string
location: Location
_created: string
geo: Geo
data: Data
detail: Detail
}
export interface Location {
lat: number
lng: number
}
export interface Geo {
latitude: number
lookupSource: string
longitude: number
localityLanguageRequested: string
continent: string
continentCode: string
countryName: string
countryCode: string
principalSubdivision: string
principalSubdivisionCode: string
city: string
locality: string
postcode: string
plusCode: string
localityInfo: LocalityInfo
}
export interface LocalityInfo {
administrative: Administrative[]
informative: Informative[]
}
export interface Administrative {
name: string
description: string
isoName?: string
order: number
adminLevel: number
isoCode?: string
wikidataId: string
geonameId: number
}
export interface Informative {
name: string
description: string
isoName?: string
order: number
isoCode?: string
wikidataId?: string
geonameId?: number
}
export interface Data {
urls: Url[]
description: string
services: Service[]
title: string
images: any[]
}
export interface Url {
name: string
url: string
}
export interface Service {
welding: boolean
assembling: boolean
machining: boolean
electronics: boolean
molds: boolean
}
export interface Detail {
services: any[]
urls: any[]
}

View File

@ -0,0 +1,11 @@
kbotd --preferences ./todos-workflow.md \
--include=./howto-model.ts \
--include=./howto_sample.json \
--disable=terminal,git,npm,user,interact,search,email,web \
--disableTools=read_file,read_files,list_files,file_exists,web \
--model=anthropic/claude-3.7-sonnet:thinking \
--mode=completion \
--filters=code \
--dst=./json-ld-howto.ts

View File

@ -5,10 +5,12 @@ import { sync as read } from '@polymech/fs/read'
import { sync as exists } from '@polymech/fs/exists'
import { sync as mkdir } from '@polymech/fs/dir'
import { sync as rm } from '@polymech/fs/remove'
import { IHowto, Image } from './howto-model.js';
import type { Loader, LoaderContext } from 'astro/loaders'
import { sanitizeFilename } from "@polymech/fs/utils"
import { filter as language } from "@/base/kbot.js";
export * from './howto-model.js'
import {
HOWTO_FILES_WEB,
HOWTO_FILES_ABS,
@ -35,8 +37,8 @@ export const ITEM_TYPE = 'howto'
//export const load = () => get(`${HOWTO_ROOT()}/${HOWTO_GLOB}`, HOWTO_ROOT(), ITEM_TYPE)
export const item_path = (item: any) => `${HOWTO_ROOT()}/${item.data.slug}`
const blacklist = [];
const blacklist_mafia = ['precious-plastic', 'fair-enough', 'mad-plastic-labs', 'the-flipflopi', 'easymoulds', 'plasticpreneur', 'sustainable-design-studio'];
const blacklist_ = [];
const blacklist = ['precious-plastic', 'fair-enough', 'mad-plastic-labs', 'the-flipflopi', 'easymoulds', 'plasticpreneur', 'sustainable-design-studio'];
const download = async (url, outputPath) => {
const stream = createWriteStream(outputPath);
@ -141,7 +143,7 @@ export const howtos = async () => {
label: 'uncategorized'
}
})
howtos = howtos.filter((h) => {
howtos = howtos.filter((h:IHowto) => {
return h.steps.length > 0 && !blacklist.includes(h._createdBy);
});
return howtos
@ -297,179 +299,3 @@ export async function applyFilters(text: string): Promise<string> {
return filtered;
}
////////////////////////////////
//
// Interfaces - Old
export interface IHowto {
_createdBy: string
mentions: any[]
_deleted: boolean
fileLink: string
slug: string
_modified: string
previousSlugs: string[]
_created: string
description: string
votedUsefulBy: string[]
creatorCountry: string
total_downloads: number
title: string
time: string
files: any[]
category: IOACategory
difficulty_level: string
_id: string
tags?: Tags
total_views: number
_contentModifiedTimestamp: string
cover_image: Image
comments: any[]
moderatorFeedback: string
steps: Step[]
moderation: string
user?: User
}
export interface Tags {
[key: string]: boolean
}
export interface CoverImage {
name: string
downloadUrl: string
type: string
fullPath: string
updated: string
size: number
timeCreated: string
contentType: string
}
export interface Step {
title: string
text: string
images: Image[]
_animationKey: string
}
export interface Image {
updated: string
size: number
fullPath: string
timeCreated: string
name: string
downloadUrl: string
contentType: string
type: string
src: string,
alt: string
}
/// Taxonomy
export interface IOACategory {
_created: string
_id: string
_deleted: boolean
label: string
_modified: string
}
export interface IOATag {
categories: string[]
image: string
_created: string
_deleted: boolean
label: string
_createdBy: string
_modified: string
_id: string
}
export interface User {
_modified: string
_id: string
subType: string
moderation: string
_deleted: boolean
verified: boolean
type: string
location: Location
_created: string
geo: Geo
data: Data
detail: Detail
}
export interface Location {
lat: number
lng: number
}
export interface Geo {
latitude: number
lookupSource: string
longitude: number
localityLanguageRequested: string
continent: string
continentCode: string
countryName: string
countryCode: string
principalSubdivision: string
principalSubdivisionCode: string
city: string
locality: string
postcode: string
plusCode: string
localityInfo: LocalityInfo
}
export interface LocalityInfo {
administrative: Administrative[]
informative: Informative[]
}
export interface Administrative {
name: string
description: string
isoName?: string
order: number
adminLevel: number
isoCode?: string
wikidataId: string
geonameId: number
}
export interface Informative {
name: string
description: string
isoName?: string
order: number
isoCode?: string
wikidataId?: string
geonameId?: number
}
export interface Data {
urls: Url[]
description: string
services: Service[]
title: string
images: any[]
}
export interface Url {
name: string
url: string
}
export interface Service {
welding: boolean
assembling: boolean
machining: boolean
electronics: boolean
molds: boolean
}
export interface Detail {
services: any[]
urls: any[]
}

View File

@ -1,339 +0,0 @@
{
"_createdBy": "gus-merckel",
"mentions": [],
"_deleted": false,
"fileLink": "",
"slug": "cut-out-shapes-out-of-plastic-sheets-with-a-cnc-",
"_modified": "2023-10-27T18:09:36.519Z",
"previousSlugs": [
"cut-out-shapes-out-of-plastic-sheets-with-a-cnc-"
],
"_created": "2023-08-23T18:20:09.098Z",
"description": "In this how to, I will show you our process to cut HDPE Sheets using a X-Carve CNC.\n\nHere is the full video in spanish with subtitles https://www.youtube.com/watch?v=4LrrFz802To ",
"votedUsefulBy": [
"sigolene",
"mattia",
"uillinoispreciousplastics"
],
"creatorCountry": "mx",
"total_downloads": 0,
"title": "Cut out shapes out of plastic sheets with a CNC ",
"time": "< 5 hours",
"files": [],
"difficulty_level": "Medium",
"_id": "038gjWgLjiyYknbEjDeI",
"tags": [
"HDPE"
],
"total_views": 232,
"_contentModifiedTimestamp": "2023-08-23T18:20:09.098Z",
"cover_image": {
"name": "IMG_20200605_142311.jpg",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=c272c174-1adc-45af-967b-771adce7295d",
"type": "image/jpeg",
"fullPath": "uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg",
"updated": "2021-04-05T15:09:00.605Z",
"size": 124661,
"timeCreated": "2021-04-05T15:09:00.605Z",
"contentType": "image/jpeg"
},
"comments": [],
"moderatorFeedback": "",
"steps": [
{
"title": "Measure the plastic sheet",
"text": "For this step we need to measure our plastic sheet: Height, Width and Thickness. Our X-Carve machine works with the CAM Software EASEL, for me, the easiest software for CNC milling out there. \n\nThe cool thing about Easel (https://easel.inventables.com/) is that you can \"simulate\" your actual material and THEY EVEN HAVE HDPE 2-Colors in their cutting material lists!!\n\n\n",
"images": [
{
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/1.jpg",
"name": "1.jpg",
"size": 74095,
"type": "image/jpeg",
"timeCreated": "2021-03-26T19:42:05.766Z",
"contentType": "image/jpeg",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F1.jpg?alt=media&token=293d733d-05a5-494a-9340-47f4564f1939",
"updated": "2021-03-26T19:42:05.766Z",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/1.jpg",
"alt": "1.jpg"
},
{
"contentType": "image/jpeg",
"timeCreated": "2021-03-26T19:42:05.669Z",
"updated": "2021-03-26T19:42:05.669Z",
"size": 69665,
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F2.jpg?alt=media&token=004f50f1-97ac-4df4-9ba9-f463aa4cbca3",
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/2.jpg",
"name": "2.jpg",
"type": "image/jpeg",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/2.jpg",
"alt": "2.jpg"
}
],
"_animationKey": "unique1"
},
{
"text": "Using the CNC clamps from the X-Carve, secure the sheet to the table, ",
"_animationKey": "unique2",
"images": [
{
"updated": "2021-03-26T19:42:06.249Z",
"size": 55544,
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/3.jpg",
"timeCreated": "2021-03-26T19:42:06.249Z",
"name": "3.jpg",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F3.jpg?alt=media&token=0b9c1914-1c75-429e-b34a-1e2b3706edef",
"contentType": "image/jpeg",
"type": "image/jpeg",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/3.jpg",
"alt": "3.jpg"
}
],
"title": "Secure sheet "
},
{
"title": "Choosing a file to cut ",
"text": "Now we go to our illustrator, such as Inkscape to design a vector file or download and open source one frome https://thenounproject.com/.\n\nWe download the SVG file, which is an open source vector format and import it to Easel. \n",
"images": [
{
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F4.jpg?alt=media&token=1cd2d49d-9335-4bb1-ac2a-e625322ca604",
"contentType": "image/jpeg",
"timeCreated": "2021-03-26T19:42:06.727Z",
"updated": "2021-03-26T19:42:06.727Z",
"name": "4.jpg",
"size": 42952,
"type": "image/jpeg",
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/4.jpg",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/4.jpg",
"alt": "4.jpg"
},
{
"size": 69255,
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/5.jpg",
"updated": "2021-03-26T19:42:06.833Z",
"timeCreated": "2021-03-26T19:42:06.833Z",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F5.jpg?alt=media&token=7cca786a-7d47-43bb-900b-b8d101c276b4",
"name": "5.jpg",
"contentType": "image/jpeg",
"type": "image/jpeg",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/5.jpg",
"alt": "5.jpg"
}
],
"_animationKey": "unique3"
},
{
"text": "Now with the file we can choose the width we want to carve/cut and then we go to cut and start the wizzard:\n- We check that the sheet is fixed.\n- We also specify the cutting bit, we are using a 1/8 flat flute bit. \n- We tell the machine where the coordinate 0-0 is, which we always choose as the down left corner.\n- We raise the bit, turn on the Router!!!\n\nAND PUM THE MAGIC BEGINS!!",
"title": "Follow the cutting Wizzard",
"images": [
{
"timeCreated": "2021-03-26T19:42:07.493Z",
"size": 72226,
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/6.jpg",
"updated": "2021-03-26T19:42:07.493Z",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F6.jpg?alt=media&token=ba7195dd-7771-435f-a188-057457697332",
"contentType": "image/jpeg",
"type": "image/jpeg",
"name": "6.jpg",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/6.jpg",
"alt": "6.jpg"
},
{
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/7.jpg",
"size": 52424,
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F7.jpg?alt=media&token=a3d5820c-cfe2-484e-8f76-f861ab8b756d",
"contentType": "image/jpeg",
"type": "image/jpeg",
"timeCreated": "2021-03-26T19:42:07.308Z",
"updated": "2021-03-26T19:42:07.308Z",
"name": "7.jpg",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/7.jpg",
"alt": "7.jpg"
},
{
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/8.jpg",
"name": "8.jpg",
"type": "image/jpeg",
"timeCreated": "2021-03-26T19:42:07.346Z",
"size": 55264,
"contentType": "image/jpeg",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F8.jpg?alt=media&token=1c9816d7-3a99-4f41-8d3c-acc2670240f6",
"updated": "2021-03-26T19:42:07.346Z",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/8.jpg",
"alt": "8.jpg"
}
],
"_animationKey": "uniquenisc2v"
},
{
"text": "You take now your glasses or object and postprocess them and of course show it to your friends, family and so on.\n\n\n",
"images": [
{
"fullPath": "uploads/howtos/pbo0Pe44aTngvlD04kGf/9.jpg",
"contentType": "image/jpeg",
"timeCreated": "2021-03-26T19:42:08.147Z",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2Fpbo0Pe44aTngvlD04kGf%2F9.jpg?alt=media&token=4dcfe37d-e1ad-41e5-a590-40b4c37c5e1a",
"name": "9.jpg",
"updated": "2021-03-26T19:42:08.147Z",
"type": "image/jpeg",
"size": 82214,
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/9.jpg",
"alt": "9.jpg"
}
],
"_animationKey": "uniquesgl34",
"title": "Post-production and show case"
},
{
"_animationKey": "uniquem4y0yi",
"title": "Hack it and try it yourself",
"text": "You can try this project with other types of CNC machines, even manual Routers or manual saw, as I did on this video: https://youtu.be/gxkcffQD3eQ, but the important thing is that you share what you do and help this community to grow!!!\n\nShare your ideas and comments!",
"images": [
{
"contentType": "image/jpeg",
"timeCreated": "2021-04-05T15:09:01.445Z",
"fullPath": "uploads/howtos/038gjWgLjiyYknbEjDeI/IMG_20200605_142311.jpg",
"type": "image/jpeg",
"downloadUrl": "https://firebasestorage.googleapis.com/v0/b/onearmyworld.appspot.com/o/uploads%2Fhowtos%2F038gjWgLjiyYknbEjDeI%2FIMG_20200605_142311.jpg?alt=media&token=f94152ff-f923-4054-a3ad-d8ec588856fa",
"size": 124661,
"updated": "2021-04-05T15:09:01.445Z",
"name": "IMG_20200605_142311.jpg",
"src": "/resources/howtos/cut-out-shapes-out-of-plastic-sheets-with-a-cnc-/IMG_20200605_142311.jpg",
"alt": "IMG_20200605_142311.jpg"
}
]
}
],
"moderation": "accepted",
"user": {
"_modified": "2024-01-08T13:28:33.484Z",
"_id": "gus-merckel",
"subType": "mix",
"moderation": "accepted",
"_deleted": false,
"verified": false,
"type": "workspace",
"location": {
"lat": 19.3935,
"lng": -99.1656
},
"_created": "2024-01-08T13:28:33.484Z",
"geo": {
"latitude": 19.3935,
"lookupSource": "coordinates",
"longitude": -99.1656,
"localityLanguageRequested": "en",
"continent": "North America",
"continentCode": "NA",
"countryName": "Mexico",
"countryCode": "MX",
"principalSubdivision": "Ciudad de Mexico",
"principalSubdivisionCode": "MX-CMX",
"city": "Mexico City",
"locality": "Benito Juarez",
"postcode": "03103",
"plusCode": "76F29RVM+CQ",
"localityInfo": {
"administrative": [
{
"name": "Mexico",
"description": "country in North America",
"isoName": "Mexico",
"order": 2,
"adminLevel": 2,
"isoCode": "MX",
"wikidataId": "Q96",
"geonameId": 3996063
},
{
"name": "Mexico City",
"description": "capital and largest city of Mexico",
"order": 5,
"adminLevel": 4,
"wikidataId": "Q1489",
"geonameId": 3530597
},
{
"name": "Ciudad de Mexico",
"description": "capital and largest city of Mexico",
"isoName": "Ciudad de Mexico",
"order": 6,
"adminLevel": 4,
"isoCode": "MX-CMX",
"wikidataId": "Q1489",
"geonameId": 3527646
},
{
"name": "Benito Juarez",
"description": "territorial demarcation of the Mexico City in Mexico",
"order": 7,
"adminLevel": 6,
"wikidataId": "Q2356998",
"geonameId": 3827406
}
],
"informative": [
{
"name": "North America",
"description": "continent and northern subcontinent of the Americas",
"isoName": "North America",
"order": 1,
"isoCode": "NA",
"wikidataId": "Q49",
"geonameId": 6255149
},
{
"name": "America/Mexico_City",
"description": "time zone",
"order": 3
},
{
"name": "Greater Mexico City",
"description": "geographical object",
"order": 4,
"wikidataId": "Q665894"
},
{
"name": "03103",
"description": "postal code",
"order": 8
}
]
}
},
"data": {
"urls": [
{
"name": "Email",
"url": "mailto:gustavomerckel@gmail.com"
},
{
"name": "Facebook",
"url": "https://www.facebook.com/pl%c3%a1stico-chido-110888520718193"
},
{
"name": "sponsor the work",
"url": "https://www.patreon.com/one_army"
}
],
"description": "Plástico Chido builds and modifies the PP machines, and also experiments with CNC and Laser Cut",
"services": [
{
"welding": false,
"assembling": false,
"machining": false,
"electronics": false,
"molds": false
}
],
"title": "Plástico Chido",
"images": []
},
"detail": {
"services": [],
"urls": []
}
},
"category": {
"label": "uncategorized"
}
}

View File

@ -0,0 +1,94 @@
import { IHowto } from './howto-model.js';
export const toJSONLD = (howto: IHowto) => {
const jsonLD: any = {
"@context": "https://schema.org",
"@type": "HowTo",
"name": howto.title,
"description": howto.description,
};
if (howto.cover_image?.downloadUrl) {
jsonLD.image = howto.cover_image.downloadUrl;
}
if (howto.time) {
let duration = "";
const timeMatch = howto.time.match(/(\d+)\s*(hour|minute|second|day|week|month|year)/i);
if (timeMatch) {
const value = parseInt(timeMatch[1], 10);
const unit = timeMatch[2].toLowerCase();
switch(unit) {
case 'hour': duration = `PT${value}H`; break;
case 'minute': duration = `PT${value}M`; break;
case 'second': duration = `PT${value}S`; break;
case 'day': duration = `P${value}D`; break;
case 'week': duration = `P${value}W`; break;
case 'month': duration = `P${value}M`; break;
case 'year': duration = `P${value}Y`; break;
}
} else if (howto.time.includes('<')) {
const lessTimeMatch = howto.time.match(/< (\d+)\s*(hour|minute|second|day|week|month|year)/i);
if (lessTimeMatch) {
const value = parseInt(lessTimeMatch[1], 10);
const unit = lessTimeMatch[2].toLowerCase();
switch(unit) {
case 'hour': duration = `PT${value - 1}H`; break;
case 'minute': duration = `PT${value - 1}M`; break;
case 'second': duration = `PT${value - 1}S`; break;
case 'day': duration = `P${value - 1}D`; break;
case 'week': duration = `P${value - 1}W`; break;
case 'month': duration = `P${value - 1}M`; break;
case 'year': duration = `P${value - 1}Y`; break;
}
}
}
if (duration) {
jsonLD.totalTime = duration;
}
}
if (howto.difficulty_level) {
jsonLD.difficultyLevel = howto.difficulty_level;
}
if (howto.category?.label) {
jsonLD.category = howto.category.label;
}
if (howto.tags && howto.tags.length > 0) {
const keywords = howto.tags.map(tag =>
typeof tag === 'string' ? tag : tag?.label || ''
).filter(Boolean).join(', ');
if (keywords) {
jsonLD.keywords = keywords;
}
}
if (howto.steps && howto.steps.length > 0) {
jsonLD.step = howto.steps.map((step, index) => {
const stepObj: any = {
"@type": "HowToStep",
"name": step.title,
"text": step.text,
"position": index + 1
};
if (step.images && step.images.length > 0) {
if (step.images.length === 1) {
stepObj.image = step.images[0].downloadUrl;
} else {
stepObj.image = step.images.map(image => image.downloadUrl);
}
}
return stepObj;
});
}
return jsonLD;
};

View File

@ -0,0 +1,9 @@
## Todos
- we use Typescript, ESM, minimal dependencies, functional programming
- dont document or comment, return just the code, ready to go
- no additional dependencies
## Todos
- [ ] function, to convert a howto (howto-model.ts) to json-ld structured data, satisfying Google, "toJSON-LD"