Add furhter processing to posts display page

This commit is contained in:
Fred Boniface 2023-08-18 21:47:52 +01:00
parent d761f72248
commit bff88838d3
13 changed files with 334 additions and 86 deletions

146
package-lock.json generated
View File

@ -8,13 +8,17 @@
"name": "fredboniface.co.uk-svelte",
"version": "0.0.1",
"dependencies": {
"mongodb": "^5.7.0"
"marked": "^7.0.3",
"mongodb": "^5.7.0",
"sanitize-html": "^2.11.0"
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.3.1",
"@sveltejs/kit": "^1.20.4",
"@types/node": "^20.5.1",
"@types/sanitize-html": "^2.9.0",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
@ -916,9 +920,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.0.tgz",
"integrity": "sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q=="
"version": "20.5.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz",
"integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg=="
},
"node_modules/@types/pug": {
"version": "2.0.6",
@ -932,6 +936,15 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"node_modules/@types/sanitize-html": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.9.0.tgz",
"integrity": "sha512-4fP/kEcKNj2u39IzrxWYuf/FnCCwwQCpif6wwY6ROUS1EPRIfWJjGkY3HIowY1EX/VbX5e86yq8AAE7UPMgATg==",
"dev": true,
"dependencies": {
"htmlparser2": "^8.0.0"
}
},
"node_modules/@types/semver": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
@ -1674,7 +1687,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -1736,6 +1748,68 @@
"node": ">=6.0.0"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
]
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
@ -1783,7 +1857,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"engines": {
"node": ">=10"
},
@ -2282,6 +2355,24 @@
"node": ">=8"
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@ -2431,6 +2522,14 @@
"node": ">=8"
}
},
"node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-reference": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.1.tgz",
@ -2585,6 +2684,17 @@
"node": ">=12"
}
},
"node_modules/marked": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-7.0.3.tgz",
"integrity": "sha512-ev2uM40p0zQ/GbvqotfKcSWEa59fJwluGZj5dcaUOwDRrB1F3dncdXy8NWUApk4fi8atU3kTBOwjyjZ0ud0dxw==",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@ -2777,7 +2887,6 @@
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [
{
"type": "github",
@ -2880,6 +2989,11 @@
"node": ">=6"
}
},
"node_modules/parse-srcset": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -2951,8 +3065,7 @@
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@ -2993,7 +3106,6 @@
"version": "8.4.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
"integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -3344,6 +3456,19 @@
"rimraf": "bin.js"
}
},
"node_modules/sanitize-html": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.11.0.tgz",
"integrity": "sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==",
"dependencies": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
"htmlparser2": "^8.0.0",
"is-plain-object": "^5.0.0",
"parse-srcset": "^1.0.2",
"postcss": "^8.3.11"
}
},
"node_modules/saslprep": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
@ -3468,7 +3593,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}

View File

@ -19,6 +19,8 @@
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.3.1",
"@sveltejs/kit": "^1.20.4",
"@types/node": "^20.5.1",
"@types/sanitize-html": "^2.9.0",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"eslint": "^8.28.0",
@ -36,6 +38,8 @@
},
"type": "module",
"dependencies": {
"mongodb": "^5.7.0"
"marked": "^7.0.3",
"mongodb": "^5.7.0",
"sanitize-html": "^2.11.0"
}
}

View File

@ -6,7 +6,7 @@ let dbClient: MongoClient | null = null;
export async function mongoConnect() {
if (!dbClient) {
dbClient = await MongoClient.connect(MongoUri)
dbClient = await MongoClient.connect(MongoUri);
}
return dbClient
return dbClient;
}

View File

@ -1,8 +1,8 @@
<script lang="ts">
import type { ArticleSummary } from "./types";
import FullWidthContent from "$lib/content-boxes/FullWidthContent.svelte";
import type { ArticleSummary } from './types';
import FullWidthContent from '$lib/content-boxes/FullWidthContent.svelte';
export let article: ArticleSummary
export let article: ArticleSummary;
</script>
<FullWidthContent>

View File

@ -1,8 +1,8 @@
<script lang="ts">
import type { Project } from "./types";
import Logos from "$lib/language-logos/Logos.svelte";
import type { Project } from './types';
import Logos from '$lib/language-logos/Logos.svelte';
export let project: Project
export let project: Project;
</script>
<article>

View File

@ -35,15 +35,12 @@
<section id="meta-links">
<div id="projects" class="col">
<h1>Projects</h1>
</div>
<div id="posts" class="col">
<h1>Posts</h1>
</div>
<div id="tags" class="col">
<h1>Tags</h1>
</div>
</section>

View File

@ -1,16 +1,57 @@
import { mongoConnect } from "$lib/database/mongo";
import { mongoConnect } from '$lib/database/mongo';
import { marked } from 'marked';
import * as path from 'path';
import sanitizeHtml from 'sanitize-html';
import type { Article } from '$lib/posts/types';
import type { _Renderer } from 'Renderer';
export async function load({ params }) {
const slug = params.slug;
const db = await mongoConnect()
const db = await mongoConnect();
const query = {
slug: slug
}
const col = db.db('fredboniface').collection('posts')
const res = col.findOne(query)
const posts = await res
};
const col = db.db('fredboniface').collection('posts');
const res = await col.findOne(query);
// Create the `Article` object here and return that rather than doing it on the page.
return {data: JSON.stringify(posts)}
if (!res) {
throw new Error('Post Not Found');
}
const renderer = new marked.Renderer();
renderer.image = function (href, title, text) {
const imgPath = path.join('/images/posts', res.slug, href);
return `<figure>
<img class="post-inline-image-546543" src="${imgPath}" alt="${text}" title="${title}">
<figcaption>${title}</figcaption>
</figure>`;
};
const post: Article = {
title: res.title,
author: res.author,
pusblished: res.published,
summary: res.summary,
content: renderMarkdown(res.content, renderer),
slug: res.slug,
tags: res.tags,
date: res.date
};
return { data: post };
}
function renderMarkdown(md: string, renderer: _Renderer): string {
const rawHtml = marked(md, { renderer });
console.log(rawHtml)
const sanitizedHtml = sanitizeHtml(rawHtml, {
allowedTags: ['a', 'br', 'code', 'figcaption', 'figure', 'img', 'p', 'pre'],
allowedAttributes: {
'a': ['href'],
'img': ['alt', 'class', 'src', 'title']
}
});
console.log(sanitizedHtml)
return sanitizedHtml
}

