site-library/src/model/howto/json-ld-howto.ts
2025-03-31 20:31:05 +02:00

149 lines
4.3 KiB
TypeScript

import { IHowto } from './howto-model.js';
// https://schema.org/HowTo
export const get = (howto: IHowto, baseUrl: string = 'creava.org', lang: string = 'en') => {
const jsonLD: any = {
"@context": "https://schema.org",
"@type": "HowTo",
"name": howto.title,
"description": howto.description,
"url": `https://${baseUrl}/${lang}/howtos/${howto.slug}`,
"inLanguage": lang
};
if (howto.cover_image?.downloadUrl) {
jsonLD.image = howto.cover_image.downloadUrl;
}
// Add user location if available
if (howto.user?.geo) {
jsonLD.locationCreated = {
"@type": "Place",
"address": {
"@type": "PostalAddress",
"addressCountry": howto.user.geo.countryName,
"addressRegion": howto.user.geo.principalSubdivision,
"addressLocality": howto.user.geo.city,
"postalCode": howto.user.geo.postcode
},
"geo": {
"@type": "GeoCoordinates",
"latitude": howto.user.geo.latitude,
"longitude": howto.user.geo.longitude
}
};
}
// Extract video URL from description or steps
const videoUrl = extractVideoUrl(howto);
if (videoUrl) {
jsonLD.video = {
"@type": "VideoObject",
"url": videoUrl
};
}
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;
};
// Helper function to extract video URL from description or steps
function extractVideoUrl(howto: IHowto): string | null {
// First try to find video URL in description
if (howto.description) {
const videoMatch = howto.description.match(/(https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be|vimeo\.com)\/[^\s]+)/i);
if (videoMatch) {
return videoMatch[1];
}
}
// Then try to find video URL in steps
if (howto.steps) {
for (const step of howto.steps) {
if (step.text) {
const videoMatch = step.text.match(/(https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be|vimeo\.com)\/[^\s]+)/i);
if (videoMatch) {
return videoMatch[1];
}
}
}
}
return null;
}