Compare commits

..

No commits in common. "16a4ab5dd55f77acaa6da31272b1d82b21aca664" and "2d76c34e2d280fecb7750bf0fc795e792cf02685" have entirely different histories.

80 changed files with 11771 additions and 3956 deletions

View file

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

45
.gitignore vendored Normal file → Executable file
View file

@ -1,34 +1,21 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
// .gitnignore
# next.js build output
.next
# dotenv environment variables file (build for Zeit Now)
.env
.env.build
# Dependency directories
node_modules/
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
#VSCode
.vscode/
#ideaj
.idea/
#Feeds
public/feed/
public/sitemap*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

19
Dockerfile Executable file
View file

@ -0,0 +1,19 @@
FROM node:alpine
ENV PORT 3000
RUN mkdir -p /usr/src
WORKDIR /usr/src
COPY package*.json /usr/src/
RUN npm install
COPY . /usr/src
RUN npm run build
EXPOSE 3000
CMD npm run start

View file

@ -1,34 +0,0 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

16
components/audioplayer.js Executable file
View file

@ -0,0 +1,16 @@
import AudioPlayer from "react-h5-audio-player";
import 'react-h5-audio-player/lib/styles.css';
const AbAudioPlayer = (audiodata) => {
return (
<div className="audio">
<AudioPlayer
src={audiodata.audio_mp3.url}
onPlay={_paq.push(['trackEvent', 'Podcasts', 'Play Podcast', audiodata.audio_mp3])}
/>
</div>
)
}
export default AbAudioPlayer

325
components/css/styles.css Executable file
View file

@ -0,0 +1,325 @@
body{
font-family: Jaldi;
font-size: 2em;
margin: 0;
padding: 0;
text-align: center;
font-display: swap;
}
html {
font-size: 10px;
margin-bottom: 60px
}
.menu{
list-style: none
}
.menu li {
display: inline-block
}
.header_image {
margin-bottom: 10px;
}
.sidebar {
display: block;
}
.episode {
margin-bottom: 50px;
text-align: left;
background-color: ghostwhite;
padding: 20px;
border-style: solid;
border-width: 1px;
border-color: darkslategrey;
}
.episode_title {
font-weight: bold;
text-align: justify;
font-size: 30px;
}
.page_title {
font-weight: bold;
padding-bottom: 0.5em;
padding-top: 0.5em;
text-align: center;
}
ul .no-style {
list-style-type: none;
}
#show-links ul {
list-style-type: none;
}
.show_list {
margin-top: 25px;
text-align: justify;
}
.show_list ul {
list-style-type: none;
}
div .ep-title{
font-size: 2em;
font-weight: bold;
}
.ep-feature-image {
display: inline;
}
.featured_episodes{
margin-right: auto;
margin-left: auto;
text-align: center;
overflow: auto;
}
.featured_episodes ul {
list-style-type: none;
display: flex
}
.show_block{
display: inline;
zoom: 1;
height: 100%;
padding: 5px;
margin: 5px;
border-style: solid;
border-width: 1px;
border-radius: 5px;
-moz-border-radius: 5px;
vertical-align: top;
position: relative;
box-shadow: 5px 5px 5px #000;
float: left;
}
.show_container {
display: inline-block;
padding: 10px;
}
.show_description {
text-align: center;
}
.show_block:hover{
box-shadow: 0px 0px 0px #000;
}
.show_block h2{
font-size: 1.2em
}
.show_block .show_teaser{
font-style: italic;
}
@media screen and (min-width: 780px) {
.show_block audio{
width: 300px;
}
}
@media screen and (max-width: 460px) {
.show_block audio{
width: 100px;
}
.featured_episodes ul {
flex-direction: column
}
}
.media {
display: block;
}
.col-xs-12 .featured_episodes .ul .li {
margin-left: 25px;
}
.col-xs-12 .show_block {
width: 100%;
}
.main_content {
margin: 0 auto;
}
.navbar-default {
}
.navbar {
margin-bottom: 5px;
}
.article_body {
text-align: justify;
}
.social_block {
width: 100%;
}
.show_link {
margin: 10px;
}
div .show_list ul li div {
display: inline-block;
text-align: justify;
}
div .episode_pages {
background-color: #fff;
display: inline-block;
float: none;
}
div .episode_pages ul {
list-style: none;
}
div .episode_pages ul li {
float: left;
margin: 5px;
}
div .episode_pages ul li .active_page {
font-weight: bold;
color: #000;
}
.latest_news .article_body {
text-align: center;
}
div .article_sidebar ul {
list-style: none;
}
div .article_sidebar ul li {
margin-left: -10px;
}
div .audio_player {
margin: 10px;
}
div .featuredimage {
margin: 0 auto;
}
@media only screen and (min-width: 768px) {
}
@media screen and (min-width: 992px) {
.col-md-3 {
width: 25%;
float: left
}
.col-md-9 {
width: 75%;
float: left
}
div #header {
text-align: center;
padding-top: 12rem;
padding-bottom: 12rem;
background-color: lightgray;
}
}
@media screen and (min-width: 1200px) {
.col-lg-4 {
width: 33.33333333%;
}
}
.navbar .dropdown-menu a{
font-size: 2em !important
}
div .footer {
height: 2rem;
background-color: black;
width: 100%
}
.container {
min-height: 100vh;
position: relative;
}
footer {
position: absolute;
bottom: 0;
left: 0;
}
.page_body {
text-align: justify;
}
h1 {
font-size: 3rem;
}
h2 {
font-size: 2rem;
font-weight: bold;
}
.featuredFlickr {
padding-top: 30px
}
footer {
padding: 10px;
width: 100%;
position: relative;
}
.transcript {
text-align: justify;
}
blockquote {
font-style: italic;
padding-left: 40px;
border-left: solid;
border-left-width: 1em;
border-left-color: gray;
padding-top: 10px;
}
table td {
font-size: 1em;
}
.carousel {
}
.carousel-item {
display: block
}
.carousel-item img {
max-height: 667px;
}

47
components/episodepager.js Executable file
View file

@ -0,0 +1,47 @@
import ReactPaginate from "react-paginate";
import { useRouter } from "next/router";
import Link from 'next/link'
const EpisodePager = ({ episodedata, config, showdata }) => {
const router = useRouter()
const handlePagination = page => {
const path = router.pathname
const query = router.query
query.page = page.selected + 1
router.push({
pathname: path,
query: query,
})
}
return (
<div className="episode_pages col-md-9 col-sm-12">
<div className="show_episodes">
{episodedata.data.map((episode) => (
<div key={episode.attributes.Slug} className="episode">
<div className="episode_title" id ={episode.Title}>
<Link href={episode.attributes.podcast_sery.data.attributes.Slug + "/episodes/" + episode.attributes.Slug}>{episode.attributes.Title}</Link>
</div>
<div className="content-date">
{episode.attributes.publishedAt}
</div>
<div className="episode_body" dangerouslySetInnerHTML={{ __html: episode.attributes.Description}}></div>
</div>
))}
<ReactPaginate
marginPagesDisplayed={2}
pageRangeDisplayed={5}
previousLabel={"previous"}
nextLabel={"next"}
breakLabel={"..."}
initialPage={episodedata.page}
pageCount={episodedata.meta.pagination.pageCount}
onPageChange={handlePagination}/>
</div>
</div>
)
}
export default EpisodePager

17
components/episodesidebar.js Executable file
View file

@ -0,0 +1,17 @@
import Link from 'next/link'
const EpisodeSideBar = (epdata) => {
return (<div className="side_content col-md-3">
<h2>Latest Episodes</h2>
<hr />
{epdata.epdata.data.map((episode) => (
<div key={episode.Slug}>
<h2><Link href="/podcasts/shows/[show_slug]/episodes/[slug]" as={"/podcasts/shows/" + episode.attributes.podcast_sery.data.attributes.Slug + "/episodes/" + episode.attributes.Slug}>{episode.attributes.Title}</Link></h2>
<div dangerouslySetInnerHTML={{__html: episode.Description}} />
<hr />
</div>
))}
</div> )
};
export default EpisodeSideBar

20
components/featureimage.js Executable file
View file

@ -0,0 +1,20 @@
import Image from "next/legacy/image";
const FeatureImage = ({ imagedata, basepath }) => {
const imgSrc = basepath + imagedata.url
return (
<div className="featuredimage">
<Image
src={imgSrc}
layout="responsive"
height={400}
width={800}
priority={true}
alt={imagedata.name}
/>
</div>
);
};
export default FeatureImage

19
components/footer.js Executable file
View file

@ -0,0 +1,19 @@
//components/Footer.js
const Footer = () => (
<footer>
<div className="row">
<div className="col-sm-4">
Contact Angrybeanie
</div>
<div className="col-sm-4">
Support Angrybeanie
</div>
<div className="col-sm-4">
Something else
</div>
</div>
</footer>
)
export default Footer;

44
components/gallerycarousel.js Executable file
View file

@ -0,0 +1,44 @@
import Image from "next/legacy/image";
import { Navigation, Pagination, Scrollbar, A11y, Autoplay } from 'swiper';
import { Carousel, CarouselItem } from "react-bootstrap";
import { useState } from "react";
import Link from 'next/link'
const GalleryCarousel = ({galleryImages, basepath, gallery}) => {
const bootstrap = galleryImages.data;
const [index, setIndex] = useState(0);
//console.log(bootstrap)
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
return(
<Carousel activeIndex={index} onSelect={handleSelect} fade>
{bootstrap.map((item =>
<Carousel.Item key={item.id} interval={4000}>
<Link href={"/gallery/" + gallery.attributes.Slug + "/" + item.attributes.Slug}><Image
src={basepath + item.attributes.Image.data.attributes.formats.large.url}
style={{
width: '100%',
height: 'auto',
}}
height={item.attributes.Image.data.attributes.formats.large.height}
width={item.attributes.Image.data.attributes.formats.large.width}
key={item.attributes.id}
//className="card-img-top"
alt={item.attributes.Title}
></Image></Link>
<Carousel.Caption >
<h3>{item.attributes.Title}</h3>
</Carousel.Caption>
</Carousel.Item>
)
)}
</Carousel>
)
}
export default GalleryCarousel

