Skip to content

Commit ce6e568

Browse files
authored
Merge pull request #5 from javaDevJT/feature/add-dte-rates-portfolio
Add DTE Rates for Home Assistant to portfolio
2 parents e6f1f2d + 40df859 commit ce6e568

5 files changed

Lines changed: 191 additions & 38 deletions

File tree

frontend/node_modules/.package-lock.json

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package-lock.json

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/pages/Portfolio.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import { motion } from 'framer-motion';
22

33
const Portfolio = () => {
44
const projects = [
5+
{
6+
title: 'DTE Rates for Home Assistant',
7+
description: 'A Home Assistant custom integration that pulls the official DTE residential electric rate card PDF, parses rates dynamically, and exposes import/export price entities that track time-of-day and season.',
8+
tech: ['Python', 'Home Assistant', 'HACS', 'Lovelace'],
9+
gradient: 'from-yellow-500 to-orange-500',
10+
link: 'https://github.com/javaDevJT/DTE-Rates-for-Home-Assistant'
11+
},
512
{
613
title: 'Interactive Dashboard',
714
description: 'A sleek dashboard for monitoring automotive performance data with real-time visualizations.',
@@ -81,13 +88,26 @@ const Portfolio = () => {
8188
</span>
8289
))}
8390
</div>
84-
<motion.button
85-
whileHover={{ scale: 1.05 }}
86-
whileTap={{ scale: 0.95 }}
87-
className="bg-gradient-to-r from-blue-500 to-purple-600 text-white px-6 py-2 rounded-full font-semibold shadow-lg hover:shadow-xl transition"
88-
>
89-
View Project
90-
</motion.button>
91+
{project.link ? (
92+
<motion.a
93+
href={project.link}
94+
target="_blank"
95+
rel="noopener noreferrer"
96+
whileHover={{ scale: 1.05 }}
97+
whileTap={{ scale: 0.95 }}
98+
className="inline-block bg-gradient-to-r from-blue-500 to-purple-600 text-white px-6 py-2 rounded-full font-semibold shadow-lg hover:shadow-xl transition"
99+
>
100+
View Project
101+
</motion.a>
102+
) : (
103+
<motion.button
104+
whileHover={{ scale: 1.05 }}
105+
whileTap={{ scale: 0.95 }}
106+
className="bg-gradient-to-r from-blue-500 to-purple-600 text-white px-6 py-2 rounded-full font-semibold shadow-lg hover:shadow-xl transition"
107+
>
108+
View Project
109+
</motion.button>
110+
)}
91111
</div>
92112
</motion.div>
93113
))}

