Skip to content

Commit

Permalink
Merge pull request #694 from lumeland/2.5-dev
Browse files Browse the repository at this point in the history
Lume 2.5
  • Loading branch information
oscarotero authored Dec 13, 2024
2 parents 69b69a9 + eb357fb commit 55d37d0
Show file tree
Hide file tree
Showing 20 changed files with 1,656 additions and 0 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project try to adheres to [Semantic Versioning](https://semver.org/).
Go to the `v1` branch to see the changelog of Lume 1.

## [Unreleased]
### Added
- New plugin: `json_ld` for generating JSON-LD tags in the output [#453]
- New plugin: `purgecss` to remove unused CSS. [#693]

## [2.4.3] - 2024-12-11
### Added
- New option `finalHandler` to `Server` class.
Expand Down Expand Up @@ -538,6 +543,7 @@ Go to the `v1` branch to see the changelog of Lume 1.
[#376]: https://github.com/lumeland/lume/issues/376
[#430]: https://github.com/lumeland/lume/issues/430
[#447]: https://github.com/lumeland/lume/issues/447
[#453]: https://github.com/lumeland/lume/issues/453
[#494]: https://github.com/lumeland/lume/issues/494
[#501]: https://github.com/lumeland/lume/issues/501
[#517]: https://github.com/lumeland/lume/issues/517
Expand Down Expand Up @@ -623,8 +629,10 @@ Go to the `v1` branch to see the changelog of Lume 1.
[#685]: https://github.com/lumeland/lume/issues/685
[#686]: https://github.com/lumeland/lume/issues/686
[#689]: https://github.com/lumeland/lume/issues/689
[#693]: https://github.com/lumeland/lume/issues/693
[#704]: https://github.com/lumeland/lume/issues/704

[Unreleased]: https://github.com/lumeland/lume/compare/v2.4.3...HEAD
[2.4.3]: https://github.com/lumeland/lume/compare/v2.4.2...v2.4.3
[2.4.2]: https://github.com/lumeland/lume/compare/v2.4.1...v2.4.2
[2.4.1]: https://github.com/lumeland/lume/compare/v2.4.0...v2.4.1
Expand Down
2 changes: 2 additions & 0 deletions core/utils/lume_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const pluginNames = [
"google_fonts",
"gzip",
"inline",
"json_ld",
"jsx",
"jsx_preact",
"katex",
Expand All @@ -36,6 +37,7 @@ export const pluginNames = [
"postcss",
"prism",
"pug",
"purgecss",
"reading_info",
"redirects",
"relations",
Expand Down
2 changes: 2 additions & 0 deletions deps/purgecss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "npm:[email protected]";
export { default as purgeHtml } from "npm:[email protected]/lib/purgecss-from-html.esm.js";
266 changes: 266 additions & 0 deletions plugins/json_ld.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { isPlainObject, merge } from "../core/utils/object.ts";
import { getDataValue } from "../core/utils/data_values.ts";

import type Site from "../core/site.ts";
import type { Page } from "../core/file.ts";
import { Graph, Thing } from "npm:[email protected]";
export interface Options {
/** The list extensions this plugin applies to */
extensions?: string[];

/** The key name for the transformations definitions */
name?: string;
}

const defaults: Options = {
extensions: [".html"],
name: "jsonLd",
};

export type JsonldData = Graph | Thing;

/**
* This variable is the result of running
* JSON.stringify(Array.from(document.querySelectorAll('th.prop-nam a')).map(a => a.textContent))
* on https://schema.org/URL and add '@id'.
*/
const urlKeys = [
"@id",
"acceptsReservations",
"acquireLicensePage",
"actionPlatform",
"actionableFeedbackPolicy",
"additionalType",
"afterMedia",
"applicationCategory",
"applicationSubCategory",
"archivedAt",
"artMedium",
"artform",
"artworkSurface",
"asin",
"associatedDisease",
"bankAccountType",
"beforeMedia",
"benefitsSummaryUrl",
"bodyType",
"category",
"childTaxon",
"codeRepository",
"colleague",
"colorSwatch",
"competencyRequired",
"constraintProperty",
"contentUrl",
"correction",
"correctionsPolicy",
"courseMode",
"credentialCategory",
"discussionUrl",
"diseasePreventionInfo",
"diseaseSpreadStatistics",
"diversityPolicy",
"diversityStaffingReport",
"documentation",
"downloadUrl",
"duringMedia",
"editEIDR",
"educationalCredentialAwarded",
"educationalLevel",
"educationalProgramMode",
"embedUrl",
"encodingFormat",
"engineType",
"ethicsPolicy",
"featureList",
"feesAndCommissionsSpecification",
"fileFormat",
"fuelType",
"gameLocation",
"gamePlatform",
"genre",
"gettingTestedInfo",
"gtin",
"hasGS1DigitalLink",
"hasMap",
"hasMenu",
"hasMolecularFunction",
"hasRepresentation",
"healthPlanMarketingUrl",
"identifier",
"image",
"inCodeSet",
"inDefinedTermSet",
"installUrl",
"isBasedOn",
"isBasedOnUrl",
"isInvolvedInBiologicalProcess",
"isLocatedInSubcellularLocation",
"isPartOf",
"keywords",
"knowsAbout",
"labelDetails",
"layoutImage",
"legislationIdentifier",
"license",
"loanType",
"logo",
"mainEntityOfPage",
"map",
"maps",
"masthead",
"material",
"measurementMethod",
"measurementTechnique",
"meetsEmissionStandard",
"memoryRequirements",
"menu",
"merchantReturnLink",
"missionCoveragePrioritiesPolicy",
"namedPosition",
"newsUpdatesAndGuidelines",
"noBylinesPolicy",
"occupationalCredentialAwarded",
"originalMediaLink",
"ownershipFundingInfo",
"parentTaxon",
"paymentUrl",
"physicalRequirement",
"prescribingInfo",
"productReturnLink",
"propertyID",
"publicTransportClosuresInfo",
"publishingPrinciples",
"quarantineGuidelines",
"relatedLink",
"releaseNotes",
"replyToUrl",
"requirements",
"roleName",
"sameAs",
"schemaVersion",
"schoolClosuresInfo",
"screenshot",
"sdLicense",
"season",
"securityClearanceRequirement",
"sensoryRequirement",
"serviceUrl",
"shippingSettingsLink",
"significantLink",
"significantLinks",
"softwareRequirements",
"speakable",
"sport",
"statType",
"storageRequirements",
"surface",
"target",
"targetUrl",
"taxonRank",
"taxonomicRange",
"temporalCoverage",
"termsOfService",
"thumbnailUrl",
"ticketToken",
"titleEIDR",
"tourBookingPage",
"trackingUrl",
"travelBans",
"unitCode",
"unnamedSourcesPolicy",
"url",
"usageInfo",
"usesHealthPlanIdStandard",
"vehicleTransmission",
"verificationFactCheckingPolicy",
"warning",
"webFeed",
];

function isEmpty(v: unknown) {
return v === undefined || v === null || v === "";
}

/**
* A plugin to insert structured JSON-LD data for SEO and social media
* @see https://lume.land/plugins/json_ld/
*/
export function jsonLd(userOptions?: Options) {
const options = merge(defaults, userOptions);

return (site: Site) => {
site.mergeKey(options.name, "object");
site.process(options.extensions, (pages) => pages.forEach(jsonLdProcessor));

function jsonLdProcessor(page: Page) {
let jsonLdData = page.data[options.name] as JsonldData | undefined;

if (!jsonLdData || !page.document) {
return;
}
const { document, data } = page;

// Recursive function to traverse and process JSON-LD data
function traverse(key: string | undefined, value: unknown): unknown {
if (typeof value === "string") {
const dataValue = getDataValue(data, value);
// Check if the value is a URL or ID that needs to be processed
if (
key &&
urlKeys.includes(key) &&
(dataValue.startsWith("/") ||
dataValue.startsWith("./") ||
dataValue.startsWith("../"))
) {
const pageUrl = site.url(data.url, true);
return new URL(dataValue, pageUrl);
}
return isEmpty(dataValue) ? undefined : dataValue;
}
if (Array.isArray(value)) {
return value.reduce((p, c) => {
const processedValue = traverse(key, c);
if (!isEmpty(value)) p.push(processedValue);
return p;
}, []);
}
if (isPlainObject(value)) {
const processedObject: Record<string, unknown> = {};
let isEmptyObject = true;
for (const [key, v] of Object.entries(value)) {
const processedValue = traverse(key, v);
// If there's no valid value other than @type, remove this object
if (!(key === "@type") && processedValue) isEmptyObject = false;
processedObject[key] = processedValue;
}
return isEmptyObject ? undefined : processedObject;
}
return value;
}
jsonLdData = traverse(undefined, jsonLdData) as JsonldData;
if (jsonLdData || Object.keys(jsonLdData ?? {}).length !== 0) {
const script = document.createElement("script");
script.setAttribute("type", "application/ld+json");
script.textContent = JSON.stringify(jsonLdData);
document.head.appendChild(script);
document.head.appendChild(document.createTextNode("\n"));
}
}
};
}

export default jsonLd;

/** Extends Data interface */
declare global {
namespace Lume {
export interface Data {
/**
* JSON_LD elements
* @see https://lume.land/plugins/json_ld/
*/
jsonLd?: JsonldData;
}
}
}
Loading

0 comments on commit 55d37d0

Please sign in to comment.