47
loading...
This website collects cookies to deliver better user experience
Refactor
When I originally wrote my site, because I wrote the onClick
handler that rendered a single blog differently than the rest of my onClick
handlers, I wrote two separate components, a SingleBlog and a FullBlog. I want to refactor these into one component because making updates in two files was tedious the last time I rewrote this page.
Markdown or HTML?
The second thing I wanted to revisit - investigating whether a markdown parsing solution is better than an HTML parsing solution for displaying what the DEV API returns. While working on this, I found a security vulnerability!
Sections, Articles, and Headings, Oh My
Based on the audit and things I've learned fixing other parts of my site, I want to make sure I only have one <h1>
on a page, only return one <section>
on my blog page, put each blog in an <article>
, and edit my blogs so there are no skipped heading levels.
Links on Links on Links
Based on reader feedback from this series, I want to add a link in each <article>
to the blog post on DEV for sharing. The audit in part 1 returned errors about empty links in the blog headings from the HTML the DEV API returned, and I thought that was solved by switching to markdown. Turns out, they were merely replaced by "target source does not exist" errors.
The CSS Mess
I had noticed my <ul>
styling needed help during the audit, and wanted to revisit code block styling after the last time I rewrote this page. After switching to markdown, I'll need to revisit all my blog styling again.
No to Reflow
From manual testing, I found I need to update my styling so my blog page is able to get to 400% zoom without causing reflow issues. I also found the page switches to scrolling horizontally in landscape on mobile and I want to prevent that behavior.
Text Formatting
During the audit for part 1, I got warnings to make sure <br>
tags are not being used for paragraph formatting and that any meaning conveyed by <em>
and <strong>
must be available to screen readers. At least one automatic tool had a recommendation to use more list elements, and multiple recommended I use <q>
or <quoteblock>
tags around quotes. Through retesting after switching to markdown, I noticed my <ol>
elements were being styled like <ul>
s, the links to headings were broken, and new issues had been created by the way I was trying to caption/source images on DEV.
The Long Alt-Text
A couple automatic tools gave me errors about long alt-text. I also want to look into how often I'm using words like "gif" because it seems much more often than I would like.
Skipping Around
As I've been testing things with a screen reader and keyboard for this blog series, I realized wanted to provide skip links as a means of bypassing blocks of content for my blog preview component and blog page.
app.js
, and the first thing I needed to update was my chooseComponent()
function that all of my navigation buttons use to display one component on the page.const chooseComponent = (component) => {
if (component.component === "SingleBlog") {
setSingle(true)
setSingleBlogID(component.id)
setSingleShow("FullBlog")
} else if (component === "FullBlog") {
setSingle(true)
setSingleBlogID(0)
setSingleShow(component)
} else {
setSingle(true)
setSingleShow(component)
}
}
id={singleBlogID}
to my FullBlog component's props. setSingleBlogID(0)
returns SingleBlogID
to it's default state and allows me to write this check in my useEffect
in my FullBlog component:if (props.id !== 0) {
fetchSingleBlog(props.id)
} else {
fetchBlogs()
}
chooseComponent
had to be updated to return "SingleBlog" as well as an id for this to work.<button className="preview_button" onClick={() => chooseComponent({component: "SingleBlog", id: blog.id})}>{blog.title}</button>
fetchSingleBlog
call from the SingleBlog component to the FullBlog component, I'm ready to format what FullBlog returns. I ended up having to update the structure of what is saved in state slightly (e.g. res.data.data
instead of res.data
), but then it was easy enough to check for the length of state.blogs
, and return one or all of the blogs based on that.if (!state.isLoading && state.blogs !== null) {
let blogList
if (state.blogs.length > 1) {
blogList = state.blogs.map((blog) => {
let blogBody = parse(blog.body_html)
return (
<li key={blog.id} className="blog">
<h1>{blog.title}</h1>
{blogBody}
</li>
)
})
} else {
let blogBody = parse(state.blogs.body_html)
blogList =
<li key={state.blogs.id} className="blog">
<h1>{state.blogs.title}</h1>
{blogBody}
</li>
}
return (
<section aria-label="Full list of Abbey's blog posts" className="full-blog">
<ul>
{blogList}
</ul>
</section>
)
} else if (!state.isLoading && state.error) {
return (
<Error />
)
} else {
return (
<Loading />
)
}
dangerouslySetInnerHTML
. Second, when I was building it, I was getting fairly regular 429, too many requests, responses from the DEV API because I'm grabbing each blog by id to get the HTML. However, I'm not seeing those anymore.const axios = require('axios')
const API_KEY = process.env.API_KEY
exports.handler = async function (event, context) {
let articles
try {
articles = await axios.get('https://dev.to/api/articles/me', {
headers: {
"Api-Key": API_KEY,
"Content-Type": 'application/json'
}
})
} catch (err) {
console.log(err)
return {
statusCode:err.statusCode || 500,
body: err.message,
headers: {
"Access-Control-Allow-Origin": "https://abbeyperini.dev",
"Access-Control-Allow-Methods": "GET"
}
}
}
return {
statusCode: 200,
body: JSON.stringify({
data: articles.data
}),
headers: {
"Access-Control-Allow-Origin": "https://abbeyperini.dev",
"Access-Control-Allow-Methods": "GET"
}
}
}
react-markdown
and pull the markdown instead of the HTML from what my lambdas return. The excellent news is this is pretty darn easy. I uninstalled html-react-parser
and installed react-markdown
and the remarkGfm
plugin. Then, I put a markdown
variable where I previously had a variable called blogBody
set to a parsed HTML string. Next, I add a ReactMarkdown
component that parses and renders my markdown string where I previously returned blogBody
.let markdown = state.blogs.body_markdown
blogList =
<li key={state.blogs.id} className="blog">
<h1>{state.blogs.title}</h1>
<ReactMarkdown children={markdown} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</li>
<section>
s negates the need to avoid multiple <h1>
s on a page persists because the HTML specs say that's true and the browsers never implemented it. First, I updated my main page with <h2>
s around my section headings. Then I double check I'm not skipping around in heading hierarchy in any of the content of the sections. I ended up updating about 16 headings.<h1>
with the page title, but it's not rendered with the rest of the content. Adding a visually hidden <h1>
page title is now part of this Github issue, which has quickly become its own accessibility project to come back to after this comprehensive audit. Miraculously, this all works without me needing to update any main page CSS.<section>
and the blogs are wrapped in <articles>
instead of in a list.<h2>
s on my site, so anything other than the title (or ridiculously long secondary titles I have a tendency add) will have to start at <h3>
and not skip any heading levels. I've been careful about not skipping levels of headings since I last rewrote this page, but I've been starting at <h2>
. At this point, I know I don't want to go any lower in hierarchy on DEV because of accessibility on their site, so I'm going to try a regex to replace the octothorps that make headings (e.g. #, ##, ###) in the markdown string.react-markdown
does not. Luckily, it's not all of them, because I had noticed this problem when I started cross posting blogs from DEV to Hashnode. I ended up editing 13 blogs - making sure they all start at <h2>
and no headings are skipped. I also removed headings from a couple places where I was using them to format captions.<h4>
, but I add a replace for <h5>
just in case. After a little trial and error, including having to reverse the order of the replaces so that everything doesn't end up being an <h6>
, my heading replace function looks like this:function replaceHeadings(markdown) {
let newHeadings
newHeadings = markdown.replace(/\s#{5}\s/g, "\n###### ")
newHeadings = newHeadings.replace(/\s#{4}\s/g, "\n##### ")
newHeadings = newHeadings.replace(/\s#{3}\s/g, "\n#### ")
newHeadings = newHeadings.replace(/\s#{2}\s/g, "\n### ")
return newHeadings
}
blogList = state.blogs.map((blog) => {
let markdown = blog.body_markdown
let replaced = replaceHeadings(markdown)
return (
<article key={blog.id} className="blog">
<h2>{blog.title}</h2>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
)
})
<article key={blog.id} className="blog">
<h2>{blog.title}</h2>
<a href={blog.url} target="_blank" rel="noreferrer"><button className="preview_button">Share</button></a>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
<a name="heading-title" href="#heading-title"></a>
. (For the record, it is recommended that you add an "id" to the heading element or an anchor element that has content, rather than adding a name
attribute to an empty anchor before the heading.) I find a google style guide with a way to add an anchor to headings using markdown, and get impressively close to a working regex solution before I realized I should test if it would even work with this markdown parser. ReactMarkdown
component and they didn't work. Turns out react-markdown
won't parse heading anchors. I read multiple issues, tried the recommended render functions and components, added a escapeHTML={false}
prop and an anchor link around a heading myself, and every time the heading was unaffected, showed the link as part of the heading content, or disappeared. The answer might be installing a plugin, but installing one involves installing multiple, and I've still got quite a few fixes to go, so I've made a new Github issue to come back to later. Luckily, if users really want to use the table of contents, they have that share button to take them to DEV for now.<ExternalLink />
component into the button, just like in part 2 of this blog series. To give them unique ids, I've generated ids in my formatting map
like this:let SVGID = "ShareExternalLink" + Math.random().toString(16).slice(2)
<h2>
instead of <h1>
. My <ul>
styling was broken because I hadn't taken <ol>
s into consideration. I also added a left margin to get the list discs in line with the text. <pre>
and <code>
structure made more sense. After updating the class rules to element selector rules, I had to switch some rules around to give my <pre>
s that weren't wrapped in <p>
s a background that covered all the lines of code, unbroken. Then, I had to play around a bit with margins and padding until I remembered what I was trying to do with the inline code. <div>
, and moved it out. <img>
s wrapped in <p>
s without a class stumped me for a while. Why is there no parent CSS selector!? A lot of googling and complaining later, I land on display: block;
and margin: auto;
which center my images! After this jogs my memory, I also add display: inline-block
to my inline formatted code and liked the way it offsets the text from the code.<h4>
rule, some margins, and a new line to a blog post where the <img>
needed to be further away from the text. I'm not super happy with my share button, but I'll use that as motivation to get to that Github issue.<pre>
blocks are set to 100% width
in my last media query. My headings have no width
rules. It looks strange when they're all different lengths, and one or both are probably the source of my reflow issues. width
constraint rule for this page that I have in all of my media queries. I find the likely overflow culprits and consolidate their width
rules into one. After this is applied, I notice my blog containers are all different sizes, and add a min-width
rule to the .blog
class rule block in all my media queries. Then I resize my window and find what is still overflowing - those pesky <ul>
s and <ol>
s! I end up with two rules repeated across media queries that look like this:.blog pre, .blog p, .blog blockquote, .blog h2, .blog h3, .blog h4, .blog ul, .blog ol {
max-width: 250px;
}
.blog {
min-width: 280px;
}
a {
word-wrap: break-word;
overflow-wrap: break-word;
}
width
100px less than the max-width
rule, and that solves the problem.<em>
elements. The <q>
and <quoteblock>
warnings are about places in my blogs where I quote myself, present a hypothetical or mantra, or put quotations around text you would see on the screen or that I'm adding to my site. The places where I quote other people are properly surrounded by <quoteblock>
. The "use more list elements" warnings are about places where a lot of links or code blocks appear under an <h3>
wrapped in a <p>
. They wouldn't make sense as lists, so those are fine.<ol>
elements are styled with discs, not numbers. Luckily, all I had to do was move list-style-type: disc;
out of my .blog li
rule and into a .blog ul
rule instead. title
on an element that isn't interactive. It would seem I added titles to two images by adding them in quotes after the link:
*Knit by Abbey Perini, pattern by Dowland by Dee O'Keefe, yarn is Meeker Street by The Jewelry Box*
<figcaption>
s, and adding <figure>
, <img>
, and <figcaption>
elements does not work with react-markdown
. I inspect these two elements and with the alt-text, no meaning is lost for screen reader users. If anything, they get more context than sighted users. I did notice, however, that one of the captions isn't centered. Our friends display: block;
and margin: auto;
quickly fixed that.react-markdown
isn't parsing <kbd>
elements, because they're so cute. I tried a few props in my ReactMarkdown
element, but they didn't work, so I morosely update my replaceHeadings
function to remove the <kbd>
s with regexes too.function replaceHeadings(markdown) {
let newHeadings
newHeadings = markdown.replace(/\s#{5}\s/g, "\n###### ")
newHeadings = newHeadings.replace(/\s#{4}\s/g, "\n##### ")
newHeadings = newHeadings.replace(/\s#{3}\s/g, "\n#### ")
newHeadings = newHeadings.replace(/\s#{2}\s/g, "\n### ")
newHeadings = newHeadings.replace(/<kbd>/g, "")
newHeadings = newHeadings.replace(/<\/kbd>/g, "")
return newHeadings
}
<strong>
- one is a line from a knitting pattern, just to make it easier to read for sighted users, so no meaning is lost on a screen reader. The other is a note about the useEffect()
dependency array, just to make it stand out. For this one, I want to take the recommendation from ARC Toolkit and put it in a heading instead, but an <h2>
is a bit huge, so I just make it a new paragraph. <h1>
s, but that will be fixed when I get to that Github issue. I'm also getting warnings about duplicate headings. Hopefully users will understand I like to end my blogs with a "Conclusion" section, and this blog series has a lot of "Problem" headings. Plus, this shouldn't be a problem for screen reader users once I add skip links.alt
with an aria-describedby
attribute, but I'd rather shorten 11 alt-texts at this point in my accessibility audit journey. Using Word Counter to get a character count and cmd + F in the elements console in dev tools on my site to find the offenders, I am able workshop them all down. You can tell when I'm proud of an image or code project I've made, because I get verbose. /* skip links */
.screenreader-text {
position: absolute;
left: -999px;
width: 1px;
height: 1px;
top: auto;
}
.screenreader-text:focus {
color: black;
display: inline-block;
height: auto;
width: auto;
position: static;
margin: auto;
}
function makeID(title) {
title = title.toLowerCase()
let replaced = title.replace(/\s+/g, "-")
replaced = replaced.replace(/#/g, "")
return replaced
}
if (!state.isLoading && state.blogs !== null) {
let blogList
let skipLinks = []
if (state.blogs.length > 1) {
blogList = state.blogs.map((blog) => {
let SVGID = "ShareExternalLink" + Math.random().toString(16).slice(2)
let markdown = blog.body_markdown
let replaced = replaceHeadings(markdown)
let blogID = makeID(blog.title)
let Href = `#${blogID}`
let skipLinkID = blogID + Math.random().toString(16).slice(2)
let skipLink = <li id={skipLinkID}><a href={Href}>{blog.title}</a></li>
skipLinks.push(skipLink)
return (
<article className="blog">
<h2 id={blogID}>{blog.title}</h2>
<a href={blog.url} target="_blank" rel="noreferrer"><button className="preview_button">Share <ExternalLink className="external-link" id={SVGID} focusable="false"/></button></a>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
)
})
return (
<section aria-label="Full list of Abbey's blog posts" className="full-blog">
<div className="screenreader-text">
Skip directly to a blog:
<ol>
{skipLinks}
</ol>
</div>
{blogList}
</section>
)
} else {
let markdown = state.blogs.body_markdown
let replaced = replaceHeadings(markdown)
return (
<section aria-label="Full list of Abbey's blog posts" className="full-blog">
<article key={state.blogs.id} className="blog">
<h2>{state.blogs.title}</h2>
<a href={state.blogs.url} target="_blank" rel="noreferrer"><button className="preview_button">Share <ExternalLink className="external-link" id="ShareExternalLink" focusable="false"/></button></a>
<ReactMarkdown children={replaced} remarkPlugins={[remarkGfm]}></ReactMarkdown>
</article>
</section>
)
} else if (!state.isLoading && state.error) {
return (
<Error />
)
} else {
return (
<Loading />
)
}
id="about"
attribute, so all I have to do is add the link to the Blog component's return statement:return (
<section aria-label="Blog Previews" className="container_blog">
<h2 aria-label="button to open full blog page" ><button className="blog-section_title" onClick={() => chooseComponent("FullBlog")}>Blog</button></h2>
<a className="screenreader-text" href='#about'>Skip directly to the next section.</a>
<div className="scroll-cropper">
<ul aria-label="previews of Abbey's blog posts" className="blog-preview">
{blogPreviewList}
</ul>
</div>
</section>
)
<p>
tag that should have been around "Skip directly to a blog:" in the first placetabIndex="0"
to that <p>
tagrole="navigation"
so that tabIndex="0"
is acceptable:focus-within
pseudo selector to the CSS rule that originally only had .screenreader-text:focus
flex-basis
and might even switch to using CSS grid. I should also come up with a solution for my local lambda server that doesn't involve hardcoded links. (You can read about how it ended up that way in the walkthrough.) GitGuardian is saying I committed my DEV API key even though I tried really hard not to, so I revoked the one I was working with in this blog and replaced it when I deployed.