src/main/java/com/jtdev/website/service/ContentService.java

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.jtdev.website.model.BlogMetadata;
44
import com.jtdev.website.model.PortfolioMetadata;
5+
import com.vladsch.flexmark.ext.tables.TablesExtension;
56
import com.vladsch.flexmark.html.HtmlRenderer;
67
import com.vladsch.flexmark.parser.Parser;
78
import com.vladsch.flexmark.util.ast.Node;
@@ -72,6 +73,7 @@ public String getMarkdownContent(String path) throws IOException {
7273

7374
// Parse markdown and convert to ASCII-friendly format
7475
MutableDataSet options = new MutableDataSet();
76+
options.set(Parser.EXTENSIONS, java.util.Arrays.asList(TablesExtension.create()));
7577
Parser parser = Parser.builder(options).build();
7678
HtmlRenderer renderer = HtmlRenderer.builder(options).build();
7779

@@ -88,9 +90,12 @@ public String getMarkdownContent(String path) throws IOException {
8890
private String convertHtmlToAscii(String html, String dir) {
8991
// First pass: Convert headers with dynamic borders
9092
html = convertDynamicHeaders(html);
91-
93+
9294
html = formatNestedLists(html);
9395

96+
// Convert tables before the general regex pass
97+
html = convertTablesToAscii(html);
98+
9499
// Convert HTML elements to ASCII formatting
95100
String text = html
96101
// Headers already converted above
@@ -124,17 +129,7 @@ private String convertHtmlToAscii(String html, String dir) {
124129

125130
// Bold and italic
126131
.replaceAll("<strong[^>]*>([^<]*)</strong>", "**$1**")
127-
.replaceAll("<em[^>]*>([^<]*)</em>", "*$1*")
128-
129-
// Tables (simplified ASCII representation)
130-
.replaceAll("<table[^>]*>", "\n┌")
131-
.replaceAll("</table>", "┘\n")
132-
.replaceAll("<tr[^>]*>", "")
133-
.replaceAll("</tr>", "\n├")
134-
.replaceAll("<th[^>]*>", "─ ")
135-
.replaceAll("</th>", " ─")
136-
.replaceAll("<td[^>]*>", "│ ")
137-
.replaceAll("</td>", " │");
132+
.replaceAll("<em[^>]*>([^<]*)</em>", "*$1*");
138133

139134
// Handle images with ASCII art
140135
Pattern imgPattern = Pattern.compile("<img[^>]*src=\"([^\"]*)\"[^>]*>");
@@ -150,8 +145,8 @@ private String convertHtmlToAscii(String html, String dir) {
150145

151146
// Handle images without src
152147
text = text.replaceAll("<img[^>]*>", "[Image]");
153-
text
154148

149+
text = text
155150
// Remove any remaining HTML tags
156151
.replaceAll("<[^>]+>", "")
157152

@@ -162,8 +157,7 @@ private String convertHtmlToAscii(String html, String dir) {
162157
.replaceAll("\\n{3,}", "\n\n")
163158
.trim();
164159

165-
// The formatting is already handled in the regex replacements above
166-
return text.trim();
160+
return text;
167161
}
168162

169163
private String formatNestedLists(String html) {
@@ -250,6 +244,90 @@ int nextIndex() {
250244
}
251245
}
252246

247+
private String convertTablesToAscii(String html) {
248+
Pattern tablePattern = Pattern.compile("<table[^>]*>(.*?)</table>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
249+
Matcher tableMatcher = tablePattern.matcher(html);
250+
StringBuffer sb = new StringBuffer();
251+
while (tableMatcher.find()) {
252+
String asciiTable = renderTableAsAscii(tableMatcher.group(1));
253+
tableMatcher.appendReplacement(sb, Matcher.quoteReplacement(asciiTable));
254+
}
255+
tableMatcher.appendTail(sb);
256+
return sb.toString();
257+
}
258+
259+
private String renderTableAsAscii(String tableHtml) {
260+
List<List<String>> rows = new ArrayList<>();
261+
262+
Pattern rowPattern = Pattern.compile("<tr[^>]*>(.*?)</tr>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
263+
Matcher rowMatcher = rowPattern.matcher(tableHtml);
264+
while (rowMatcher.find()) {
265+
List<String> cells = new ArrayList<>();
266+
Pattern cellPattern = Pattern.compile("<t[hd][^>]*>(.*?)</t[hd]>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
267+
Matcher cellMatcher = cellPattern.matcher(rowMatcher.group(1));
268+
while (cellMatcher.find()) {
269+
String cell = cellMatcher.group(1)
270+
.replaceAll("<[^>]+>", "")
271+
.replaceAll("&amp;", "&").replaceAll("&lt;", "<")
272+
.replaceAll("&gt;", ">").replaceAll("&quot;", "\"")
273+
.replaceAll("&#39;", "'").replaceAll("&nbsp;", " ")
274+
.trim();
275+
cells.add(cell);
276+
}
277+
if (!cells.isEmpty()) rows.add(cells);
278+
}
279+
280+
if (rows.isEmpty()) return "";
281+
282+
int numCols = rows.stream().mapToInt(List::size).max().orElse(0);
283+
int[] colWidths = new int[numCols];
284+
for (List<String> row : rows) {
285+
for (int i = 0; i < row.size(); i++) {
286+
colWidths[i] = Math.max(colWidths[i], row.get(i).length());
287+
}
288+
}
289+
290+
StringBuilder ascii = new StringBuilder("\n");
291+
292+
// Top border
293+
ascii.append("┌");
294+
for (int i = 0; i < numCols; i++) {
295+
ascii.append("─".repeat(colWidths[i] + 2));
296+
ascii.append(i < numCols - 1 ? "┬" : "┐");
297+
}
298+
ascii.append("\n");
299+
300+
for (int r = 0; r < rows.size(); r++) {
301+
List<String> row = rows.get(r);
302+
ascii.append("│");
303+
for (int i = 0; i < numCols; i++) {
304+
String cell = i < row.size() ? row.get(i) : "";
305+
ascii.append(" ").append(cell).append(" ".repeat(colWidths[i] - cell.length() + 1)).append("│");
306+
}
307+
ascii.append("\n");
308+
309+
if (r == 0 && rows.size() > 1) {
310+
// Header separator
311+
ascii.append("├");
312+
for (int i = 0; i < numCols; i++) {
313+
ascii.append("─".repeat(colWidths[i] + 2));
314+
ascii.append(i < numCols - 1 ? "┼" : "┤");
315+
}
316+
ascii.append("\n");
317+
} else if (r == rows.size() - 1) {
318+
// Bottom border
319+
ascii.append("└");
320+
for (int i = 0; i < numCols; i++) {
321+
ascii.append("─".repeat(colWidths[i] + 2));
322+
ascii.append(i < numCols - 1 ? "┴" : "┘");
323+
}
324+
ascii.append("\n");
325+
}
326+
}
327+
328+
return ascii.toString();
329+
}
330+
253331
private String convertDynamicHeaders(String html) {
254332
// Convert H1 headers with dynamic borders
255333
Pattern h1Pattern = Pattern.compile("<h1[^>]*>(.*?)</h1>", Pattern.DOTALL);

0 commit comments

Comments
 (0)