Skip to content

Commit 206d5e6

Browse files
feat: Enhance rendering of meeting minutes with Tailwind CSS styles; add collapsible sections and improve list handling
1 parent bbdea69 commit 206d5e6

File tree

2 files changed

+114
-71
lines changed

2 files changed

+114
-71
lines changed

src/templates/view.html

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,14 +361,15 @@ <h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Results (<
361361
x-show="!showTranscript && !loading" x-transition:enter="transition-opacity ease-out duration-300"
362362
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
363363
<template x-if="file.minutes">
364-
<div x-html="highlightSearchResults(file.minutes)"
365-
:class="darkMode ? 'bg-gray-900 text-gray-100' : 'bg-white text-gray-900'"
366-
class="rounded-lg transition-colors duration-300"></div>
364+
<div
365+
x-html="highlightSearchResults(file.minutes)"
366+
class="rounded-lg transition-colors duration-300 bg-white dark:bg-gray-900 shadow-md px-8 py-6 my-6 mx-auto"
367+
style="margin-top:2rem; margin-bottom:2rem; max-width: 100%;">
368+
</div>
367369
</template>
368370

369371
<template x-if="!file.minutes">
370-
<div class="flex flex-col items-center justify-center p-8 text-center rounded-lg border-2 border-dashed"
371-
:class="darkMode ? 'border-gray-700 text-gray-400' : 'border-gray-300 text-gray-500'">
372+
<div class="flex flex-col items-center justify-center p-8 text-center rounded-lg border-2 border-dashed">
372373
<i class="fa-regular fa-file-lines text-5xl mb-4 opacity-70"></i>
373374
<h3 class="text-xl font-medium mb-2">No meeting minutes available</h3>
374375
<p class="max-w-sm">Meeting minutes haven't been generated yet for this file. Please check

src/utils.py

Lines changed: 108 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,114 @@
11
from markdown import markdown
2-
from bs4 import BeautifulSoup
2+
from bs4 import BeautifulSoup, Tag
3+
import re
34

4-
def render_minutes_with_tailwind(md_text):
5-
html = markdown(md_text, extensions=['fenced_code', 'codehilite', 'tables', 'nl2br'])
5+
TAG_STYLES = {
6+
'h1': 'text-4xl font-extrabold text-indigo-800 mb-6 mt-8 scroll-mt-24 flex justify-between items-center',
7+
'h2': 'text-3xl font-bold text-indigo-700 mb-5 mt-7 scroll-mt-24 flex justify-between items-center',
8+
'h3': 'text-2xl font-semibold text-indigo-600 mb-4 mt-6 flex justify-between items-center',
9+
'h4': 'text-xl font-medium text-indigo-500 mb-3 mt-5',
10+
'h5': 'text-lg font-medium text-indigo-400 mb-2 mt-4',
11+
'h6': 'text-base font-medium text-indigo-300 mb-1 mt-3',
12+
'p': 'mb-4 text-gray-800 leading-relaxed tracking-normal',
13+
'ul': 'list-disc list-inside pl-6 mb-4 text-gray-800',
14+
'ol': 'list-decimal list-inside pl-6 mb-4 text-gray-800',
15+
'li': 'mb-1',
16+
'blockquote': 'border-l-4 border-blue-400 pl-6 italic text-gray-700 bg-blue-50 py-3 px-4 rounded-md my-6',
17+
'hr': 'my-8 border-t border-gray-300',
18+
'a': 'text-blue-700 hover:text-blue-900 underline',
19+
'code': 'bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-purple-700',
20+
'pre': 'bg-gray-100 text-gray-800 p-5 rounded-lg overflow-x-auto text-sm shadow-inner my-6 whitespace-pre-wrap break-words',
21+
'table': 'table-auto w-full border-collapse border border-gray-300 shadow-sm my-8 text-sm',
22+
'thead': 'bg-gray-100',
23+
'th': 'border px-4 py-3 text-left bg-gray-200 text-gray-700 font-semibold',
24+
'td': 'border px-4 py-2 text-gray-800'
25+
}
26+
27+
HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
28+
29+
def wrap_collapsible(start_tag, soup):
30+
"""
31+
Wraps content under heading inside a collapsible div with toggle icon
32+
"""
33+
level = int(start_tag.name[1])
34+
toggle_id = f"section-{id(start_tag)}"
35+
content_div = soup.new_tag("div", **{'id': toggle_id, 'class': 'collapsible-content'})
36+
37+
next_sibling = start_tag.find_next_sibling()
38+
while next_sibling and (
39+
not isinstance(next_sibling, Tag)
40+
or next_sibling.name not in HEADING_TAGS
41+
or int(next_sibling.name[1]) > level
42+
):
43+
temp = next_sibling
44+
next_sibling = next_sibling.find_next_sibling()
45+
content_div.append(temp.extract())
46+
47+
start_tag.insert_after(content_div)
48+
49+
# Add toggle button
50+
toggle_button = soup.new_tag("button", **{
51+
'class': 'toggle-button text-indigo-700 text-xl font-bold ml-2 focus:outline-none',
52+
'onclick': f"toggleContent('{toggle_id}', this)"
53+
})
54+
toggle_button.string = '+'
55+
start_tag.append(toggle_button)
56+
57+
def fix_nested_lists(soup):
58+
for ul in soup.find_all(['ul', 'ol']):
59+
for li in ul.find_all('li', recursive=False):
60+
next_elem = li.find_next_sibling()
61+
if next_elem and next_elem.name in ['ul', 'ol']:
62+
nested_list = next_elem.extract()
63+
li.append(nested_list)
64+
65+
def process_nested_items(soup):
66+
for li in soup.find_all('li'):
67+
if li.strong and li.strong.text.strip().endswith(':'):
68+
next_sibling = li.find_next_sibling()
69+
sublist = soup.new_tag('ul', **{'class': TAG_STYLES['ul'].split()})
70+
while next_sibling and next_sibling.name == 'li' and not next_sibling.strong:
71+
temp = next_sibling
72+
next_sibling = next_sibling.find_next_sibling()
73+
sublist.append(temp.extract())
74+
if sublist.find('li'):
75+
li.append(sublist)
76+
77+
def handle_special_formatting(soup):
78+
for code in soup.find_all('code'):
79+
if code.parent.name != 'pre':
80+
code['class'] = TAG_STYLES['code'].split()
81+
# Leave checkbox syntax unchanged
82+
83+
def render_minutes_with_tailwind(md_text: str) -> str:
84+
html = markdown(md_text, extensions=['fenced_code', 'codehilite', 'tables', 'nl2br', 'extra'])
685
soup = BeautifulSoup(html, 'html.parser')
786

8-
# Headings – updated for white background
9-
heading_styles = {
10-
'h1': 'text-4xl font-extrabold text-indigo-800 mb-6 mt-8',
11-
'h2': 'text-3xl font-bold text-indigo-700 mb-5 mt-7',
12-
'h3': 'text-2xl font-semibold text-indigo-600 mb-4 mt-6',
13-
'h4': 'text-xl font-medium text-indigo-500 mb-3 mt-5',
14-
'h5': 'text-lg font-medium text-indigo-400 mb-2 mt-4',
15-
'h6': 'text-base font-medium text-indigo-300 mb-1 mt-3',
16-
}
17-
for tag, classes in heading_styles.items():
18-
for el in soup.find_all(tag):
19-
el['class'] = classes + ' scroll-mt-24' # type: ignore
20-
21-
# Paragraphs
22-
for tag in soup.find_all('p'):
23-
tag['class'] = 'mb-4 text-gray-800 leading-relaxed tracking-normal' # type: ignore
24-
25-
# Lists
26-
for tag in soup.find_all('ul'):
27-
tag['class'] = 'list-disc list-inside pl-6 mb-4 text-gray-800' # type: ignore
28-
for tag in soup.find_all('ol'):
29-
tag['class'] = 'list-decimal list-inside pl-6 mb-4 text-gray-800' # type: ignore
30-
for tag in soup.find_all('li'):
31-
tag['class'] = 'mb-1' # type: ignore
32-
33-
# Blockquotes
34-
for tag in soup.find_all('blockquote'):
35-
tag['class'] = (
36-
'border-l-4 border-blue-400 pl-6 italic text-gray-700 '
37-
'bg-blue-50 py-3 px-4 rounded-md my-6'
38-
) # type: ignore
39-
40-
# Horizontal Rules
41-
for tag in soup.find_all('hr'):
42-
tag['class'] = 'my-8 border-t border-gray-300' # type: ignore
43-
44-
# Links
45-
for tag in soup.find_all('a'):
46-
tag['class'] = 'text-blue-700 hover:text-blue-900 underline' # type: ignore
47-
tag['target'] = '_blank' # type: ignore
48-
tag['rel'] = 'noopener noreferrer' # type: ignore
49-
50-
# Inline Code
51-
for tag in soup.find_all('code'):
52-
if tag.parent.name != 'pre': # type: ignore
53-
tag['class'] = 'bg-gray-100 px-1.5 py-0.5 rounded text-sm font-mono text-purple-700' # type: ignore
54-
55-
# Code Blocks
56-
for tag in soup.find_all('pre'):
57-
tag['class'] = (
58-
'bg-gray-100 text-gray-800 p-5 rounded-lg overflow-x-auto text-sm '
59-
'shadow-inner my-6 whitespace-pre-wrap break-words'
60-
) # type: ignore
61-
62-
# Tables
63-
for tag in soup.find_all('table'):
64-
tag['class'] = 'table-auto w-full border-collapse border border-gray-300 shadow-sm my-8 text-sm' # type: ignore
65-
for tag in soup.find_all('thead'):
66-
tag['class'] = 'bg-gray-100' # type: ignore
67-
for tag in soup.find_all('th'):
68-
tag['class'] = 'border px-4 py-3 text-left bg-gray-200 text-gray-700 font-semibold' # type: ignore
69-
for tag in soup.find_all('td'):
70-
tag['class'] = 'border px-4 py-2 text-gray-800' # type: ignore
87+
fix_nested_lists(soup)
88+
process_nested_items(soup)
89+
handle_special_formatting(soup)
90+
91+
for tag in soup.find_all(HEADING_TAGS):
92+
level = int(tag.name[1])
93+
if level <= 3:
94+
wrap_collapsible(tag, soup)
95+
96+
for tag_name, class_list in TAG_STYLES.items():
97+
for el in soup.find_all(tag_name):
98+
if tag_name == 'code' and el.parent.name == 'pre':
99+
continue
100+
existing_classes = el.get('class', [])
101+
el['class'] = list(set(existing_classes + class_list.split()))
102+
if tag_name == 'a':
103+
el['target'] = '_blank'
104+
el['rel'] = 'noopener noreferrer'
71105

72106
return str(soup)
107+
108+
if __name__ == "__main__":
109+
with open("data.md", "r") as f:
110+
md_text = f.read()
111+
112+
html = render_minutes_with_tailwind(md_text)
113+
with open("output.html", "w") as f:
114+
f.write(html)

0 commit comments

Comments
 (0)