From 58cbb66ceb41c9f27fced9c14402d1860052dd64 Mon Sep 17 00:00:00 2001 From: Sarah Wang Date: Sat, 17 Jan 2026 14:20:20 -0500 Subject: [PATCH 1/2] impl --- src/components/blackout/Blackout.tsx | 62 ++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/components/blackout/Blackout.tsx b/src/components/blackout/Blackout.tsx index d05b121..5f643de 100644 --- a/src/components/blackout/Blackout.tsx +++ b/src/components/blackout/Blackout.tsx @@ -1,9 +1,52 @@ -import React, { useState } from "react"; +import React, { useState, useMemo } from "react"; import { Button } from "@chakra-ui/react"; import type { PoemSnapshot } from "../../types"; import { MdOutlineUndo } from "react-icons/md"; import { MdOutlineRedo } from "react-icons/md"; +interface Token { + text: string; + spaceAfter: boolean; +} + +const tokenizeText = (text: string): Token[] => { + const tokens: Token[] = []; + // Regex to match words (\w+) or single punctuation characters ([^\s\w]) + const regex = /(\w+)|([^\s\w])/g; + let match; + const rawTokens: { text: string; index: number }[] = []; + + while ((match = regex.exec(text)) !== null) { + rawTokens.push({ text: match[0], index: match.index }); + } + + for (let i = 0; i < rawTokens.length; i++) { + const current = rawTokens[i]; + const next = rawTokens[i + 1]; + + let spaceAfter = false; + + if (next) { + const endOfCurrent = current.index + current.text.length; + const startOfNext = next.index; + + // Check if there's a space in the original text between tokens + const hasSpaceBetween = text.slice(endOfCurrent, startOfNext).includes(" "); + + // Hyphens: no space before or after + if (current.text === "-" || next.text === "-") { + spaceAfter = false; + } else if (hasSpaceBetween) { + spaceAfter = true; + } + } + + tokens.push({ text: current.text, spaceAfter }); + } + + return tokens; +}; + interface BlackoutProps { passageText: string; selectedWordIndexes: number[]; @@ -17,7 +60,7 @@ const BlackoutPoetry: React.FC = ({ setSelectedWordIndexes, setPoemSnapshots, }) => { - const words = passageText.split(" "); + const tokens = useMemo(() => tokenizeText(passageText), [passageText]); const [historyIndex, setHistoryIndex] = useState(-1); // Track undo/redo position const [history, setHistory] = useState([]); @@ -139,7 +182,7 @@ const BlackoutPoetry: React.FC = ({ className="leading-relaxed flex flex-wrap select-none h-max" onCopy={(e) => e.preventDefault()} > - {words.map((word, i) => { + {tokens.map((token, i) => { const isSelected = selectedWordIndexes.includes(i); const textColor = isSelected ? "text-main text-light-grey-1" @@ -149,9 +192,10 @@ const BlackoutPoetry: React.FC = ({ toggleSelect(i)} - className={`cursor-pointer transition px-1 duration-200 ${textColor}`} + className={`cursor-pointer transition duration-200 ${textColor}`} > - {word + " "} + {token.text} + {token.spaceAfter && {" "}} ); })} @@ -162,18 +206,20 @@ const BlackoutPoetry: React.FC = ({ className="leading-relaxed flex flex-wrap select-none h-max" onCopy={(e) => e.preventDefault()} > - {words.map((word, i) => { + {tokens.map((token, i) => { const isSelected = selectedWordIndexes.includes(i); const blackoutStyle = isSelected ? "text-main text-dark-grey" : "text-main text-dark-grey bg-dark-grey"; + const spaceStyle = isSelected ? "" : "bg-dark-grey"; return ( - {word + " "} + {token.text} + {token.spaceAfter && {" "}} ); })} From 09715c2e66c2734292ea2dc2b7d2c3a5a7a93a03 Mon Sep 17 00:00:00 2001 From: Sarah Wang Date: Sat, 17 Jan 2026 14:23:31 -0500 Subject: [PATCH 2/2] fix --- src/components/blackout/Blackout.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/blackout/Blackout.tsx b/src/components/blackout/Blackout.tsx index 5f643de..2f499c3 100644 --- a/src/components/blackout/Blackout.tsx +++ b/src/components/blackout/Blackout.tsx @@ -211,15 +211,14 @@ const BlackoutPoetry: React.FC = ({ const blackoutStyle = isSelected ? "text-main text-dark-grey" : "text-main text-dark-grey bg-dark-grey"; - const spaceStyle = isSelected ? "" : "bg-dark-grey"; + const spacingStyle = token.spaceAfter ? "pr-2" : ""; return ( {token.text} - {token.spaceAfter && {" "}} ); })}