View File

@ -1,24 +1,109 @@
<script lang="ts">
import Header from "$lib/header.svelte";
import type { Article } from "$lib/posts/types";
import Header from '$lib/header.svelte';
import type { Article } from '$lib/posts/types';
import { afterUpdate } from 'svelte';
export let data: any;
const post: Article = JSON.parse(data.data)
const post: Article = data.data;
afterUpdate(() => {
const codeBlocks = document.querySelectorAll('article pre');
codeBlocks.forEach((codeBlock) => {
const copyButton = document.createElement('button');
copyButton.textContent = '📋 Copy code';
copyButton.classList.add('article-code-copy-button-32984456');
copyButton.addEventListener('click', () => {
const codeText = codeBlock.textContent || '';
const trimmedCodeText = codeText.replace(copyButton.textContent || '', '');
navigator.clipboard
.writeText(trimmedCodeText)
.then(() => {
console.log('Copied code to clipboard');
})
.catch((error) => {
console.error(
'Error copying code to clipboard, this may be due to privacy settings in your browser'
);
});
});
codeBlock.appendChild(copyButton);
});
});
</script>
<article>
<Header title={post.title} />
<p>{#each post.tags as tag}
<div class="column-container">
<div class="main-column">
<article>
<p>
{#each post.tags as tag}
<span>{tag}</span>
{/each}<br>
{post.date}<br>
{post.author}<br>
{post.content}</p>
{/each}<br />
{post.date.toLocaleDateString()}<br />
Written by: {post.author}
</p>
<content>
{@html post.content}
</content>
</article>
</div>
<div class="side-column">
<p>HELLO FROM COLUMN 2</p>
</div>
</div>
<style>
.column-container {
display: flex;
flex-direction: column;
gap: 20px;
}
@media (min-width: 768px) {
.column-container {
flex-direction: row; /* Switch to row layout for larger screens */
}
.main-column {
flex: 2; /* Take up 2/3 of the available width */
}
.side-column {
flex: 1; /* Take up 1/3 of the available width */
}
}
span {
padding: 5px;
}
:global(article pre) {
position: relative;
text-align: left;
background-color: rgb(46, 46, 46);
color: white;
padding: 15px;
padding-top: 30px;
padding-bottom: 30px;
font-family: 'Courier New', Courier, monospace;
border-radius: 8px;
width: 90%;
max-width: 800px;
margin: auto;
white-space: pre;
overflow: auto;
}
:global(.article-code-copy-button-32984456) {
position: absolute;
top: 10px;
right: 15px;
color: white;
background: none;
border: none;
cursor: pointer;
}
</style>

View File

@ -1,19 +1,16 @@
import { mongoConnect } from "$lib/database/mongo";
import { mongoConnect } from '$lib/database/mongo';
export async function load({ params }) {
const tag = params.tag;
const db = await mongoConnect()
const db = await mongoConnect();
const query = {
tags: {
$in: [
tag
]
$in: [tag]
}
}
const col = db.db('fredboniface').collection('posts')
const res = col.find(query)
const posts = await res.toArray()
};
const col = db.db('fredboniface').collection('posts');
const res = col.find(query);
const posts = await res.toArray();
return {data: JSON.stringify(posts)}
return { data: JSON.stringify(posts) };
}

View File

@ -1,11 +1,11 @@
<script lang="ts">
import PostsSummary from "$lib/posts/PostsSummary.svelte";
import type { ArticleSummary } from "$lib/posts/types.js";
import PostsSummary from '$lib/posts/PostsSummary.svelte';
import type { ArticleSummary } from '$lib/posts/types.js';
export let data;
const posts: ArticleSummary[] = JSON.parse(data.data)
const posts: ArticleSummary[] = JSON.parse(data.data);
console.log("Posts:", posts)
console.log('Posts:', posts);
</script>
<h1>Testing</h1>

View File

@ -19,14 +19,14 @@
/>
<p>
Working full time on the 'iron road', left me wanting a faster way to get the information I
needed. OwlBoard evolved from <a href="/articles/athena">Athena</a> and grew to provide more
information that frontline rail colleagues need.
needed. OwlBoard evolved from <a href="/articles/athena">Athena</a> and grew to provide more information
that frontline rail colleagues need.
</p>
</FullWidthContent>
<FullWidthContent>
<h1><a href="/posts/tag/map-dots">map-dots</a></h1>
<br />
<GitLink url={"https://git.fjla.uk/fred.boniface/map-dots"} />
<GitLink url={'https://git.fjla.uk/fred.boniface/map-dots'} />
<Logos langs={['go', 'py']} plats={[]} />
<p>
I like to collect data, I am just not always sure what to do with that data. map-dots takes in

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB