diff options
| author | Leonardo Bishop <me@leonardobishop.com> | 2023-08-05 21:11:48 +0100 |
|---|---|---|
| committer | Leonardo Bishop <me@leonardobishop.com> | 2023-08-05 21:11:48 +0100 |
| commit | 18cc5f69129615850e48a995f7c3406b74d8d2f4 (patch) | |
| tree | 1fdc6eadae4b0a6da69319f9b6733379ced2e4c2 | |
| parent | 64c36dcef8ab1c0b985d79da627cecd30fd50336 (diff) | |
Redesign website
52 files changed, 1823 insertions, 1806 deletions
diff --git a/.env.defaults b/.env.defaults index 1c11ae4..3dd7039 100644 --- a/.env.defaults +++ b/.env.defaults @@ -1,6 +1,8 @@ PORT=3000 PAGES_DIR=pages -REBUILD_COOLDOWN_MIN=10 -PURGE_COOLDOWN_MIN=10 -PARSER_MAX_RECURSION=20 LOGGING_LEVEL=info + +SPOTIFY_CLIENT_ID= +SPOTIFY_CLIENT_SECRET= +SPOTIFY_AUTH_CODE= +SPOTIFY_REDIRECT_URI= diff --git a/app/directory.ts b/app/directory.ts deleted file mode 100644 index 77acacf..0000000 --- a/app/directory.ts +++ /dev/null @@ -1,312 +0,0 @@ -import * as parser from './wikiparser.js'; -import { readFileSync } from 'fs'; -import glob from 'glob'; -import { logger } from './logger.js' - -/** - * Build a page. - * - * @param path standard name for page - * @returns newly built page, or undefined - */ -export function buildPage(directory: PageDirectory, page: Page) { - const result = parser.parse(directory, page.raw); - const title = result.metadata.displayTitle ?? page.standardName; - const content = `${result.metadata.notitle ? '' : `<h1>${title}</h1>`}${result.html}`; - - page.html = content; - page.buildTime = Date.now(); - page.metadata.includeInNavbar = result.metadata.primary ?? false; - page.metadata.sortOrder = result.metadata.sortOrder ?? -1; - page.metadata.showTitle = !result.metadata.notitle ?? true; - page.metadata.displayTitle = title; -} - -/** - * Convert a page name to a standard name. - * A standard name is the key used by the page directory. - * - * @param name non-standard name for a page - */ -export function convertNameToStandard(name: string): string { - name = name.replace(/[^a-z0-9:]/gi, '_').toLowerCase(); - if (!name.includes(':')) { - name = `main:${name}`; - } - return name; -} - -/** - * Convert a standard name to a file path. - * - * @param name standard name for a page - */ -export function convertStandardToFilePath(name: string): string { - const [first, second] = name.replace('main:', '').split(':'); - const [title, subpage] = ((second) ? second : first).split('.') - const namespace = (second) ? first : undefined - - return `${namespace ? `${namespace}/` : ''}${title}${subpage ? `.${subpage}` : ''}.wiki` -} - -export class PageDirectory { - pages: Record<string, Page>; - primaryPages: Page[]; - pagePath: string; - lastBuild: number; - - constructor(root: string) { - this.lastBuild = 0; - this.pages = {}; - this.pagePath = root; - - this.rebuild(); - } - - /** - * Build this page directory. - * - * @returns whether the directory was built - */ - rebuild(): boolean { - if (this.lastBuild + parseInt(process.env.REBUILD_COOLDOWN_MIN, 10) * 60 * 1000 > Date.now()) { - return false; - } - for (const page in this.pages) { - delete this.pages[page]; - } - - const pages = glob.sync(`**/*.wiki`, { cwd: this.pagePath }) - - // Load page content - pages.forEach(page => { - page = convertNameToStandard(page.replace('.wiki', '').replace('/', ':')); - this.pages[page] = { - standardName: page, - raw: this.loadRaw(page), - buildTime: 0, - metadata: { - dependencies: new Set(), - dependents: new Set(), - errors: [] - } - } - }); - - const dependencyGraph: Record<string, string[]> = {}; - - Object.keys(this.pages).forEach(name => dependencyGraph[name] = Array.from(parser.findDependencies(this.pages[name].raw)).map(e => convertNameToStandard(e))); - - // Revursive dependency graph traversal function - function traverseGraph(dependents: string[], current: string, dependencies: string[], recursionCount: number, pages: Record<string, Page>) { - if (recursionCount > parseInt(process.env.PARSER_MAX_RECURSION, 10)) { - throw new RecursionError('max recursion reached'); - } - - dependencies?.forEach(e => { - pages[current]?.metadata.dependencies.add(e) - if (e !== current) { - pages[e]?.metadata.dependents.add(current); - } - }); - - dependencies?.forEach((dependency: string) => { - if (dependencyGraph[dependency]?.length != 0) { - dependents.forEach((dependent: string) => { - if (dependencyGraph[dependency]?.includes(dependent)) { - throw new DependencyError(`circular dependency between ${dependent} and ${dependency}`, [dependent, dependency]); - } - }); - traverseGraph([...dependents, dependency], dependency, dependencyGraph[dependency], recursionCount + 1, pages); - } - }); - } - - // Catch circular dependencies and build dependency tree - Object.keys(dependencyGraph).forEach(name => { - try { - traverseGraph([name], name, dependencyGraph[name], 1, this.pages); - } catch (e) { - if (e instanceof RecursionError) { - this.pages[name].metadata.errors.push({ - identifier: 'max-recursion-reached', - message: `maximum dependency depth of ${process.env.PARSER_MAX_RECURSION} reached` - }) - logger.warn(`max recursion for ${name} reached`) - } else if (e instanceof DependencyError) { - if (e.pages.includes(name)) { - this.pages[name].metadata.errors.push({ - identifier: 'circular-dependency', - message: e.message - }) - logger.warn(`${e.pages[0]} has a circular dependency with ${e.pages[1]}`) - } else { - logger.warn(`transclusions on page ${name} may not resolve due to dependency errors in its dependency tree`) - } - } else { - throw e; - } - } - }); - - function recursiveBulld(pages: Record<string, Page>, current: Page, directory: PageDirectory, buildPage: (directory: PageDirectory, page: Page) => void) { - if (current.metadata.errors.length == 0) { - current.metadata.dependencies.forEach(dependency => { - if (pages[dependency].buildTime == 0) { - recursiveBulld(pages, pages[dependency], directory, buildPage); - } - }); - buildPage(directory, current) - } - } - - // Build pages in order - const primaryPages = []; - Object.keys(this.pages).forEach(name => { - recursiveBulld(this.pages, this.pages[name], this, buildPage); - - if (this.pages[name].metadata.includeInNavbar) { - primaryPages.push(this.pages[name]); - } - }); - - // Sort primary pages - primaryPages.sort((a, b) => { - return a.metadata.sortOrder - b.metadata.sortOrder; - }); - this.primaryPages = primaryPages; - this.lastBuild = Date.now(); - return true; - } - - /** - * Get whether a page exists with this name. - * - * @param name standard name for page - * @returns whether the page exists - */ - exists(name: string): boolean { - return !!this.pages[convertNameToStandard(name)]; - } - - /** - * Get a page. - * - * @param name standard name for page - * @returns page - */ - get(name: string): Page { - name = convertNameToStandard(name); - const page = this.pages[name]; - if (!page) { - return undefined; - } - - return page; - } - - /** - * Get the raw wikitext for a page. - * - * @param name standard name for page - * @returns raw wikitext - */ - getRaw(name: string): string { - name = convertNameToStandard(name); - return this.pages[name]?.raw; - } - - /** - * Purge (rebuild) a page. - * - * @param name standard name for page - * @returns whether the page was rebuilt - */ - purge(name: string): boolean { - name = convertNameToStandard(name); - const page = this.pages[name]; - if (page) { - if (page.buildTime + parseInt(process.env.PURGE_COOLDOWN_MIN, 10) * 60 * 1000 > Date.now()) { - return false; - } else { - // delete this.pages[name]; - // if (this.buildPage(name)) { - // return true; - // } - } - } - return false; - } - - /** - * Get all pages. - * - * @returns all pages - */ - getPages(): Record<string, Page> { - return this.pages; - } - - /** - * Get primary pages. - * - * @param current - * @returns - */ - getPrimaryPages(): Page[] { - return this.primaryPages; - } - - private loadRaw(name: string): string { - name = convertNameToStandard(name); - let data: string; - try { - data = readFileSync(`${this.pagePath}/${convertStandardToFilePath(name)}`, 'utf-8'); - } catch { - return undefined; - } - return data; - } -} - -export type Page = { - html?: string; - raw?: string; - standardName: string; - buildTime: number; - metadata: PageMetadata; -}; - -export type PageMetadata = { - displayTitle?: string; - sortOrder?: number; - showTitle?: boolean; - includeInNavbar?: boolean; - dependencies: Set<string>; - dependents: Set<string>; - errors: PageError[]; -}; - -export type PageError = { - identifier: string; - message: string; -} - -export class DependencyError extends Error { - pages: string[] - - constructor(message: string, pages: string[]) { - super(message); - this.pages = pages; - - Object.setPrototypeOf(this, DependencyError.prototype); - } -} - -export class RecursionError extends Error { - constructor(message: string) { - super(message); - - Object.setPrototypeOf(this, RecursionError.prototype); - } -} diff --git a/app/index.ts b/app/index.ts index 8514380..3558d1f 100644 --- a/app/index.ts +++ b/app/index.ts @@ -1,46 +1,56 @@ -import { PageDirectory } from './directory.js'; import express from 'express'; import dotenv from 'dotenv-defaults'; import * as page from './routes/page/router.js'; -import * as special from './routes/special/router.js'; -import { navbar } from './middlewares/index.js' +import * as blog from './routes/blog/router.js'; import { logger } from './logger.js' +import { PageDirectory } from './pages.js'; +// import { SpotifyClient } from './spotify/client.js'; +// import { WebSocketServer } from 'ws'; +// import * as spotifyauth from './routes/spotify/router.js'; +// import * as spotifyWs from './websocket/spotify.js'; + +// TODO: Figure out Spotify's tedious auth flow dotenv.config() const app = express(); -const directory = new PageDirectory(process.env.PAGES_DIR); - app.set('view engine', 'ejs'); app.set('views', 'views'); -app.use(express.static('static')); - -app.use((req, res, next) => { - res.locals.directory = directory; - next(); -}); +app.use(express.static('static', { + etag: true, + maxAge: '1d' +})); +app.use(blog.router); app.use(page.router); -app.use(special.router); +// app.use(spotifyauth.router); -app.use(navbar, (req, res) => { +app.use((req, res) => { res.render('error.ejs', { code: '404', - navbar: res.locals.navbarHtml }); }); const server = app.listen(process.env.PORT, () => { logger.info(`App listening on port ${process.env.PORT}`); }); +// const websocketServer: WebSocketServer = spotifyWs.createWebsocketServer(server); const exit = () => { logger.info('Stopping server...'); + // websocketServer.clients.forEach(client => { + // client.terminate(); + // }); + // websocketServer.close(); server.close(() => { process.exit(0); }) } +PageDirectory.rebuild('pages'); + +// SpotifyClient.initialise(); + process.on('SIGINT', exit); process.on('SIGTERM', exit); diff --git a/app/middlewares/blogs.ts b/app/middlewares/blogs.ts new file mode 100644 index 0000000..8fd07c6 --- /dev/null +++ b/app/middlewares/blogs.ts @@ -0,0 +1,17 @@ +import { PageDirectory } from "../pages.js"; + +export const blogs = ((req, res, next) => { + let blogs = []; + for (const page of Object.values(PageDirectory.pages)) { + if (page.route.startsWith('blog/')) { + blogs.push(page); + } + } + + blogs.sort((a, b) => { + return b.metadata.date.getTime() - a.metadata.date.getTime(); + }); + + res.locals.blogs = blogs; + next(); +}); diff --git a/app/middlewares/index.ts b/app/middlewares/index.ts index d07726a..81431ca 100644 --- a/app/middlewares/index.ts +++ b/app/middlewares/index.ts @@ -1,18 +1,11 @@ -export const navbar = ((req, res, next) => { - let navbar = ''; - res.locals.directory.primaryPages.forEach(page => { - navbar += `<div class="navbar-element"><a href="/${page.standardName}"${(req.params.page ?? '' )== page.standardName ? ' class="highlight"' : ''}>${page.metadata.displayTitle}</a></div>`; - }) - res.locals.navbarHtml = navbar; - next(); -}); +import { PageDirectory } from "../pages.js"; export const page = ((req, res, next) => { - const path = req.params.page ?? 'index'; + const path = req.originalUrl == "/" ? 'index' : req.originalUrl.substring(1); res.locals.path = path; - const page = res.locals.directory.get(path); - + const page = PageDirectory.get(path); + if (!page) { next(); return; diff --git a/app/pages.ts b/app/pages.ts new file mode 100644 index 0000000..3bef4a9 --- /dev/null +++ b/app/pages.ts @@ -0,0 +1,87 @@ +import { readFileSync } from 'fs'; +import glob from 'glob'; +import { logger } from './logger.js' +import { marked } from 'marked'; +import matter from 'gray-matter'; + +export function buildPage(page: Page) { + logger.info(`Building ${page.path}`); + try { + const result = matter(page.raw); + const metadata = result.data; + const html = marked.parse(result.content, { mangle: false, headerIds: false }); + + page.html = html; + page.metadata = metadata; + page.buildTime = Date.now(); + } catch (e) { + logger.error(`Failed to build page ${page.path}: ${e.message}`); + } +} + +export namespace PageDirectory { + export const pages: Record<string, Page> = {}; + export let lastBuild: number; + + export const rebuild = (pagePath: string): boolean => { + for (const page in pages) { + delete pages[page]; + } + + const localPages = glob.sync(`**/*.{md,html}`, { cwd: pagePath }) + + // Load page content + localPages.forEach(page => { + let route = page.replace(/\.[^.]*$/,'') + let name = /[^/]*$/.exec(route)[0]; + let path = `${pagePath}/${page}` + let raw: string; + try { + raw = loadRaw(path); + } catch (e) { + logger.error(`Failed to read page ${path}: ${e.message}`); + return; + } + + pages[route] = { + route: route, + name: name, + path: path, + raw: raw, + buildTime: 0, + metadata: { + title: "A Page" + } + } + }); + + // Build pages + Object.values(pages).forEach(page => buildPage(page)); + + lastBuild = Date.now(); + return true; + } + + export function get(name: string): Page { + const page = pages[name]; + if (!page) { + return undefined; + } + + return page; + } + + function loadRaw(path: string): string { + return readFileSync(`${path}`, 'utf-8'); + } +} + +export type Page = { + html?: string; + raw?: string; + route: string; + name: string; + path: string; + buildTime: number; + metadata: any; +}; diff --git a/app/routes/blog/router.ts b/app/routes/blog/router.ts new file mode 100644 index 0000000..bbd09d5 --- /dev/null +++ b/app/routes/blog/router.ts @@ -0,0 +1,19 @@ +import express from 'express'; +import { page } from '../../middlewares/index.js'; +import { blogs } from '../../middlewares/blogs.js'; + +export const router = express.Router({ mergeParams: true }); + +router.use('/blog/:page?', page); +router.use('/blog/:page?', blogs); + +router.get('/blog/:page?', (req, res, next) => { + let page = res.locals.page; + let index = !page || res.locals.path === 'blog'; + + res.render('blog.ejs', { + index: index, + blogs: res.locals.blogs, + page: page, + }); +}); diff --git a/app/routes/page/router.ts b/app/routes/page/router.ts index dc819f8..5c0a39b 100644 --- a/app/routes/page/router.ts +++ b/app/routes/page/router.ts @@ -1,51 +1,20 @@ import express from 'express'; -import { navbar, page } from '../../middlewares/index.js'; +import { page } from '../../middlewares/index.js'; export const router = express.Router({ mergeParams: true }); -router.use('/:page.wiki', page); router.use('/:page?', page); -router.get('/:page.wiki', (req, res, next) => { - const page = res.locals.page; - - if (!page) { - next(); - return; - } - - res.type('text/plain'); - res.send(page.raw).end(); -}); - -router.get('/:page?', navbar, (req, res, next) => { - const page = res.locals.page; - +router.get('/:page?', (req, res, next) => { + let page = res.locals.page; if (!page) { next(); return; - } - - let html: string; - let title: string; - - if (page.metadata.errors.length != 0) { - html = '<div class="box-red">This page could not be built due to the following errors:<br><ul>' - page.metadata.errors.forEach(e => { - html += `<li>${e.identifier}: ${e.message}</li>` - }); - html += '</ul>Go <a href="/">home</a>?</div>' - title = 'Page error' - } else { - html = page.html; - title = page.metadata.displayTitle; } - - res.render('page.ejs', { - navbar: res.locals.navbarHtml, - path: res.locals.path, - content: html, - title: title, - buildTime: new Date(page.buildTime) + + res.render('index.ejs', { + content: page.html, + stylesheets: page.metadata.stylesheets, + scripts: page.metadata.scripts, }); }); diff --git a/app/routes/special/router.ts b/app/routes/special/router.ts deleted file mode 100644 index 2015a35..0000000 --- a/app/routes/special/router.ts +++ /dev/null @@ -1,79 +0,0 @@ -import express from 'express'; -import { navbar, page } from '../../middlewares/index.js'; -import { logger } from './../../logger.js' - -export const router = express.Router({ mergeParams: true }); - -router.use('/special/purge/:page?', page); -router.use('/special/purge/:page/confirm', page); - -router.get('/special/purge/:page?', navbar, (req, res, next) => { - const page = res.locals.page; - - if (!page) { - next(); - return; - } - - res.render('purge.ejs', { - navbar: res.locals.navbarHtml, - page: res.locals.path, - buildTime: new Date(page.buildTime) ?? 'never', - buildTimeRelative: Math.round((Date.now() - page.buildTime) / 1000 / 60) - }); -}); - -router.get('/special/purge/:page/confirm', (req, res, next) => { - const page = res.locals.page; - - if (!page) { - next(); - return; - } - - logger.info(`Purge for page ${page.standardName} requested by ${req.headers['x-forwarded-for'] || req.socket.remoteAddress }`) - if (res.locals.directory.purge(res.locals.path)) { - res.status(200).send(); - } else { - res.status(429).send(); - } -}); - -router.use('/special/info/:page?', page); - -router.get('/special/info/:page?', navbar, (req, res, next) => { - const page = res.locals.page; - - if (!page) { - next(); - return; - } - - res.render('pageinfo.ejs', { - navbar: res.locals.navbarHtml, - standardName: page.standardName, - displayTitle: page.metadata.displayTitle, - buildTime: page.buildTime, - primary: page.metadata.includeInNavbar, - showTitle: page.metadata.showTitle, - sortOrder: page.metadata.sortOrder, - dependencies: page.metadata.dependencies, - dependents: page.metadata.dependents, - errors: page.metadata.errors, - }); -}); - -router.get('/special/rebuild', navbar, (req, res) => { - res.render('rebuild.ejs', { - navbar: res.locals.navbarHtml - }); -}); - -router.get('/special/rebuild/confirm', (req, res) => { - logger.info(`Directory rebuild requested by ${req.headers['x-forwarded-for'] || req.socket.remoteAddress }`) - if (res.locals.directory.rebuild()) { - res.status(200).send(); - } else { - res.status(429).send(); - } -}); diff --git a/app/routes/spotify/router.ts b/app/routes/spotify/router.ts new file mode 100644 index 0000000..faf8f6d --- /dev/null +++ b/app/routes/spotify/router.ts @@ -0,0 +1,26 @@ +import express from 'express'; + +export const router = express.Router({ mergeParams: true }); + +router.get('/spotify/auth', (req, res, next) => { + let scope = 'user-read-currently-playing'; + let params = new URLSearchParams(); + params.append('response_type', 'code'); + params.append('client_id', process.env.SPOTIFY_CLIENT_ID); + params.append('scope', scope); + params.append('redirect_uri', process.env.SPOTIFY_REDIRECT_URI); + + res.redirect('https://accounts.spotify.com/authorize?' + params.toString()); +}); + +router.get('/spotify/auth/callback', (req, res, next) => { + if (req.query.error) { + res.send('Error: ' + req.query.error); + return; + } + if (!req.query.code) { + res.send('No code'); + return; + } + res.send('Your authentication code: ' + req.query.code); +}); diff --git a/app/spotify/client.ts b/app/spotify/client.ts new file mode 100644 index 0000000..2cdf527 --- /dev/null +++ b/app/spotify/client.ts @@ -0,0 +1,158 @@ +import axios from 'axios'; +import { logger } from '../logger.js'; +import { WebSocket } from 'ws'; + +export namespace SpotifyClient { + let clients = new Set<WebSocket>(); + let interval: NodeJS.Timeout; + + let acceptingClients = false; + let authenticationFailed = false; + + let accessToken: string; + let refreshToken: string; + + export const addClient = (client: WebSocket) => { + if (acceptingClients) { + clients.add(client); + } else { + client.close(); + } + } + + const apiTokenUrl = 'https://accounts.spotify.com/api/token'; + + const spotifyClientHeaders = { + 'Authorization': 'Basic ' + Buffer.from(process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET).toString('base64'), + 'Content-Type': 'application/x-www-form-urlencoded', + } + + const handleApiError = (err: any, verb: string) => { + if (err.response?.data?.error) { + logger.error(`Failed to ${verb} access token: ${err.message}: ${err.response.data.error}`); + } else { + logger.error(`Failed to get access token: ${err.message} (${err.response.status} ${err.response.statusText} ${err.response.data.error})`); + } + accessToken = undefined; + refreshToken = undefined; + } + + export const requestAccessToken = async () => { + logger.info('Requesting access token from Spotify'); + await axios.post(apiTokenUrl, { + grant_type: 'authorization_code', + code: process.env.SPOTIFY_AUTH_CODE, + redirect_uri: process.env.SPOTIFY_REDIRECT_URI, + }, + { headers: spotifyClientHeaders, + }).then(res => { + logger.info('Authenticated with Spotify'); + accessToken = res.data.access_token; + }).catch(err => { + handleApiError(err, 'request'); + }); + } + + export const refreshAccessToken = async () => { + logger.info('Refreshing access token from Spotify'); + await axios.post(apiTokenUrl, { + grant_type: 'refresh_token', + refresh_token: refreshToken, + }, + { headers: spotifyClientHeaders, + }).then(res => { + logger.info('Refreshed access token from Spotify'); + accessToken = res.data.access_token; + }).catch(err => { + handleApiError(err, 'refresh'); + }); + } + + + export const initialise = async () => { + if (!accessToken) { + await requestAccessToken(); + if (!accessToken) { + logger.error('Failed to get access token, giving up permanently'); + authenticationFailed = true; + return; + } + } + await updateTimeout(); + acceptingClients = true; + } + + const updateTimeout = async () => { + await update(); + interval = setTimeout(updateTimeout, 5000); + } + + export const update = async () => { + if (authenticationFailed) { + return; + } + clients.forEach(client => { + if (client.readyState !== WebSocket.OPEN) { + clients.delete(client); + } + }); + if (clients.size === 0) { + return; + } + await axios.get('https://api.spotify.com/v1/me/player/currently-playing', { + headers: { + 'Authorization': 'Bearer ' + accessToken, + } + }).then(async (res) => { + if (res.status === 401) { + logger.info('Access token expired, refreshing'); + await refreshAccessToken(); + if (!accessToken) { + authenticationFailed = true; + logger.error('Failed to get access token, giving up permanently'); + stop(); + return; + } + await update(); + return; + } + if (res.status !== 200) { + logger.error(`Failed to get current song: ${res.status} ${res.statusText}`); + return; + } + try { + let song = res.data.item.name; + let duration = res.data.item.duration_ms; + let artist = res.data.item.artists[0].name; + let time = res.data.progress_ms; + let album = res.data.item.album.name; + let albumImage = res.data.item.album.images[0].url; + clients.forEach(client => { + client.send(JSON.stringify({ + song: song, + artist: artist, + time: time, + duration: duration, + album: album, + albumImage: albumImage, + })); + }); + } catch (err) { + logger.error(`Failed to parse and send current song: ${err.message}`); + } + }).catch(err => { + if (err.response?.data?.error?.message) { + logger.error(`Failed to get current song: ${err.message}: ${err.response.data.error.message}`); + } else { + logger.error(`Failed to get current song: ${err.message} (${err.response.status} ${err.response.statusText} ${err.response.data.error})`); + } + }); + } + + export const stop = () => { + clearInterval(interval); + acceptingClients = false; + clients.forEach(client => client.close()); + } + +} diff --git a/app/websocket/spotify.ts b/app/websocket/spotify.ts new file mode 100644 index 0000000..4b81fe0 --- /dev/null +++ b/app/websocket/spotify.ts @@ -0,0 +1,18 @@ +import { Server } from 'http'; +import { WebSocketServer } from 'ws'; +import { SpotifyClient } from '../spotify/client.js'; + +export const createWebsocketServer = (server: Server): WebSocketServer => { + const wss = new WebSocketServer({ noServer: true }); + server.on('upgrade', (req, socket, head) => { + wss.handleUpgrade(req, socket, head, (ws) => { + wss.emit('connection', ws, req) + }) + }) + + wss.on('connection', (ws) => { + SpotifyClient.addClient(ws); + }); + + return wss; +} diff --git a/app/wikiparser.ts b/app/wikiparser.ts deleted file mode 100644 index c131a22..0000000 --- a/app/wikiparser.ts +++ /dev/null @@ -1,327 +0,0 @@ -/* - * This file is a modified version of Nixinova/Wikity, whose license is given below: - * Original: https://www.npmjs.com/package/wikity - * - * > ISC License - * > - * > Copyright © 2021 Nixinova - * > - * > Permission to use, copy, modify, and/or distribute this software for any purpose with or - * > without fee is hereby granted, provided that the above copyright notice and this - * > permission notice appear in all copies. - * > - * > The software is provided "as is" and the author disclaims all warranties with regard to - * > this software including all implied warranties of merchantability and fitness. In no - * > event shall the author be liable for any special, direct, indirect, or consequential - * > damages or any damages whatsoever resulting from loss of use, data or profits, whether - * > in an action of contract, negligence or other tortious action, arising out of or in - * > connection with the use or performance of this software. - * - * Additonally, this project and my modifications are also licensed under the ISC license. - */ -import dateFormat from 'dateformat'; -import htmlEscape from 'escape-html'; -import { PageDirectory } from './directory'; - -export class Result { - public html: string; - public metadata: any; - - constructor(html: string, metadata: any) { - this.html = html; - this.metadata = metadata; - } -} - -const re = (regex, flag = 'mgi') => { - return RegExp(regex.replace(/ /g, '').replace(/\|\|.+?\|\|/g, ''), flag); -}; -const r = String.raw; -const arg = r`\s*([^|}]+?)\s*`; - -export function findDependencies(data: string): Set<string> { - const pages = new Set<string>(); - - let outText = data; - for (let l = 0, last = ''; l < parseInt(process.env.PARSER_MAX_RECURSION, 10); l++) { - if (last === outText) break; last = outText; - - outText = outText - // Remove non-template magic words - .replace(re(r`<(/?) \s* (?= script|link|meta|iframe|frameset|object|embed|applet|form|input|button|textarea )`), '<$1') - .replace(re(r`(?<= <[^>]+ ) (\bon(\w+))`), 'data-$2') - .replace(/<!--[^]+?-->/g, '') - .replace(re(r`{{ \s* displayTitle: ([^}]+) }}`), '') - .replace(re(r`{{ \s* navbarSortOrder: ([^}]+) }}`), '') - .replace(re(r`{{ \s* ! \s* }}`), '|') - .replace(re(r`{{ \s* = \s* }}`), '=') - .replace(re(r`{{ \s* [Rr]eflist \s* }}`), '<references/>') - .replace(re(r`{{ \s* #? urlencode: ${arg} }}`), '') - .replace(re(r`{{ \s* #? urldecode: ${arg} }}`), '') - .replace(re(r`{{ \s* #? lc: ${arg} }}`), '') - .replace(re(r`{{ \s* #? uc: ${arg} }}`), '') - .replace(re(r`{{ \s* #? lcfirst: ${arg} }}`), '') - .replace(re(r`{{ \s* #? ucfirst: ${arg} }}`), '') - .replace(re(r`{{ \s* #? len: ${arg} }}`), '') - .replace(re(r`{{ \s* #? pos: ${arg} \|${arg} (?: \s*\|${arg} )? }}`), '') - .replace(re(r`{{ \s* #? sub: ${arg} \|${arg} (?:\|${arg})? }}`), '') - .replace(re(r`{{ \s* #? padleft: ${arg} \|${arg} \|${arg} }}`), '') - .replace(re(r`{{ \s* #? padright: ${arg} \|${arg} \|${arg} }}`), '') - .replace(re(r`{{ \s* #? replace: ${arg} \|${arg} \|${arg} }}`), '') - .replace(re(r`{{ \s* #? explode: ${arg} \|${arg} \|${arg} }}`), '') - .replace(re(r`{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), '') - - // Templates: {{template}} - .replace(re(r`{{ \s* ([^#}|]+?) (\|[^}]+)? }} (?!})`), (_, title, params = '') => { - if (/{{/.test(params)) return _; - const page = title.includes(':') ? title : `Template:${title}` - pages.add(page); - return ''; - }) - } - return pages; -} - -export function parse(directory: PageDirectory, data: string): Result { - const vars = {}; - const metadata: any = {}; - const nowikis = []; - const refs = []; - - let nowikiCount = 0; - let rawExtLinkCount = 0; - let refCount = 0; - - let outText = data; - - for (let l = 0, last = ''; l < parseInt(process.env.PARSER_MAX_RECURSION, 10); l++) { - if (last === outText) break; last = outText; - - outText = outText - - // Nowiki: <nowiki></nowiki> - .replace(re(r`<nowiki> ([^]+?) </nowiki>`), (_, m) => `%NOWIKI#${nowikis.push(m), nowikiCount++}%`) - - // Sanitise unacceptable HTML - .replace(re(r`<(/?) \s* (?= script|link|meta|iframe|frameset|object|embed|applet|form|input|button|textarea )`), '<$1') - .replace(re(r`(?<= <[^>]+ ) (\bon(\w+))`), 'data-$2') - - // Comments: <!-- --> - .replace(/<!--[^]+?-->/g, '') - - // Lines: ---- - .replace(/^-{4,}/gm, '<hr>') - - // Metadata: displayTitle, __NOTOC__, etc - .replace(re(r`{{ \s* displayTitle: ([^}]+) }}`), (_, title) => (metadata.displayTitle = title, '')) - .replace(re(r`{{ \s* navbarSortOrder: ([^}]+) }}`), (_, order) => (metadata.sortOrder = parseInt(order, 10), '')) - .replace(re(r`__NOINDEX__`), () => (metadata.noindex = true, '')) - .replace(re(r`__NOTOC__`), () => (metadata.notoc = true, '')) - .replace(re(r`__FORCETOC__`), () => (metadata.toc = true, '')) - .replace(re(r`__TOC__`), () => (metadata.toc = true, '<toc></toc>')) - .replace(re(r`__PRIMARY__`), () => (metadata.primary = true, '')) - .replace(re(r`__NOTITLE__`), () => (metadata.notitle = true, '')) - - // Magic words: {{!}}, {{reflist}}, etc - .replace(re(r`{{ \s* ! \s* }}`), '|') - .replace(re(r`{{ \s* = \s* }}`), '=') - .replace(re(r`{{ \s* [Rr]eflist \s* }}`), '<references/>') - - // String functions: {{lc:}}, {{ucfirst:}}, {{len:}}, etc - .replace(re(r`{{ \s* #? urlencode: ${arg} }}`), (_, m) => encodeURI(m)) - .replace(re(r`{{ \s* #? urldecode: ${arg} }}`), (_, m) => decodeURI(m)) - .replace(re(r`{{ \s* #? lc: ${arg} }}`), (_, m) => m.toLowerCase()) - .replace(re(r`{{ \s* #? uc: ${arg} }}`), (_, m) => m.toUpperCase()) - .replace(re(r`{{ \s* #? lcfirst: ${arg} }}`), (_, m) => m[0].toLowerCase() + m.substr(1)) - .replace(re(r`{{ \s* #? ucfirst: ${arg} }}`), (_, m) => m[0].toUpperCase() + m.substr(1)) - .replace(re(r`{{ \s* #? len: ${arg} }}`), (_, m) => m.length) - .replace(re(r`{{ \s* #? pos: ${arg} \|${arg} (?: \s*\|${arg} )? }}`), (_, find, str, n = 0) => find.substr(n).indexOf(str)) - .replace(re(r`{{ \s* #? sub: ${arg} \|${arg} (?:\|${arg})? }}`), (_, str, from, len) => str.substr(+from - 1, +len)) - .replace(re(r`{{ \s* #? padleft: ${arg} \|${arg} \|${arg} }}`), (_, str, n, char) => str.padStart(+n, char)) - .replace(re(r`{{ \s* #? padright: ${arg} \|${arg} \|${arg} }}`), (_, str, n, char) => str.padEnd(+n, char)) - .replace(re(r`{{ \s* #? replace: ${arg} \|${arg} \|${arg} }}`), (_, str, find, rep) => str.split(find).join(rep)) - .replace(re(r`{{ \s* #? explode: ${arg} \|${arg} \|${arg} }}`), (_, str, delim, pos) => str.split(delim)[+pos]) - - // Parser functions: {{#if:}}, {{#switch:}}, etc - .replace(re(r`{{ \s* (#\w+) \s* : \s* ( [^{}]+ ) \s* }} ( ?!} )`), (_, name, content) => { - if (/{{\s*#/.test(content)) return _; - const args = content.trim().split(/\s*\|\s*/); - switch (name) { - case '#if': - return (args[0] ? args[1] : args[2]) || ''; - case '#ifeq': - return (args[0] === args[1] ? args[2] : args[3]) || ''; - case '#vardefine': - vars[args[0]] = args[1] || ''; - return ''; - case '#var': - if (re(r`{{ \s* #vardefine \s* : \s* ${args[0]}`).test(outText)) return _; // wait until var is set - return vars[args[0]] || args[1] || ''; - case '#switch': - return args.slice(1) - .map(arg => arg.split(/\s*=\s*/)) - .filter(duo => args[0] === duo[0].replace('#default', args[0]))[0][1]; - case '#time': - case '#date': - case '#datetime': - return dateFormat(args[1] ? new Date(args[1]) : new Date(), args[0]); - } - }) - - // Templates: {{template}} - .replace(re(r`{{ \s* ([^#}|]+?) (\|[^}]+)? }} (?!})`), (_, title, params = '') => { - if (/{{/.test(params)) return _; - const page = title.includes(':') ? title : `Template:${title}` - - // Retrieve template content - const content = directory.get(page); - if (!content?.html) { - return `<a class="internal-link redlink" title="${title}" href="/${page}">Template:${title}</a>`; - } - - // Remove non-template sections - let raw = content.raw - .replace(/<noinclude>.*?<\/noinclude>/gs, '') - .replace(/.*<(includeonly|onlyinclude)>|<\/(includeonly|onlyinclude)>.*/gs, ''); - - // Substitite arguments - const argMatch = (arg) => re(r`{{{ \s* ${arg} (?:\|([^}]*))? \s* }}}`); - const args = params.split('|').slice(1); - for (const i in args) { - const parts = args[i].split('='); - const [arg, val] = parts[1] ? [parts[0], ...parts.slice(1)] : [(+i + 1) + '', parts[0]]; - raw = raw.replace(argMatch(arg), (_, m) => val || m || ''); - } - for (let i = 1; i <= 10; i++) { - raw = raw.replace(argMatch(arg), '$2'); - } - - return raw; - }) - - // Images: [[File:Image.png|options|caption]] - .replace(re(r`\[\[ (?:File|Image): (.+?) (\|.+?)? \]\]`), (_, file, params) => { - if (/{{/.test(params)) return _; - const path = file.trim().replace(/ /g, '_'); - let caption = ''; - const imageData: any = {}; - const imageArgs = params?.split('|').map((arg) => arg.replace(/"/g, '"')); - if (imageArgs) { - for (const param of imageArgs) { - if (['left', 'right', 'center', 'none'].includes(param)) { - imageData.float = param; - } - if (['baseline', 'sub', 'super', 'top', 'text-bottom', 'middle', 'bottom', 'text-bottom'].includes(param)) { - imageData.align = param; - } - else if (['border', 'frameless', 'frame', 'framed', 'thumb', 'thumbnail'].includes(param)) { - imageData.type = { framed: 'frame', thumbnail: 'thumb' }[param] || param; - if (imageData.type === 'thumb') imageData.hasCaption = true; - } - else if (param.endsWith('px')) { - param.replace(/(?:(\w+)?(x))?(\w+)px/, (_, size1, auto, size2) => { - if (size1) Object.assign(imageData, { width: size1, height: size2 }); - else if (auto) Object.assign(imageData, { width: 'auto', height: size2 }); - else Object.assign(imageData, { width: size2, height: 'auto' }); - return ''; - }); - } - else if (param.startsWith('upright=')) { - imageData.width = +param.replace('upright=', '') * 300; - } - else if (param.startsWith('link=')) { - imageData.link = param.replace('link=', ''); - } - else if (param.startsWith('alt=')) { - imageData.alt = param.replace('alt=', ''); - } - else if (param.startsWith('style=')) { - imageData.style = param.replace('style=', ''); - } - else if (param.startsWith('class=')) { - imageData.class = param.replace('class=', ''); - } - else { - caption = param; - } - } - } - let content = ` - <figure - class="${imageData.class || ''} image-container image-${imageData.type || 'default'}" - style="float:${imageData.float || 'none'};vertical-align:${imageData.align || 'unset'};${imageData.style || ''}" - > - <img - src="/image/${path}" - alt="${imageData.alt || file}" - width="${imageData.width || 300}" - height="${imageData.height || 300}" - > - ${imageData.hasCaption ? `<figcaption>${caption}</figcaption>` : ''} - </figure> - `; - if (imageData.link) content = `<a href="/${imageData.link}" title="${imageData.link}">${content}</a>`; - return content; - }) - - // Markup: '''bold''' and '''italic''' - .replace(re(r`''' ([^']+?) '''`), '<b>$1</b>') - .replace(re(r`'' ([^']+?) ''`), '<i>$1</i>') - - // Headings: ==heading== - .replace(re(r`^ (=+) \s* (.+?) \s* \1 \s* $`), (_, lvl, txt) => `<h${lvl.length} id="${encodeURI(txt.replace(/ /g, '_'))}">${txt}</h${lvl.length}>`) - - // Internal links: [[Page]] and [[Page|Text]] - .replace(re(r`\[\[ ([^\]|]+?) \]\]`), '<a class="internal-link" title="$1" href="$1">$1</a>') - .replace(re(r`\[\[ ([^\]|]+?) \| ([^\]]+?) \]\]`), '<a class="internal-link" title="$1" href="/$1">$2</a>') - .replace(re(r`(</a>)([a-z]+)`), '$2$1') - - // External links: [href Page] and just [href] - .replace(re(r`\[ ((?:\w+:)?\/\/ [^\s\]]+) (\s [^\]]+?)? \]`), (_, href, txt) => `<a class="external-link" href="${href}">${txt || '[' + (++rawExtLinkCount) + ']'}</a>`) - - // Bulleted list: *item - .replace(re(r`^ (\*+) (.+?) $`), (_, lvl, txt) => `${'<ul>'.repeat(lvl.length)}<li>${txt}</li>${'</ul>'.repeat(lvl.length)}`) - .replace(re(r`</ul> (\s*?) <ul>`), '$1') - - // Numbered list: #item - .replace(re(r`^ (#+) (.+?) $`), (_, lvl, txt) => `${'<ol>'.repeat(lvl.length)}<li>${txt}</li>${'</ol>'.repeat(lvl.length)}`) - .replace(re(r`</ol> (\s*?) <ol>`), '$1') - - // Definition list: ;head, :item - .replace(re(r`^ ; (.+) $`), '<dl><dt>$1</dt></dl>') - .replace(re(r`^ (:+) (.+?) $`), (_, lvl, txt) => `${'<dl>'.repeat(lvl.length)}<dd>${txt}</dd>${'</dl>'.repeat(lvl.length)}`) - .replace(re(r`</dl> (\s*?) <dl>`), '$1') - - // Tables: {|, |+, !, |-, |, |} - .replace(re(r`^ \{\| (.*?) $`), (_, attrs) => `<table ${attrs}><tr>`) - .replace(re(r`^ ! ([^]+?) (?= \n^[!|] )`), (_, content) => `<th>${content}</th>`) - .replace(re(r`^ \|\+ (.*?) $`), (_, content) => `<caption>${content}</caption>`) - .replace(re(r`^ \|[^-+}] ([^]*?) (?= \n^[!|] )`), (_, content) => `<td>${content}</td>`) - .replace(re(r`^ \|- (.*?) $`), (_, attrs) => `</tr><tr ${attrs}>`) - .replace(re(r`^ \|\}`), '</tr></table>') - - // References: <ref></ref>, <references/> - .replace(re(r`<ref> (.+?) </ref>`), (_, text) => { - refs.push(text); - refCount++; - return `<sup><a id="cite-${refCount}" class="ref" href="#ref-${refCount}">[${refCount}]</a></sup>`; - }) - .replace(re(r`<references \s* /?>`), '<ol>' + refs.map((ref, i) => - `<li id="ref-${+i + 1}"> <a href="#cite-${+i + 1}">↑</a> ${ref} </li>`).join('\n') + '</ol>' - ) - - // Nonstandard: ``code`` and ```code blocks``` - .replace(re(r` \`\`\` ([^\`]+?) \`\`\` `), '<pre class="code-block">$1</pre>') - .replace(re(r` <pre> ([^\`]+?) </pre> `), '<pre class="code-block">$1</pre>') - - // Spacing - .replace(/(\r?\n){2}/g, '\n</p><p>\n') - - // Restore nowiki contents - .replace(/%NOWIKI#(\d+)%/g, (_, n) => htmlEscape(nowikis[n])); - } - metadata.buildTime = new Date(); - - const result = new Result(outText, metadata); - return result; -} diff --git a/package-lock.json b/package-lock.json index 4e52a2a..a210bf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,15 +13,20 @@ "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", "@types/glob": "^7.2.0", + "axios": "^1.4.0", "dateformat": "^5.0.2", "dotenv-defaults": "^3.0.0", "ejs": "^3.1.6", "escape-html": "^1.0.3", "express": "^4.17.1", "glob": "^7.2.0", - "winston": "^3.3.3" + "gray-matter": "^4.0.3", + "marked": "^6.0.0", + "winston": "^3.3.3", + "ws": "^8.13.0" }, "devDependencies": { + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.7.0", "@typescript-eslint/parser": "^5.7.0", "eslint": "^8.5.0" @@ -264,6 +269,15 @@ "@types/node": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.7.0.tgz", @@ -491,12 +505,12 @@ } }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" @@ -558,16 +572,35 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansi-styles/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ansi-styles/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -589,9 +622,24 @@ } }, "node_modules/async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } }, "node_modules/balanced-match": { "version": "1.0.2", @@ -599,23 +647,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/brace-expansion": { @@ -640,13 +691,25 @@ } }, "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -657,16 +720,18 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/color": { @@ -717,34 +782,45 @@ "text-hex": "1.0.x" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -790,18 +866,30 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/dir-glob": { "version": "3.0.1", @@ -846,14 +934,14 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", - "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "dependencies": { - "jake": "^10.6.1" + "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" @@ -870,7 +958,7 @@ "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } @@ -892,14 +980,6 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/eslint": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.5.0.tgz", @@ -1013,55 +1093,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/eslint/node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -1104,15 +1135,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -1128,18 +1150,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/espree": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz", @@ -1154,6 +1164,18 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", @@ -1199,43 +1221,44 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1244,6 +1267,17 @@ "node": ">= 0.10.0" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1317,11 +1351,30 @@ } }, "node_modules/filelist": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", - "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dependencies": { - "minimatch": "^3.0.4" + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, "node_modules/fill-range": { @@ -1337,16 +1390,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -1377,6 +1430,38 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1388,7 +1473,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } @@ -1398,12 +1483,31 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1470,27 +1574,94 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/iconv-lite": { @@ -1548,9 +1719,9 @@ } }, "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -1565,6 +1736,14 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1613,20 +1792,20 @@ "dev": true }, "node_modules/jake": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", - "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", "dependencies": { - "async": "0.9.x", - "chalk": "^2.4.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/js-yaml": { @@ -1653,6 +1832,14 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -1706,10 +1893,21 @@ "node": ">=10" } }, + "node_modules/marked": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-6.0.0.tgz", + "integrity": "sha512-7E3m/xIlymrFL5gWswIT4CheIE3fDeh51NV09M4x8iOc7NDYlyERcQMLAIHcSlrvwliwbPQ4OGD+MpPSYiQcqw==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { "node": ">= 0.6" } @@ -1761,28 +1959,28 @@ } }, "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1793,7 +1991,7 @@ "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -1802,17 +2000,25 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -1946,6 +2152,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -1956,11 +2167,17 @@ } }, "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/queue-microtask": { @@ -1992,12 +2209,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -2088,9 +2305,23 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-stable-stringify": { "version": "1.1.1", @@ -2102,10 +2333,22 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2118,51 +2361,51 @@ } }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -2185,6 +2428,19 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -2202,6 +2458,11 @@ "node": ">=8" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -2211,11 +2472,11 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string_decoder": { @@ -2226,25 +2487,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2257,6 +2499,14 @@ "node": ">=8" } }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2270,14 +2520,14 @@ } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/text-hex": { @@ -2304,9 +2554,9 @@ } }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "engines": { "node": ">=0.6" } @@ -2390,7 +2640,7 @@ "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { "node": ">= 0.8" } @@ -2478,15 +2728,10 @@ "node": ">= 6.4.0" } }, - "node_modules/winston/node_modules/async": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", - "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" - }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2497,6 +2742,26 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -2712,6 +2977,15 @@ "@types/node": "*" } }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.7.0.tgz", @@ -2847,12 +3121,12 @@ } }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { @@ -2893,11 +3167,26 @@ "dev": true }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" + }, + "dependencies": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } } }, "argparse": { @@ -2918,9 +3207,24 @@ "dev": true }, "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } }, "balanced-match": { "version": "1.0.2", @@ -2928,20 +3232,22 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" } }, "brace-expansion": { @@ -2963,9 +3269,18 @@ } }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } }, "callsites": { "version": "3.1.0", @@ -2974,13 +3289,12 @@ "dev": true }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "color": { @@ -3028,28 +3342,36 @@ "text-hex": "1.0.x" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", @@ -3086,15 +3408,20 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "dir-glob": { "version": "3.0.1", @@ -3130,14 +3457,14 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "ejs": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", - "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "requires": { - "jake": "^10.6.1" + "jake": "^10.8.5" } }, "enabled": { @@ -3148,7 +3475,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "enquirer": { "version": "2.3.6", @@ -3164,11 +3491,6 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, "eslint": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.5.0.tgz", @@ -3215,40 +3537,6 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -3274,12 +3562,6 @@ "estraverse": "^5.2.0" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -3291,15 +3573,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -3355,6 +3628,11 @@ "eslint-visitor-keys": "^3.1.0" } }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, "esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", @@ -3388,45 +3666,54 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3493,11 +3780,29 @@ } }, "filelist": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", - "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "requires": { - "minimatch": "^3.0.4" + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "fill-range": { @@ -3510,16 +3815,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" } }, @@ -3544,6 +3849,21 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3552,19 +3872,35 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -3610,21 +3946,69 @@ "slash": "^3.0.0" } }, + "gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "requires": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" } }, "iconv-lite": { @@ -3667,9 +4051,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ipaddr.js": { "version": "1.9.1", @@ -3681,6 +4065,11 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3714,14 +4103,14 @@ "dev": true }, "jake": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", - "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", "requires": { - "async": "0.9.x", - "chalk": "^2.4.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" } }, "js-yaml": { @@ -3745,6 +4134,11 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -3794,10 +4188,15 @@ "yallist": "^4.0.0" } }, + "marked": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-6.0.0.tgz", + "integrity": "sha512-7E3m/xIlymrFL5gWswIT4CheIE3fDeh51NV09M4x8iOc7NDYlyERcQMLAIHcSlrvwliwbPQ4OGD+MpPSYiQcqw==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { "version": "1.0.1", @@ -3831,22 +4230,22 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -3854,7 +4253,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "natural-compare": { "version": "1.4.0", @@ -3863,14 +4262,19 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -3968,6 +4372,11 @@ "ipaddr.js": "1.9.1" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -3975,9 +4384,12 @@ "dev": true }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } }, "queue-microtask": { "version": "1.2.3", @@ -3991,12 +4403,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -4048,9 +4460,9 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-stable-stringify": { "version": "1.1.1", @@ -4062,57 +4474,66 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "requires": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + } + }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" } }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "shebang-command": { "version": "2.0.0", @@ -4129,6 +4550,16 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -4143,15 +4574,20 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "string_decoder": { "version": "1.3.0", @@ -4159,13 +4595,6 @@ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } } }, "strip-ansi": { @@ -4177,6 +4606,11 @@ "ansi-regex": "^5.0.1" } }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==" + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4184,11 +4618,11 @@ "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, "text-hex": { @@ -4212,9 +4646,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "triple-beam": { "version": "1.3.0", @@ -4270,7 +4704,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "uri-js": { "version": "4.4.1", @@ -4325,13 +4759,6 @@ "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.4.0" - }, - "dependencies": { - "async": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", - "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" - } } }, "winston-transport": { @@ -4345,9 +4772,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrappy": { @@ -4355,6 +4782,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "requires": {} + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 293868e..d700de3 100644 --- a/package.json +++ b/package.json @@ -14,16 +14,21 @@ "@types/escape-html": "^1.0.1", "@types/express": "^4.17.13", "@types/glob": "^7.2.0", + "axios": "^1.4.0", "dateformat": "^5.0.2", "dotenv-defaults": "^3.0.0", "ejs": "^3.1.6", "escape-html": "^1.0.3", "express": "^4.17.1", "glob": "^7.2.0", - "winston": "^3.3.3" + "gray-matter": "^4.0.3", + "marked": "^6.0.0", + "winston": "^3.3.3", + "ws": "^8.13.0" }, "type": "module", "devDependencies": { + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.7.0", "@typescript-eslint/parser": "^5.7.0", "eslint": "^8.5.0" diff --git a/pages/about.wiki b/pages/about.wiki deleted file mode 100644 index 383b1f5..0000000 --- a/pages/about.wiki +++ /dev/null @@ -1,61 +0,0 @@ -__PRIMARY__ -{{NAVBARSORTORDER:2}} -{{DISPLAYTITLE:About}} - -Hello! - -I am a student in the United Kingdom studying computer science at university. -I mainly enjoy building web services and have some interest in cybersecurity, though most of my programming experience is in Java. -Most of my public projects have something to do with ''Minecraft'', as it was through that game where I really got into programming and computer science. - -I also have an interest in physics, specifically astronomy. Had I not chosen to study computer science, I would have likely studied physics instead. - -== Projects == - -Most of my large projects are related to ''Minecraft''. -A lot of other projects aren't listed here simply because they aren't significant enough, and/or because I can't waffle on about them for long enough to fill a whole subsection. - - -=== Quests === - -{{sourcecode|[https://github.com/LMBishop/Quests LMBishop/Quests]}} [[File:questcompass2-256.png|right|150px|Quests logo]] - -Quests enables ''Minecraft'' server owners to create goals or missions for their players to work towards. -It is probably one of my largest projects in terms of both how many people use it and the time I've spent working on it. -As of writing, it is used by approximately 1,200 servers and up to 9,000 players each day. -Unfortunately, I lack the time to properly maintain it, but it is still a project which I am mostly pleased with, as it gave me a good bit of experience designing somewhat large Java projects. - -The main description page can be found on [https://www.spigotmc.org/resources/23696/ SpigotMC]. - -=== Next bus === - -Next bus is a small service which returns live bus data based on open telemetry. -I wrote it as my nearest bus stop lacked a departure board and I knew that bus timings were openly available online. -This was one of my first web projects, and while I admit the the front-end is terrible, it was never designed to look pretty or for anyone but myself to use. -This project helped me to both learn the basics of how the web works (...and to also stop turning up late to lessons). - -While I have no need for it anymore, it is still accessible at [https://leonardobishop.com/~/nextbus/ https://leonardobishop.com/~/nextbus/]. - -=== This webiste === - -{{sourcecode|[https://github.com/LMBishop/website LMBishop/webste]}} -This website was written in TypeScript, and it parses pages written in a markup language and renders it as HTML. -I could've simply written these pages as static HTML, but I hate myself so I decided to over-engineer this solution instead. - -There are some caveats with this system though, in no particular order: -* re-rendering pages are a pain after edits -* files are read from <code>/static/image</code> rather than <code>/pages/file</code> (which breaks the point of the entire namespace system) -* templates can't transclude other templates -* the Template namespace is the only transcludable namespace -* code is a bit wack in places -* I'm never happy with the visual design -* I don't actually know what to fill this website with - -=== u/YTLinkerBot === - -{{sourcecode|[https://github.com/LMBishop/ytlinkerbot LMBishop/ytlinkerbot]}} - -YTLinkerBot is a small reddit bot which automatically crossposts YouTube links to reddit whenever a creator uploads. -This bot was originally written in Java, but it is (mostly) in the process of being re-written with NodeJS. -This is due to the fact that it constantly hits the YouTube Data API quota, as it works by continously polling YouTube for each registered channel. -The new recode will use Google's WebSub service, though the challenge comes with having to automatically manage channel subscriptions for a service which anyone can sign up to, and right now I don't have the time to implement this. diff --git a/pages/blog/steam-deck-workstation.md b/pages/blog/steam-deck-workstation.md new file mode 100644 index 0000000..884282d --- /dev/null +++ b/pages/blog/steam-deck-workstation.md @@ -0,0 +1,99 @@ +--- +title: Steam Deck Workstation +date: !!timestamp '2023-08-03' +--- + +I have this dilemma. Do I: a) spend ages tearing down my PC so I can lug it onto +a train to take to my uni house and rebuild it again there, where it could +get stolen, or b) forego the PC and simply not have a desktop workstation / +gaming rig instead? + +Earlier this year, genius me came up with a third option: +convert my [Steam Deck](https://www.steamdeck.com/en/) +into a desktop PC instead! + +## Why + +While I was back at home over Christmas, I had played and really enjoyed +Overwatch 2, and wanted to keep playing once I went +back to university. Unfortunately for me, my laptop is both not powerful enough, +and not capable of outputting to external displays for Complicated reasons. + +The obvious solution would be to simply bring my PC, however I had a criminally +small desk at the time (which means I have absolutely nowhere to put it, not +even the floor), and it would take too much effort to bring back on the train +with everything else I brought home. + +I remembered I had recently bought a Steam Deck, which had been sitting in +my bag collecting dust, and figured I could try and turn that into my +desktop PC. After all, it *is* a device designed to be portable. + +## Testing Valve's claim + +Valve champions the fact that their Steam Deck can be used as a normal PC. +They have Windows drivers, and even sell a +[dock](https://store.steampowered.com/steamdeckdock), which I bought solely for +this purpose. + +Unfortunately Overwatch breaks when using Proton, which meant I had to install +Windows. This involves the tedious task of partitioning the Steam Deck. +Additionally, my scope had expanded to wanting to install a Linux environment +as well, so I could use it instead of my aforementioned dodgy laptop to work on +projects. + +<figure> + <img src="/images/steam-deck-gparted.jpg" width=300> + <figcaption> + GParted running on the Deck. It was as annoying to use as it looks. + </figcaption> +</figure> + +SteamOS is based off Arch, so in theory I could have just installed all +my packages on there and replaced KDE with by preferred sway. However, the root +partition is mounted as read only by default, and any changes made to it get +completely wiped whenever SteamOS is updated. No go. + +Instead, I repartitioned the internal disk and decided to do a triple-boot +setup: SteamOS (so I can still use it as it was intended), Windows (solely to +play Overwatch 2), and Arch (because I use Arch btw). + +<figure> + <img src="/images/steam-deck-partitions.jpg"> + <figcaption> + Internal drive partition table + </figcaption> +</figure> + +As the Deck is only 512gb in size, I decided to go with booting Arch +off a microSD card, which would be left inserted in the Deck's built-in microSD +slot. + +## The experience + +For productivity, it was great. I managed to work on some projects using it, +got some university work done, and procrastinated a lot. + +<figure> + <img src="/images/steam-deck-arch.jpg"> + <figcaption>My university setup</figcaption> +</figure> + +For gaming, it was not so great. Turns out running games on a 1080p +monitor is quite a bit more demanding than 720p. Overwatch 2 experienced quite +frequent frame drops, and Counter-Strike was even worse somehow. Not to mention +I was also on Wi-Fi, which means I'm rubber banding all over the joint anyway. + +There were some other issues too. The Steam Deck is nice and fast when a single +game open is in +[gamescope](https://github.com/ValveSoftware/gamescope). +However, when trying to use it as a general desktop PC, +its limitations start becoming much more obvious. Simple things like updating +the system with `pacman` left the system effectively unresponsive until it +completed. Loading a heavy program like IntelliJ also froze up the system until +it finished. I don't know if this is an IO bottleneck (this partition is booted +off a microSD card after all) but I did observe the same 100% CPU utilisation +when doing simple tasks in Windows too. + +## Conclusion + +Maybe just plug a laptop into the monitor next time. diff --git a/pages/contact.html b/pages/contact.html new file mode 100644 index 0000000..bb315a2 --- /dev/null +++ b/pages/contact.html @@ -0,0 +1,59 @@ +--- +stylesheets: + - /css/contact.css +--- +<h1>Contact</h1> +<div class="grid-outer"> + <div class="grid-inner"> + <div class="grid-title"> + <p>name</p> + </div> + <div class="grid-content"> + <p> + Leonardo Bishop + </p> + </div> + </div> + <div class="grid-inner"> + <div class="grid-title"> + <p>pronouns</p> + </div> + <div class="grid-content"> + <p> + he/him + </p> + </div> + </div> + <div class="grid-inner"> + <div class="grid-title"> + <p>website</p> + </div> + <div class="grid-content"> + <a href="https://leonardobishop.com">leonardobishop.com</a> + </div> + </div> + <div class="grid-inner"> + <div class="grid-title"> + <p>email</p> + </div> + <div class="grid-content"> + <a href="mailto:me@leonardobishop.com">me@leonardobishop.com</a> + </div> + </div> + <div class="grid-inner"> + <div class="grid-title"> + <p>github</p> + </div> + <div class="grid-content"> + <a href="https://github.com/LMBishop">LMBishop</a> + </div> + </div> + <div class="grid-inner"> + <div class="grid-title"> + <p>subatomic particle</p> + </div> + <div class="grid-content"> + <p><a href="http://www.schrodingerskitten.co.uk/articles/subatomic-quiz.html">Neutrino</a><p> + </div> + </div> +</div> diff --git a/pages/contact.wiki b/pages/contact.wiki deleted file mode 100644 index 3ecc059..0000000 --- a/pages/contact.wiki +++ /dev/null @@ -1,16 +0,0 @@ -__PRIMARY__ -{{NAVBARSORTORDER:3}} -{{DISPLAYTITLE:Contact}} - -{{incomplete}} - -You can find me at: -* GitHub: [https://github.com/LMBishop https://github.com/LMBishop] - -You can contact me on: -* Email: contact (at) leonardobishop.com - -== PGP signature == -<pre> - -</pre> diff --git a/pages/index.html b/pages/index.html new file mode 100644 index 0000000..df4cbae --- /dev/null +++ b/pages/index.html @@ -0,0 +1,42 @@ +--- +stylesheets: + - /css/home.css +--- +<div class="spacer"></div> +<div class="grid-outer"> + <div class="grid-inner"> + <div class="grid-title"> + <p>who</p> + </div> + <div class="grid-content"> + <p> + Hello I'm Leonardo. I'm currently completing a degree in Computer Science + at university, and I like to spend some of free time working on projects + like Quests, or learning new tools and languages. + </p> + <p> + You may see me around online using the nick "LMBishop". + </p> + </div> + </div> + <div class="grid-inner"> + <div class="grid-title"> + <p>where</p> + </div> + <div class="grid-content"> + <iframe width="100%" height="200" src="https://www.openstreetmap.org/export/embed.html?bbox=-2.408065795898438%2C52.318553202553275%2C-1.5264129638671877%2C52.670969529275744&layer=mapnik" style="border: 0px; border-radius: 10px;"></iframe> + </div> + </div> + <div class="grid-inner"> + <div class="grid-title"> + <p>what</p> + </div> + <div class="grid-content"> + <p> + Feel free to look around. This is my personal site where I post about + things which interest me, or share stuff I'm working on. If you're not sure + where to start, check out the <a href="/now">now</a> page. + </p> + </div> + </div> +</div> diff --git a/pages/index.wiki b/pages/index.wiki deleted file mode 100644 index 64c1415..0000000 --- a/pages/index.wiki +++ /dev/null @@ -1,37 +0,0 @@ -__NOTITLE__ -__PRIMARY__ -{{NAVBARSORTORDER:1}} -{{DISPLAYTITLE:Home}} -<pre style="text-align:center"> -██╗ ███╗ ███╗██████╗ ██╗███████╗██╗ ██╗ ██████╗ ██████╗ -██║ ████╗ ████║██╔══██╗██║██╔════╝██║ ██║██╔═══██╗██╔══██╗ -██║ ██╔████╔██║██████╔╝██║███████╗███████║██║ ██║██████╔╝ -██║ ██║╚██╔╝██║██╔══██╗██║╚════██║██╔══██║██║ ██║██╔═══╝ -███████╗██║ ╚═╝ ██║██████╔╝██║███████║██║ ██║╚██████╔╝██║ -╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ -</pre> - -Hello! 👋 - -I'm '''Leonardo Bishop''', a programmer studying computer science full-time at university. - -You can check out my projects on the [[about]] page. - -== New website == - -This new website is written in TypeScript and these pages are witten in wikitext. - -The source code for this website is available on [https://github.com/LMBishop/website GitHub], freely available under the ISC licence. (If you have suggestions on how to improve it, specifically how the code is designed, please let me know!) - -== Accessing old content == - -All traffic to leonardobishop.com is now redirected to this new website. To access old content on here, you should prepend the path with a tilde (~). - -For example, the following -<pre> -https://leonardobishop.com/nextbus/ -</pre> -will become -<pre> -https://leonardobishop.com/~/nextbus/ -</pre> diff --git a/pages/kpnx.wiki b/pages/kpnx.wiki deleted file mode 100644 index 099a133..0000000 --- a/pages/kpnx.wiki +++ /dev/null @@ -1,98 +0,0 @@ -<!-- This is a test page, imported from Wikipedia --> -<!-- {{NAVBARSORTORDER:5}} -'''KPNX''', [[virtual channel]] 12 ([[Ultra high frequency|UHF]] [[digital terrestrial television|digital]] channel 18), is an [[NBC]]-[[network affiliate|affiliated]] [[television station]] serving [[Phoenix, Arizona]], United States that is [[city of license|licensed]] to the suburb of [[Mesa, Arizona|Mesa]]. The station is owned by [[McLean, Virginia]]-based [[Tegna Inc.]] KPNX's studios are located at the Republic Media building on [[Van Buren Street (Arizona)|Van Buren Street]] in [[downtown Phoenix]] (which also houses formerly co-owned newspaper ''[[The Arizona Republic]]''), and its transmitter is located atop [[South Mountains (Arizona)|South Mountain]] on the city's south side; it is also broadcast on [[KNAZ-TV]] (channel 2) in [[Flagstaff, Arizona|Flagstaff]], which formerly was a separate NBC affiliate, and a network of [[Low-power broadcasting#Television|low-power]] [[Broadcast relay station#Broadcast translators|translators]] across northern and central Arizona. - -Channel 12 was the second TV station on the air in the Phoenix area, starting in 1953. Originally established in Mesa itself, it was acquired by Phoenix radio station [[KTAR (AM)|KTAR (620 AM)]] in 1954 in a maneuver that ended a contest over channel 3 in Phoenix and was co-owned with that outlet for 25 years. It has been owned by Tegna and its predecessor, Gannett, since 1979, when it became KPNX. - -==History== -===Early years=== -[[File:South Mountain Towers (32196913494).jpg|KTYL-TV was the first Phoenix-area TV station to broadcast from South Mountain|thumb|left|alt=The South Mountain antenna farm]] -On November 1, 1952, Harkins Broadcasting, Inc. filed an application to build a new television station on channel 12 in Mesa, Arizona.<ref name="hc">{{FCC letter|hcards=yes|callsign=KPNX|letterid=85306}}</ref> Harkins Broadcasting was a joint venture of two movie theater operators, [[Harkins Theatres]] and Harry Nace, and owned Mesa radio stations [[KIHP (AM)|KTYL (1310 AM)]] and [[KZZP|KTYL-FM 104.7]]. The [[Federal Communications Commission]] granted the construction permit on February 18, 1953.<ref name="okay">{{cite news|url=https://www.newspapers.com/clip/85829727/|title=KTYL Given Okay To Build TV Station|work=[[The Arizona Republic]]|page=1|date=February 19, 1953|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044446/https://www.newspapers.com/clip/85829727/ktyl-given-okay-to-build-tv-station/|url-status=live}}</ref> At the end of March 1953, the city of Phoenix's parks board approved a [[South Mountains (Arizona)|South Mountain]] transmitter, reversing an earlier decision that would have denied television stations not licensed to Phoenix the use of the site and which was protested by television set owners who wanted to be assured reception of all stations from one site.<ref name="parks">{{cite news|url=https://www.newspapers.com/clip/32885335/|title=South Mountain TV Site Granted Mesa Station: KTYL Gets City Parks Board Okay|first=Orren|last=Beaty|work=The Arizona Republic|date=April 1, 1953|page=1|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044434/https://www.newspapers.com/clip/32885335/ktyl-gets-city-parks-board-okay/|url-status=live}}</ref> - -With the site approved by the FCC and the city of Phoenix, construction began nearly immediately. Much of the studio equipment, installed at an expansion to the KTYL facilities on Main Street in Mesa, was already on hand.<ref>{{cite news|url=https://www.newspapers.com/clip/32885363/|page=2|title=Mesa KTYL-TV Sets April 25 For Debut|work=The Arizona Republic|date=April 2, 1953|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044435/https://www.newspapers.com/clip/32885363/mesa-ktyl-tv-sets-april-25-for-debut/|url-status=live}}</ref> The station began broadcasting on May 2, with its introductory program being a 19-hour [[telethon]] to benefit [[United Cerebral Palsy]].<ref>{{cite news|url=https://www.newspapers.com/clip/85830007/ktyl-tv-opener-draws-40000-raises-53/|title=KTYL-TV Opener Draws 40,000, Raises $53,340 In Palsy Fight|work=The Arizona Republic|date=May 4, 1953|page=15|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044435/https://www.newspapers.com/clip/85830007/ktyl-tv-opener-draws-40000-raises/|url-status=live}}</ref> An NBC affiliate from the outset,<ref>{{cite news|url=https://worldradiohistory.com/Archive-BC/BC-1953/BC-1953-06-08.pdf|work=Broadcasting|date=June 8, 1953|title=KTYL-TV Can Capture Arizona For You|page=17|access-date=September 23, 2021|archive-date=March 8, 2021|archive-url=https://web.archive.org/web/20210308031201/https://worldradiohistory.com/Archive-BC/BC-1953/BC-1953-06-08.pdf|url-status=live}}</ref> the station briefly maintained a Phoenix office which closed just two months after launch.<ref>{{cite news|url=https://www.newspapers.com/clip/44182763/ktyl-tv-closes-office-here-operates-at/|first=Maggie|last=Wilson|title=KTYL-TV Closes Office Here, Operates At Suburban Studio|work=The Arizona Republic|page=12|date=July 13, 1953|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044434/https://www.newspapers.com/clip/44182763/ktyl-tv-closes-office-here-operates-at/|url-status=live}}</ref> - -Lurking under the embryonic Phoenix television landscape was the absence of one of the state's pioneer radio stations. In 1948, [[KTAR (AM)|KTAR]] (620 AM) had filed for Phoenix's channel 3, only to see the FCC plunge television applications into a four-year-long freeze. As early as 1945, KTAR had arranged for exclusive rights to the South Mountain space that would later be used by all of the Phoenix TV stations as a transmitter site—a concession that was overturned in the run-up to KTYL-TV's launch.{{r|parks}} When the freeze was lifted in 1952, KTAR declared it would be on the air within three months of a construction permit grant, having already selected a site for and broken ground on a proposed television and radio studio at Central Avenue and Portland Street and contracted for equipment to furnish it.<ref>{{cite news|url=https://www.newspapers.com/clip/85830443/2-new-stations-planned-as-us-ends-tv-f/|title=2 New Stations Planned As U.S. Ends TV Freeze|pages=1, [https://www.newspapers.com/clip/85830492/two-new-phoenix-tv-stations-planned-as-t/ 2]|work=The Arizona Republic|date=April 14, 1952|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044435/https://www.newspapers.com/clip/85830443/2-new-stations-planned-as-us-ends-tv/|url-status=live}}</ref><ref>{{cite news|url=https://www.newspapers.com/clip/85849305/|first=Henry|last=Fuller|work=The Arizona Republic|title=KTAR Marks 30th Anniversary As New Station Project Begins|date=June 15, 1952|page=II:6|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044436/https://www.newspapers.com/clip/85849305/ktar-marks-30th-anniversary-as-new/|url-status=live}}</ref> It was speculated that [[KPHO-TV]] owner [[Meredith Corporation]]—whose station was the only pre-freeze outlet in the state—might have decided to let KTYL-TV have NBC because of the sense that, as soon as KTAR won a television station, it would sign up with NBC, mirroring the radio station.<ref>{{cite news|url=https://www.newspapers.com/clip/85830286/networks-split-made-official/|first=Maggie|last=Wilson|title=Networks Split Made Official|page=3:6|work=The Arizona Republic|date=April 30, 1953|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044435/https://www.newspapers.com/clip/85830286/networks-split-made-official/|url-status=live}}</ref> - -However, KTAR's channel 3 picture became cloudy in February 1953, just as the FCC was about to hand down a decision. A new applicant, the Arizona Television Company, filed for the channel.{{r|okay}} This applicant added a major power broker to its ranks months later: [[Ernest McFarland]], former senator and soon to be governor.<ref>{{Cite news |date=May 31, 1953 |title=McFarland Joins in TV Station |page=3 |via=Newspapers.com |url=https://www.newspapers.com/clip/63264150/ |url-status=live |access-date=November 14, 2020 |archive-url=https://web.archive.org/web/20210630063733/https://www.newspapers.com/clip/63264150/mcfarland-joins-in-tv-station/ |archive-date=June 30, 2021}}</ref> In February 1954, hearings were held on the channel 3 assignment.<ref>{{Cite news |date=February 25, 1954 |title=TV Hearings Open Tomorrow |page=35 |work=The Arizona Republic |via=Newspapers.com |url=https://www.newspapers.com/clip/63264365/ |url-status=live |access-date=November 14, 2020 |archive-url=https://web.archive.org/web/20210630063808/https://www.newspapers.com/clip/63264365/tv-hearings-open-tomorrow/ |archive-date=June 30, 2021}}</ref> - -The channel 3 contest ended in April 1954, when KTAR announced it would buy KTYL-TV for $250,000, a decision that cleared the way for the Arizona Television Company to build [[KTVK]].<ref name="announced">{{Cite news |date=April 30, 1954 |title=Purchase Of KTYL-TV By KTAR Announced |page=1 |work=The Arizona Republic |via=Newspapers.com |url=https://www.newspapers.com/clip/44184548/ |url-status=live |access-date=November 14, 2020 |archive-url=https://web.archive.org/web/20210630063709/https://www.newspapers.com/clip/44184548/purchase-of-ktyl-tv-by-ktar-announced/ |archive-date=June 30, 2021}}</ref><ref>{{Cite news |date=May 5, 1954 |title=KTYL-TV Tag $250,000 |page=22 |work=The Arizona Republic |via=Newspapers.com |url=https://www.newspapers.com/clip/63264666/ |url-status=live |access-date=November 14, 2020 |archive-url=https://web.archive.org/web/20210630063718/https://www.newspapers.com/clip/63264666/ktyl-tv-tag-250000/ |archive-date=June 30, 2021}}</ref> In announcing the purchase, KTAR owner John J. Louis explained that he wanted to bring KTAR television to the Phoenix area without going through hearings.{{r|announced}} - -When the sale closed in July 1954, KTYL-TV became KVAR; immediately, KTAR-purchased equipment was added to the studios,<ref>{{cite news|url=https://www.newspapers.com/clip/56178001/|date=July 1, 1954|work=The Arizona Daily Star|title=Mesa TV Station Control Changes|page=7A|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044436/https://www.newspapers.com/clip/56178001/mesa-tv-station-control-changes/|url-status=live}}</ref> which were then moved to Phoenix in 1956 over KTVK's objection;<ref>{{cite news|url=https://worldradiohistory.com/Archive-BC/BC-1957/1957-07-29-BC.pdf|work=Broadcasting|date=July 29, 1957|page=64|title=FCC Affirms KVAR (TV) Grant For Move of Its Main Studio|access-date=September 23, 2021|archive-date=March 8, 2021|archive-url=https://web.archive.org/web/20210308024129/https://worldradiohistory.com/Archive-BC/BC-1957/1957-07-29-BC.pdf|url-status=live}}</ref> the station was also allowed to identify as "Phoenix/Mesa" in 1958.{{r|hc}} In 1960, a new tower and maximum-power transmitter were commissioned;<ref>{{Cite news|url=https://www.newspapers.com/clip/34846577/kvar-ups-power-today/|title=KVAR Ups Power Today|date=September 15, 1960|work=The Arizona Republic|page=29|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044436/https://www.newspapers.com/clip/34846577/kvar-ups-power-today/|url-status=live}}</ref> the prior facility was then sold to [[Arizona State University]] and used to launch educational station [[KAET]] on channel 8 in 1961.<ref>{{cite news|url=https://www.newspapers.com/clip/68802793/|title=ASU Wants Channel 8 For Educational Use|work=The Arizona Republic|date=July 31, 1960|page=8|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044439/https://www.newspapers.com/clip/68802793/asu-wants-channel-8-for-educational-use/|url-status=live}}</ref><ref>{{cite news|url=https://www.newspapers.com/clip/68802700/|date=January 28, 1960|title=Regents Okay Plans For Educational TV On Channel 8|first=Virgil|last=Meibert|work=The Arizona Republic|page=18|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044437/https://www.newspapers.com/clip/68802700/regents-okay-plans-for-educational-tv/|url-status=live}}</ref> In April 1961, the call sign was changed to KTAR-TV, which had not been previously available to the television station because it was licensed to a different location from the radio station.{{r|hc}} - -===Growth=== -[[Image:KPNX circa 1970.jpg|160px|thumb|right|KTAR-TV logo used from the late 1960s to 1975.|alt=Refer to caption]] -In 1968, the Louis family's KTAR and Eller Outdoor Advertising, owned by [[Karl Eller]], merged into Combined Communications Corporation.<ref>{{cite news|url=https://www.newspapers.com/clip/85830670/ad-agency-broadcast-merger-okd/|title=Ad agency, broadcast merger OKd|page=2-B|work=The Arizona Republic|date=October 18, 1968|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044436/https://www.newspapers.com/clip/85830670/ad-agency-broadcast-merger-okd/|url-status=live}}</ref> Combined then grew into owning other television and radio stations and owned a full complement of seven by 1974, when it merged with Pacific & Southern Broadcasting Company.<ref>{{cite news|url=https://worldradiohistory.com/Archive-BC/BC-1973/1973-04-23-BC.pdf|date=April 23, 1973|work=Broadcasting|title=P&S goes under wing of CCC|pages=22–23|access-date=September 23, 2021|archive-date=March 8, 2021|archive-url=https://web.archive.org/web/20210308035417/https://worldradiohistory.com/Archive-BC/BC-1973/1973-04-23-BC.pdf|url-status=live}}</ref> - -In 1978, Combined Communications agreed to merge with the [[Gannett|Gannett Company]]. The merged company opted to retain channel 12 and divest the Phoenix radio stations;<ref>{{Cite news|url=https://www.newspapers.com/clip/85850336/combined-communications-and-gannett-co/|title=Combined Communications and Gannett Co. to merge|first=Joe|last=Cole|page=1|date=May 9, 1978|work=The Arizona Republic|access-date=September 23, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044437/https://www.newspapers.com/clip/85850336/combined-communications-and-gannett-co/|url-status=live}}</ref> Combined's ownership of the KTAR stations had been [[grandfather clause|grandfathered]] earlier in the decade when the FCC forbade common ownership of television and radio stations in top-50 markets, but with the Gannett merger, the KTAR cluster lost its grandfathered protection. The radio stations were traded to Pulitzer Broadcasting in 1979 for [[KTRS (AM)|KSD]] radio in St. Louis and $2 million.<ref>{{cite news|url=https://worldradiohistory.com/Archive-BC/BC-1978/1978-10-02-BC.pdf|date=October 2, 1978|work=Broadcasting|page=30|title=In Brief|access-date=September 23, 2021|archive-date=March 8, 2021|archive-url=https://web.archive.org/web/20210308033120/https://worldradiohistory.com/Archive-BC/BC-1978/1978-10-02-BC.pdf|url-status=live}}</ref> KTAR-TV then changed its call sign to KPNX on June 4, 1979, since the radio properties had held the KTAR call letters first; at the time, broadcast stations with different owners could not share the same call letters.<ref>{{cite news|url=https://www.newspapers.com/clip/28881272/kpnx-tv-was-not-an-ez-pik/|title=KPNX-TV was not an EZ PIK|first=Hardy|last=Price|work=The Arizona Republic|page=B-10|date=May 31, 1979}}</ref> - -From 1977 to 1995, KPNX was run by general manager C.E. "Pep" Cooney, who also did on-air editorials; he then became a senior vice president of Gannett for several years prior to his retirement in 1998.<ref>{{cite news|url=https://www.newspapers.com/clip/85886577/channel-3-to-air-events-from-d-backs-op/|title=Channel 3 to air events from D-Backs' opener|work=The Arizona Republic|first=Dave|last=Walker|page=D17|date=April 3, 1998|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044549/https://www.newspapers.com/clip/85886577/channel-3-to-air-events-from-d-backs/|url-status=live}}</ref><ref>{{cite news|url=https://www.newspapers.com/clip/85886525/local-tv-icon-pep-cooney-dies-at-age-7/|title=Local TV icon 'Pep' Cooney dies at age 70|first=Dennis|last=Wagner|first2=Connie|last2=Cone Sexton|date=September 4, 2003|pages=A1, [https://www.newspapers.com/clip/85886623/ A10]|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044550/https://www.newspapers.com/clip/85886525/local-tv-icon-pep-cooney-dies-at-age/|url-status=live}}</ref> In 1985, it was the first Phoenix TV station to broadcast in stereo.<ref>{{cite news|date=June 14, 1985|page=E15|title=KPNX-TV becomes Valley's first stereo-active station|url=https://www.newspapers.com/clip/85886125/|first=Bud|last=Wilkinson|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044550/https://www.newspapers.com/clip/85886125/kpnx-tv-becomes-valleys-first/|url-status=live}}</ref> - -The fact that KPNX was the only Phoenix station unaffected by a major realignment of network affiliations in 1994 and 1995 fueled a run of success for KPNX and its news department that lasted more than a decade. In 2005, the station had the highest revenue of any in Phoenix: $75 million, representing almost 20 percent of the market.<ref name="mw06">{{cite news|url=https://worldradiohistory.com/Archive-Mediaweek/2006/Mediaweek-2006-05-29.pdf|title=Phoenix|first=Eileen|last=Davis Hudson|date=May 29, 2006|work=Mediaweek|pages=10–16|access-date=September 24, 2021|archive-date=March 10, 2021|archive-url=https://web.archive.org/web/20210310034517/https://worldradiohistory.com/Archive-Mediaweek/2006/Mediaweek-2006-05-29.pdf|url-status=live}}</ref> - -[[Image:KPNX circa 1973.jpg|160px|thumb|right|KTAR-TV logo from 1975. Based on the lettering used by [[NBC News]] at the time, the "12" logo survived until early 1986, long after the station became KPNX.|alt=Refer to caption]] - -===Newspaper co-ownership=== -[[File:Republic Media Building.jpg|200px|thumb|left|Southwest corner of the Republic Media Building in 2013|alt=Refer to caption]] -In 2000, Gannett merged with Central Newspapers, owner of ''[[The Arizona Republic]]'', in the second-largest newspaper deal ever at the time.<ref>{{cite news|url=https://www.newspapers.com/clip/85885100/gannett-acquiring-republic-28-billi/|title=Gannett acquiring 'Republic': $2.8 billion deal ends years of Pulliam control|first=Dawn|last=Gilbertson|pages=A1, [https://www.newspapers.com/clip/85885138/gannett-acquiring-arizona-republic/ A8]|date=June 29, 2000|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044611/https://www.newspapers.com/clip/85885100/gannett-acquiring-republic-28/|url-status=live}}</ref> While the FCC barred the common ownership of newspapers and television stations in the same market, Gannett successfully banked on a potential rule change; even as written at the time before being relaxed in 2003, the issue would not have been pressed until KPNX's license came up for renewal in 2006.<ref>{{cite news|title=Gannett Co. buyout bets on FCC rule|id={{ProQuest|225068322}} |via=ProQuest|date=July 7, 2000|first=Anne|last=Robertson|work=Phoenix Business Journal|page=1}}</ref> With Gannett owning the then-number one station in Phoenix and the state's largest newspaper, the two merged their websites in 2001.<ref>{{cite news|url=https://worldradiohistory.com/Archive-Mediaweek/2002/Mediaweek-2002-01-14.pdf|date=January 14, 2002|work=Mediaweek|title=Phoenix|pages=10–16|first=Eileen|last=Davis Hudson|access-date=September 24, 2021|archive-date=March 10, 2021|archive-url=https://web.archive.org/web/20210310035248/https://worldradiohistory.com/Archive-Mediaweek/2002/Mediaweek-2002-01-14.pdf|url-status=live}}</ref> - -In January 2011, KPNX left its longtime home on Central Avenue and consolidated its operations with ''The Republic'' at the Republic Media Building on East [[Van Buren Street (Arizona)|Van Buren Street]] in downtown Phoenix, with the station's local newscasts broadcasting from a streetside studio.<ref>{{cite news|first=Lynn|last=Ducey|url=http://www.bizjournals.com/phoenix/stories/2010/10/11/story7.html?page=all|title=Local stations debut new shows; KPNX moving to downtown studio|work=[[Phoenix Business Journal]]|date=October 10, 2010|access-date=September 24, 2012|archive-date=February 28, 2014|archive-url=https://web.archive.org/web/20140228122607/http://www.bizjournals.com/phoenix/stories/2010/10/11/story7.html?page=all|url-status=live}}</ref> The Central Avenue facility was then significantly renovated and became the [[Bob Parsons|Parsons]] Center for Health and Wellness, the headquarters complex for the Southwest Center for HIV/AIDS.<ref>{{cite web|url=http://www.businesswire.com/news/home/20120514006301/en/Parsons-Foundation-5MM-Fight-HIVAIDS|title=The Parsons Foundation Gives $5MM to Fight HIV/AIDS; Southwest Center to Build Parsons Foundation Center for Health, Education & Wellness|date=May 14, 2012|work=[[Businesswire]]|access-date=March 21, 2016|archive-date=April 18, 2016|archive-url=https://web.archive.org/web/20160418125722/http://www.businesswire.com/news/home/20120514006301/en/Parsons-Foundation-5MM-Fight-HIVAIDS|url-status=live}}</ref> - -===Tegna ownership=== -On June 29, 2015, the Gannett Company split in two, with one side specializing in print media and the other side specializing in broadcast and digital media. KPNX was retained by the broadcasting company, which took the name [[Tegna Inc.|Tegna]].<ref>{{Cite web|title = Separation of Gannett into two public companies completed {{!}} TEGNA|url = http://www.tegna.com/separation-of-gannett-into-two-public-companies-completed/|publisher = Tegna|access-date = June 29, 2015|archive-date = July 2, 2015|archive-url = https://web.archive.org/web/20150702005302/http://www.tegna.com/separation-of-gannett-into-two-public-companies-completed/|url-status = live}}</ref> KPNX and ''The Republic'' continue to operate in the same building as separate entities; as a consequence of the split, KPNX regained a separate website, having shared azcentral.com with the newspaper.<ref>{{cite news|url=https://www.bizjournals.com/phoenix/blog/business/2015/07/divorce-separate-bedrooms-and-bed-bugs-breaking.html|work=Phoenix Business Journal|first=Mike|last=Sunnucks|title=Divorce, separate bedrooms and bedbugs: Breaking down the Gannett breakup for 'Arizona Republic,' KPNX|date=July 6, 2015|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044612/https://www.bizjournals.com/phoenix/blog/business/2015/07/divorce-separate-bedrooms-and-bed-bugs-breaking.html|url-status=live}}</ref> - -==Local programming== -===Newscasts=== -[[File:KPNX 12 News Studios.jpg|200px|thumb|right|The KPNX streetside studio in 2013|alt=Refer to caption]] -KTAR-TV was the Phoenix pioneer of the so-called "happy talk" news format when it reformatted its newscasts under the ''[[Action News]]'' format in late 1973,<ref>{{cite news|first=Thomas|last=Goldthwaite|title=The TV news tease|url=https://www.newspapers.com/clip/85888605/|work=The Arizona Republic|page=B-11|date=November 7, 1973|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044551/https://www.newspapers.com/clip/85888605/the-tv-news-tease/|url-status=live}}</ref> with longtime anchor Ray Thompson paired alongside Bob Hughes, weatherman Dewey Hopper (last with [[Air America Radio]] affiliate [[KPHX]] and a longtime weather forecaster in Sacramento) and sportscaster Ted Brown. By 1980, the station had moved into a solid second-place position behind [[KSAZ-TV|KOOL-TV]].<ref>{{cite news|url=https://www.newspapers.com/clip/85885705/|work=The Arizona Republic|first=Bud|last=Wilkinson|date=June 2, 1980|title=Aftershocks of ABC affiliate switch in Atlanta might be felt in Phoenix|page=B5|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044551/https://www.newspapers.com/clip/85885705/aftershocks-of-abc-affiliate-switch-in/|url-status=live}}</ref> The "Action News" moniker was dropped in 1986.<ref>{{cite news|url=https://www.newspapers.com/clip/28882279/|work=The Arizona Republic|first=Bud|last=Wilkinson|title=2 stations revamp look of newscasts with new sets|date=April 29, 1986|page=C9|access-date=September 25, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044552/https://www.newspapers.com/clip/28882279/2-stations-revamp-look-of-newscasts/|url-status=live}}</ref> KTVK's rise in the late 1980s and early 1990s led to a more competitive environment.<ref>{{Cite news|url=https://www.newspapers.com/clip/34063179/ktvk-wins-prime-time-gains-in-news/|title=KTVK wins prime time, gains in news ratings|work=The Arizona Republic|first=Bud|last=Wilkinson|date=December 29, 1988|page=C6|access-date=September 25, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044648/https://www.newspapers.com/clip/34063179/ktvk-wins-prime-time-gains-in-news/|url-status=live}}</ref> - -In 1994, KPNX was the only station unaffected by a major realignment of network affiliations in the Phoenix market. This status and the strength of NBC in the late 1990s helped to catalyze a decade of ratings success for channel 12, which put together nearly 50 consecutive ratings book wins at 10 p.m. from 1996 to 2007, even while NBC's ratings faltered toward the end of the run.<ref>{{cite news|url=https://www.nexttv.com/news/phoenix-risingfast-83393|first=Michael|last=Malone|title=Phoenix Rising—Fast|work=Broadcasting & Cable|date=July 27, 2007|access-date=September 24, 2021|archive-date=January 24, 2021|archive-url=https://web.archive.org/web/20210124193101/https://www.nexttv.com/news/phoenix-risingfast-83393|url-status=live}}</ref> It was the first station in the state to convert its news production to high definition in 2006.<ref>{{cite news|url=https://www.twice.com/news/local-news-making-switch-hd-21352|work=TWICE|first=Allison|last=Romano|title=Local News Is Making Switch To HD|date=January 3, 2007|access-date=September 24, 2021|archive-date=January 17, 2021|archive-url=https://web.archive.org/web/20210117010131/https://www.twice.com/news/local-news-making-switch-hd-21352|url-status=live}}</ref> - -Channel 12 began using a helicopter in 1978; it was the market's second, and it was piloted by Jerry Foster, who was hired from KOOL-TV.<ref>{{cite news|url=https://www.newspapers.com/clip/85921903/|title=Big names, helicopters aid in TV news battle|page=A-2|date=August 26, 1979|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044552/https://www.newspapers.com/clip/85921903/big-names-helicopters-aid-in-tv-news/|url-status=live}}</ref> "Sky 12" was frequently called upon for search and rescue missions,<ref>{{cite news|url=https://www.newspapers.com/clip/85923588/jerry-foster-to-the-rescue-institution/|work=The Arizona Republic|first=Dick|last=Rose|title=Jerry Foster to the rescue: 'Institution' saves family of 3|page=B2|date=August 30, 1981|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044553/https://www.newspapers.com/clip/85923588/jerry-foster-to-the-rescue/|url-status=live}}</ref><ref>{{cite news|url=https://www.newspapers.com/clip/85923665/wrecked-plane-is-found-near-sedona-4-ar/|title=Wrecked plane is found near Sedona; 4 are dead|first=John|last=Schroeder|work=The Arizona Republic|date=February 14, 1982|page=A8|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044613/https://www.newspapers.com/clip/85923665/wrecked-plane-is-found-near-sedona-4/|url-status=live}}</ref> and Foster received a [[Harmon Trophy]] in 1981.{{r|harmon}} He left KPNX in 1988<ref>{{cite news|title=Jerry Foster to give up controls of TV copter|date=July 1, 1988|first=Bud|last=Wilkinson|work=The Arizona Republic|url=https://www.newspapers.com/clip/85923781/jerry-foster-to-give-up-controls-of-tv-c/|page=E14|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044553/https://www.newspapers.com/clip/85923781/jerry-foster-to-give-up-controls-of-tv/|url-status=live}}</ref> and later worked at KTVK, his career ending when he was indicted on methamphetamine charges in 1996.<ref name="harmon">{{cite news|url=https://www.azcentral.com/story/life/az-narratives/2014/03/06/phoenix-tv-star-jerry-foster/6138343/|work=The Arizona Republic|date=March 6, 2014|first=Richard|last=Ruelas|title=Phoenix TV's Jerry Foster fell from grace|access-date=September 25, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044553/https://www.azcentral.com/story/life/az-narratives/2014/03/06/phoenix-tv-star-jerry-foster/6138343/|url-status=live}}</ref> On March 1, 2009, KPNX began to share a news helicopter operated by Helicopters Inc., as part of an agreement with KPHO-TV and KTVK; the helicopter was named "News Chopper 20", as a combination of the channel numbers of the three stations (3, 5 and 12).<ref>{{cite news|url=http://www.azcentral.com/community/phoenix/articles/2009/02/24/20090224abrk-helicoptersharing.html|title=3 TV stations to share helicopter to cover the news|date=February 24, 2009|work=AZCentral.com|publisher=Belo|access-date=February 25, 2009|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044554/https://help.azcentral.com/|url-status=live}}</ref><ref>{{cite news|url=http://www.kpho.com/news/18791257/detail.html#-|title=Channels 5, 3, 12 To Share Chopper|date=February 25, 2009|work=KPHO|access-date=February 25, 2009|archive-date=June 14, 2011|archive-url=https://web.archive.org/web/20110614065823/http://www.kpho.com/news/18791257/detail.html#-|url-status=live}}</ref><ref>{{cite news|url=http://news.ktar.com/?nid=6&sid=1092013|title=Phoenix TV stations to share news helicopter|date=February 25, 2009|work=KTAR|access-date=February 26, 2009|url-status=dead|archive-url=https://web.archive.org/web/20090220223234/http://news.ktar.com/?nid=6|archive-date=February 20, 2009}}</ref> All four Phoenix television newsrooms now share a helicopter.<ref name="royale">{{Cite news |last=Malone |first=Michael |date=February 15, 2021 |title=Local News Battle Royale in Phoenix |language=en |work=Broadcasting & Cable |url=https://www.nexttv.com/features/local-news-battle-royale-in-phoenix |url-status=live |access-date=March 17, 2021 |archive-url=https://web.archive.org/web/20210303190327/https://www.nexttv.com/features/local-news-battle-royale-in-phoenix |archive-date=March 3, 2021}}</ref> - -===Sports programming=== -Karl Eller, who owned the company that became Combined Communications, was also one of the original founding owners of the city's first major professional sports team, the [[National Basketball Association|NBA]]'s [[Phoenix Suns]]. Channel 12 carried Suns games from the team's [[1968–69 Phoenix Suns season|1968 inception]]<ref>{{cite news|url=https://www.newspapers.com/clip/85887809/radio-some-television-ktar-to-air-suns/|title=KTAR to Air Suns|page=67|date=June 27, 1968|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044553/https://www.newspapers.com/clip/85887809/radio-some-television-ktar-to-air-suns/|url-status=live}}</ref> until 1973; KPHO-TV aired the Suns for six seasons until they returned to KPNX from 1979<ref>{{Cite news|url=https://www.newspapers.com/clip/85888379/suns-sign-5-year-pact-with-ktar-tv/|title=Suns sign 5-year pact with KTAR-TV|date=May 14, 1979|page=D-4|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044621/https://www.newspapers.com/clip/85888379/suns-sign-5-year-pact-with-ktar-tv/|url-status=live}}</ref> to 1985, when the game telecasts moved to then-[[Independent station (North America)|independent station]] [[KNXV-TV]].<ref>{{cite news|url=https://www.newspapers.com/clip/85888162/channel-15-phoenix-suns-agree-on-pact-f/|title=Channel 15, Phoenix Suns agree on pact for '85-86 season|first=Bud|last=Wilkinson|work=The Arizona Republic|page=F12|date=May 24, 1985|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044555/https://www.newspapers.com/clip/85888162/channel-15-phoenix-suns-agree-on-pact/|url-status=live}}</ref> - -In 2017, KPNX acquired the rights to preseason games of the [[Arizona Cardinals]] and also began airing team-oriented programming.<ref>{{Cite news|url=https://www.revengeofthebirds.com/2017/7/19/15999582/arizona-cardinals-preseason-games-to-be-available-on-nbc-locally|title=Arizona Cardinals preseason games to be available on NBC locally|work=Revenge of the Birds (SB Nation)|access-date=August 15, 2017|archive-date=August 15, 2017|archive-url=https://web.archive.org/web/20170815235736/https://www.revengeofthebirds.com/2017/7/19/15999582/arizona-cardinals-preseason-games-to-be-available-on-nbc-locally|url-status=live}}</ref> - -===Notable former on-air staff=== -* [[Pat Finn (game show host)|Pat Finn]] – host of ''Finn & Friends'' (1989–1990; later host of ''[[The Joker's Wild]]'', ''[[Shop 'til You Drop]]'' and [[California State Lottery|California Lottery]]'s ''[[The Big Spin]]'')<ref>{{Cite news|url=https://www.newspapers.com/clip/35699614/|title='NewsCenter 10' retains top rating in Valley market|date=December 19, 1985|page=F5|first=Bud|last=Wilkinson|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044555/https://www.newspapers.com/clip/35699614/newscenter-10-retains-top-rating-in/|url-status=live}}</ref> -* [[Jineane Ford]] – anchor (1991–2007; most recently anchored ''Arizona Midday'', still fills in as ''Arizona Midday'' anchor)<ref>{{cite web |author=R. Cordova |url=http://www.azcentral.com/arizonarepublic/arizonaliving/articles/1228jineane1228.html |title=Signing off |work=Arizona Republic |date=December 28, 2006 |access-date=January 3, 2007 |archive-date=September 25, 2021 |archive-url=https://web.archive.org/web/20210925044719/https://help.azcentral.com/ |url-status=live }}</ref> -* [[Mike Hambrick]] – anchor (1978–1979)<ref name="hambrick">{{cite news|url=https://www.newspapers.com/clip/85885625/kool-newscaster-kent-dana-to-replace-ham/|date=August 10, 1979|title=KOOL newscaster Kent Dana to replace Hambrick at KPNX|first=Hardy|last=Price|work=The Arizona Republic|page=D-6|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044556/https://www.newspapers.com/clip/85885625/kool-newscaster-kent-dana-to-replace/|url-status=live}}</ref> -* [[Sean McLaughlin (meteorologist)|Sean McLaughlin]] – chief meteorologist (1992–2004; moved at first to [[MSNBC]]/[[NBC Weather Plus]], now primary anchor at [[KPHO-TV]])<ref>{{Cite news|url=https://www.newspapers.com/clip/85942459/sean-mclaughlin-to-leave-channel-12-to-w/|first=Bill|last=Goodykoontz|page=B1|work=The Arizona Republic|title=Sean McLaughlin to leave Channel 12 to work for MSNBC|date=May 25, 2004|access-date=September 25, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044556/https://www.newspapers.com/clip/85942459/sean-mclaughlin-to-leave-channel-12-to/|url-status=live}}</ref> -* [[Fred Roggin]] – sports anchor (late 1970s; now at [[KNBC]] in [[Los Angeles]])<ref>{{Cite news|url=https://www.newspapers.com/clip/85890031/travolta-turns-fight-fan/|page=C-2|date=May 16, 1979|first=Hardy|last=Price|title=Travolta turns fight fan|work=The Arizona Republic|access-date=September 24, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044556/https://www.newspapers.com/clip/85890031/travolta-turns-fight-fan/|url-status=live}}</ref> -* [[Ric Romero]] – investigative reporter (1980s, later [[KABC-TV]], now retired)<ref>{{cite web|url=http://abclocal.go.com/kabc/bio?section=resources/inside_station/newsteam&id=5744031 |title=Ric Romero bio |publisher=[[KABC-TV]] |access-date=March 12, 2013 |url-status=dead |archive-url=https://web.archive.org/web/20110823103916/http://abclocal.go.com/kabc/bio?section=resources%2Finside_station%2Fnewsteam&id=5744031 |archive-date=August 23, 2011 }}</ref> -* [[Mary Kim Titla]] – reporter (1993–2005; publisher of Native Youth Magazine online; unsuccessfully sought Congressional seat in 2008)<ref>{{cite web |url=http://www.gallupindependent.com/2007/may/050907jch_kimtitlaazcong.html |title=Mary Kim Titla to run for Arizona Congress |author=John Christian Hopkins |work=Gallup Independent |date=May 9, 2007 |access-date=December 25, 2007 |archive-date=October 5, 2007 |archive-url=https://web.archive.org/web/20071005112608/http://www.gallupindependent.com/2007/may/050907jch_kimtitlaazcong.html |url-status=live }}</ref> -* [[Rick DeBruhl]] – reporter (1978–2009; formerly a motorsports announcer with [[ESPN]], currently working for [[Motor Trend Group]])<ref>{{cite news|url=https://www.azcentral.com/story/sports/motor/2016/05/26/100th-indianapolis-500-arizona-connections/84979502/|first=Michael|last=Knight|title=Arizona connections to the 100th Indy 500|work=The Arizona Republic|date=May 26, 2016|access-date=September 25, 2021|archive-date=September 25, 2021|archive-url=https://web.archive.org/web/20210925044555/https://www.azcentral.com/story/sports/motor/2016/05/26/100th-indianapolis-500-arizona-connections/84979502/|url-status=live}}</ref> - -==Technical information== -===Subchannels=== -The station's digital signal is [[Multiplex (TV)|multiplexed]]: -{| class="wikitable" -|- -! scope="col" | [[Digital subchannel#United States|Channel]] -! scope="col" | [[Display resolution|Video]] -! scope="col" | [[Aspect ratio (image)|Aspect]] -! scope="col" | Short name -! scope="col" | Programming<ref name="re">{{Cite web |title=RabbitEars TV Query for KPNX |url=http://www.rabbitears.info/market.php?request=station_search&callsign=KPNX#station |url-status=live |website=rabbitears.info |archive-date=March 16, 2016 |archive-url=https://web.archive.org/web/20160316164006/http://www.rabbitears.info/market.php?request=station_search&callsign=KPNX#station }}</ref> -|- -! scope="row" | 12.1 -| [[1080i]] || rowspan="7" | [[16:9]] || KPNX-HD || Main KPNX programming / [[NBC]] -|- -! scope="row" | 12.2 -| rowspan=6|[[480i]] || Weather || [[Shop LC]] -|- -! scope="row" | 12.3 -| Crime || [[True Crime Network]] -|- -! scope="row" | 12.4 -| Quest || [[Quest (American TV network)|Quest]] -|- -! scope="row" | 12.5 -| Twist || [[Twist (TV network)|Twist]] -|- style="background-color:#cee0f2" -! scope="row" | [[KFPH-DT|35.3]] -| getTV || [[getTV]] ([[KFPH-DT|KFPH-CD]]) -|- style="background-color:#cee0f2" -! scope="row" | [[KASW|61.2]] -| Grit || [[Grit (TV network)|Grit]] (KASW) -|} --> diff --git a/pages/now.md b/pages/now.md new file mode 100644 index 0000000..1713514 --- /dev/null +++ b/pages/now.md @@ -0,0 +1,16 @@ +# Now + +Learning Rust. Trying to understand the hype. + +Working on [murder](https://github.com/LMBishop/murder), a port of the classic +gmod game mode by Mechanical Mind for [s&box](https://sbox.facepunch.com/). + +Trying to expand the number of things I can Actually Cook. + +Enjoying the relative freedom and lack of stress during the few weeks I have +after the end of semester, and before my year long placement. + +<br> + +*I update this page every so often with what I'm up to. Its contents are +ephemeral, I do not keep a log of them.* diff --git a/pages/spotify.html b/pages/spotify.html new file mode 100644 index 0000000..b0110ba --- /dev/null +++ b/pages/spotify.html @@ -0,0 +1,21 @@ +--- +scripts: + - /scripts/spotify.js +--- +<h1>Spotify</h1> +<b> + This is a work in progress once I figure out Spotify's tedious + authentication flow. +</b> +<div id="music-player"> + <span id="song-title"></span> + <span id="song-artist"></span> + <span id="song-album"></span> + <br> + <span id="connection-status"></span> +</div> +<br> +<em> + This page shows what I'm currently listening to on Spotify. + It requires JavaScript to work. +</em> diff --git a/pages/template/blog.wiki b/pages/template/blog.wiki deleted file mode 100644 index 92b60bd..0000000 --- a/pages/template/blog.wiki +++ /dev/null @@ -1,2 +0,0 @@ -<span>Blog post • {{{date}}}</span><br> -<hr> diff --git a/pages/template/incomplete.wiki b/pages/template/incomplete.wiki deleted file mode 100644 index 126992f..0000000 --- a/pages/template/incomplete.wiki +++ /dev/null @@ -1 +0,0 @@ -<div class='box'>🛠 This page is a work in progress! Some information may be missing, incomplete, or out of date.</div> diff --git a/pages/template/sourcecode.wiki b/pages/template/sourcecode.wiki deleted file mode 100644 index 28ea390..0000000 --- a/pages/template/sourcecode.wiki +++ /dev/null @@ -1 +0,0 @@ -: <small>''Source code available at {{{1}}}''</small> diff --git a/pages/test.wiki b/pages/test.wiki deleted file mode 100644 index efb04e8..0000000 --- a/pages/test.wiki +++ /dev/null @@ -1,9 +0,0 @@ -{{NAVBARSORTORDER:4}} -{{DISPLAYTITLE:Test page}} - -This is a test page -* somewhere -* somewhere else - -== Heading == -Content diff --git a/static/css/blog.css b/static/css/blog.css new file mode 100644 index 0000000..2b52dff --- /dev/null +++ b/static/css/blog.css @@ -0,0 +1,63 @@ +#title { + margin: 0 0 3rem 0; +} + +#title > h1 { + margin-bottom: 0.4rem; +} + +figure { + text-align: center; + font-style: italic; + font-size: 0.8rem; +} + +figure img { + max-width: 100%; + border-radius: 10px; +} + +.blog-post, .blog-post-header { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.blog-post-title { + flex-grow: 1; +} + +.blog-post { + padding: 0.2rem 0; +} + +.blog-post-header { + padding: 0.5rem 0; + color: var(--color-text-muted); +} + +.blog-post-header, .blog-post:last-of-type { + border-bottom: 1px dotted var(--color-text-muted); +} + +#content { + position: relative; +} + +#back-header { + position: relative; + font-size: 1rem; + top: -2rem; + font-family: var(--font-monospace); + width: 700px; +} + +#back-container { + position: absolute; + height: 0; + width: 0; +} + +#back-footer { + margin-top: 4rem; +} diff --git a/static/css/contact.css b/static/css/contact.css new file mode 100644 index 0000000..42bd8d9 --- /dev/null +++ b/static/css/contact.css @@ -0,0 +1,24 @@ +.grid-outer { + width: 100%; + display: flex; + flex-direction: column; + gap: 0; +} + +.grid-inner { + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; +} + +.grid-title { + flex-basis: 250px; + font-family: var(--font-monospace); + color: var(--color-text-muted); + text-align: right; +} + +.grid-content { + width: 100% +} diff --git a/static/css/globalstyles.css b/static/css/globalstyles.css index 1f22f5e..9fbb974 100644 --- a/static/css/globalstyles.css +++ b/static/css/globalstyles.css @@ -1,113 +1,81 @@ -@import url('https://fonts.googleapis.com/css2?family=Cousine:ital,wght@0,400;0,700;1,400;1,700&display=swap'); - -.website-name { - font-size: 10px; - font-weight: 700; - line-height: 1.2; - color: #ddd; - text-shadow: 0px 1px 10px #9876aa; -} +@import 'variables.css'; html, body { border: 0; margin: 0; background-color: #111; color: #ddd; - font-family: 'Cousine', monospace, sans-serif; - line-height: 1.3; + line-height: 1.6; + font-size: 18px; + font-family: var(--font-sans-serif); } -h1, h2, h3, h4, h5, h6 { - color: #cc7832 -} - -.code-block { - background-color: #222; - border: solid 1px #333; - padding: 10px; -} - -#navbar { - background-color: #222; - width: 100%; +#main-container { display: flex; flex-direction: row; - justify-content: flex-start; - align-items: stretch; - gap: 10px; + width: 100%; + max-height: calc(100vh - 1rem); } -.navbar-element { - background-color: #222; +#navbar { + padding: 2rem 1rem 1rem 6rem; display: flex; - height: 30px; - text-align: center; - line-height: 30px; - padding: 10px; -} - -.navbar-element:hover { - background-color: #888; - transition: 0.2s; - cursor: pointer; + flex-direction: column; + align-items: end; + gap: 0.4rem; + font-size: 0.9em; + font-family: var(--font-monospace); } -.navbar-element > a { - color: #fff; - text-decoration: none; +@media only screen and (max-width: 1000px) { + #navbar { + padding-left: 1rem; + } } -.navbar-element > .highlight { - color: #ffc66d; +a { + color: var(--color-text-link); + text-decoration: underline dotted var(--color-text-link); } -#main-container { - max-width: 1200px; - margin: 0 auto; +a:hover { + background-color: var(--color-text-link-bg); } #content-container { - box-shadow: 0px 0px 15px 10px rgba(152,118,170,0.05); + overflow: scroll; + max-height: 100%; + flex-grow: 1; + scrollbar-color: var(--color-scrollbar) var(--color-background); } -#content { - padding: 20px; - max-width: 1200px; - background-color: #2b2b2b; - margin: 0 auto; +::-webkit-scrollbar { + width: 4px; + background: var(--color-background); } -a { - color: #9876aa; - text-decoration: underline; +::-webkit-scrollbar-thumb { + background: var(--color-scrollbar); + border-radius: 2px; } -.highlight { - color: #ffc66d; +::-webkit-scrollbar-corner { + background: none; } -.footer { - font-size: 10px; -} - -.redlink { - color: #ff4136; +#content { + margin: 8rem auto 6rem auto; + width: 700px; } -.box { - border: solid 1px #fff; - padding: 10px; - background-color: rgba(0, 0, 0, 0.1); +h1, h2, h3, h4 { + font-family: var(--font-monospace); } -.box-red { - border: solid 1px rgba(255, 0, 0, 0.5); - padding: 10px; - background-color: rgba(255, 0, 0, 0.2); +.monospace { + font-family: var(--font-monospace); } -table, th, td { - border: 1px solid rgb(200, 200, 200); - border-collapse: collapse; - padding: 10px; +code { + font-family: var(--font-monospace); } diff --git a/static/css/home.css b/static/css/home.css new file mode 100644 index 0000000..4920ee3 --- /dev/null +++ b/static/css/home.css @@ -0,0 +1,27 @@ +.grid-outer { + width: 100%; + display: flex; + flex-direction: column; + gap: 4rem; +} + +.grid-inner { + display: flex; + flex-direction: row; + gap: 1rem; +} + +.grid-title { + flex-basis: 50px; + font-family: var(--font-monospace); + color: var(--color-text-muted); + text-align: right; +} + +.grid-content { + width: 100% +} + +.spacer { + height: 3rem; +} diff --git a/static/css/variables.css b/static/css/variables.css new file mode 100644 index 0000000..123ee30 --- /dev/null +++ b/static/css/variables.css @@ -0,0 +1,21 @@ +@import url('https://fonts.googleapis.com/css2?family=Chivo:wght@200&family=Hind&family=Noto+Sans&display=swap'); + +@font-face { + font-family: 'Iosevka Web'; + src: url(/fonts/iosevka-medium.woff2) format('woff2'); + font-stretch: condensed; + font-display: swap; +} + +:root { + --font-sans-serif: 'Hind', 'Noto Sans', 'Segoe UI', 'Chivo', sans-serif; + --font-monospace: 'Iosevka Web', monospace; + + --color-background: #111; + --color-text: #ddd; + --color-text-muted: #aaa; + --color-text-link: #ff851b; + --color-text-link-bg: rgba(255, 133, 27, 0.2); + + --color-scrollbar: rgba(255, 255, 255, 0.4); +} diff --git a/static/fonts/iosevka-medium.woff2 b/static/fonts/iosevka-medium.woff2 Binary files differnew file mode 100644 index 0000000..e8d53cd --- /dev/null +++ b/static/fonts/iosevka-medium.woff2 diff --git a/static/image/questcompass2-256.png b/static/image/questcompass2-256.png Binary files differdeleted file mode 100644 index 0d0194f..0000000 --- a/static/image/questcompass2-256.png +++ /dev/null diff --git a/static/images/steam-deck-arch.jpg b/static/images/steam-deck-arch.jpg Binary files differnew file mode 100644 index 0000000..35dbb42 --- /dev/null +++ b/static/images/steam-deck-arch.jpg diff --git a/static/images/steam-deck-gparted.jpg b/static/images/steam-deck-gparted.jpg Binary files differnew file mode 100644 index 0000000..70b72af --- /dev/null +++ b/static/images/steam-deck-gparted.jpg diff --git a/static/images/steam-deck-partitions.jpg b/static/images/steam-deck-partitions.jpg Binary files differnew file mode 100644 index 0000000..87d3d9e --- /dev/null +++ b/static/images/steam-deck-partitions.jpg diff --git a/static/scripts/purge.js b/static/scripts/purge.js deleted file mode 100644 index 5ee34f0..0000000 --- a/static/scripts/purge.js +++ /dev/null @@ -1,15 +0,0 @@ -$(() => { - $('#confirm').click(() => { - let page = $('#confirm').data('page'); - $.ajax({ - type: 'GET', - url: `/special/purge/${page}/confirm`, - success: () => { - $('#response').html('<div class=\'box\'>Successfully purged page.</div>'); - }, - error: () => { - $('#response').html('<div class=\'box\'>Could not purge page. Try again later.</div>'); - } - }); - }); -}); diff --git a/static/scripts/rebuild.js b/static/scripts/rebuild.js deleted file mode 100644 index 8fd0e2e..0000000 --- a/static/scripts/rebuild.js +++ /dev/null @@ -1,14 +0,0 @@ -$(() => { - $('#confirm').click(() => { - $.ajax({ - type: 'GET', - url: `/special/rebuild/confirm`, - success: () => { - $('#response').html('<div class=\'box\'>Successfully rebuilt page directory.</div>'); - }, - error: () => { - $('#response').html('<div class=\'box\'>Could not rebuild page directory. Try again later.</div>'); - } - }); - }); -}); diff --git a/static/scripts/spotify.js b/static/scripts/spotify.js new file mode 100644 index 0000000..e6551a8 --- /dev/null +++ b/static/scripts/spotify.js @@ -0,0 +1,12 @@ +const connectWebsocket = () => { + document.getElementById('connection-status').innerHTML = "Connecting..."; + let url = new URL(window.location.href); + url.protocol = url.protocol.replace('http', 'ws'); + const socket = new WebSocket(url); + socket.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log(data); + } +} + +document.addEventListener("DOMContentLoaded", connectWebsocket); diff --git a/tsconfig.json b/tsconfig.json index 4477ab2..b37cc35 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "outDir": "./build", "allowJs": true, - "target": "es5", + "target": "es2020", "module": "es2020", "moduleResolution": "node", "allowSyntheticDefaultImports": true, diff --git a/views/blog.ejs b/views/blog.ejs new file mode 100644 index 0000000..238c935 --- /dev/null +++ b/views/blog.ejs @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<head> + <%- include('partials/head') %> + <link rel="stylesheet" href="/css/blog.css"> +</head> +<body> + <div id="main-container"> + <%- include('partials/navbar') %> + <div id="content-container"> + <% if (index) { %> + <div id="content"> + <h1>Blog</h1> + <div class="blog-post-header"> + <span class="blog-post-title monospace">title</span> + <span class="monospace">date</span> + </div> + <% for (const blog of blogs) { %> + <a class="blog-post" href="/<%= blog.route %>"> + <span class="blog-post-title"> + <%= blog.metadata.title %> + </span> + <span class="blog-post-date monospace"> + <%= blog.metadata.date?.toISOString().substring(0, 10) %> + </span> + </a> + <% } %> + </div> + <% } else { %> + <div id="content"> + <div id="back-container"> + <div id="back-header"> + <a href="/blog" class="monospace">← blog posts</a> + </div> + </div> + <div id="title" class="monospace"> + <h1><%= page.metadata.title %></h1> + <span><%= page.metadata.date?.toDateString() %></span> + </div> + <%- page.html %> + <div id="back-footer"> + <a href="/blog" class="monospace">← blog posts</a> + </div> + </div> + <% } %> + </div> + </div> +</body> +</html> diff --git a/views/error.ejs b/views/error.ejs index 88e1a27..838da5b 100644 --- a/views/error.ejs +++ b/views/error.ejs @@ -1,17 +1,15 @@ <!DOCTYPE html> <html> <head> - <title>Error: <%= code %></title> + <title>Page not found</title> <link rel="stylesheet" href="/css/globalstyles.css"> </head> <body> <div id="main-container"> - <%- include('partials/header') %> + <%- include('partials/navbar') %> <div id="content-container"> - <%- include('partials/navbar') %> <div id="content"> - <h1>An error occurred (<%= code %>)</h1> - <p>Go <a href="/">home</a>?</p> + <h1>Page not found</h1> </div> </div> </div> diff --git a/views/index.ejs b/views/index.ejs index f47b830..e638ad4 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -1,16 +1,14 @@ <!DOCTYPE html> <html> <head> - <title><%= title %></title> - <link rel="stylesheet" href="/css/globalstyles.css"> + <%- include('partials/head') %> </head> <body> <div id="main-container"> - <%- include('partials/header') %> + <%- include('partials/navbar') %> <div id="content-container"> - <%- include('partials/navbar') %> <div id="content"> - <%- page %> + <%- content %> </div> </div> </div> diff --git a/views/page.ejs b/views/page.ejs deleted file mode 100644 index 2bd4262..0000000 --- a/views/page.ejs +++ /dev/null @@ -1,20 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title><%= title %></title> - <link rel="stylesheet" href="/css/globalstyles.css"> -</head> -<body> - <div id="main-container"> - <%- include('partials/header') %> - <div id="content-container"> - <%- include('partials/navbar') %> - <div id="content"> - <%- content %> - <hr> - <span class=footer><a href="https://github.com/LMBishop/website">GitHub</a> | <a href="/<%= path %>.wiki">View raw</a> | Page built: <%= buildTime %> | <a href="/special/info/<%= path %>">Page info</a></span> - </div> - </div> - </div> -</body> -</html> diff --git a/views/pageinfo.ejs b/views/pageinfo.ejs deleted file mode 100644 index c0ba59d..0000000 --- a/views/pageinfo.ejs +++ /dev/null @@ -1,83 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>Page information for <%= standardName %></title> - <link rel="stylesheet" href="/css/globalstyles.css"> -</head> -<body> - <div id="main-container"> - <%- include('partials/header') %> - <div id="content-container"> - <%- include('partials/navbar') %> - <div id="content"> - <h1>Information for page <%= standardName %></h1> - <table> - <tr> - <td>Standard name</td> - <td><%= standardName %></td> - </tr> - <tr> - <td>Display title</td> - <td><%= displayTitle ?? '-' %></td> - </tr> - <tr> - <td>Last build</td> - <td><%- (buildTime != 0) ? new Date(buildTime) : '-' %></td> - </tr> - <tr> - <td>Primary</td> - <td><%= primary ?? false %></td> - </tr> - <tr> - <td>Show title</td> - <td><%= showTitle ?? false %></td> - </tr> - <tr> - <td>Sort order</td> - <td><%- sortOrder ?? '-' %></td> - </tr> - <tr> - <td>Dependencies</td> - <td> - <% if (dependencies.size === 0) { %> - <%- '-' %> - <% } else { %> - <% dependencies.forEach(e => { %> - <%- `${e}<br>` %> - <% }); %> - <%}%> - </td> - </tr> - <tr> - <td>Pages which depend on this page</td> - <td> - <% if (dependents.size === 0) { %> - <%- '-' %> - <% } else { %> - <% dependents.forEach(e => { %> - <%- `${e}<br>` %> - <% }); %> - <%}%> - </td> - </tr> - <tr> - <td>Errors</td> - <td> - <% if (errors.length === 0) { %> - <%- '-' %> - <% } else { %> - <% errors.forEach(e => { %> - <%- `${e.identifier}: ${e.message}<br>` %> - <% }); %> - <%}%> - </td> - </tr> - </table> - <p><a href="/special/purge/<%= standardName %>">Purge this page?</a></p> - <hr> - <span class=footer><a href="https://github.com/LMBishop/website">GitHub</a> | <a href="/<%= path %>">View original</a></span> - </div> - </div> - </div> -</body> -</html> diff --git a/views/partials/head.ejs b/views/partials/head.ejs new file mode 100644 index 0000000..d66fe97 --- /dev/null +++ b/views/partials/head.ejs @@ -0,0 +1,14 @@ +<title>Leonardo Bishop</title> +<link rel="stylesheet" href="/css/globalstyles.css"> + +<% if (typeof stylesheets == 'object') { %> + <% for (const sheet of stylesheets) { %> +<link rel="stylesheet" href="<%= sheet %>"> + <% } %> +<% } %> + +<% if (typeof scripts == 'object') { %> + <% for (const script of scripts) { %> +<script src="<%= script %>"></script> + <% } %> +<% } %> diff --git a/views/partials/header.ejs b/views/partials/header.ejs deleted file mode 100644 index bd11ce0..0000000 --- a/views/partials/header.ejs +++ /dev/null @@ -1,12 +0,0 @@ -<pre class="website-name"> - -<!-- -██╗ ███╗ ███╗██████╗ ██╗███████╗██╗ ██╗ ██████╗ ██████╗ -██║ ████╗ ████║██╔══██╗██║██╔════╝██║ ██║██╔═══██╗██╔══██╗ -██║ ██╔████╔██║██████╔╝██║███████╗███████║██║ ██║██████╔╝ -██║ ██║╚██╔╝██║██╔══██╗██║╚════██║██╔══██║██║ ██║██╔═══╝ -███████╗██║ ╚═╝ ██║██████╔╝██║███████║██║ ██║╚██████╔╝██║ -╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ - - --> -</pre> diff --git a/views/partials/navbar.ejs b/views/partials/navbar.ejs index ff0c84d..3dbeb6a 100644 --- a/views/partials/navbar.ejs +++ b/views/partials/navbar.ejs @@ -1,3 +1,8 @@ <div id="navbar"> - <%- navbar %> + <h2>Leonardo Bishop</h2> + <a href="/">home</a> + <a href="/now">now</a> + <a href="/blog">blog</a> + <!-- <a href="/spotify">spotify</a> --> + <a href="/contact">contact</a> </div> diff --git a/views/purge.ejs b/views/purge.ejs deleted file mode 100644 index df48073..0000000 --- a/views/purge.ejs +++ /dev/null @@ -1,26 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>Purge page</title> - <link rel="stylesheet" href="/css/globalstyles.css"> - <script src="https://code.jquery.com/jquery-3.6.0.min.js" ntegrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> - <script src="/scripts/purge.js"></script> -</head> -<body> - <div id="main-container"> - <%- include('partials/header') %> - <div id="content-container"> - <%- include('partials/navbar') %> - <div id="content"> - <h1>Purge page</h1> - <span id="response"></span> - <p>Are you sure you wish to purge the page <span class="highlight"><%= page %></span>?</p> - <p>The last build time for this page was <span class="highlight"><%= buildTime %></span> (<span class="highlight"><%= buildTimeRelative %></span> minutes ago).</p> - <button id="confirm" data-page="<%= page %>">Confirm</button> - <hr> - <span class=footer><a href="https://github.com/LMBishop/website">GitHub</a> | <a href="/<%= page %>">View original</a></span> - </div> - </div> - </div> -</body> -</html> diff --git a/views/rebuild.ejs b/views/rebuild.ejs deleted file mode 100644 index 9f9cfaa..0000000 --- a/views/rebuild.ejs +++ /dev/null @@ -1,23 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>Rebuild</title> - <link rel="stylesheet" href="/css/globalstyles.css"> - <script src="https://code.jquery.com/jquery-3.6.0.min.js" ntegrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> - <script src="/scripts/rebuild.js"></script> -</head> -<body> - <div id="main-container"> - <%- include('partials/header') %> - <div id="content-container"> - <%- include('partials/navbar') %> - <div id="content"> - <h1>Rebuild</h1> - <span id="response"></span> - <p>Are you sure you wish to rebuild the page directory?</p> - <button id="confirm">Confirm</button> - </div> - </div> - </div> -</body> -</html> |
