Skip to content

Commit

Permalink
Add structured author data to articles.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zemnmez committed Dec 5, 2024
1 parent 94670a0 commit 205d884
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 7 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"@types/seedrandom": "3.0.8",
"@types/selenium-webdriver": "4.1.22",
"@types/serve-handler": "6.1.4",
"@types/trusted-types": "^2.0.7",
"base64-js": "1.5.1",
"csstype": "3.1.3",
"d3-array": "3.2.4",
Expand Down Expand Up @@ -148,6 +149,7 @@
"source-map-loader": "5.0.0",
"to-vfile": "8.0.0",
"typescript-eslint": "8.17.0",
"trusted-types": "^2.0.0",
"unified": "11.0.5",
"vfile": "6.0.3",
"zod": "3.23.8",
Expand All @@ -162,4 +164,4 @@
"imports": {
"#root/*": "./*"
}
}
}
24 changes: 22 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions project/zemn.me/app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ts_project(
"//:node_modules/@types/d3-array",
"//:node_modules/@types/d3-scale",
"//:node_modules/@types/react",
"//:node_modules/@types/trusted-types",
"//:node_modules/d3-array",
"//:node_modules/d3-scale",
"//:node_modules/immutable",
Expand All @@ -33,6 +34,7 @@ ts_project(
"//ts/react/ErrorDisplay",
"//ts/react/PrettyJSON",
"//ts/react/lang",
"//ts/trusted_types",
],
)

