knowii-oss / knowii

Knowii is a next-gen Community Knowledge Management platform
https://knowii.net
GNU Affero General Public License v3.0
9 stars 1 forks source link

Reimplement blog #592

Open dsebastien opened 1 year ago

dsebastien commented 1 month ago

Old layout with customizable metadata:

import Head from 'next/head';
import React, { PropsWithChildren } from 'react';
import Footer from './footer';
import { propertiesOf } from '@knowii/common';
import { useRouter } from 'next/router';
import { NavBar } from './nav-bar';

// eslint-disable-next-line  @typescript-eslint/no-var-requires
const site = require('../../metadata.json').siteName;
// eslint-disable-next-line  @typescript-eslint/no-var-requires
const siteAuthor = require('../../metadata.json').author;
// eslint-disable-next-line  @typescript-eslint/no-var-requires
const siteAuthorAlias = require('../../metadata.json').authorAlias;
// eslint-disable-next-line  @typescript-eslint/no-var-requires
const siteDescription = require('../../metadata.json').description;
// eslint-disable-next-line  @typescript-eslint/no-var-requires
const siteKeywords = require('../../metadata.json').keywords;
// eslint-disable-next-line  @typescript-eslint/no-var-requires
const siteTitle = require('../../metadata.json').title;
// eslint-disable-next-line  @typescript-eslint/no-var-requires
const siteUrl = require('../../metadata.json').url;

/**
 * Page metadata that pages can customize
 * WARNING: Whenever this changes, the for loop below processing custom meta MUST be adapted
 */
export interface SupportedMeta {
  siteName: string;
  author?: string;
  authorAlias?: string;
  image: string;
  title: string;
  description: string;
  type: string;
  date?: string;
  keywords?: string;
  canonicalUrl?: string;
}

const supportedMetaProperties = propertiesOf<SupportedMeta>();

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface LayoutProps
  extends PropsWithChildren<{
    customMeta?: Partial<SupportedMeta>;
  }> {}

export function Layout({ children, customMeta }: LayoutProps) {
  const router = useRouter();

  /**
   * Default metadata
   */
  const meta: SupportedMeta = {
    siteName: site,
    author: siteAuthor,
    authorAlias: siteAuthorAlias,
    title: siteTitle,
    description: siteDescription,
    image: `/images/blog/sebastien-avatar.jpeg`, // FIXME replace with a real banner
    type: 'website',
    canonicalUrl: `${siteUrl}${router.asPath}`,
    keywords: siteKeywords.join(),
  };

  if (customMeta) {
    for (const customMetaKey of Object.keys(customMeta ? customMeta : [])) {
      let propertyHandled = false;

      if (supportedMetaProperties('title') === customMetaKey) {
        if (customMeta.title && customMeta.title.trim().length > 0) {
          meta.title = customMeta.title;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('date') === customMetaKey) {
        if (customMeta.date && customMeta.date.trim().length > 0) {
          meta.date = customMeta.date;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('description') === customMetaKey) {
        if (customMeta.description && customMeta.description.trim().length > 0) {
          meta.description = customMeta.description;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('type') === customMetaKey) {
        if (customMeta.type && customMeta.type.trim().length > 0) {
          meta.type = customMeta.type;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('image') === customMetaKey) {
        if (customMeta.image && customMeta.image.trim().length > 0) {
          meta.image = customMeta.image;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('keywords') === customMetaKey) {
        if (customMeta.keywords && customMeta.keywords.trim().length > 0) {
          meta.keywords = customMeta.keywords;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('canonicalUrl') === customMetaKey) {
        if (customMeta.canonicalUrl && customMeta.canonicalUrl.trim().length > 0) {
          meta.canonicalUrl = customMeta.canonicalUrl;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('siteName') === customMetaKey) {
        if (customMeta.siteName && customMeta.siteName.trim().length > 0) {
          meta.siteName = customMeta.siteName;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('author') === customMetaKey) {
        if (customMeta.author && customMeta.author.trim().length > 0) {
          meta.author = customMeta.author;
        }
        propertyHandled = true;
      }

      if (supportedMetaProperties('authorAlias') === customMetaKey) {
        if (customMeta.authorAlias && customMeta.authorAlias.trim().length > 0) {
          meta.authorAlias = customMeta.authorAlias;
        }
        propertyHandled = true;
      }

      if (!propertyHandled) {
        throw new Error(`Unhandled meta property: ${customMetaKey}`);
      }
    }
  }

  return (
    <>
      <Head>
        <title>{meta.title}</title>
        <meta name="description" content={meta.description} />
        <meta name="keywords" content={meta.keywords} />
        <meta property="og:url" content={`${siteUrl}${router.asPath}`} />
        <link rel="canonical" href={meta.canonicalUrl} />
        <meta property="og:type" content={meta.type} />
        <meta property="og:site_name" content={meta.siteName} />
        <meta property="og:description" content={meta.description} />
        <meta property="og:title" content={meta.title} />
        <meta property="og:image" content={meta.image} />
        <meta property="og:locale" content="en_US" />
        <meta property="image" content={meta.image} />
        <meta name="author" content={meta.author} />
        <meta property="article:author" content={meta.author} />
        <meta name="twitter:creator" content={meta.authorAlias} />
        <meta name="twitter:site" content={meta.authorAlias} />
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:title" content={meta.title} />
        <meta name="twitter:description" content={meta.description} />
        <meta name="twitter:image" content={meta.image} />
        {meta.date && <meta property="article:published_time" content={meta.date} />}
      </Head>
      <a href="#main" aria-label="Skip to the content" className="sr-only focus:not-sr-only">
        Skip to content
      </a>
      <NavBar />

      <main id="main" className="relative pt-20 min-h-[80vh]">
        {children}
      </main>

      <footer>
        <Footer />
      </footer>
    </>
  );
}

export default Layout;
dsebastien commented 1 month ago

Old front-matter intf:

export interface FrontMatter {
  /**
   * Override the default slug if needed
   */
  slug?: string;
  title: string;
  summary: string;
  publishedOn: string;
  image: string;
  /**
   * Image details will be added automatically
   */
  imageDetails?: {
    width: number;
    height: number;
    src: string;
  };
  author?: string;
  /**
   * Image to use for the author. Default will be used if not provided
   */
  authorImage?: string;
  /**
   * Link for the author avatar
   */
  authorLink?: string;
  /**
   * Whether the post is published or not
   */
  published: boolean;
  /**
   * To be used for highlighting posts
   */
  featured?: boolean;
  /**
   * To be used for display (tags)
   */
  categories: string[];
  /**
   * Used as page meta keywords if provided
   */
  keywords: string[];
  /**
   * Used to override the canonical URL of the page
   */
  canonicalUrl?: string;
  /**
   * Added automatically
   */
  wordCount?: number;
  /**
   * Added automatically
   */
  readingTime?: {
    text: string;
    minutes: number;
    time: number;
    words: number;
  };
}
dsebastien commented 1 month ago

Old code: https://github.com/knowii-oss/knowii/tree/ac83ecfd25883c5401c0727a427be03dc60e3c5d