52
components/gallerylist.js Executable file
View file

@ -0,0 +1,52 @@
import ReactPaginate from "react-paginate";
import { useRouter } from "next/router";
import Image from "next/legacy/image";
import { Link } from "react-router-dom";
const GalleryList = ({gallery, basepath}) => {
const router = useRouter()
const handlePagination = page => {
const path = router.pathname
const query = router.query
query.page = page.selected + 1
router.push({
pathname: path,
query: query,
})
}
return (
<div>
{gallery.data.map((gall) => {
var image = gall.attributes.gallery_images.data[0].attributes.Image
var imageUrl = basepath + image.data.attributes.formats.small.url
return (
<a href={"/gallery/" + gall.attributes.Slug}>
<div>{gall.attributes.Title}
<Image
src={imageUrl}
layout="responsive"
height={image.data.attributes.formats.small.height}
width={image.data.attributes.formats.small.width}
key={image.data.attributes.id}
className="card-img-top"
alt={gall.attributes.Title}></Image>
</div>
</a>
)
})}
<ReactPaginate
marginPagesDisplayed={2}
pageRangeDisplayed={5}
previousLabel={"previous"}
nextLabel={"next"}
breakLabel={"..."}
initialPage={gallery.meta.pagination.page}
pageCount={gallery.meta.pagination.pageCount}
onPageChange={handlePagination} />
</div>
)
}
export default GalleryList

100
components/gallerypager.js Executable file
View file

@ -0,0 +1,100 @@
import ReactPaginate from "react-paginate";
import { useRouter } from "next/router";
import Image from "next/legacy/image";
import Link from 'next/link'
const GalleryPager = ({galleryImages, basepath, gallery}) => {
const router = useRouter()
const handleImageClick = (e, path) => {
alert(path)
}
const handlePagination = page => {
const path = router.pathname
const query = router.query
query.page = page.selected + 1
router.push({
pathname: path,
query: query,
})
}
const carouselList = []
for (var i = 0; i < galleryImages.meta.pagination.total; i++) {
carouselList.push(<li data-target="carouselExample" data-slide-to={i}></li>)
}
return (
<div className="episode_pages col-sm-12">
<div className="card-columns">
{galleryImages.data.map((galleryImage) => {
var imageUrl = basepath + galleryImage.attributes.Image.data.attributes.formats.small.url
var imageLink = "/gallery/" + gallery.attributes.Slug + "/" + galleryImage.attributes.Slug
return (
<div className="card p-3 hover:cursor-pointer" data-target="#carouselExample" data-slide-to="0">
<Link href={imageLink}><Image
src={imageUrl}
layout="responsive"
height={galleryImage.attributes.Image.data.attributes.formats.small.height}
width={galleryImage.attributes.Image.data.attributes.formats.small.width}
key={galleryImage.attributes.id}
className="card-img-top"
alt={galleryImage.attributes.Title}
></Image></Link>
</div>
)})}
</div>
<div className="modal fade" id="exampleModal" tabIndex="-1" role="dialog" aria-hidden="true">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<div id="carouselExample" className="carousel slide" data-ride="carousel">
<ol>
{carouselList}
</ol>
<div className="carousel-inner">
{galleryImages.data.map((galleryImage) => {
<div className="carousel-item">
<img className="d-block w-100"
src={basepath + galleryImage.attributes.Image.data.attributes.formats.medium.url}></img>
</div>
})}
<a className="carousel-control-prev" href="#carouselExample" role="button" data-slide="prev">
<span className="carousel-control-prev-icon" aria-hidden="true"></span>
<span className="sr-only">Previous</span>
</a>
<a className="carousel-control-next" href="#carouselExample" role="button" data-slide="next">
<span className="carousel-control-next-icon" aria-hidden="true"></span>
<span className="sr-only">Next</span>
</a>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<ReactPaginate
marginPagesDisplayed={2}
pageRangeDisplayed={5}
previousLabel={"previous"}
nextLabel={"next"}
breakLabel={"..."}
initialPage={galleryImages.page}
pageCount={galleryImages.meta.pagination.pageCount}
onPageChange={handlePagination} />
</div>
)
}
export default GalleryPager

24
components/header.js Executable file
View file

@ -0,0 +1,24 @@
// components/Header.js
import Link from "next/link"
const headerStyle = {
backgroundColor: "blue",
color: "white",
width: "100%",
height: "50px"
};
const Header = () => (
<div id="header">
<div className="col-md-12">
<a href="/" alt="Home">
<img className="header_image img-fluid" src="/images/logo.png" alt="Angry Beanie" />
</a>
</div>
<div className="col-md-12">
Follow on <Link href="/feed/Angry-Beanie-feed.xml">RSS</Link>
</div>
</div>
);
export default Header;

29
components/latestepisodes.js Executable file
View file

@ -0,0 +1,29 @@
import Link from 'next/link';
import AudioPlayer from "react-h5-audio-player";
import 'react-h5-audio-player/lib/styles.css';
const LatestEpisodes = ({ episodedata, config }) => (
<div className="featured_episodes">
<h1>Latest Episodes</h1>
<ul className="no-style">
{episodedata.episodes.map((episode) =>(
<li className="col-xs-12 col-md-4 col-lg-4">
<div className="show_block">
<div className="feature_show_title">
<Link href={"/podcasts/shows/"+episode.show_slug} >
{episode.show}
</Link>
</div>
<div className="ep-title">{episode.title}</div>
<AudioPlayer
src={config.audio_path + episode.audio_mp3}
/>
</div>
</li>
))}
</ul>
</div>
);
export default LatestEpisodes

37
components/main.js Executable file
View file

@ -0,0 +1,37 @@
import Header from "./header"
import Footer from "./footer"
import NavBar from "./navbar"
import SideBar from "./sidebar"
import Head from 'next/head'
import { render } from "less"
import { Route, Switch } from 'react-router';
import StorySideBar from "./storysidebar"
const Layout = (props) => {
return (
<div className="Layout">
<Head>
<title>{props.pagedata.title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
<meta name="description" content="Angry Beanie" key='description'></meta>
<meta charSet="utf-8" />
<meta property="og:title" content={props.pagedata.title} key='title'></meta>
<link rel="alternate" type="application/rss+xml" title="RSS Feed for AngryBeanie.com" href="/feed/Angry-Beanie-feed.xml" />
</Head>
<Header />
<div className="container">
<NavBar sections={props.sections}/>
<div className="row">
{props.children}
</div>
<div className="row">
{/* <Footer></Footer> */}
</div>
</div>
</div>
)
}
export default Layout

39
components/navbar.js Executable file
View file

@ -0,0 +1,39 @@
import React, { useState } from 'react';
import Link from 'next/link';
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import NavLink from 'react-bootstrap/NavLink';
import NavItem from 'react-bootstrap/NavItem';
import NavDropdown from 'react-bootstrap/NavDropdown';
const NavBar = (props, sections) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(prevState => !prevState);
return (
<Navbar expand="lg" collapseOnSelect bg="light" variant="light">
<Navbar.Toggle aria-controls="responsive-navbar-nav" />
<Navbar.Collapse>
<Nav className="m-auto">
<NavLink href="/">Home</NavLink>
</Nav>
<Nav className="m-auto">
<NavLink href="/about">About</NavLink>
</Nav>
<NavItem className="m-auto">
<NavLink href="/news">News and such</NavLink>
</NavItem>
<NavDropdown title="Media Projects" className='m-auto'>
<NavDropdown.Item href="/podcasts">Podcasts</NavDropdown.Item>
<NavDropdown.Item href="/photography">Photography</NavDropdown.Item>
</NavDropdown>
<NavItem className="m-auto">
<NavLink href="/tech-and-disability">Tech and Disability</NavLink>
</NavItem>
</Navbar.Collapse>
</Navbar>
);
}
export default NavBar

View file

@ -0,0 +1,14 @@
const PublishedInfo = (publishData) => {
var publishedDate = new Date(publishData.publishData.publishedAt)
var updatedDate = new Date(publishData.publishData.updatedAt)
return (
<div className="publishInfo">
First Published: {publishedDate.toDateString()}<br />
Last Updated: {updatedDate.toDateString()}<br />
</div>
)
}
export default PublishedInfo

2
components/showlist.js Executable file
View file

@ -0,0 +1,2 @@
import Link from "next/link"

26
components/showsidebar.js Executable file
View file

@ -0,0 +1,26 @@
import Image from "next/legacy/image";
import { Link } from 'react-router-dom';
import config from '../data/internal/config'
const ShowSideBar = (props) => {
return(
<div className="side_content col-md-3">
<Image
src={config.siteURL + props.props.data[0].attributes.Logo.data.attributes.url}
height={props.props.data[0].attributes.Logo.data.attributes.height}
width={props.props.data[0].attributes.Logo.data.attributes.width}
/>
<div dangerouslySetInnerHTML={{ __html: props.props.data[0].attributes.Description }}></div>
<h2>Feeds</h2>
<div><a href={props.props.data[0].attributes.iTunesLink}>
Apple Podcast Feed <br />
<img src="/images/iTunes.png" alt="Get it on iTunes"></img>
</a></div>
<div><a href={props.props.data[0].attributes.rssLink+".xml"}>
RSS Feed <br></br>
<img src="/images/rss.png" alt="RSS Feed"></img>
</a></div>
</div>);
}
export default ShowSideBar

