Spaces:
Sleeping
Sleeping
| from fasthtml.common import * | |
| from monsterui.all import * | |
| import frontmatter | |
| import pathlib | |
| from fasthtml.components import Uk_theme_switcher | |
| from monsterui.foundations import * | |
| app, rt = fast_app(hdrs=Theme.gray.headers(daisy=True)) | |
| def load_book(file_path): | |
| """Load and parse a book's markdown file""" | |
| with open(file_path) as f: | |
| post = frontmatter.load(f) | |
| return post | |
| def create_mode_picker(): | |
| def _opt(val, txt, **kwargs): return Option(txt, value=val, **kwargs) | |
| def _optgrp(key, lbl, opts): return Optgroup(data_key=key, label=lbl)(*opts) | |
| group = _optgrp('mode', '', | |
| [ | |
| _opt('light','',data_icon='sun'), | |
| _opt('dark','',data_icon='moon') | |
| ]) | |
| return Div(Uk_theme_switcher( | |
| Select(group, hidden=True, selected=True), | |
| id="mode-picker" | |
| ), | |
| cls="fixed top-4 right-4 z-50 p-2", | |
| # style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;" | |
| ) | |
| def create_book_page(post): | |
| """Create the detailed book page using our template""" | |
| metadata = post.metadata | |
| return Title(metadata["title"]), Container( | |
| random_theme_script(), | |
| DivCentered( | |
| Card( | |
| DivCentered( | |
| H1(metadata["title"], | |
| cls="text-transparent bg-clip-text bg-gradient-to-r from-primary via-muted to-primary transition-all duration-1000 hover:scale-105", | |
| style="font-size: 2.5rem; font-weight: 700; -webkit-text-stroke: 0.5px rgba(0, 0, 0, 0.7);"), | |
| H2(metadata["author"], cls=(TextT.muted)), | |
| A( | |
| Img( | |
| src=metadata['cover_img_url'], | |
| cls="rounded-lg hover:scale-105 shadow-lg transition-all duration-1000", | |
| style="width:300px" | |
| ), | |
| cls="rounded-lg overflow-hidden", | |
| href=metadata['book_url'] | |
| ), | |
| cls="text-center space-y-6" | |
| ), | |
| DivCentered( | |
| Section( | |
| DivHStacked( | |
| Label(f"π {metadata['date'].strftime('%B %d, %Y')}", cls=LabelT.secondary), | |
| Label(f"π {metadata['genre']}", cls=LabelT.secondary), | |
| cls="space-x-2" | |
| ), | |
| cls=SectionT.xs | |
| ), | |
| cls="mb-6" | |
| ), | |
| Div( | |
| render_md( | |
| post.content, | |
| class_map={ | |
| 'h3': f'text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary {TextT.xl} {TextT.bold} mb-6 mt-8', | |
| 'ul': f'{ListT.disc} space-y-4 mb-8', | |
| 'li': f'{TextT.lg} {TextT.normal} leading-relaxed', | |
| 'ul ul': f'{ListT.circle} ml-8 mt-4', | |
| 'ul ul li': f'{TextT.normal} leading-relaxed', | |
| 'p': f'{TextT.lg} {TextT.normal} mb-4', | |
| 'img': 'rounded-lg shadow-md hover:shadow-xl transition-shadow duration-200', | |
| '*[@class="gallery"]': 'flex flex-row items-center justify-center gap-8' | |
| } | |
| ), | |
| cls = 'space-y-6' | |
| ), | |
| cls=CardT.default | |
| ) | |
| ), | |
| cls=(ContainerT.lg, 'p-8'), | |
| style="position: relative; overflow: hidden;" | |
| ) | |
| def create_book_card(metadata, filename): | |
| """Create a card for the book listing""" | |
| return A( | |
| Card( | |
| DivCentered( | |
| Img( | |
| src=metadata['cover_img_url'], | |
| cls="rounded-lg shadow-md hover:scale-105 transition-all duration-300", | |
| style="width:200px" | |
| ), | |
| H3(metadata['title'], | |
| cls="text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary"), | |
| P(metadata['author'], cls=TextT.muted), | |
| DivHStacked( | |
| Label(metadata['genre'], cls=LabelT.secondary), | |
| Label(metadata['date'].strftime('%B %d, %Y'), cls=LabelT.secondary), | |
| cls="mt-4 space-x-2" | |
| ), | |
| cls="space-y-4 p-4" | |
| ), | |
| cls=(CardT.hover, "transition-all duration-300 hover:shadow-xl") | |
| ), | |
| href=f"/book/{filename}", | |
| ) | |
| def random_theme_script(): | |
| return Script(""" | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const themes = ['uk-theme-zinc', 'uk-theme-slate', 'uk-theme-red', | |
| 'uk-theme-rose', 'uk-theme-orange', 'uk-theme-green', | |
| 'uk-theme-blue', 'uk-theme-yellow', 'uk-theme-violet']; | |
| const randomTheme = themes[Math.floor(Math.random() * themes.length)]; | |
| document.documentElement.className = randomTheme; | |
| }); | |
| """) | |
| def index(): | |
| """Homepage with grid of book cards""" | |
| books_path = pathlib.Path('books') | |
| book_files = list(books_path.glob('*.md')) | |
| # Create cards for each book | |
| book_cards = [] | |
| for file in book_files: | |
| post = load_book(file) | |
| book_cards.append(create_book_card(post.metadata, file.stem)) | |
| return Title("NotesMD"), Container( | |
| create_mode_picker(), | |
| random_theme_script(), | |
| Grid(*book_cards, | |
| cols_sm=1, cols_md=2, cols_lg=3, cols_xl=4, | |
| gap=6), | |
| cls=(ContainerT.xl, 'p-8') | |
| ) | |
| def get(filename: str): | |
| """Individual book page""" | |
| try: | |
| books_path = pathlib.Path('books') | |
| book_file = books_path / f"{filename}.md" | |
| if not book_file.exists(): | |
| raise FileNotFoundError | |
| post = load_book(book_file) | |
| return create_book_page(post) | |
| except (FileNotFoundError, ValueError) as e: | |
| return Title("Not Found!"), Container( | |
| DivCentered( | |
| H1("Book Not Found", cls=TextT.error), | |
| P("Sorry, we couldn't find the book you're looking for."), | |
| A("Return to Library", href="/", cls=ButtonT.primary), | |
| cls="space-y-6 py-12" | |
| ) | |
| ) | |
| serve() | |