Merge pull request #148 from sambecker/html-description
Rich about text formatting
This commit is contained in:
commit
57c9b30e35
@ -98,8 +98,8 @@ Application behavior can be changed by configuring the following environment var
|
||||
|
||||
#### Site meta
|
||||
- `NEXT_PUBLIC_SITE_TITLE` (seen in browser tab)
|
||||
- `NEXT_PUBLIC_SITE_DESCRIPTION` (seen in nav, under title)
|
||||
- `NEXT_PUBLIC_SITE_ABOUT` (e.g., seen in grid sidebar)
|
||||
- `NEXT_PUBLIC_SITE_DESCRIPTION` (seen in nav, beneath title)
|
||||
- `NEXT_PUBLIC_SITE_ABOUT` (seen in grid sidebar—accepted rich formatting tags: `<b>`, `<strong>`, `<i>`, `<em>`, `<u>`, `<br>`)
|
||||
|
||||
#### Site behavior
|
||||
- `NEXT_PUBLIC_GRID_HOMEPAGE = 1` shows grid layout on homepage
|
||||
|
||||
17
__tests__/html.test.ts
Normal file
17
__tests__/html.test.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { htmlHasBrParagraphBreaks, safelyParseFormattedHtml } from '@/utility/html';
|
||||
import { parameterize } from '@/utility/string';
|
||||
|
||||
describe('HTML', () => {
|
||||
it('safely parses', () => {
|
||||
expect(safelyParseFormattedHtml('<p>TEXT</p>')).toBe('TEXT');
|
||||
expect(safelyParseFormattedHtml('<b>TEXT</b>')).toBe('<b>TEXT</b>');
|
||||
});
|
||||
it('detects br-style paragraph breaks', () => {
|
||||
expect(htmlHasBrParagraphBreaks('TEXT<br><br>')).toBeTruthy();
|
||||
expect(htmlHasBrParagraphBreaks('TEXT<br /><br />')).toBeTruthy();
|
||||
expect(htmlHasBrParagraphBreaks('TEXT<br><br />')).toBeTruthy();
|
||||
expect(htmlHasBrParagraphBreaks('TEXT')).toBeFalsy();
|
||||
expect(htmlHasBrParagraphBreaks('TEXT<br/>')).toBeFalsy();
|
||||
expect(htmlHasBrParagraphBreaks('TEXT<br />')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@ -23,6 +23,7 @@
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/react": "18.3.8",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/sanitize-html": "^2.13.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@upstash/ratelimit": "^2.0.3",
|
||||
@ -51,6 +52,7 @@
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"sanitize-html": "^2.13.0",
|
||||
"sharp": "^0.33.5",
|
||||
"sonner": "^1.5.0",
|
||||
"swr": "^2.2.5",
|
||||
|
||||
71
pnpm-lock.yaml
generated
71
pnpm-lock.yaml
generated
@ -50,6 +50,9 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: 18.3.0
|
||||
version: 18.3.0
|
||||
'@types/sanitize-html':
|
||||
specifier: ^2.13.0
|
||||
version: 2.13.0
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^7.17.0
|
||||
version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2)
|
||||
@ -134,6 +137,9 @@ importers:
|
||||
react-icons:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0(react@18.3.1)
|
||||
sanitize-html:
|
||||
specifier: ^2.13.0
|
||||
version: 2.13.0
|
||||
sharp:
|
||||
specifier: ^0.33.5
|
||||
version: 0.33.5
|
||||
@ -1677,6 +1683,9 @@ packages:
|
||||
'@types/react@18.3.8':
|
||||
resolution: {integrity: sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==}
|
||||
|
||||
'@types/sanitize-html@2.13.0':
|
||||
resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
|
||||
|
||||
'@types/stack-utils@2.0.3':
|
||||
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
|
||||
|
||||
@ -2354,11 +2363,24 @@ packages:
|
||||
dom-accessibility-api@0.6.3:
|
||||
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
|
||||
|
||||
domelementtype@2.3.0:
|
||||
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
|
||||
|
||||
domexception@4.0.0:
|
||||
resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==}
|
||||
engines: {node: '>=12'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
domhandler@5.0.3:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
domutils@3.1.0:
|
||||
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
|
||||
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
@ -2785,6 +2807,9 @@ packages:
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||
|
||||
http-proxy-agent@5.0.0:
|
||||
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
|
||||
engines: {node: '>= 6'}
|
||||
@ -3521,6 +3546,9 @@ packages:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
parse-srcset@1.0.2:
|
||||
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
|
||||
|
||||
parse5@7.1.2:
|
||||
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
|
||||
|
||||
@ -3897,6 +3925,9 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
sanitize-html@2.13.0:
|
||||
resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==}
|
||||
|
||||
sax@1.2.4:
|
||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||
|
||||
@ -6514,6 +6545,10 @@ snapshots:
|
||||
'@types/prop-types': 15.7.12
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/sanitize-html@2.13.0':
|
||||
dependencies:
|
||||
htmlparser2: 8.0.2
|
||||
|
||||
'@types/stack-utils@2.0.3': {}
|
||||
|
||||
'@types/tough-cookie@4.0.5': {}
|
||||
@ -7262,10 +7297,28 @@ snapshots:
|
||||
|
||||
dom-accessibility-api@0.6.3: {}
|
||||
|
||||
dom-serializer@2.0.0:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
entities: 4.5.0
|
||||
|
||||
domelementtype@2.3.0: {}
|
||||
|
||||
domexception@4.0.0:
|
||||
dependencies:
|
||||
webidl-conversions: 7.0.0
|
||||
|
||||
domhandler@5.0.3:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
domutils@3.1.0:
|
||||
dependencies:
|
||||
dom-serializer: 2.0.0
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
@ -7842,6 +7895,13 @@ snapshots:
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.1.0
|
||||
entities: 4.5.0
|
||||
|
||||
http-proxy-agent@5.0.0:
|
||||
dependencies:
|
||||
'@tootallnate/once': 2.0.0
|
||||
@ -8744,6 +8804,8 @@ snapshots:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
parse-srcset@1.0.2: {}
|
||||
|
||||
parse5@7.1.2:
|
||||
dependencies:
|
||||
entities: 4.5.0
|
||||
@ -9091,6 +9153,15 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
sanitize-html@2.13.0:
|
||||
dependencies:
|
||||
deepmerge: 4.3.1
|
||||
escape-string-regexp: 4.0.0
|
||||
htmlparser2: 8.0.2
|
||||
is-plain-object: 5.0.0
|
||||
parse-srcset: 1.0.2
|
||||
postcss: 8.4.47
|
||||
|
||||
sax@1.2.4: {}
|
||||
|
||||
saxes@6.0.0:
|
||||
|
||||
@ -16,6 +16,8 @@ import { useAppState } from '@/state/AppState';
|
||||
import { useMemo } from 'react';
|
||||
import HiddenTag from '@/tag/HiddenTag';
|
||||
import { SITE_ABOUT } from '@/site/config';
|
||||
import { htmlHasBrParagraphBreaks, safelyParseFormattedHtml } from '@/utility/html';
|
||||
import { clsx } from 'clsx/lite';
|
||||
|
||||
export default function PhotoGridSidebar({
|
||||
tags,
|
||||
@ -43,10 +45,14 @@ export default function PhotoGridSidebar({
|
||||
{SITE_ABOUT && <HeaderList
|
||||
items={[<p
|
||||
key="about"
|
||||
className="max-w-60 normal-case text-main"
|
||||
>
|
||||
{SITE_ABOUT}
|
||||
</p>]}
|
||||
className={clsx(
|
||||
'max-w-60 normal-case text-main',
|
||||
htmlHasBrParagraphBreaks(SITE_ABOUT) && 'pb-2',
|
||||
)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: safelyParseFormattedHtml(SITE_ABOUT),
|
||||
}}
|
||||
/>]}
|
||||
/>}
|
||||
{tags.length > 0 && <HeaderList
|
||||
title='Tags'
|
||||
|
||||
12
src/utility/html.ts
Normal file
12
src/utility/html.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
|
||||
const ALLOWED_FORMATTING_TAGS = ['b', 'strong', 'i', 'em', 'u', 'br'];
|
||||
|
||||
export const safelyParseFormattedHtml = (text: string) =>
|
||||
sanitizeHtml(text, {
|
||||
allowedTags: ALLOWED_FORMATTING_TAGS,
|
||||
});
|
||||
|
||||
// Matches two or more <br> or <br /> tags in a row
|
||||
export const htmlHasBrParagraphBreaks = (text: string) =>
|
||||
text.match(/(<br\s*\/?>){2}/i);
|
||||
Loading…
Reference in New Issue
Block a user