7
components/sidebar.js Executable file
View file

@ -0,0 +1,7 @@
const SideBar = (props) => (
<div className="side_content col-md-3">
{props.children}
</div>
);
export default SideBar

45
components/storypager.js Executable file
View file

@ -0,0 +1,45 @@
import ReactPaginate from "react-paginate";
import { useRouter } from "next/router";
import Link from 'next/link'
const StoryPager = ({ storydata }) => {
const router = useRouter()
const handlePagination = page => {
const path = router.pathname
const query = router.query
query.page = page.selected + 1
router.push({
pathname: path,
query: query,
})
}
return (
<div className="episode_pages col-sm-12">
<div className="show_episodes">
{storydata.data.map((story) => (
<div key={story.slug}>
<div className="episode_title">
<Link href="/news/[slug]" as={"/news/" + story.attributes.Slug}>{story.attributes.Title}</Link>
</div>
<div className="article_body" dangerouslySetInnerHTML={{ __html: story.attributes.Abstract }} />
</div>
))}
<ReactPaginate
marginPagesDisplayed={2}
pageRangeDisplayed={5}
previousLabel={"previous"}
nextLabel={"next"}
breakLabel={"..."}
initialPage={storydata.page}
pageCount={storydata.meta.pagination.pageCount}
onPageChange={handlePagination} />
</div>
</div>
)
}
export default StoryPager

17
components/storysidebar.js Executable file
View file

@ -0,0 +1,17 @@
import Link from 'next/link'
const StorySideBar = ({ stories }) => (
<div className="side_content col-md-3">
<h2>Latest Stories</h2>
<hr />
{stories.map((article) => (
<div key={article.attributes.Slug}>
<h2><Link href="/news/[slug]" as={"/news/" + article.attributes.Slug}>{article.attributes.Title}</Link></h2>
<div dangerouslySetInnerHTML={{__html: article.attributes.Lead}} />
<hr></hr>
</div>
))}
</div>
);
export default StorySideBar

510
data/external/cms.js vendored Executable file
View file

@ -0,0 +1,510 @@
import getConfig from 'next/config'
export const getAllPosts = async (filter, page, limit) => {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const qs = require('qs')
const qVal = []
if (page == null) {
page = 0
}
if (limit == null) {
limit = 50;
}
var filters = {}
if (filter) {
filters = {
project: {
Slug: {
$in: filter
}
}
}
}
const qArray = {
sort: ['publishedAt:desc'],
pagination: {
page: page,
pageSize: limit
},
populate: '*',
filters
}
const query = qs.stringify( qArray, {
encodeValuesOnly: true,
})
const res = await fetch(serverRuntimeConfig.base_path + `articles?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await res.json()
}
export const getSinglePost = async (postId) => {
const { serverRuntimeConfig} = getConfig()
const url = serverRuntimeConfig.base_path + 'articles?filters[Slug][$eq]=' + postId + '&populate=*'
const res = await fetch(url, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const artdata = await res.json()
return artdata.data[0].attributes
}
export const getAllPodcastSeries = async () => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
const query = qs.stringify({
populate: {
Logo: '*',
podcast_episodes: {
populate:['Audio_MP3', 'Subtitles']
}
}
}, {
encodeValuesOnly: true,
})
const res = await fetch(serverRuntimeConfig.base_path + `podcast-series?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await res.json()
}
export const getAllPodcastEpisodes = async () => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
const query = qs.stringify({
populate: {}
}, {
encodeValuesOnly: true
})
const res = await fetch(serverRuntimeConfig.base_path + `podcast-episodes?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await res.json()
}
export const getProjectDetails = async (projectId) => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
const query = qs.stringify({
filters: {
Slug: {
$eq: projectId
}
},
populate: '*'
}, {
encodeValuesOnly: true,
})
const res = await fetch(serverRuntimeConfig.base_path + `projects?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await res.json()
}
export const getPodcastEpisode = async (episodeId) => {
const { serverRuntimeConfig} = getConfig()
const qs = require('qs')
const query = qs.stringify({
filters: {
Slug: {
$eq: episodeId
}
},
populate: '*'
}, {
encodeValuesOnly: true,
})
const epres = await fetch(serverRuntimeConfig.base_path + `podcast-episodes?${query}`, {
headers: new Headers({
'Authorization':serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await epres.json()
}
export const getPodcastSeries = async (podcastId) => {
const { serverRuntimeConfig} = getConfig()
const qs = require('qs')
const query = qs.stringify({
filters: {
Slug: {
$eq: podcastId
}
},
populate: '*'
}, {
encodeValuesOnly: true,
})
const url = serverRuntimeConfig.base_path + `podcast-series?${query}`;
const showres = await fetch(url, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await showres.json()
}
export const getPodcastSeriesEpisodes = async (podcastId, limit, page) => {
const { serverRuntimeConfig} = getConfig()
const qs = require('qs')
const query = qs.stringify({
filters: {
podcast_sery: {
Slug: {
$eq: podcastId
}
}
},
pagination: {
page: page,
pageSize: limit
},
sort: ['publishedAt:desc'],
populate: '*'
}, {
encodeValuesOnly: true,
})
const epres = await fetch(serverRuntimeConfig.base_path + `podcast-episodes?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await epres.json();
}
export const getLatestPodcastEpisode = async () => {
const { serverRuntimeConfig} = getConfig()
const qs = require('qs')
var query = qs.stringify({
sort: ['publishedAt:desc'],
pagination: {
page: 1,
pageSize: 1,
},
populate: '*'
})
const epres = await fetch(serverRuntimeConfig.base_path + `podcast-episodes?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const episode = await epres.json()
const seriesres = await getPodcastSeries(episode.data[0].attributes.podcast_sery.data.attributes.Slug)
const res = {
...episode,
...seriesres.data[0].attributes
}
return res
}
export const getAllGalleries = async(page, limit) => {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const qs = require('qs')
var query = qs.stringify({
sort: ['publishedAt:desc'],
pagination: {
page: page,
pageSize: limit
},
populate: {
gallery_images: {
populate: '*'
}
}
})
const galres = await fetch(process.env.API + `galleries?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await galres.json()
}
export const getGalleries = async () => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
var query = qs.stringify({
sort: ['publishedAt:desc'],
pagination: {
page: 1,
pageSize: 5,
},
populate: '*'
})
const galres = await fetch(process.env.API + `galleries?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await galres.json()
}
export const getGallery = async (gallerySlug) => {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const qs = require('qs')
var query = qs.stringify({
filters: {
Slug: {
$eq: gallerySlug
}
},
populate: '*'
})
const galres = await fetch(process.env.API + `galleries?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await galres.json()
}
export const getAllGalleryImages = async () => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
var query = qs.stringify({
sort: ['createdAt:desc'],
populate: '*'
})
const imageres = await fetch(process.env.API + `gallery-images?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return imageres.json()
}
export const getLatestGalleryImage = async () => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
var query = qs.stringify({
pagination: {
page: 1,
pageSize: 1
},
populate: '*'
})
const imageres = await fetch(process.env.API + `gallery-images?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return imageres.json()
}
export const getGalleryImage = async (imageSlug) => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
var query = qs.stringify({
filters: {
Slug: {
$eq: imageSlug
}
},
populate: '*'
})
const imageres = await fetch(process.env.API + `gallery-images?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await imageres.json()
}
export const getGalleryImages = async (galleryId, page, limit) => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
if (page == null) {
page = 0
}
var query = qs.stringify({
sort: ['createdAt:desc'],
filters: {
galleries: {
Slug: {
$eq: galleryId
}
}
},
populate: '*',
pagination: {
page: page,
pageSize: limit
}
})
const galres = await fetch(process.env.API + `gallery-images?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await galres.json()
}
export const getPodcastList = async (podcastStatus) => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
const query = qs.stringify({
filters: {
status: {
$eq: true
}
},
populate: '*'
}, {
encodeValuesOnly: true,
})
const currpodcastres = await fetch(serverRuntimeConfig.base_path + `podcast-series?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
return await currpodcastres.json()
}
export const getLatestContent = async () => {
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
const newsQuery = qs.stringify({
filters: {
status: {
$eq: true
},
pagination: {
page: 1,
pageSize: 1
}
},
populate: '*'
}, {
encodeValuesOnly: true,
})
const latestNews = await fetch(serverRuntimeConfig.base_path + `articles?${newsQuery}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const galleryImageQuery = qs.stringify({
filters: {
status: {
$eq: true
},
pagination: {
page: 1,
pageSize: 1
}
},
populate: '*'
}, {
encodeValuesOnly: true,
})
const latestGalleryImages = await fetch(serverRuntimeConfig.base_path + `gallery_images?${galleryImageQuery}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const podcastEpisodeQuery = qs.stringify({
filters: {
status: {
$eq: true
},
pagination: {
page: 1,
pageSize: 1
}
},
populate: '*'
}, {
encodeValuesOnly: true,
})
const latestPodcastEpisodes = await fetch(serverRuntimeConfig.base_path + `podcast_episode?${podcastEpisodeQuery}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const combined = latestNews
return combined
}

8
data/internal/config.js Executable file
View file

@ -0,0 +1,8 @@
export const config = {
siteName: "AngryBeanie",
siteDescription: "A place for all my projects, thoughts and ramblings",
siteLogo: "",
siteURL: "https://www.angrybeanie.com"
}
export default config

334
data/internal/feed-generator.js Executable file
View file

@ -0,0 +1,334 @@
import { Feed } from "feed"
import { Podcast } from 'podcast';
import getConfig from 'next/config'
import { getAllGalleryImages, getAllPodcastSeries, getAllPosts, getProjectDetails } from "../external/cms"
import fs from "fs"
import config from './config'
import htmlFindReplaceElementAttrs from "html-find-replace-element-attrs"
import Image from "next/legacy/image"
export const generateRssFeed = async (filter) => {
const project = await getProjectDetails(filter);
const posts = typeof filter === "undefined" ? await getAllPosts() : await getAllPosts(project.data[0].attributes.Slug);
const siteURL = config.siteURL;
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const date = new Date();
const Title = typeof filter === "undefined" ? "Angry Beanie" : project.data[0].attributes.Title
const Description = typeof filter == "undefined" ? "A place for all my thoughts and projects" : project.data[0].attributes.Description
const feedTitle = typeof filter === "undefined" ? "Angry-Beanie" : project.data[0].attributes.Slug
const author = {
name: "James Purser",
email: "james@angrybeanie.com",
link: "https://www.angrybeanie.com",
};
const feed = new Feed({
title: Title,
description: Description,
id: siteURL,
link: siteURL,
image: `${siteURL}/public/images/logo.svg`,
favicon: `${siteURL}/public/images/favicon.png`,
copyright: `All rights reserved ${date.getFullYear()}, James Purser`,
updated: date,
generator: "Feed for Node.js",
feedLinks: {
rss2: `${siteURL}/feed/${feedTitle}-feed.xml`,
json: `${siteURL}/feed/${feedTitle}-feed.json`,
atom: `${siteURL}/feed/${feedTitle}-atom.xml`,
},
author,
});
posts.data.forEach((post) => {
const url = `${siteURL}/news/${post.attributes.Slug}`;
const body = htmlFindReplaceElementAttrs.replace(
post.attributes.FullBody,
item => item.parsedUrl,
{
tag: "img",
attr: "src",
parseAttrValueAsUrl: true,
baseUrl: config.siteURL,
urlProtocol: "https",
}
)
var fullbody;
if (post.attributes.FeatureImage.data) {
const imagepath = serverRuntimeConfig.media_path + post.attributes.FeatureImage.data.attributes.formats.large.url
const featuredImage = <Image src={imagepath} height="100%" width="100%" />
fullbody = "<p><img src=" + imagepath + "></img></p>"+body
} else {
fullbody = body
}
feed.addItem({
title: post.attributes.Title,
id: url,
link: url,
description: fullbody,
content: fullbody,
author: [author],
contributor: [author],
date: new Date(post.attributes.publishedAt),
});
});
fs.mkdirSync("./public/feed", { recursive: true });
fs.writeFileSync(`./public/feed/${feedTitle}-feed.xml`, feed.rss2());
fs.writeFileSync(`./public/feed/${feedTitle}-atom.xml`, feed.atom1());
fs.writeFileSync(`./public/feed/${feedTitle}-feed.json`, feed.json1());
}
export const generatePodcastFeeds = async () => {
const podcastSeries = await getAllPodcastSeries()
const siteURL = config.siteURL;
const author = {
name: "James Purser",
email: "james@angrybeanie.com",
link: "https://aus.social/purserj",
};
const date = new Date();
podcastSeries.data.forEach((series) => {
const feed = new Podcast({
itunesAuthor: "James Purser",
itunesOwner: {
name: "James Purser",
email: "james@angrybeanie.com"
},
title: series.attributes.Title,
description: series.attributes.Description,
id: siteURL + "/podcasts/shows/" + series.attributes.Slug,
link: siteURL + "/podcasts/shows/" + series.attributes.Slug,
image: `${siteURL}` + series.attributes.Logo.data.attributes.url,
itunesImage: `${siteURL}` + series.attributes.Logo.data.attributes.url,
language: "English",
favicon: `${siteURL}/public/images/favicon.png`,
copyright: `All rights reserved ${date.getFullYear()}, James Purser`,
updated: date,
generator: "Feed for Node.js",
itunesCategory: [{text: series.attributes.iTunesCategory}],
});
const episodes = series.attributes.podcast_episodes.data
episodes.forEach((episode) => {
var subtitles = {}
if ( episode.attributes.Subtitles.data !== null) {
subtitles = {
url: `${siteURL}${episode.attributes.Subtitles.data.attributes.url}`,
type: episode.attributes.Subtitles.data.attributes.mime,
language: "en"
}
//console.log(subtitles)
}
const url = `${siteURL}/podcasts/shows/${series.attributes.Slug}/${episode.attributes.Slug}`;
const media_url = `${siteURL}${episode.attributes.Audio_MP3.data.attributes.url}`
const media = {
url: media_url,
type: episode.attributes.Audio_MP3.data.attributes.mime,
size: episode.attributes.Audio_MP3.data.attributes.size,
title: episode.attributes.Audio_MP3.data.attributes.name,
duration: 0,
}
feed.addItem({
title: episode.attributes.Title,
id: url,
url: url,
description: episode.attributes.Description,
content: episode.attributes.Description,
author: [author],
contributor: [author],
date: new Date(episode.attributes.publishedAt),
enclosure: media,
itunesSubtitle: subtitles,
podcastTranscript: subtitles
})
})
fs.writeFileSync(`./public/feed/${series.attributes.Slug}.xml`, feed.buildXml());
})
}
export const generateGalleryImageFeed = async () => {
const galleryImages = await getAllGalleryImages()
const Title = "Angry Beanie Gallery Image feed"
const Description = "A feed of the photos that I post"
const feedTitle = "gallery-image-feed"
const siteURL = config.siteURL;
const date = new Date();
const author = {
name: "James Purser",
email: "james@angrybeanie.com",
link: "https://www.angrybeanie.com",
};
const feed = new Feed({
title: Title,
description: Description,
id: siteURL,
link: siteURL,
image: `${siteURL}/public/images/logo.png`,
favicon: `${siteURL}/public/images/favicon.png`,
copyright: `All rights reserved ${date.getFullYear()}, James Purser`,
updated: date,
generator: "Feed for Node.js",
feedLinks: {
rss2: `${siteURL}/feed/${feedTitle}-feed.xml`,
json: `${siteURL}/feed/${feedTitle}-feed.json`,
atom: `${siteURL}/feed/${feedTitle}-atom.xml`,
},
author,
});
galleryImages.data.forEach((post) => {
const siteURL = config.siteURL;
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const imgUrl = `${siteURL}${post.attributes.Image.data.attributes.url}?width=700`
var postBody = `<img src="${imgUrl}" alt="${post.attributes.Image.data.attributes.alternativeText}" />`
const url = `${siteURL}/gallery/${post.attributes.galleries.data[0].attributes.Slug}/${post.attributes.Slug}`
feed.addItem({
title: post.attributes.Title,
id: url,
link: url,
description: postBody,
content: postBody,
author: [author],
contributor: [author],
date: new Date(post.attributes.publishedAt),
});
})
fs.writeFileSync(`./public/feed/${feedTitle}-feed.xml`, feed.rss2());
fs.writeFileSync(`./public/feed/${feedTitle}-atom.xml`, feed.atom1());
fs.writeFileSync(`./public/feed/${feedTitle}-feed.json`, feed.json1());
}

View file

@ -0,0 +1,65 @@
import { getAllPosts } from "../external/cms"
import fs from "fs"
import prettier from "prettier"
import config from './config'
import path from "path"
export const generateSitemap = async () => {
const getDate = new Date().toISOString()
const staticPaths = fs.readdirSync("pages", {withFileTypes: true})
.filter((staticPage) => {
if(staticPage.isFile()) {
return ![
"api",
"_app.js",
"_document.js",
"404.js",
"sitemap.xml.js",
"index.js"
].includes(staticPage.name);
}
})
.map((staticPagePath) => {
return `${config.siteURL}/${path.parse(staticPagePath.name).name}`;
}
);
const postData = await getAllPosts()
const postList = []
postData.data.forEach(post => postList.push({slug: post.attributes.Slug, updatedAt: post.attributes.updatedAt}))
const formatted = sitemap => prettier.format(sitemap, { parser: "html" });
const pageListMap = staticPaths.map(page => {
return `
<url>
<loc>${page}</loc>
<lastmod>${getDate}</lastmod>
<priority>1</priority>
</url>`
}).join("")
const postListSiteMap = postList.map(post => {
return `
<url>
<loc>${`${config.siteURL}/news/${post.slug}`}</loc>
<lastmod>${post.updatedAt}</lastmod>
<priority>0.5</priority>
</url>`
})
.join("")
const generatedSitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
>${pageListMap}${postListSiteMap}
</urlset>
`
const formattedSitemap = [formatted(generatedSitemap)];
fs.writeFileSync("public/sitemap.xml", generatedSitemap, "utf8")
}

16
lib/rss.js Executable file
View file

@ -0,0 +1,16 @@
import Parser from 'rss-parser'
export const FEEDS = [
{
name: "tad",
url: "https://tad.angrybeanie.com/feed/"
}
];
export async function getFeed(feedUrl) {
let parser = new Parser();
let feed = await parser.parseURL(feedUrl);
return feed;
}

14
lib/usePageTracking.js Executable file
View file

@ -0,0 +1,14 @@
import { useEffect} from "react";
import { useLocation } from "react-router-dom";
import ReactGA from "react-ga";
const usePageTracking = () => {
const location = useLocation();
useEffect(() => {
ReactGA.initialize("UA-000000000-0");
ReactGA.pageview(location.pathname + location.search);
}, [location]);
};
export default usePageTracking;

20
next.config.js Normal file → Executable file
View file

@ -1,3 +1,21 @@
// next.config.js
const withCSS = require('@zeit/next-css');
const withFonts = require('next-fonts');
const withImages = require('next-images');
const withPlugins = require("next-compose-plugins");
module.exports = withPlugins([withCSS, withFonts, withImages])
module.exports = {
reactStrictMode: true,
serverRuntimeConfig: {
// Will only be available on the server side
base_path: 'http://localhost:1337/api/',
audio_path: 'https://audio.angrybeanie.com/',
media_path: process.env.MEDIA_BASE,
strapi_token: process.env.STRAPI_TOKEN
},
images: {
domains: ['www.angrybeanie.com', 'localhost', 'cms.local.angrybeanie.com', '127.0.0.1']
},
publicRuntimeConfig: {
}
}

8590
package-lock.json generated Normal file → Executable file

File diff suppressed because it is too large Load diff

47
package.json Normal file → Executable file
View file

@ -1,20 +1,43 @@
{
"name": "angrybeanie_frontend",
"version": "0.1.0",
"private": true,
"version": "0.0.1",
"description": "The frontend for the angrybeanie website",
"main": "index.js",
"scripts": {
"dev": "next dev",
"dev": "NODE_OPTIONS='--inspect' next",
"build": "next build",
"start": "next start",
"lint": "next lint"
"start": "next start"
},
"author": "James Purser",
"license": "ISC",
"dependencies": {
"next": "11.1.2",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"eslint": "7.32.0",
"eslint-config-next": "11.1.2"
"@socialgouv/matomo-next": "^1.6.1",
"@zeit/next-css": "^1.0.1",
"@zeit/next-less": "^1.0.1",
"bootstrap": "^4.6.0",
"eslint-config-next": "^13.4.3",
"feed": "^4.2.2",
"fs": "^0.0.1-security",
"html-find-replace-element-attrs": "^1.0.0",
"less": "^3.13.1",
"mdbreact": "^5.1.0",
"next": "^13.4.3",
"next-compose-plugins": "^2.2.1",
"next-fonts": "^1.5.1",
"next-images": "^1.8.1",
"podcast": "github:purserj/node-podcast",
"postcss": "^8.3.6",
"prettier": "^2.7.1",
"qs": "^6.10.3",
"react": "^18.2.0",
"react-bootstrap": "^1.6.7",
"react-dom": "^18.2.0",
"react-ga": "^3.3.0",
"react-h5-audio-player": "^3.7.1",
"react-paginate": "^6.5.0",
"react-router-dom": "^5.2.1",
"rss-parser": "^3.12.0",
"sharp": "^0.30.6",
"swiper": "^8.3.1"
}
}

3
pages/404.js Normal file
View file

@ -0,0 +1,3 @@
export default function Custom404() {
return <h1>500 - Server-side error occurred</h1>
}

3
pages/500.js Normal file
View file

@ -0,0 +1,3 @@
export default function Custom500() {
return <h1>500 - Server-side error occurred</h1>
}

46
pages/_app.js Normal file → Executable file
View file

@ -1,7 +1,45 @@
import '../styles/globals.css'
// import App from 'next/app'
import "@fortawesome/fontawesome-free/css/all.min.css";
import 'bootstrap-css-only/css/bootstrap.min.css';
import 'mdbreact/dist/css/mdb.css';
import { Fragment, useEffect } from 'react';
import "../components/css/styles.css"
import { Router, useRouter } from 'next/router'
import { init } from "@socialgouv/matomo-next";
const MATOMO_URL = process.env.NEXT_PUBLIC_MATOMO_URL;
const MATOMO_SITE_ID = process.env.NEXT_PUBLIC_MATOMO_SITE_ID;
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url) => {
export default MyApp
};
init({ url: MATOMO_URL, siteId: MATOMO_SITE_ID });
router.events.on("routeChangeComplete", handleRouteChange);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}, [router.events]);
return (
<Component {...pageProps} />
)
}
// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext) => {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);
//
// return { ...appProps }
// }
export default MyApp

30
pages/_document.js Executable file
View file

@ -0,0 +1,30 @@
import Document, { Html, Head, Main, NextScript } from 'next/document';
import React from 'react';
import { Fragment } from 'react'
class CustomDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<Fragment>
<link rel="icon" href="/images/favicon-32.png" sizes="32x32" />
<link rel="icon" href="/images/favicon-57.png" sizes="57x57" />
<link rel="icon" href="/images/favicon-76.png" sizes="76x76" />
<link rel="icon" href="/images/favicon-96.png" sizes="96x96" />
<link rel="icon" href="/images/favicon-128.png" sizes="128x128" />
<link rel="icon" href="/images/favicon-192.png" sizes="192x192" />
<link rel="icon" href="/images/favicon-228.png" sizes="228x228" />
</Fragment>
<link href="https://fonts.googleapis.com/css?family=Cabin|Jaldi&display=swap" rel="stylesheet" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default CustomDocument;

46
pages/about.js Normal file
View file

@ -0,0 +1,46 @@
import "../components/main"
import getConfig from 'next/config'
import Layout from "../components/main"
import Link from 'next/link'
export async function getServerSideProps(context) {
const { serverRuntimeConfig } = getConfig()
const secres = await fetch(serverRuntimeConfig.base_path + `/api/sections`)
const secdata = await secres.json()
const pagedata = {'title': 'About Angry Beanie'}
return {
props: { sections : secdata, pagedata }, // will be passed to the page component as props
}
}
function About({sections, pagedata}) {
return <Layout sections={sections} pagedata={pagedata}>
<h1 className="page_title col-sm-12">About Angry Beanie</h1>
<div className="col-sm-12 article_body">
Welcome to Angry Beanie. A place where I stick stuff that I make, whether it's blog posts, podcasts, videos, photos, whatever. If I make it I put it here.
</div>
<div className="col-sm-6 article_body">
<h2>Contact me</h2>
If you want to get a hold of me regarding anything I put up here, you can get me on the socials.
<div>
Mastodon: <a href="https://aus.social/@purserj" rel="me">https://aus.social/@purserj</a>
</div>
<div>
Twitter: <Link href="https://twitter.com/purserj">https://twitter.com/purserj</Link>
</div>
<div>
Instagram: <Link href="https://www.instagram.com/purserj/">https://www.instagram.com/purserj/</Link>
</div>
<div>
Pixelfed: <Link href="https://pixelfed.au/purserj">https://pixelfed.au/purserj</Link>
</div>
</div>
</Layout>
}
export default About

View file

@ -1,5 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}

View file

@ -0,0 +1,59 @@
import "../../components/main.js"
import getConfig from 'next/config'
import Layout from "../../components/main.js"
import { getGallery, getGalleryImages } from "../../data/external/cms.js"
import GalleryPager from "../../components/gallerypager.js"
import Head from 'next/head'
import config from "../../data/internal/config.js"
import GalleryCarousel from "../../components/gallerycarousel.js"
export async function getServerSideProps(context) {
const gallery = await getGallery(context.params.gallery)
if(context.query.page == null || context.query.page == '0') {
var page = 1;
} else {
var page = Number(context.query.page)
}
const galleryImages = await getGalleryImages(context.params.gallery, page, 9)
//console.log(galleryImages)
const { serverRuntimeConfig } = getConfig()
const pagedata = {
'title': "Angry Beanie - " + gallery.data[0].attributes.Title
}
const og_image = serverRuntimeConfig.media_path + galleryImages.data[0].attributes.Image.data.attributes.formats.large.url
return {
props: {pagedata, gallery: gallery.data[0], galleryImages, serverRuntimeConfig, og_image},
}
}
const Gallery = ({pagedata, gallery, galleryImages, serverRuntimeConfig, og_image}) => {
if (!gallery) return null
return(
<Layout pagedata={pagedata}>
<Head>
<meta name="twitter:card" content={ gallery.attributes.Title } key="twcard" />
<meta name="twitter:creator" content="angrybeanie" key="twhandle" />
<meta name="og:url" content={config.siteURL + "/galleries/" + gallery.attributes.Slug}></meta>
<meta name="og:type" content="Photo Gallery"></meta>
<meta name="og:title" content={ gallery.attributes.Title } key="title"></meta>
<meta name="og:image" content={og_image}></meta>
<meta name="og:description" content={ gallery.attributes.Description } key="description"></meta>
</Head>
<h1 className="page_title col-sm-12">{gallery.attributes.Title}</h1>
<GalleryCarousel galleryImages={galleryImages} basepath={serverRuntimeConfig.media_path} gallery={gallery}></GalleryCarousel>
<GalleryPager galleryImages={galleryImages} basepath={serverRuntimeConfig.media_path} gallery={gallery}></GalleryPager>
</Layout>
)
}
export default Gallery

View file

@ -0,0 +1,78 @@
import Head from "next/head";
import Image from "next/legacy/image";
import getConfig from 'next/config'
import "../../../components/main"
import Layout from "../../../components/main.js"
import { getAllGalleryImages, getGalleryImage } from "../../../data/external/cms.js";
import config from "../../../data/internal/config.js"
const galleryImage = ({pagedata, imageDetails, basepath, config} ) => {
if(!imageDetails) return null
let sanitisedDesc = imageDetails.data[0].attributes.Description.replace(/(<([^>]+)>)/gi, "")
return (<Layout pagedata={pagedata} imageDetails={imageDetails}>
<Head>
<meta name="twitter:card" content="summary_large_image" key="twcard" />
<meta name="twitter:creator" content="angrybeanie" key="twhandle" />
<meta name="twitter:site" content="@angrybeanie"></meta>
<meta name="twitter:title" content={imageDetails.data[0].attributes.Title}></meta>
<meta name="og:url" content={config.siteURL + "/galleryimages/" + imageDetails.data[0].attributes.Slug}></meta>
<meta name="og:type" content="article"></meta>
<meta name="og:title" content={ imageDetails.data[0].attributes.Title } key="title"></meta>
<meta name="og:description" content={ sanitisedDesc } key="description"></meta>
<meta name="og:image" content={basepath + imageDetails.data[0].attributes.Image.data.attributes.formats.large.url}></meta>
<meta name="og:image:alt" content={ sanitisedDesc }></meta>
</Head>
<div className="main_content col-sm-12">
<Image
src={basepath + imageDetails.data[0].attributes.Image.data.attributes.formats.large.url}
layout="intrinsic"
width={imageDetails.data[0].attributes.Image.data.attributes.formats.large.width}
height={imageDetails.data[0].attributes.Image.data.attributes.formats.large.height}
alt={imageDetails.data[0].attributes.Image.data.attributes.alternativeText}
></Image>
<div className="col-sm-9 article_body">
<h2>Photo Information</h2>
<div>
<p><strong>Title:</strong> {imageDetails.data[0].attributes.Title}</p>
<p><strong>Gallery:</strong> <a href={"/gallery/" + imageDetails.data[0].attributes.galleries.data[0].attributes.Slug}>{imageDetails.data[0].attributes.galleries.data[0].attributes.Title}</a></p>
<p dangerouslySetInnerHTML={{ __html: imageDetails.data[0].attributes.Description}}></p>
</div>
</div>
</div>
</Layout>)
}
export default galleryImage
export async function getStaticPaths() {
const posts = await getAllGalleryImages()
const paths = posts.data.map((post) => ({
params: { galleryImage: post.attributes.Slug, gallery: post.attributes.galleries.data[0].attributes.Slug },
}))
return {
paths,
fallback: true // false or 'blocking'
};
}
export async function getStaticProps (context){
const { serverRuntimeConfig } = getConfig()
const slug = context.params.galleryImage
const galImage = await getGalleryImage(slug)
const pagedata = {
'title': "Angry Beanie - " + galImage.data[0].attributes.Title
}
return {
props: { pagedata, imageDetails: galImage, basepath: serverRuntimeConfig.media_path, config },
revalidate: 60
}
}

139
pages/index.js Normal file → Executable file
View file

@ -1,69 +1,88 @@
import Layout from "../components/main"
import FeatureImage from "../components/featureimage"
import PublishedInfo from '../components/publishedinfo.js'
import getConfig from 'next/config'
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import config from '../data/internal/config'
import { generateGalleryImageFeed, generatePodcastFeeds, generateRssFeed } from "../data/internal/feed-generator"
import { generateSitemap } from "../data/internal/sitemap-generator"
import { getLatestContent, getLatestPodcastEpisode } from "../data/external/cms"
//import { getLatestGalleryImage } from "../data/external/cms"
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
export async function getStaticProps(context) {
generateRssFeed()
generateRssFeed('tech-and-disability')
generatePodcastFeeds()
generateGalleryImageFeed()
generateSitemap()
const { serverRuntimeConfig } = getConfig()
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
const qs = require('qs')
const query = qs.stringify({
pagination: {
limit: 1
},
populate: {
FeatureImage: '*'
},
sort: ['publishedAt:desc'],
}, {
encodeValuesOnly: true,
})
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.js</code>
</p>
const res = await fetch(serverRuntimeConfig.base_path +`articles?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const artdata = await res.json()
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h2>Documentation &rarr;</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
const article = artdata.data[0].attributes
<a href="https://nextjs.org/learn" className={styles.card}>
<h2>Learn &rarr;</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
const pagedata = {'title': 'Angry Beanie'}
<a
href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card}
>
<h2>Examples &rarr;</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
const combined = getLatestContent()
<a
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
>
<h2>Deploy &rarr;</h2>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
return {
props: { article, pagedata, config: serverRuntimeConfig, siteConfig: config},
revalidate: 60 // will be passed to the page component as props
}
}
function HomePage (props) {
var featureImage
if (props.article.FeatureImage.data) {
if (props.article.FeatureImage.data.attributes.formats.large) {
featureImage = props.article.FeatureImage.data.attributes.formats.large
featureImage.name = props.article.FeatureImage.data.attributes.alternativeText
}
}
var article_desc = props.article.Abstract.replace(new RegExp('<[^>]*>', 'g'), '')
return <Layout pagedata={props.pagedata}>
<Head>
<meta name="twitter:card" content={ article_desc } key="twcard" />
<meta name="twitter:creator" content="angrybeanie" key="twhandle" />
<meta name="og:url" content={config.siteURL + "/news/" + props.article.Slug}></meta>
<meta name="og:type" content="article"></meta>
<meta name="og:title" content={ props.article.Title } key="title"></meta>
<meta name="og:description" content={ article_desc } key="description"></meta>
{ props.article.FeatureImage.data != null &&
<meta name="og:image" content={props.config.media_path + featureImage.url}></meta>
}
</Head>
<div className="main_content col-md-9 col-sm-12">
{ props.article.FeatureImage.data != null &&
<FeatureImage imagedata = {featureImage} basepath = {props.config.media_path} ></FeatureImage>
}
<h1 className="page_title col-sm-12">{ props.article.Title }</h1>
<PublishedInfo publishData={props.article}></PublishedInfo>
<div className="article_body" dangerouslySetInnerHTML={{ __html: props.article.FullBody }}></div>
</div>
</Layout>
}
export default HomePage

138
pages/index_old.js Normal file
View file

@ -0,0 +1,138 @@
import Layout from "../components/main"
import LatestEpisodes from "../components/latestepisodes"
import Link from 'next/link'
import getConfig from 'next/config'
import Head from 'next/head'
import Image from "next/legacy/image";
import config from '../data/internal/config';
import { FEEDS, getFeed } from "../lib/rss"
import { generatePodcastFeeds, generateRssFeed } from "../data/internal/feed-generator"
import { generateSitemap } from "../data/internal/sitemap-generator"
import { getLatestPodcastEpisode } from "../data/external/cms"
import { getLatestGalleryImage } from "../data/external/cms"
export async function getStaticProps(context) {
generateRssFeed()
generateRssFeed('tech-and-disability')
generatePodcastFeeds()
generateSitemap()
const { serverRuntimeConfig } = getConfig()
const qs = require('qs')
const query = qs.stringify({
pagination: {
limit: 1
},
populate: {
FeatureImage: '*'
},
sort: ['publishedAt:desc'],
}, {
encodeValuesOnly: true,
})
const res = await fetch(serverRuntimeConfig.base_path +`articles?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const artdata = await res.json()
const article = artdata.data[0].attributes
const epdata = await getLatestPodcastEpisode()
const firstimage = await getLatestGalleryImage()
const pagedata = {'title': 'Angry Beanie'}
return {
props: { article, pagedata, config: serverRuntimeConfig, firstimage, episodedata: epdata, siteConfig: config},
revalidate: 60 // will be passed to the page component as props
}
}
function HomePage (props) {
return (
<div>
<Head>
<title>{props.pagedata.title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>
<meta name="description" content="Angry Beanie"></meta>
<meta charSet="utf-8" />
<meta property="og-title" content="Angry Beanie - Home"></meta>
</Head>
<div className="container">
<div className="frontPageImage">
<img className="header_image img-fluid" src="/images/logo.png" alt="Angry Beanie" />
</div>
<div className="row">
<div className="col-sm-4">
<div className="card">
<div className="card-img-top">
{/* <img src={props.episodedata.Logo.data.attributes.url} alt={episode.show} height={props.episodedata.Logo.data.attributes.height}></img> */}
<Image
src={props.config.media_path + props.article.FeatureImage.data.attributes.formats.thumbnail.url}
alt={props.article.Title}
layout="fixed"
height={props.article.FeatureImage.data.attributes.formats.thumbnail.height}
width={props.article.FeatureImage.data.attributes.formats.thumbnail.width}
className="card-img-top"
></Image>
</div>
<div className="card-title">
<h3>Latest Blog Post</h3>
</div>
<div className="card-body">
<Link href="/news/[slug]" as={"/news/" + props.article.Slug}>{props.article.Title}</Link>
<div dangerouslySetInnerHTML={{ __html:props.article.Abstract}}></div>
</div>
</div>
</div>
{props.episodedata.data.map((episode) => (
<div className="col-sm-4">
<div className="card">
<div className="card-img-top">
{/* <img src={props.episodedata.Logo.data.attributes.url} alt={episode.show} height={props.episodedata.Logo.data.attributes.height}></img> */}
<Image
src={props.config.media_path + props.episodedata.Logo.data.attributes.formats.thumbnail.url}
alt={episode.show}
layout="fixed"
height={props.episodedata.Logo.data.attributes.formats.thumbnail.height}
width={props.episodedata.Logo.data.attributes.formats.thumbnail.width}
className="card-img-top"
></Image>
</div>
<div className="card-title">
<h3>Latest Podcast</h3>
</div>
<div className="card-body">
<Link href={"/podcasts/shows/" + props.episodedata.data[0].attributes.podcast_sery.Slug + "/" + props.episodedata.data[0].attributes.Slug}>{props.episodedata.data[0].attributes.Title}</Link>
<div dangerouslySetInnerHTML={{ __html: episode.episode_lead}}></div>
</div>
</div>
</div>
))}
<div className="col-sm-4">
<div className="card">
<div className="card-img-top">
<a href={"/galleryimages/" + props.firstimage.data[0].attributes.Slug}>
<img className="featuredFlickr" src={ props.config.media_path + props.firstimage.data[0].attributes.Image.data.attributes.formats.thumbnail.url }></img>
</a>
</div>
<div className="card-title">
<h3>Latest Photo</h3>
</div>
<div className="card-body">
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default HomePage

34
pages/news.js Executable file
View file

@ -0,0 +1,34 @@
import "../components/main"
import getConfig from 'next/config'
import Layout from "../components/main"
import StoryPager from "../components/storypager"
import { getAllPosts } from "../data/external/cms"
export async function getServerSideProps(context) {
if(context.query.page == null || context.query.page == '0') {
var page = 1;
} else {
var page = Number(context.query.page)
}
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const articles = await getAllPosts(null, page, 5)
const secres = await fetch(serverRuntimeConfig.base_path + `/api/sections`)
const secdata = await secres.json()
const pagedata = {'title': 'Angry Beanie News'}
return {
props: { articles, sections : secdata, pagedata }, // will be passed to the page component as props
}
}
function News({ articles, sections, pagedata }) {
return <Layout sections={sections} pagedata={pagedata}>
<h1 className="page_title col-sm-12">NEWS</h1>
<StoryPager storydata={articles} />
</Layout>
}
export default News

125
pages/news/[slug].js Executable file
View file

@ -0,0 +1,125 @@
import getConfig from 'next/config'
import "../../components/main.js"
import Layout from "../../components/main.js"
import FeatureImage from "../../components/featureimage.js"
import StorySideBar from '../../components/storysidebar.js'
import PublishedInfo from '../../components/publishedinfo.js'
import { getAllPosts, getSinglePost } from '../../data/external/cms'
import Image from "next/legacy/image";
import Head from 'next/head'
import config from "../../data/internal/config"
const Article = ({article_obj, sections, pagedata, stories, serverRuntimeConfig, config}) => {
if (!article_obj) return null
var featureImage
if (article_obj.FeatureImage.data) {
if (article_obj.FeatureImage.data.attributes.formats.large) {
featureImage = article_obj.FeatureImage.data.attributes.formats.large
featureImage.name = article_obj.FeatureImage.data.attributes.alternativeText
}
}
var article_desc = article_obj.Abstract.replace(new RegExp('<[^>]*>', 'g'), '')
var rssFeed
if (article_obj.project.data && article_obj.project.data.attributes.HasFeed == true) {
rssFeed = config.siteURL+"/feed/"+article_obj.project.data.attributes.Slug+"-feed.xml"
}
return ( <Layout sections={sections} pagedata={pagedata}>
<Head>
<meta name="twitter:card" content={ article_desc } key="twcard" />
<meta name="twitter:creator" content="angrybeanie" key="twhandle" />
<meta name="og:url" content={config.siteURL + "/news/" + article_obj.Slug}></meta>
<meta name="og:type" content="article"></meta>
<meta name="og:title" content={ article_obj.Title } key="title"></meta>
<meta name="og:description" content={ article_desc } key="description"></meta>
{ article_obj.FeatureImage.data != null &&
<meta name="og:image" content={serverRuntimeConfig.media_path + featureImage.url}></meta>
}
{ article_obj.project.data != null && article_obj.project.data.attributes.HasFeed == true &&
<link rel="alternate" type="application/rss+xml" title={article_obj.project.data.attributes.Title + " Feed"} href={rssFeed} />
}
</Head>
<div className="main_content col-md-9 col-sm-12">
{ article_obj.FeatureImage.data != null &&
<FeatureImage imagedata = {featureImage} basepath = {serverRuntimeConfig.media_path} ></FeatureImage>
}
<h1 className="page_title col-sm-12">{ article_obj.Title }</h1>
<PublishedInfo publishData={article_obj}></PublishedInfo>
<div className="article_body" dangerouslySetInnerHTML={{ __html: article_obj.FullBody }}></div>
</div>
<StorySideBar stories={stories} />
</Layout>
)
}
export default Article
export async function getStaticPaths() {
const posts = await getAllPosts()
const paths = posts.data.map((post) => ({
params: { slug: post.attributes.Slug },
}))
return {
paths,
fallback: true // false or 'blocking'
};
}
export async function getStaticProps({params}) {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const conf = config
const slug = params.slug
const article_obj = await getSinglePost(slug)
var filters = {};
if (article_obj.project.data != null) {
filters = (
{
project: {
Slug: article_obj.project.data.attributes.Slug
}
})
}
const qs = require('qs')
const query = qs.stringify({
pagination: {
limit: 5
},
filters,
sort: ['publishedAt:desc']
}, {
encodeValuesOnly: true,
})
const storiesQuery = await fetch(serverRuntimeConfig.base_path + `articles?${query}`, {
headers: new Headers({
'Authorization': serverRuntimeConfig.strapi_token,
'Content-Type': 'application/x-www-form-urlencoded'
})
})
const storydata = await storiesQuery.json()
const stories = storydata.data
const pagedata = {
'title': "Angry Beanie - " + article_obj.Title
}
const secres = await fetch(serverRuntimeConfig.base_path + '/api/sections')
const secdata = await secres.json()
return {
props: { article_obj, sections: secdata, pagedata, stories, serverRuntimeConfig, config }, // will be passed to the page component as props
revalidate: 60
}
}

48
pages/photography.js Executable file
View file

@ -0,0 +1,48 @@
import Layout from "../components/main.js"
import GalleryList from "../components/gallerylist.js"
import { getAllGalleries } from "../data/external/cms.js"
import getConfig from 'next/config'
import config from "../data/internal/config"
import Head from 'next/head'
export async function getStaticProps({params}) {
const pagedata = {
title: "Angrybeanie - Photography"
}
const { serverRuntimeConfig } = getConfig()
const rssFeed = config.siteURL+"/feed/gallery-images-feed.xml"
const gallerylist = await getAllGalleries()
return {
props: { pagedata, serverRuntimeConfig, gallerylist, rssFeed },
revalidate: 60
}
}
const Page = ({pagedata, serverRuntimeConfig, gallerylist, rssFeed}) => {
return(<Layout pagedata={pagedata} serverRuntimeConfig={serverRuntimeConfig} gallerylist={gallerylist}>
<Head>
<link rel="alternate" type="application/rss+xml" title="Gallery Image Feed" href={rssFeed} />
</Head>
<h1 className="page_title col-sm-12">Photography</h1>
<div className="col-sm-12 col-md-6 article_body">
<p>I've always been drawn to photography. As a kid my Dad used to have his own darkroom and I remember the smell
of the chemicals and the wonder as the images went from the slightly disconcerting inverted colours on the negatives
to a full colour explosion on the paper.</p>
<p>I like taking pictures, I love capturing a moment and freezing it in time, so that I can look back and remember, or that I can
share that moment with others.</p>
<p>I'm also learning new things. I'm pushing myself to do more with the camera. Not just the "candid" shots that my family put up with,
but branching out into Macro photography, portraits and street photography.</p>
<p>So here is the space where I post the images I make, and the journey I take in my photography.</p>
</div>
<div className="col-sm-12 col-md-6">
<GalleryList gallery={gallerylist} basepath={serverRuntimeConfig.media_path}></GalleryList>
</div>
</Layout>)
}
export default Page

64
pages/podcasts.js Executable file
View file

@ -0,0 +1,64 @@
import "../components/main.js"
import Layout from "../components/main.js"
import Link from 'next/link'
import getConfig from 'next/config'
import Image from "next/legacy/image";
import { getPodcastList } from "../data/external/cms.js";
export async function getServerSideProps(context) {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const secres = await fetch(serverRuntimeConfig.base_path + `/api/sections`)
const secdata = await secres.json()
const currpodcastlist = await getPodcastList(true)
const archpodcastlist = await getPodcastList(false)
const episodedata = "hi there"
const pagedata = {'title': 'Angry Beanie - Current Podcast Projects'}
return {
props: { sections : secdata, currpodcastlist: currpodcastlist.data, archpodcastlist, episodedata, pagedata, serverRuntimeConfig }, // will be passed to the page component as props
}
}
const Podcasts = ({sections, currpodcastlist, archpodcastlist, episodedata, pagedata, serverRuntimeConfig}) => {
return (
<Layout sections={sections} episodedata={episodedata} pagedata={pagedata} serverRuntimeConfig>
<h1 className="page_title col-sm-12">Podcasts</h1>
<div className="page_body col-sm-12"><p>Over the years I have made a number of podcasts.</p></div>
<div className="row col-sm-12">
<div className="col-sm-6">
<h2>Current Podcasts</h2>
{currpodcastlist.map((podcast) => (
<div>
<Image
src={serverRuntimeConfig.media_path + "/" + podcast.attributes.Logo.data.attributes.formats.thumbnail.url}
height={podcast.attributes.Logo.data.attributes.formats.thumbnail.height}
width={podcast.attributes.Logo.data.attributes.formats.thumbnail.width}
alt={podcast.attributes.Title}
></Image><br />
<Link href={"/podcasts/shows/" + podcast.attributes.Slug}>{podcast.attributes.Title}</Link></div>
))}
</div>
<div className="col-sm-6">
<h2>Archived Podcasts</h2>
{archpodcastlist.length > 0 &&
archpodcastlist.map((podcast) => (
<div>
<Image
src={serverRuntimeConfig.media_path + "/" + podcast.attributes.Logo.data.attributes.formats.thumbnail.url}
height={podcast.attributes.Logo.data.attributes.formats.thumbnail.height}
width={podcast.attributes.Logo.data.attributes.formats.thumbnail.width}
alt={podcast.attributes.Title}
></Image>
<Link href={"/podcasts/shows/" + podcast.attributes.Slug}>{podcast.attributes.Title}</Link></div>
))}
</div>
</div>
</Layout>)
}
export default Podcasts

View file

@ -0,0 +1,82 @@
import { useRouter } from 'next/router'
import getConfig from 'next/config'
import Layout from "../../../components/main.js"
import AudioPlayer from "react-h5-audio-player";
import 'react-h5-audio-player/lib/styles.css';
import EpisodeSideBar from "../../../components/episodesidebar"
import { getPodcastEpisode, getPodcastSeriesEpisodes } from '../../../data/external/cms.js';
import Head from "next/head"
import config from "../../../data/internal/config"
import { push } from '@socialgouv/matomo-next';
export async function getServerSideProps(context) {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const slug = context.params.episode
const secres = await fetch(serverRuntimeConfig.base_path + '/api/sections')
const secdata = await secres.json()
const episode = await getPodcastEpisode(slug)
const sepisodes = await getPodcastSeriesEpisodes(episode.data[0].attributes.podcast_sery.data.attributes.Slug, 5, 0)
const audiodata = {
audio_path: serverRuntimeConfig.audio_path,
audio_mp3: episode.data[0].attributes.Audio_MP3.data.attributes
}
const pagedata = {
'title': 'Angry Beanie - ' + episode.data[0].attributes.Title
}
return {
props: { pagedata, sections: secdata, episode, audiodata, sepisodes, config }
}
}
const Episode = ( props ) => {
function pushLocalStart() {
if(typeof window !== "undefined") {push(["trackEvent", "MediaStats", "AudioStart", props.audiodata.audio_mp3])}
}
function pushLocalPause() {
if(typeof window !== "undefined") {push(["trackEvent", "MediaStats", "AudioPause", props.audiodata.audio_mp3])}
}
function pushLocalEnd() {
if(typeof window !== "undefined") {push(["trackEvent", "MediaStats", "AudioEnd", props.audiodata.audio_mp3])}
}
return (<Layout
pagedata={props.pagedata}
sections={props.sections}
episode={props.episode}
audiodata={props.audiodata}
sepdata={props.sepisodes}>
<Head>
<meta name="twitter:card" content={ props.episode.Description } key="twcard" />
<meta name="twitter:creator" content="angrybeanie" key="twhandle" />
<meta name="og:url" content={props.config.siteURL + "/podcasts/shows/" + props.episode.data[0].attributes.podcast_sery.data.attributes.Slug + "/" + props.episode.data[0].attributes.Slug}></meta>
<meta name="og:type" content="podcast"></meta>
<meta name="og:title" content={ props.episode.data[0].attributes.Title } key="title"></meta>
<meta name="og:description" content={ props.episode.data[0].attributes.Body } key="description"></meta>
</Head>
<div className="main_content col-md-9 col-sm-12">
<h1>{props.episode.data[0].attributes.Title}</h1>
<AudioPlayer
src={props.audiodata.audio_mp3.url}
onPlay={pushLocalStart}
onPause={pushLocalPause}
onEnded={pushLocalEnd}
/>
<div className="article_body" dangerouslySetInnerHTML={{ __html: props.episode.data[0].attributes.Description}} />
<h3>Transcript</h3>
<div className="transcript" dangerouslySetInnerHTML={{ __html: props.episode.data[0].attributes.Transcript}}></div>
</div>
<EpisodeSideBar epdata={props.sepisodes}></EpisodeSideBar>
</Layout>)
}
export default Episode

View file

@ -0,0 +1,39 @@
import getConfig from 'next/config'
import "../../../components/main.js"
import Layout from "../../../components/main.js"
import EpisodePager from "../../../components/episodepager.js"
import { getPodcastSeries, getPodcastSeriesEpisodes } from '../../../data/external/cms.js'
import ShowSideBar from '../../../components/showsidebar.js'
export async function getServerSideProps(context) {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const slug = context.params.podcast
const showdata = await getPodcastSeries(slug)
if(context.query.page == null || context.query.page == '0') {
var page = 0;
} else {
var page = Number(context.query.page)
}
const epdata = await getPodcastSeriesEpisodes(slug, 5, page)
const secres = await fetch(serverRuntimeConfig.base_path + '/api/sections')
const secdata = await secres.json()
const pagedata = {
'title': 'Angry Beanie - ' + showdata.title
}
return {
props: {pagedata, sections: secdata, showdata, epdata, serverRuntimeConfig}
}
}
const Podcast = (props) => (
<Layout pagedata={props.pagedata} sections={props.sections} showdata={props.showdata}>
<h1 className="page_title col-sm-12">{props.showdata.title}</h1>
<EpisodePager episodedata={props.epdata} config={props.serverRuntimeConfig} showdata={props.showdata}/>
<ShowSideBar props={props.showdata}></ShowSideBar>
</Layout>
)
export default Podcast

29
pages/sections/[page].js Executable file
View file

@ -0,0 +1,29 @@
import Layout from "../../components/main"
import Link from 'next/link'
import getConfig from 'next/config'
export async function getServerSideProps(context) {
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const pslug = context.params.page
const res = await fetch(serverRuntimeConfig.base_path + `/api/page/` + pslug)
const pagedata = await res.json()
pagedata.title = "Angry Beanie - " + pagedata.title
const secres = await fetch(serverRuntimeConfig.base_path + `/api/sections`)
const secdata = await secres.json()
return {
props: { sections : secdata, pagedata }, // will be passed to the page component as props
}
}
function Page ({articles, sections, pagedata}) {
return (<Layout sections={sections} pagedata={pagedata}>
<h1>{pagedata.title}</h1>
<div className="page_body" dangerouslySetInnerHTML={{ __html: pagedata.body }}>
</div>
</Layout>);
}
export default Page

46
pages/tech-and-disability.js Executable file
View file

@ -0,0 +1,46 @@
import "../components/main"
import getConfig from 'next/config'
import config from "../data/internal/config"
import Layout from "../components/main"
import StoryPager from "../components/storypager"
import { getAllPosts, getProjectDetails } from "../data/external/cms";
import Head from 'next/head'
//import { popStoryPager } from "../data/external/cms"
export async function getServerSideProps(context) {
if(context.query.page == null || context.query.page == '0') {
var page = 1;
} else {
var page = Number(context.query.page)
}
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig()
const project = await getProjectDetails('tech-and-disability')
const rssFeed = config.siteURL+"/feed/"+project.data[0].attributes.Slug+"-feed.xml"
const articles = await getAllPosts(project.data[0].attributes.Slug, page, 5)
const secres = await fetch(serverRuntimeConfig.base_path + `/api/sections`)
const secdata = await secres.json()
const pagedata = {'title': 'Tech and Disability'}
return {
props: { articles, sections : secdata, pagedata, project, rssFeed}, // will be passed to the page component as props
}
}
function Project({ articles, sections, pagedata, project, rssFeed }) {
return <div><Head>
<link rel="alternate" type="application/rss+xml" title="Tech and Disability Feed" href={rssFeed} />
</Head>
<Layout sections={sections} pagedata={pagedata}>
<h1 className="page_title col-sm-12">{project.data[0].attributes.Title}</h1>
<div dangerouslySetInnerHTML={{ __html: project.data[0].attributes.Description }}></div>
<StoryPager storydata={articles} />
</Layout></div>
}
export default Project

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

BIN
public/images/facebok.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/images/favicon-114.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
public/images/favicon-120.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/images/favicon-144.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
public/images/favicon-150.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

BIN
public/images/favicon-152.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
public/images/favicon-16.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/images/favicon-160.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
public/images/favicon-180.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/images/favicon-192.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/images/favicon-310.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
public/images/favicon-32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/images/favicon-57.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
public/images/favicon-60.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/images/favicon-64.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
public/images/favicon-70.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

BIN
public/images/favicon-72.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
public/images/favicon-76.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
public/images/favicon-96.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
public/images/iTunes.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
public/images/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
public/images/og.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

BIN
public/images/rss.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/images/twitter.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

23
public/robots.txt Normal file
View file

@ -0,0 +1,23 @@
# Used for many other (non-commercial) purposes as well
User-agent: CCBot
Disallow: /
# For new training only
User-agent: GPTBot
Disallow: /
# Not for training, only for user requests
User-agent: ChatGPT-User
Disallow: /
# Marker for disabling Bard and Vertex AI
User-agent: Google-Extended
Disallow: /
# Speech synthesis only?
User-agent: FacebookBot
Disallow: /
# Multi-purpose, commercial uses; including LLMs
User-agent: Omgilibot
Disallow: /

View file

@ -1,4 +0,0 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,121 +0,0 @@
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
}
.main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.footer {
width: 100%;
height: 100px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
}
.footer a {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}
.title a {
color: #0070f3;
text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
line-height: 1.5;
font-size: 1.5rem;
}
.code {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
font-size: 1.1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
}
.card {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
width: 45%;
}
.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
.card h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}
.logo {
height: 1em;
margin-left: 0.5rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}

View file

@ -1,16 +0,0 @@
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}

3304
yarn.lock Executable file

File diff suppressed because it is too large Load diff