seo
This commit is contained in:
parent
313a81f270
commit
ca79656a90
@ -61,6 +61,11 @@
|
||||
"tslog": "^4.9.3",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"yargs": "^18.0.0"
|
||||
"tailwindcss": "^4.0.7",
|
||||
"flowbite": "^3.1.2",
|
||||
"yargs": "^18.0.0",
|
||||
"html-escaper": "^3.0.3",
|
||||
"html-validate": "^8.18.2",
|
||||
"@types/html-escaper": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -278,7 +278,6 @@ export const gallery = async (
|
||||
const meta_path_md = `${mediaPath}/${parts.name}.md`
|
||||
const meta_markdown = exists(meta_path_md) ? read(meta_path_md, "string") as string : "" as string
|
||||
|
||||
console.log('filePath', filePath)
|
||||
let imageMeta: any = await loadImage(filePath)
|
||||
let exifRaw: any = null
|
||||
try {
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
---
|
||||
import "../styles/flowbite.css"
|
||||
// import "../styles/flowbite.css"
|
||||
|
||||
import "../styles/global.css"
|
||||
import "../styles/custom.scss"
|
||||
// import "../styles/global.css"
|
||||
// import "../styles/custom.scss"
|
||||
|
||||
import { sync as read } from '@polymech/fs/read'
|
||||
import { AstroSeo } from "@astrolib/seo"
|
||||
import { default as AstroSeo } from "../seo/AstroSeo.astro"
|
||||
|
||||
import { I18N_SOURCE_LANGUAGE } from "../app/config.js"
|
||||
import { translate } from '@polymech/astro-base/base/i18n.js'
|
||||
import { item_defaults } from '@/base/index.js'
|
||||
import { translate } from '../base/i18n.js'
|
||||
import { item_defaults } from '../base/index.js'
|
||||
|
||||
import { LANGUAGES_PROD } from "../app/config.js"
|
||||
import config from "../app/config.json"
|
||||
import { plainify } from "@polymech/astro-base/base/strings.js"
|
||||
import { plainify } from "../base/strings.js"
|
||||
|
||||
import StructuredData from '@polymech/astro-base/components/ArticleStructuredData.astro'
|
||||
import Hreflang from '@polymech/astro-base/components/hreflang.astro'
|
||||
import StructuredData from '../components/ArticleStructuredData.astro'
|
||||
import Hreflang from '../components/hreflang.astro'
|
||||
|
||||
export interface Props {
|
||||
title?: string;
|
||||
|
||||
42
packages/polymech/src/seo/AstroSeo.astro
Normal file
42
packages/polymech/src/seo/AstroSeo.astro
Normal file
@ -0,0 +1,42 @@
|
||||
---
|
||||
import type { AstroSeoProps } from "./types";
|
||||
import { buildTags } from "./utils/buildTags";
|
||||
|
||||
export interface Props extends AstroSeoProps {}
|
||||
|
||||
const {
|
||||
title,
|
||||
titleTemplate,
|
||||
noindex,
|
||||
nofollow,
|
||||
robotsProps,
|
||||
description,
|
||||
canonical,
|
||||
mobileAlternate,
|
||||
languageAlternates,
|
||||
openGraph,
|
||||
facebook,
|
||||
twitter,
|
||||
additionalMetaTags,
|
||||
additionalLinkTags,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<Fragment
|
||||
set:html={buildTags({
|
||||
title,
|
||||
titleTemplate,
|
||||
noindex,
|
||||
nofollow,
|
||||
robotsProps,
|
||||
description,
|
||||
canonical,
|
||||
mobileAlternate,
|
||||
languageAlternates,
|
||||
openGraph,
|
||||
facebook,
|
||||
twitter,
|
||||
additionalMetaTags,
|
||||
additionalLinkTags,
|
||||
})}
|
||||
/>
|
||||
1
packages/polymech/src/seo/index.ts
Normal file
1
packages/polymech/src/seo/index.ts
Normal file
@ -0,0 +1 @@
|
||||
|
||||
455
packages/polymech/src/seo/types.ts
Normal file
455
packages/polymech/src/seo/types.ts
Normal file
@ -0,0 +1,455 @@
|
||||
export type OpeningHoursSpecification = {
|
||||
opens: string;
|
||||
closes: string;
|
||||
dayOfWeek: string | string[];
|
||||
validFrom?: string;
|
||||
validThrough?: string;
|
||||
};
|
||||
|
||||
export type Offer = {
|
||||
priceSpecification: PriceSpecification;
|
||||
itemOffered: Service;
|
||||
};
|
||||
|
||||
export type PriceSpecification = {
|
||||
type: string;
|
||||
priceCurrency: string;
|
||||
price: string;
|
||||
};
|
||||
|
||||
export type Service = {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type Geo = {
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
};
|
||||
|
||||
export type GeoCircle = {
|
||||
geoMidpoint: Geo;
|
||||
geoRadius: string;
|
||||
};
|
||||
|
||||
export type Action = {
|
||||
actionName: string;
|
||||
actionType: string;
|
||||
target: string;
|
||||
};
|
||||
|
||||
export type Step = {
|
||||
type: string;
|
||||
name: string;
|
||||
url?: string;
|
||||
itemListElement?: StepDetails[];
|
||||
image?: string;
|
||||
};
|
||||
|
||||
export type StepDetails = {
|
||||
type: "HowToTip" | "HowToDirection";
|
||||
text: string;
|
||||
};
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
export interface Answer {
|
||||
text: string;
|
||||
dateCreated?: string;
|
||||
upvoteCount?: number;
|
||||
url?: string;
|
||||
author?: Person;
|
||||
}
|
||||
|
||||
export interface Question {
|
||||
name: string;
|
||||
answerCount: number;
|
||||
acceptedAnswer?: Answer;
|
||||
suggestedAnswer?: Answer[];
|
||||
text?: string;
|
||||
author?: Person;
|
||||
upvoteCount?: number;
|
||||
dateCreated?: string;
|
||||
}
|
||||
|
||||
export interface Instruction {
|
||||
name?: string;
|
||||
text: string;
|
||||
url?: string;
|
||||
image?: string;
|
||||
}
|
||||
export interface Performer {
|
||||
type?: "Person" | "PerformingGroup";
|
||||
name: string;
|
||||
}
|
||||
export interface Place {
|
||||
name: string;
|
||||
address: Address;
|
||||
sameAs?: string;
|
||||
}
|
||||
|
||||
export interface VirtualLocation {
|
||||
name?: string;
|
||||
sameAs?: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type Location = Place | VirtualLocation;
|
||||
|
||||
export type EventStatus =
|
||||
| "EventCancelled"
|
||||
| "EventMovedOnline"
|
||||
| "EventPostponed"
|
||||
| "EventRescheduled"
|
||||
| "EventScheduled";
|
||||
|
||||
export type EventAttendanceMode =
|
||||
| "MixedEventAttendanceMode"
|
||||
| "OfflineEventAttendanceMode"
|
||||
| "OnlineEventAttendanceMode";
|
||||
|
||||
export interface Organizer {
|
||||
type: "Person" | "Organization";
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ContactPoint {
|
||||
contactType: string;
|
||||
telephone: string;
|
||||
areaServed?: string | string[];
|
||||
availableLanguage?: string | string[];
|
||||
contactOption?: string | string[];
|
||||
}
|
||||
export interface CreativeWork {
|
||||
author: string;
|
||||
about: string;
|
||||
name: string;
|
||||
datePublished: string;
|
||||
audience?: string;
|
||||
keywords?: string;
|
||||
thumbnailUrl?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
export interface Producer {
|
||||
name: string;
|
||||
url?: string;
|
||||
}
|
||||
export interface ContactPoint {
|
||||
contactType: string;
|
||||
telephone: string;
|
||||
areaServed?: string | string[];
|
||||
availableLanguage?: string | string[];
|
||||
contactOption?: string | string[];
|
||||
}
|
||||
|
||||
export interface Question {
|
||||
questionName: string;
|
||||
acceptedAnswerText: string;
|
||||
}
|
||||
export interface Provider {
|
||||
type?: "Organization" | "Person";
|
||||
name: string;
|
||||
url?: string;
|
||||
}
|
||||
export interface ItemListElements {
|
||||
item: string;
|
||||
name: string;
|
||||
position: number;
|
||||
}
|
||||
export interface OpenGraphMedia {
|
||||
url: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
alt?: string;
|
||||
type?: string;
|
||||
secureUrl?: string;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
streetAddress: string;
|
||||
addressLocality: string;
|
||||
addressRegion?: string;
|
||||
postalCode: string;
|
||||
addressCountry: string;
|
||||
}
|
||||
|
||||
export interface Video {
|
||||
name: string;
|
||||
description: string;
|
||||
thumbnailUrls: string[];
|
||||
uploadDate: string;
|
||||
contentUrl?: string;
|
||||
duration?: string;
|
||||
embedUrl?: string;
|
||||
expires?: string;
|
||||
hasPart?: Clip | Clip[];
|
||||
watchCount?: number;
|
||||
publication?: BroadcastEvent | BroadcastEvent[];
|
||||
regionsAllowed?: string | string[];
|
||||
}
|
||||
|
||||
export interface Clip {
|
||||
name: string;
|
||||
startOffset: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface BroadcastEvent {
|
||||
name?: string;
|
||||
isLiveBroadcast: boolean;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
|
||||
export type Offers = {
|
||||
price: string;
|
||||
priceCurrency: string;
|
||||
priceValidUntil?: string;
|
||||
itemCondition?: string;
|
||||
availability?: string;
|
||||
url?: string;
|
||||
seller: {
|
||||
name: string;
|
||||
};
|
||||
validFrom?: string;
|
||||
};
|
||||
|
||||
export type AggregateOffer = {
|
||||
priceCurrency: string;
|
||||
lowPrice: string;
|
||||
highPrice?: string;
|
||||
offerCount?: string;
|
||||
offers?: Offers | Offers[];
|
||||
};
|
||||
|
||||
export interface OpenGraphVideoActors {
|
||||
profile: string;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export interface OpenGraph {
|
||||
url?: string;
|
||||
type?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
images?: ReadonlyArray<OpenGraphMedia>;
|
||||
videos?: ReadonlyArray<OpenGraphMedia>;
|
||||
defaultImageHeight?: number;
|
||||
defaultImageWidth?: number;
|
||||
locale?: string;
|
||||
site_name?: string;
|
||||
profile?: OpenGraphProfile;
|
||||
book?: OpenGraphBook;
|
||||
article?: OpenGraphArticle;
|
||||
video?: OpenGraphVideo;
|
||||
}
|
||||
|
||||
export interface OpenGraphProfile {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
username?: string;
|
||||
gender?: string;
|
||||
}
|
||||
|
||||
export interface OpenGraphBook {
|
||||
authors?: ReadonlyArray<string>;
|
||||
isbn?: string;
|
||||
releaseDate?: string;
|
||||
tags?: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface OpenGraphArticle {
|
||||
publishedTime?: string;
|
||||
modifiedTime?: string;
|
||||
expirationTime?: string;
|
||||
|
||||
authors?: ReadonlyArray<string>;
|
||||
section?: string;
|
||||
tags?: ReadonlyArray<string>;
|
||||
}
|
||||
|
||||
export interface OpenGraphVideo {
|
||||
actors?: ReadonlyArray<OpenGraphVideoActors>;
|
||||
directors?: ReadonlyArray<string>;
|
||||
writers?: ReadonlyArray<string>;
|
||||
duration?: number;
|
||||
releaseDate?: string;
|
||||
tags?: ReadonlyArray<string>;
|
||||
series?: string;
|
||||
}
|
||||
|
||||
export interface Twitter {
|
||||
handle?: string;
|
||||
site?: string;
|
||||
cardType?: string;
|
||||
}
|
||||
|
||||
interface MobileAlternate {
|
||||
media: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface LanguageAlternate {
|
||||
hreflang: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
interface LinkTag {
|
||||
rel: string;
|
||||
href: string;
|
||||
sizes?: string;
|
||||
media?: string;
|
||||
type?: string;
|
||||
color?: string;
|
||||
as?: string;
|
||||
crossOrigin?: string;
|
||||
}
|
||||
|
||||
export interface BaseMetaTag {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface HTML5MetaTag extends BaseMetaTag {
|
||||
name: string;
|
||||
property?: undefined;
|
||||
httpEquiv?: undefined;
|
||||
}
|
||||
|
||||
export interface RDFaMetaTag extends BaseMetaTag {
|
||||
property: string;
|
||||
name?: undefined;
|
||||
httpEquiv?: undefined;
|
||||
}
|
||||
|
||||
export interface HTTPEquivMetaTag extends BaseMetaTag {
|
||||
httpEquiv:
|
||||
| "content-security-policy"
|
||||
| "content-type"
|
||||
| "default-style"
|
||||
| "x-ua-compatible"
|
||||
| "refresh";
|
||||
name?: undefined;
|
||||
property?: undefined;
|
||||
}
|
||||
|
||||
export type MetaTag = HTML5MetaTag | RDFaMetaTag | HTTPEquivMetaTag;
|
||||
|
||||
export type ImagePrevSize = "none" | "standard" | "large";
|
||||
|
||||
export type AggregateRating = {
|
||||
ratingValue: string;
|
||||
reviewCount?: string;
|
||||
ratingCount?: string;
|
||||
bestRating?: string;
|
||||
};
|
||||
|
||||
export type GamePlayMode = "CoOp" | "MultiPlayer" | "SinglePlayer";
|
||||
|
||||
export type Review = {
|
||||
author: string;
|
||||
datePublished?: string;
|
||||
reviewBody?: string;
|
||||
name?: string;
|
||||
publisher?: Publisher;
|
||||
reviewRating: ReviewRating;
|
||||
};
|
||||
|
||||
export type ReviewRating = {
|
||||
bestRating?: string;
|
||||
ratingValue: string;
|
||||
worstRating?: string;
|
||||
};
|
||||
|
||||
export type Author = {
|
||||
type: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ArticleAuthor = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export type Publisher = {
|
||||
type: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ReviewedBy = {
|
||||
type?: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ApplicationCategory =
|
||||
| "Game"
|
||||
| "SocialNetworking"
|
||||
| "Travel"
|
||||
| "Shopping"
|
||||
| "Sports"
|
||||
| "Lifestyle"
|
||||
| "Business"
|
||||
| "Design"
|
||||
| "Developer"
|
||||
| "Driver"
|
||||
| "Educational"
|
||||
| "Health"
|
||||
| "Finance"
|
||||
| "Security"
|
||||
| "Browser"
|
||||
| "Communication"
|
||||
| "DesktopEnhancement"
|
||||
| "Entertainment"
|
||||
| "Multimedia"
|
||||
| "Home"
|
||||
| "Utilities"
|
||||
| "Reference";
|
||||
|
||||
export type OrganizationCategory =
|
||||
| "Airline"
|
||||
| "Consortium"
|
||||
| "Corporation"
|
||||
| "EducationalOrganization"
|
||||
| "FundingScheme"
|
||||
| "GovernmentOrganization"
|
||||
| "LibrarySystem"
|
||||
| "LocalBusiness"
|
||||
| "MedicalOrganization"
|
||||
| "NGO"
|
||||
| "NewsMediaOrganization"
|
||||
| "PerformingGroup"
|
||||
| "Project"
|
||||
| "ResearchOrganization"
|
||||
| "SportsOrganization"
|
||||
| "WorkersUnion"
|
||||
| "Organization";
|
||||
|
||||
export interface AdditionalRobotsProps {
|
||||
nosnippet?: boolean;
|
||||
maxSnippet?: number;
|
||||
maxImagePreview?: ImagePrevSize;
|
||||
maxVideoPreview?: number;
|
||||
noarchive?: boolean;
|
||||
unavailableAfter?: string;
|
||||
noimageindex?: boolean;
|
||||
notranslate?: boolean;
|
||||
}
|
||||
|
||||
export interface AstroSeoProps {
|
||||
title?: string;
|
||||
titleTemplate?: string;
|
||||
noindex?: boolean;
|
||||
nofollow?: boolean;
|
||||
robotsProps?: AdditionalRobotsProps;
|
||||
description?: string;
|
||||
canonical?: string;
|
||||
mobileAlternate?: MobileAlternate;
|
||||
languageAlternates?: ReadonlyArray<LanguageAlternate>;
|
||||
openGraph?: OpenGraph;
|
||||
facebook?: { appId: string };
|
||||
twitter?: Twitter;
|
||||
additionalMetaTags?: ReadonlyArray<MetaTag>;
|
||||
additionalLinkTags?: ReadonlyArray<LinkTag>;
|
||||
}
|
||||
457
packages/polymech/src/seo/utils/buildTags.ts
Normal file
457
packages/polymech/src/seo/utils/buildTags.ts
Normal file
@ -0,0 +1,457 @@
|
||||
import { escape } from "html-escaper";
|
||||
import type { AstroSeoProps, OpenGraphMedia } from "../types.js";
|
||||
|
||||
const createMetaTag = (attributes: Record<string, string>): string => {
|
||||
const attrs = Object.entries(attributes)
|
||||
.map(([key, value]) => `${key}="${escape(value)}"`)
|
||||
.join(" ");
|
||||
return `<meta ${attrs}>`;
|
||||
};
|
||||
|
||||
const createLinkTag = (attributes: Record<string, string>): string => {
|
||||
const attrs = Object.entries(attributes)
|
||||
.map(([key, value]) => `${key}="${escape(value)}"`)
|
||||
.join(" ");
|
||||
return `<link ${attrs}>`;
|
||||
};
|
||||
|
||||
const createOpenGraphTag = (property: string, content: string): string => {
|
||||
return createMetaTag({ property: `og:${property}`, content });
|
||||
};
|
||||
|
||||
const buildOpenGraphMediaTags = (
|
||||
mediaType: "image" | "video",
|
||||
media: ReadonlyArray<OpenGraphMedia>
|
||||
): string => {
|
||||
let tags = "";
|
||||
|
||||
const addTag = (tag: string) => {
|
||||
tags += tag + "\n";
|
||||
};
|
||||
|
||||
media.forEach((medium) => {
|
||||
addTag(createOpenGraphTag(mediaType, medium.url));
|
||||
|
||||
if (medium.alt) {
|
||||
addTag(createOpenGraphTag(`${mediaType}:alt`, medium.alt));
|
||||
}
|
||||
|
||||
if (medium.secureUrl) {
|
||||
addTag(createOpenGraphTag(`${mediaType}:secure_url`, medium.secureUrl));
|
||||
}
|
||||
|
||||
if (medium.type) {
|
||||
addTag(createOpenGraphTag(`${mediaType}:type`, medium.type));
|
||||
}
|
||||
|
||||
if (medium.width) {
|
||||
addTag(createOpenGraphTag(`${mediaType}:width`, medium.width.toString()));
|
||||
}
|
||||
|
||||
if (medium.height) {
|
||||
addTag(
|
||||
createOpenGraphTag(`${mediaType}:height`, medium.height.toString())
|
||||
);
|
||||
}
|
||||
});
|
||||
return tags;
|
||||
};
|
||||
|
||||
export const buildTags = (config: AstroSeoProps): string => {
|
||||
let tagsToRender = "";
|
||||
|
||||
const addTag = (tag: string) => {
|
||||
tagsToRender += tag + "\n";
|
||||
};
|
||||
|
||||
const addMetaTag = (attributes: Record<string, string>) => {
|
||||
addTag(
|
||||
`<meta ${Object.entries(attributes)
|
||||
.map(([key, value]) => `${key}="${escape(value)}"`)
|
||||
.join(" ")} />`
|
||||
);
|
||||
};
|
||||
|
||||
const addLinkTag = (attributes: Record<string, string>) => {
|
||||
addTag(
|
||||
`<link ${Object.entries(attributes)
|
||||
.map(([key, value]) => `${key}="${escape(value)}"`)
|
||||
.join(" ")} />`
|
||||
);
|
||||
};
|
||||
|
||||
const addOpenGraphTag = (property: string, content: string) => {
|
||||
addMetaTag({ property: `og:${property}`, content });
|
||||
};
|
||||
|
||||
// Title
|
||||
if (config.title) {
|
||||
const formattedTitle = config.titleTemplate
|
||||
? config.titleTemplate.replace("%s", config.title)
|
||||
: config.title;
|
||||
addTag(`<title>${escape(formattedTitle)}</title>`);
|
||||
}
|
||||
|
||||
// Description
|
||||
if (config.description) {
|
||||
addTag(createMetaTag({ name: "description", content: config.description }));
|
||||
}
|
||||
|
||||
// Robots: noindex, nofollow, and other robotsProps
|
||||
let robotsContent: string[] = [];
|
||||
if (typeof config.noindex !== "undefined") {
|
||||
robotsContent.push(config.noindex ? "noindex" : "index");
|
||||
}
|
||||
|
||||
if (typeof config.nofollow !== "undefined") {
|
||||
robotsContent.push(config.nofollow ? "nofollow" : "follow");
|
||||
}
|
||||
|
||||
if (config.robotsProps) {
|
||||
const {
|
||||
nosnippet,
|
||||
maxSnippet,
|
||||
maxImagePreview,
|
||||
noarchive,
|
||||
unavailableAfter,
|
||||
noimageindex,
|
||||
notranslate,
|
||||
} = config.robotsProps;
|
||||
|
||||
if (nosnippet) robotsContent.push("nosnippet");
|
||||
if (typeof maxSnippet === 'number') robotsContent.push(`max-snippet:${maxSnippet}`);
|
||||
if (maxImagePreview)
|
||||
robotsContent.push(`max-image-preview:${maxImagePreview}`);
|
||||
if (noarchive) robotsContent.push("noarchive");
|
||||
if (unavailableAfter)
|
||||
robotsContent.push(`unavailable_after:${unavailableAfter}`);
|
||||
if (noimageindex) robotsContent.push("noimageindex");
|
||||
if (notranslate) robotsContent.push("notranslate");
|
||||
}
|
||||
|
||||
if (robotsContent.length > 0) {
|
||||
addTag(createMetaTag({ name: "robots", content: robotsContent.join(",") }));
|
||||
}
|
||||
|
||||
// Canonical
|
||||
if (config.canonical) {
|
||||
addTag(createLinkTag({ rel: "canonical", href: config.canonical }));
|
||||
}
|
||||
|
||||
// Mobile Alternate
|
||||
if (config.mobileAlternate) {
|
||||
addTag(
|
||||
createLinkTag({
|
||||
rel: "alternate",
|
||||
media: config.mobileAlternate.media,
|
||||
href: config.mobileAlternate.href,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Language Alternates
|
||||
if (config.languageAlternates && config.languageAlternates.length > 0) {
|
||||
config.languageAlternates.forEach((languageAlternate) => {
|
||||
addTag(
|
||||
createLinkTag({
|
||||
rel: "alternate",
|
||||
hreflang: languageAlternate.hreflang,
|
||||
href: languageAlternate.href,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// OpenGraph
|
||||
if (config.openGraph) {
|
||||
const title = config.openGraph?.title || config.title;
|
||||
if (title) {
|
||||
addTag(createOpenGraphTag("title", title));
|
||||
}
|
||||
|
||||
const description = config.openGraph?.description || config.description;
|
||||
if (description) {
|
||||
addTag(createOpenGraphTag("description", description));
|
||||
}
|
||||
|
||||
if (config.openGraph.url) {
|
||||
addTag(createOpenGraphTag("url", config.openGraph.url));
|
||||
}
|
||||
|
||||
if (config.openGraph.type) {
|
||||
addTag(createOpenGraphTag("type", config.openGraph.type));
|
||||
}
|
||||
|
||||
if (config.openGraph.images && config.openGraph.images.length) {
|
||||
addTag(buildOpenGraphMediaTags("image", config.openGraph.images));
|
||||
}
|
||||
|
||||
if (config.openGraph.videos && config.openGraph.videos.length) {
|
||||
addTag(buildOpenGraphMediaTags("video", config.openGraph.videos));
|
||||
}
|
||||
|
||||
if (config.openGraph.locale) {
|
||||
addTag(createOpenGraphTag("locale", config.openGraph.locale));
|
||||
}
|
||||
|
||||
if (config.openGraph.site_name) {
|
||||
addTag(createOpenGraphTag("site_name", config.openGraph.site_name));
|
||||
}
|
||||
|
||||
// Open Graph Profile
|
||||
if (config.openGraph.profile) {
|
||||
if (config.openGraph.profile.firstName) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"profile:first_name",
|
||||
config.openGraph.profile.firstName
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.profile.lastName) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"profile:last_name",
|
||||
config.openGraph.profile.lastName
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.profile.username) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"profile:username",
|
||||
config.openGraph.profile.username
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.profile.gender) {
|
||||
addTag(
|
||||
createOpenGraphTag("profile:gender", config.openGraph.profile.gender)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Open Graph Book
|
||||
if (config.openGraph.book) {
|
||||
if (
|
||||
config.openGraph.book.authors &&
|
||||
config.openGraph.book.authors.length
|
||||
) {
|
||||
config.openGraph.book.authors.forEach((author) => {
|
||||
addTag(createOpenGraphTag("book:author", author));
|
||||
});
|
||||
}
|
||||
if (config.openGraph.book.isbn) {
|
||||
addTag(createOpenGraphTag("book:isbn", config.openGraph.book.isbn));
|
||||
}
|
||||
if (config.openGraph.book.releaseDate) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"book:release_date",
|
||||
config.openGraph.book.releaseDate
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.book.tags && config.openGraph.book.tags.length) {
|
||||
config.openGraph.book.tags.forEach((tag) => {
|
||||
addTag(createOpenGraphTag("book:tag", tag));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Open Graph Article
|
||||
if (config.openGraph.article) {
|
||||
if (config.openGraph.article.publishedTime) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"article:published_time",
|
||||
config.openGraph.article.publishedTime
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.article.modifiedTime) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"article:modified_time",
|
||||
config.openGraph.article.modifiedTime
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.article.expirationTime) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"article:expiration_time",
|
||||
config.openGraph.article.expirationTime
|
||||
)
|
||||
);
|
||||
}
|
||||
if (
|
||||
config.openGraph.article.authors &&
|
||||
config.openGraph.article.authors.length
|
||||
) {
|
||||
config.openGraph.article.authors.forEach((author) => {
|
||||
addTag(createOpenGraphTag("article:author", author));
|
||||
});
|
||||
}
|
||||
if (config.openGraph.article.section) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"article:section",
|
||||
config.openGraph.article.section
|
||||
)
|
||||
);
|
||||
}
|
||||
if (
|
||||
config.openGraph.article.tags &&
|
||||
config.openGraph.article.tags.length
|
||||
) {
|
||||
config.openGraph.article.tags.forEach((tag) => {
|
||||
addTag(createOpenGraphTag("article:tag", tag));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Open Graph Video
|
||||
if (config.openGraph.video) {
|
||||
if (
|
||||
config.openGraph.video.actors &&
|
||||
config.openGraph.video.actors.length
|
||||
) {
|
||||
config.openGraph.video.actors.forEach((actor) => {
|
||||
addTag(createOpenGraphTag("video:actor", actor.profile));
|
||||
if (actor.role) {
|
||||
addTag(createOpenGraphTag("video:actor:role", actor.role));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (
|
||||
config.openGraph.video.directors &&
|
||||
config.openGraph.video.directors.length
|
||||
) {
|
||||
config.openGraph.video.directors.forEach((director) => {
|
||||
addTag(createOpenGraphTag("video:director", director));
|
||||
});
|
||||
}
|
||||
if (
|
||||
config.openGraph.video.writers &&
|
||||
config.openGraph.video.writers.length
|
||||
) {
|
||||
config.openGraph.video.writers.forEach((writer) => {
|
||||
addTag(createOpenGraphTag("video:writer", writer));
|
||||
});
|
||||
}
|
||||
if (config.openGraph.video.duration) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"video:duration",
|
||||
config.openGraph.video.duration.toString()
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.video.releaseDate) {
|
||||
addTag(
|
||||
createOpenGraphTag(
|
||||
"video:release_date",
|
||||
config.openGraph.video.releaseDate
|
||||
)
|
||||
);
|
||||
}
|
||||
if (config.openGraph.video.tags && config.openGraph.video.tags.length) {
|
||||
config.openGraph.video.tags.forEach((tag) => {
|
||||
addTag(createOpenGraphTag("video:tag", tag));
|
||||
});
|
||||
}
|
||||
if (config.openGraph.video.series) {
|
||||
addTag(
|
||||
createOpenGraphTag("video:series", config.openGraph.video.series)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Facebook
|
||||
if (config.facebook && config.facebook.appId) {
|
||||
addTag(
|
||||
createMetaTag({ property: "fb:app_id", content: config.facebook.appId })
|
||||
);
|
||||
}
|
||||
|
||||
// Twitter
|
||||
if (config.twitter) {
|
||||
if (config.twitter.cardType) {
|
||||
addTag(
|
||||
createMetaTag({
|
||||
name: "twitter:card",
|
||||
content: config.twitter.cardType,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (config.twitter.site) {
|
||||
addTag(
|
||||
createMetaTag({ name: "twitter:site", content: config.twitter.site })
|
||||
);
|
||||
}
|
||||
|
||||
if (config.twitter.handle) {
|
||||
addTag(
|
||||
createMetaTag({
|
||||
name: "twitter:creator",
|
||||
content: config.twitter.handle,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Additional Meta Tags
|
||||
if (config.additionalMetaTags && config.additionalMetaTags.length > 0) {
|
||||
config.additionalMetaTags.forEach((metaTag) => {
|
||||
const attributes: Record<string, string> = {
|
||||
content: metaTag.content,
|
||||
};
|
||||
|
||||
if ("name" in metaTag && metaTag.name) {
|
||||
attributes.name = metaTag.name;
|
||||
} else if ("property" in metaTag && metaTag.property) {
|
||||
attributes.property = metaTag.property;
|
||||
} else if ("httpEquiv" in metaTag && metaTag.httpEquiv) {
|
||||
attributes["http-equiv"] = metaTag.httpEquiv;
|
||||
}
|
||||
|
||||
addTag(createMetaTag(attributes));
|
||||
});
|
||||
}
|
||||
|
||||
// Additional Link Tags
|
||||
if (config.additionalLinkTags && config.additionalLinkTags.length > 0) {
|
||||
config.additionalLinkTags.forEach((linkTag) => {
|
||||
const attributes: Record<string, string> = {
|
||||
rel: linkTag.rel,
|
||||
href: linkTag.href,
|
||||
};
|
||||
|
||||
if (linkTag.sizes) {
|
||||
attributes.sizes = linkTag.sizes;
|
||||
}
|
||||
if (linkTag.media) {
|
||||
attributes.media = linkTag.media;
|
||||
}
|
||||
if (linkTag.type) {
|
||||
attributes.type = linkTag.type;
|
||||
}
|
||||
if (linkTag.color) {
|
||||
attributes.color = linkTag.color;
|
||||
}
|
||||
if (linkTag.as) {
|
||||
attributes.as = linkTag.as;
|
||||
}
|
||||
if (linkTag.crossOrigin) {
|
||||
attributes.crossorigin = linkTag.crossOrigin;
|
||||
}
|
||||
|
||||
addTag(createLinkTag(attributes));
|
||||
});
|
||||
}
|
||||
|
||||
return tagsToRender.trim();
|
||||
};
|
||||
118
packages/polymech/src/styles/custom.scss
Normal file
118
packages/polymech/src/styles/custom.scss
Normal file
@ -0,0 +1,118 @@
|
||||
.p-20 {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
UL,
|
||||
LI {
|
||||
list-style-type: inherit;
|
||||
padding: inherit;
|
||||
margin: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.bullets {
|
||||
list-style-type: disc;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.bullets li {
|
||||
list-style-type: inherit;
|
||||
}
|
||||
|
||||
.bullets ul {
|
||||
list-style-type: inherit;
|
||||
}
|
||||
|
||||
.specs TABLE {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.specs TABLE thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.specs .table TR td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table td+td,
|
||||
.table th+th {
|
||||
text-align: right
|
||||
}
|
||||
|
||||
.widget-table {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.widget-table table>tbody>tr>td:nth-of-type(1) {
|
||||
/*background: #f1f1f1;*/
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.widget-table tbody tr:nth-child(even) {
|
||||
background-color: #00c3ff17;
|
||||
}
|
||||
|
||||
.widget-table table {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.widget-table table td {
|
||||
border: 0px solid #dfdfdf;
|
||||
color: #808080;
|
||||
line-height: 1.4;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.widget-table table tbody td {
|
||||
padding: 4px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.widget-table table thead tr th {
|
||||
padding: 4px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: #dfdfdf86;
|
||||
}
|
||||
|
||||
.mySwiper .swiper-slide {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mySlide {}
|
||||
|
||||
.swiper-slide img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.main-image {
|
||||
background-image: none !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.lightbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.max-w-9\/10 {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.max-h-9\/10 {
|
||||
max-height: 90%;
|
||||
}
|
||||
.imagetools-img{
|
||||
background-image: none !important;
|
||||
}
|
||||
2
packages/polymech/src/styles/flowbite.css
Normal file
2
packages/polymech/src/styles/flowbite.css
Normal file
File diff suppressed because one or more lines are too long
1403
packages/polymech/src/styles/global.css
Normal file
1403
packages/polymech/src/styles/global.css
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user