Skip to content

Commit

Permalink
Merge branch '1.x' into readme
Browse files Browse the repository at this point in the history
  • Loading branch information
sherakama authored Dec 19, 2023
2 parents 7e007ee + fdbdb67 commit e20adc8
Show file tree
Hide file tree
Showing 204 changed files with 2,630 additions and 2,931 deletions.
25 changes: 12 additions & 13 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
NEXT_PUBLIC_DRUPAL_BASE_URL=${NEXT_PUBLIC_DRUPAL_BASE_URL}
NEXT_IMAGE_DOMAIN=${NEXT_IMAGE_DOMAIN}
NEXT_PUBLIC_SITE_NAME=${NEXT_PUBLIC_SITE_NAME}
NEXT_PUBLIC_DRUPAL_BASE_URL=https://library-dev.sites-pro.stanford.edu
NEXT_PUBLIC_SITE_NAME=Local Environment

#DRUPAL_DRAFT_CLIENT=${DRUPAL_DRAFT_CLIENT}
#DRUPAL_DRAFT_SECRET=${DRUPAL_DRAFT_SECRET}
DRUPAL_PREVIEW_SECRET=${DRUPAL_PREVIEW_SECRET}
DRUPAL_REVALIDATE_SECRET=${DRUPAL_REVALIDATE_SECRET}
# Grab these from the Vercel Environment Variables.
#DRUPAL_DRAFT_CLIENT=CLIENT
#DRUPAL_DRAFT_SECRET=SECRET
#DRUPAL_PREVIEW_SECRET=SECRET
#DRUPAL_REVALIDATE_SECRET=SECRET

#LIBCAL_CLIENT_ID=${LIBCAL_CLIENT_ID}
#LIBCAL_CLIENT_SECRET=${LIBCAL_CLIENT_SECRET}
#LIBGUIDE_CLIENT_ID=${LIBGUIDE_CLIENT_ID}
#LIBGUIDE_CLIENT_SECRET=${LIBGUIDE_CLIENT_SECRET}
# LibApps credentials.
#LIBGUIDE_CLIENT_ID=ID
#LIBGUIDE_CLIENT_SECRET=SECRET