Expand Down
2 changes: 2 additions & 0 deletions project/zemn.me/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const lora = Lora({

export function RootLayout({ children }: Props) {
return (
<>
<Providers>
<html>
<head>
Expand All @@ -42,6 +43,7 @@ export function RootLayout({ children }: Props) {
</body>
</html>
</Providers>
</>
);
}

Expand Down
3 changes: 3 additions & 0 deletions project/zemn.me/components/Article/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ ts_project(
"//:node_modules/@types/node",
"//:node_modules/@types/react",
"//:node_modules/@types/react-dom",
"//:node_modules/@types/trusted-types",
"//:node_modules/next",
"//project/zemn.me/bio",
"//project/zemn.me/components",
"//project/zemn.me/components/Link",
"//ts/react/lang",
"//ts/schema.org",
"//ts/time",
],
)
Expand Down
14 changes: 14 additions & 0 deletions project/zemn.me/components/Article/article.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"use client";
import { useState } from 'react';
import type { Article } from 'schema-dts';

import { schema } from '#root/project/zemn.me/bio/schema.js';
import style from '#root/project/zemn.me/components/Article/style.module.css';
import { tocSegment } from '#root/project/zemn.me/components/Article/toc_context.js'
import { ArticleProps } from '#root/project/zemn.me/components/Article/types/article_types.js';
import { Date } from '#root/ts/react/lang/date.js';
import { Schema } from '#root/ts/schema.org/schema.js';
import { nativeDateFromUnknownSimpleDate } from '#root/ts/time/date.js';

export function Article(props: ArticleProps) {
Expand All @@ -18,6 +21,17 @@ export function Article(props: ArticleProps) {
<tocSegment.Provider value={toc}>
{props.children}
</tocSegment.Provider>
<Schema>
{{
'@context': 'https://schema.org',
'@type': 'Article',
'author': [
schema
],
headline: props.title,
datePublished: props.date ? nativeDateFromUnknownSimpleDate.parse(props.date).toISOString() : undefined
}}
</Schema>
</article>
</div>
}
Expand Down
1 change: 1 addition & 0 deletions ts/next.js/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ts_project(
"//:node_modules/next",
"//:node_modules/react",
"//:node_modules/react-dom",
"//ts/trusted_types",
],
)

Expand Down
15 changes: 11 additions & 4 deletions ts/next.js/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Head from 'next/head';

import { DeclareTrustedTypesPolicy } from '#root/ts/trusted_types/trusted_types.js';

export * as config from '#root/ts/next.js/next.config.js';

type scheme = 'https:' | 'data:';
Expand All @@ -17,7 +19,8 @@ type directives =
| 'default-src'
| 'media-src'
| 'font-src'
| 'require-trusted-types-for';
| 'require-trusted-types-for'
| 'trusted-types';

export type CspPolicy = Partial<Record<directives, sourceList>>;

Expand Down Expand Up @@ -46,9 +49,11 @@ export const DefaultContentSecurityPolicy: CspPolicy = {
'https://*.google-analytics.com',
'https://*.doubleclick.net',
]),
...(process.env.NODE_ENV == 'development'
? {} // below temp disabled while i work out wtf i did wrong...
: {}), // { 'require-trusted-types-for': new Set(["'script'"]) }),

// temp disabled
//'require-trusted-types-for': new Set(["'script'"]),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
'trusted-types': new Set(['default', 'nextjs#bundler']) as any, // 🤷
'script-src': new Set([
"'self'",
"'unsafe-inline'", // https://github.com/vercel/next.js/discussions/54907#discussioncomment-8178117
Expand All @@ -73,6 +78,7 @@ export function HeaderTagsPagesRouter({
}: HeaderTagsProps) {
return (
<>
<DeclareTrustedTypesPolicy/>
<Head>
<meta
content={Object.entries(cspPolicy)
Expand Down Expand Up @@ -103,6 +109,7 @@ export function HeaderTagsAppRouter({
}: HeaderTagsProps) {
return (
<>
<DeclareTrustedTypesPolicy/>
<meta
content={Object.entries(cspPolicy)
.map(([k, v]) => [k, ...v].join(' '))
Expand Down
21 changes: 21 additions & 0 deletions ts/schema.org/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load("//bzl:rules.bzl", "bazel_lint")
load("//ts:rules.bzl", "ts_project")

ts_project(
name = "schema.org",
visibility = ["//:__subpackages__"],
deps = [
"//:base_defs",
"//:node_modules/@types/node",
"//:node_modules/@types/react",
"//:node_modules/@types/react-dom",
"//:node_modules/@types/trusted-types",
"//:node_modules/schema-dts",
"//ts/trusted_types",
],
)

bazel_lint(
name = "bazel_lint",
srcs = ["BUILD.bazel"],
)
24 changes: 24 additions & 0 deletions ts/schema.org/schema.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Thing, WithContext } from "schema-dts";

import { dangerouslyDeclareSafeHTML } from "#root/ts/trusted_types/trusted_types.js";

export interface SchemaProps<T extends Thing> {
readonly children: WithContext<T>
}

/**
* Declare a schema.org compliant schema for this page.
*
* This function uses {@link dangerouslyDeclareSafeHTML}; I think
* it might be safe, but I can't prove it yet. So be careful!
*/
export function Schema<T extends Thing>({children}: SchemaProps<T>) {
return <script
dangerouslySetInnerHTML={{
__html:
dangerouslyDeclareSafeHTML(
JSON.stringify(children))
}}
type="application/ld+json"
/>
}
24 changes: 24 additions & 0 deletions ts/trusted_types/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
load("//bzl:rules.bzl", "bazel_lint")
load("//ts:rules.bzl", "ts_project")

package(default_visibility = ["//:__subpackages__"])

ts_project(
name = "trusted_types",
deps = [
"//:node_modules/@types/memoizee",
"//:node_modules/@types/react",
"//:node_modules/@types/react-dom",
"//:node_modules/@types/trusted-types",
"//:node_modules/memoizee",
"//:node_modules/react",
"//:node_modules/trusted-types",
],
)

bazel_lint(
name = "bazel_lint",
srcs = [
"BUILD.bazel",
],
)
41 changes: 41 additions & 0 deletions ts/trusted_types/trusted_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use client";
import memoize from 'memoizee';
import { useEffect } from 'react';
import { trustedTypes } from 'trusted-types';
import { TrustedHTML } from 'trusted-types/lib';

const trustedTypesPolicy = memoize(() =>
trustedTypes.createPolicy("default", {
createHTML: v => v
})
);

/**
* Declare an HTML fragment as safe against XSS. If no policy
* is set, then this function is an identity operation.
*/
export function dangerouslyDeclareSafeHTML(html: string) {
// below is needed because window can be
// undefined in next prerender.
const h = (trustedTypesPolicy().createHTML)(html)

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (globalThis?.window?.TrustedHTML && !(h instanceof TrustedHTML)) {
throw new Error(`Expected TrustedHTML, got ${typeof h}`);
}

return h;
}

/**
* React component that declares a trusted types policy when mounted.
*
* Unmounting it has no effect.
*/
export function DeclareTrustedTypesPolicy() {
useEffect(() => {
trustedTypesPolicy();
}, []);
return null;
}

0 comments on commit 205d884

Please sign in to comment.