# When running `yarn build` this will build every page.
#BUILD_COMPLETE=true
# To build every page when running `yarn build`, change this to 'true' (prod environment).
BUILD_COMPLETE=false
14 changes: 8 additions & 6 deletions .github/workflows/build_lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@ jobs:
container:
image: node:18
env:
NODE_ENV: development
BUILD_COMPLETE: ${{ secrets.BUILD_COMPLETE }}
LIBGUIDE_CLIENT_ID: ${{ secrets.LIBGUIDE_CLIENT_ID }}
LIBGUIDE_CLIENT_SECRET: ${{ secrets.LIBGUIDE_CLIENT_SECRET }}
NEXT_PUBLIC_DRUPAL_BASE_URL: ${{ secrets.NEXT_PUBLIC_DRUPAL_BASE_URL }}
NEXT_IMAGE_DOMAIN: ${{ secrets.NEXT_IMAGE_DOMAIN }}
NEXT_PUBLIC_SITE_NAME: ${{ secrets.NEXT_PUBLIC_SITE_NAME }}
steps:
- uses: actions/checkout@v2
- name: Restore Cache
uses: actions/cache@v3
with:
path: |
node_modules
key: 1.x-${{ hashFiles('package.json') }}-${{ hashFiles('yarn.lock') }}
key: 2.x-${{ hashFiles('package.json') }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
1.x-${{ hashFiles('package.json') }}-${{ hashFiles('yarn.lock') }}
1.x-${{ hashFiles('package.json') }}-
1.x-
2.x-${{ hashFiles('package.json') }}-${{ hashFiles('yarn.lock') }}
2.x-${{ hashFiles('package.json') }}-
2.x-
- name: Lint
run: |
yarn
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@
5. Get the environment variables from https://library.sites-pro.stanford.edu/admin/config/services/next/sites/netlify/env and add them to your `.env` file
6. Get DRUPAL_CLIENT_SECRET from Mike or Marc
7. Run `yarn dev`

### Development Notes
The Drupal environment uses a customized paragraph preview implementation that uses an iframe. That preview is in the
`app/(admin)` route group. The reason it is in a different route group is to allow the `(public)` group to have the
layout component for the home page and all slug pages, but not in the preview route.
27 changes: 13 additions & 14 deletions app/(admin)/admin/paragraph/[id]/paragraph-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
"use client";

import {useEffect, useId, useLayoutEffect, useRef, useState} from "react";
import {useEffect, useLayoutEffect, useRef, useState} from "react";
import {DrupalParagraph} from "next-drupal";
import {deserialize} from "@/lib/drupal/deserialize";
import dynamic from "next/dynamic";
import {ArrowPathIcon} from "@heroicons/react/20/solid";

const Paragraph = dynamic(() =>
import('../../../../../components/paragraph/index'), {
loading: () => <ArrowPathIcon className="su-mx-auto su-animate-spin" width={30} height={30}/>
import('../../../../../src/components/paragraph/index'), {
loading: () => <ArrowPathIcon className="mx-auto animate-spin" width={30} height={30}/>
});

const ParagraphPreview = ({}) => {
const heightEmitted = useRef(false);
const elementId = useId()
const previewRef = useRef<HTMLDivElement>(null);
const [paragraph, setParagraph] = useState<DrupalParagraph | null>(null);
const [iframeId, setIframeId] = useState<string | null>(null);

const setParagraphData = ({data}) => {
const setParagraphData = ({data}: { data: string }) => {
try {
const jsonData = JSON.parse(data);
setParagraph(deserialize(jsonData) as DrupalParagraph)
Expand All @@ -28,16 +27,15 @@ const ParagraphPreview = ({}) => {

const emitComponentHeight = () => {

if ((document.getElementById(elementId)?.clientHeight ?? 0) < 100) {
if ((previewRef.current?.clientHeight || 0) < 100) {
setTimeout(emitComponentHeight, 300);
return;
}

heightEmitted.current = true;
if (iframeId && paragraph) {
const message = JSON.stringify({
id: iframeId,
height: document.getElementById(elementId)?.clientHeight
height: previewRef.current?.clientHeight
})
window.parent.postMessage(message, '*');
}
Expand All @@ -54,17 +52,18 @@ const ParagraphPreview = ({}) => {
}, [])

useEffect(() => {
if (iframeId && paragraph) {
return;
}
if (iframeId && paragraph) return;

window.parent.postMessage(JSON.stringify({message: 'refresh'}), '*');
}, [iframeId, paragraph])

useLayoutEffect(() => emitComponentHeight(), [emitComponentHeight, paragraph]);

return (
<div id={elementId} className="su-p-30">
<Paragraph paragraph={paragraph ?? {}}/>
<div ref={previewRef} className="p-30">
{paragraph &&
<Paragraph paragraph={paragraph}/>
}
</div>
)
}
Expand Down
19 changes: 8 additions & 11 deletions app/(public)/[...slug]/metadata.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import {DrupalNode, DrupalParagraph} from "next-drupal";
import {BasicPage, Event, Library, News, Person} from "@/lib/drupal/drupal";
import {DrupalParagraph} from "next-drupal";
import {BasicPage, Event, Library, News, Person, StanfordNode} from "@/lib/drupal/drupal";

interface keyable {
[key: string]: any
}

export const getNodeMetadata = (node: DrupalNode): keyable => {
let metadata: keyable = {};
export const getNodeMetadata = (node: StanfordNode): Record<string, any> => {
let metadata: Record<string, any> = {};
switch (node.type) {
case 'node--stanford_page':
metadata = getMetadataForBasicPage(node);
break;

case 'node--stanford_person':
metadata = getMetadataForPersonPage(node as Person);
metadata = getMetadataForPersonPage(node);
break;
case 'node--stanford_event':
metadata = getMetadataForEventPage(node as Event);
metadata = getMetadataForEventPage(node);
break;

case 'node--stanford_news':
metadata = getMetadataForNewsPage(node as News);
metadata = getMetadataForNewsPage(node);
break;

case 'node--sul_library':
metadata = getMetadataForBranchPage(node as Library);
metadata = getMetadataForBranchPage(node);
break;
}

Expand Down
82 changes: 39 additions & 43 deletions app/(public)/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,33 @@ import {getPathsFromContext} from "@/lib/drupal/get-paths";
import NodePageDisplay from "@/components/node";
import {notFound, redirect} from "next/navigation";
import {translatePathFromContext} from "@/lib/drupal/translate-path";
import {DrupalMenuLinkContent, DrupalNode} from "next-drupal";
import {DrupalMenuLinkContent} from "next-drupal";
import {GetStaticPathsResult, Metadata} from "next";
import {getNodeMetadata} from "./metadata";
import Conditional from "@/components/utils/conditional";
import LibraryHeader from "@/components/node/sul-library/library-header";
import {Library} from "@/lib/drupal/drupal";
import {PageProps, Params, StanfordNode} from "@/lib/drupal/drupal";
import InternalHeaderBanner from "@/components/patterns/internal-header-banner";
import {Suspense} from "react";
import SecondaryMenu from "@/components/menu/secondary-menu";
import {getMenu} from "@/lib/drupal/get-menu";
import {DrupalJsonApiParams} from "drupal-jsonapi-params";
import {isDraftMode} from "@/lib/drupal/is-draft-mode";
import UnpublishedBanner from "@/components/patterns/unpublished-banner";

export const revalidate = 86400;
export const revalidate = 2592000;

class RedirectError extends Error {
constructor(public message: string) {
super(message);
}
}

const fetchNodeData = async (context) => {
const fetchNodeData = async (params: Params) => {
const draftMode = isDraftMode();
const path = await translatePathFromContext(context, {draftMode});
const path = await translatePathFromContext({params}, {draftMode});

// Check for redirect.
if (path?.redirect?.[0].to) {
const currentPath = '/' + (typeof context.params.slug === 'object' ? context.params.slug.join('/') : context.params.slug);
const currentPath = '/' + (typeof params.slug === 'object' ? params.slug.join('/') : params.slug);
const [destination] = path.redirect;

if (destination.to != currentPath) {
Expand All @@ -43,20 +41,20 @@ const fetchNodeData = async (context) => {
throw new Error('Unable to translate path');
}

if (context?.params?.slug?.[0] === 'node' && path?.entity?.path) {
if (params?.slug?.[0] === 'node' && path?.entity?.path) {
throw new RedirectError(path.entity.path);
}

const node = await getResourceFromContext<DrupalNode>(path.jsonapi.resourceName, context,{draftMode})
const node = await getResourceFromContext<StanfordNode>(path.jsonapi.resourceName, {params}, {draftMode})
const fullWidth: boolean = (node?.type === 'node--stanford_page' && node.layout_selection?.resourceIdObjMeta?.drupal_internal__target_id === 'stanford_basic_page_full') ||
(node?.type === 'node--sul_library' && node.layout_selection?.resourceIdObjMeta?.drupal_internal__target_id === 'sul_library_full_width');

return {node, fullWidth}
}

export const generateMetadata = async (context): Promise<Metadata> => {
export const generateMetadata = async ({params}: PageProps): Promise<Metadata> => {
try {
const {node} = await fetchNodeData(context);
const {node} = await fetchNodeData(params);
if (!node) return {};

return getNodeMetadata(node);
Expand All @@ -66,7 +64,7 @@ export const generateMetadata = async (context): Promise<Metadata> => {
return {};
}

const NodePage = async (context) => {
const NodePage = async ({params}: PageProps) => {
let tree: DrupalMenuLinkContent[] = [];
try {
({tree} = await getMenu('main'));
Expand All @@ -75,80 +73,78 @@ const NodePage = async (context) => {

let nodeData;
try {
nodeData = await fetchNodeData(context);
nodeData = await fetchNodeData(params);
} catch (e) {
if (e instanceof RedirectError) {
redirect(e.message);
}
notFound();
}
const {node, fullWidth} = nodeData;
if (!node) notFound();

return (
<main id="main-content" className="su-mb-50">
<main id="main-content" className="mb-50">
{!node.status &&
<UnpublishedBanner/>
}
<Conditional showWhen={node.type === 'node--sul_library'}>
<LibraryHeader node={node as Library}/>
</Conditional>
{node.type === 'node--sul_library' &&
<LibraryHeader node={node}/>
}

<Conditional showWhen={node.type === 'node--stanford_news'}>
{node.type === 'node--stanford_news' &&
<InternalHeaderBanner>
<div
className="su-flex su-flex-col su-w-full su-max-w-[calc(100vw-10rem)] md:su-max-w-[calc(100vw-20rem)] 3xl:su-max-w-[calc(1500px-20rem)] su-mx-auto su-mt-80 md:mt-100 su-mb-50 su-p-0">
className="flex flex-col w-full max-w-[calc(100vw-10rem)] md:max-w-[calc(100vw-20rem)] 3xl:max-w-[calc(1500px-20rem)] mx-auto mt-80 md:mt-100 mb-50 p-0">
<h1
className="su-text-white su-order-2">
className="text-white order-2">
{node.title}
</h1>

{(node.su_news_topics && node.su_news_topics.length > 0) &&
<div className="su-mb-20 su-order-1">
<div className="mb-20 order-1">
{node.su_news_topics.slice(0, 1).map((topic, index) =>
<span key={topic.id} className="su-text-illuminating-dark su-font-semibold">{topic.name}</span>
<span key={topic.id} className="text-illuminating-dark font-semibold">{topic.name}</span>
)}
</div>
}
</div>
</InternalHeaderBanner>
</Conditional>
}

<Conditional showWhen={!(node.type === 'node--sul_library' || node.type === 'node--stanford_news')}>
{!(node.type === 'node--sul_library' || node.type === 'node--stanford_news') &&
<InternalHeaderBanner>
<h1
className="su-w-full su-max-w-[calc(100vw-10rem)] md:su-max-w-[calc(100vw-20rem)] 3xl:su-max-w-[calc(1500px-20rem)] su-mx-auto su-relative su-text-white su-mt-80 md:mt-100 su-mb-50 su-p-0">
className="w-full max-w-[calc(100vw-10rem)] md:max-w-[calc(100vw-20rem)] 3xl:max-w-[calc(1500px-20rem)] mx-auto relative text-white mt-80 md:mt-100 mb-50 p-0">
{node.title}
</h1>
</InternalHeaderBanner>
</Conditional>
}

<Conditional showWhen={fullWidth}>
{fullWidth &&
<div>
<NodePageDisplay node={node}/>
</div>
</Conditional>
}

<Conditional showWhen={!fullWidth}>
<div
className="su-centered su-flex su-flex-col lg:su-flex-row su-justify-between su-gap-[8rem]">
{!fullWidth &&
<div className="centered flex flex-col lg:flex-row justify-between gap-[8rem]">

<Suspense fallback={<></>}>
<SecondaryMenu menuItems={tree}/>
</Suspense>
<SecondaryMenu menuItems={tree} currentPath={node.path.alias}/>

<div className="su-flex-1">
<div className="flex-1">
<NodePageDisplay node={node}/>
</div>
</div>
</Conditional>
}
</main>
)
}

export default NodePage;

export const generateStaticParams = async (context) => {

export const generateStaticParams = async () => {
const completeBuild = process.env.BUILD_COMPLETE === 'true'
const params = new DrupalJsonApiParams();
params.addPageLimit(50);
let paths: GetStaticPathsResult["paths"] = [];
Expand All @@ -160,9 +156,9 @@ export const generateStaticParams = async (context) => {
'node--stanford_news',
'node--stanford_person',
'node--sul_library'
], {}, {params: params.getQueryObject()});
], {params: params.getQueryObject()});

let fetchMore = process.env.BUILD_COMPLETE === 'true';
let fetchMore = completeBuild;
let fetchedData: GetStaticPathsResult["paths"] = []
let page = 1;
while (fetchMore) {
Expand All @@ -175,13 +171,13 @@ export const generateStaticParams = async (context) => {
'node--stanford_news',
'node--stanford_person',
'node--sul_library'
], {}, {params: params.getQueryObject()})
], {params: params.getQueryObject()})
paths = [...paths, ...fetchedData];
fetchMore = fetchedData.length > 0;
page++;
}
} catch (e) {

}
return paths.map(path => typeof path !== "string" ? path?.params : path).slice(0, (process.env.BUILD_COMPLETE ? -1 : 5));
return paths.map(path => typeof path !== "string" ? path?.params : path).slice(0, (completeBuild ? -1 : 5));
}
Loading

0 comments on commit e20adc8

Please sign in to comment.