Skip to content

Commit

Permalink
Merge pull request #30 from 99ch/highlight-js-integration
Browse files Browse the repository at this point in the history
Integrate Highlight.js into headers with theme synchronization
  • Loading branch information
Isaac-Flath authored Jan 14, 2025
2 parents 3b7d35d + 3d045ff commit 390fada
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 105 deletions.
18 changes: 11 additions & 7 deletions docs/api_reference/api_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,14 @@ def ex_theme_switcher():
return Uk_theme_switcher()

docs_theme_headers = create_doc_section(
"To get headers with a default theme use `hdrs=Theme.<color>.headers()`. For example for the blue theme you would use `hdrs=Theme.blue.headers()`. Theme options are:",
"> Note: Tailwind is included in the headers for convenience",
"""
To get headers with a default theme use `hdrs=Theme.<color>.headers()`. For example for the blue theme you would use `hdrs=Theme.blue.headers()`. The theme integrated together different frameworks and allows tailwind, FrankenUI, HighlighJS, and DaisyUI components to work well together.
Tailwind, FrankenUI and DaisyUI are imported by default. You must use DaisyUI headers to use anything in the `daisy` module, and FrankenUI headers to use anything in the `franken` module.
HighlightJS is not added by default, but you can add it by setting `highlightjs=True` in the headers function. The `render_md` function will use HighlightJS for code blocks.
Theme options are:""",
Card(Grid(map(P,Theme)),cls='mb-8'),
H3("Theme Picker"),
fn2code_string(ex_theme_switcher),
Expand Down Expand Up @@ -226,10 +232,8 @@ def ex_ps():
def ex_other():
return Div(
CodeSpan("This is a CodeSpan element"),
Blockquote("This is a blockquote element"))



Blockquote("This is a blockquote element"),
CodeBlock("#This is a CodeBlock element\n\ndef add(a,b): return a+b"))

docs_typography = create_doc_section(
PLarge("High Level Options"),
Expand All @@ -245,7 +249,7 @@ def ex_other():
TextT,
H1, H2, H3, H4, Titled,
PParagraph, PLarge, PLead, PSmall, PMuted,
CodeSpan, Blockquote,
CodeSpan, Blockquote, CodeBlock,
title="Text Style")


Expand Down
4 changes: 2 additions & 2 deletions docs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
from functools import partial
from monsterui.all import *
from fasthtml.components import Uk_theme_switcher
from utils import hjs, render_nb
from utils import render_nb
from pathlib import Path

# Setup the app
app,rt = fast_app(pico=False, hdrs=(*Theme.blue.headers(),*hjs, Link(rel="icon", type="image/x-icon", href="/favicon.ico")))
app,rt = fast_app(pico=False, hdrs=(*Theme.blue.headers(highlightjs=True), Link(rel="icon", type="image/x-icon", href="/favicon.ico")))

def is_htmx(request=None):
"Check if the request is an HTMX request"
Expand Down
40 changes: 0 additions & 40 deletions docs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,6 @@ def get_last_statement(code): return ast.unparse(ast.parse(code).body[-1])
from pathlib import Path


hjs = (
# Core highlight.js and copy plugin
Script(src='https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js'),
Script(src='https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/languages/python.min.js'),
Script(src='https://cdn.jsdelivr.net/gh/arronhunt/highlightjs-copy/dist/highlightjs-copy.min.js'),

# Themes and styles
Link(rel='stylesheet', href='https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/atom-one-dark.css', id='hljs-dark'),
Link(rel='stylesheet', href='https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/atom-one-light.css', id='hljs-light'),
Link(rel='stylesheet', href='https://cdn.jsdelivr.net/gh/arronhunt/highlightjs-copy/dist/highlightjs-copy.min.css'),


# Initialization script
Script('''
// Initialize highlight.js with copy plugin
hljs.addPlugin(new CopyButtonPlugin());
hljs.configure({
cssSelector: 'pre code',
languages: ['python'],
ignoreUnescapedHTML: true
});
// Theme switching logic
function updateTheme() {
const isDark = document.documentElement.classList.contains('dark');
document.getElementById('hljs-dark').disabled = !isDark;
document.getElementById('hljs-light').disabled = isDark;
}
// Watch for theme changes
new MutationObserver(mutations =>
mutations.forEach(m => m.target.tagName === 'HTML' &&
m.attributeName === 'class' && updateTheme())
).observe(document.documentElement, { attributes: true });
// Initial setup
updateTheme();
htmx.onLoad(hljs.highlightAll);
''', type='module')
)
def create_flippable_card(content, source_code, extra_cls=None):
"Creates a card that flips between content and source code"
_id = 'f'+str(unqid())
Expand Down
1 change: 1 addition & 0 deletions monsterui/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
'monsterui/franken.py'),
'monsterui.franken.CardTitle': ('franken.html#cardtitle', 'monsterui/franken.py'),
'monsterui.franken.CheckboxX': ('franken.html#checkboxx', 'monsterui/franken.py'),
'monsterui.franken.CodeBlock': ('franken.html#codeblock', 'monsterui/franken.py'),
'monsterui.franken.CodeSpan': ('franken.html#codespan', 'monsterui/franken.py'),
'monsterui.franken.Container': ('franken.html#container', 'monsterui/franken.py'),
'monsterui.franken.ContainerT': ('franken.html#containert', 'monsterui/franken.py'),
Expand Down
67 changes: 49 additions & 18 deletions monsterui/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,10 @@
# %% ../nbs/01_core.ipynb
import fasthtml.common as fh
from .foundations import *
from fasthtml.common import is_listy, Div, P, Span, Script, FastHTML, FT, to_xml, show,fast_app
from fasthtml.svg import Svg
from fasthtml.components import Uk_theme_switcher, Main
from fasthtml.common import FastHTML, fast_app
from enum import Enum, auto
from fasthtml.components import Uk_select,Uk_input_tag,Uk_icon
from functools import partial
from itertools import zip_longest
from typing import Union, Tuple, Optional
from fastcore.all import *
import copy, re, httpx
import httpx
from pathlib import Path

# %% ../nbs/01_core.ipynb
Expand Down Expand Up @@ -67,7 +61,14 @@ def _headers_theme(color, mode='auto'):
'franken_js': "https://unpkg.com/[email protected]/dist/js/core.iife.js",
'icon_js': "https://cdn.jsdelivr.net/gh/answerdotai/monsterui@main/monsterui/icon.iife.js",
'tailwind': "https://cdn.tailwindcss.com",
'daisyui': "https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css"}
'daisyui': "https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css",
'highlight_js': "https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js",
'highlight_python': "https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/languages/python.min.js",
'highlight_light_css': "https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/atom-one-light.css",
'highlight_dark_css': "https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/atom-one-dark.css",
'highlight_copy': "https://cdn.jsdelivr.net/gh/arronhunt/highlightjs-copy/dist/highlightjs-copy.min.js",
'highlight_copy_css': "https://cdn.jsdelivr.net/gh/arronhunt/highlightjs-copy/dist/highlightjs-copy.min.css",
}

def _download_resource(url, static_dir):
"Download a single resource and return its local path"
Expand Down Expand Up @@ -96,7 +97,7 @@ def _download_resource(url, static_dir):

# %% ../nbs/01_core.ipynb
class Theme(Enum):
"Selector to choose theme and get all headers needed for app. Includes frankenui + tailwind"
"Selector to choose theme and get all headers needed for app. Includes frankenui + tailwind + daisyui + highlight.js options"
def _generate_next_value_(name, start, count, last_values): return name
slate = auto()
stone = auto()
Expand All @@ -111,25 +112,55 @@ def _generate_next_value_(name, start, count, last_values): return name
violet = auto()
zinc = auto()


def _create_headers(self, urls, mode='auto', daisy=True):
def _create_headers(self, urls, mode='auto', daisy=True, highlightjs=False):
"Create header elements with given URLs"
hdrs = [
fh.Link(rel="stylesheet", href=urls['franken_css']),
fh.Script(type="module", src=urls['franken_js']),
fh.Script(type="module", src=urls['icon_js']),
fh.Script(src=urls['tailwind']),
_headers_theme(self.value, mode=mode)]
if daisy:
hdrs += [fh.Link(rel="stylesheet", href=urls['daisyui']),daisy_styles]

if daisy:
hdrs += [fh.Link(rel="stylesheet", href=urls['daisyui']), daisy_styles]

if highlightjs:
hdrs += [
fh.Script(src=urls['highlight_js']),
fh.Script(src=urls['highlight_python']),
fh.Link(rel="stylesheet", href=urls['highlight_light_css'], id='hljs-light'),
fh.Link(rel="stylesheet", href=urls['highlight_dark_css'], id='hljs-dark'),
fh.Script(src=urls['highlight_copy']),
fh.Link(rel="stylesheet", href=urls['highlight_copy_css']),
fh.Script('''
hljs.addPlugin(new CopyButtonPlugin());
hljs.configure({
cssSelector: 'pre code',
languages: ['python'],
ignoreUnescapedHTML: true
});
function updateTheme() {
const isDark = document.documentElement.classList.contains('dark');
document.getElementById('hljs-dark').disabled = !isDark;
document.getElementById('hljs-light').disabled = isDark;
}
new MutationObserver(mutations =>
mutations.forEach(m => m.target.tagName === 'HTML' &&
m.attributeName === 'class' && updateTheme())
).observe(document.documentElement, { attributes: true });
updateTheme();
htmx.onLoad(hljs.highlightAll);
''', type='module'),
]

return hdrs

def headers(self, mode='auto', daisy=True):
def headers(self, mode='auto', daisy=True, highlightjs=False):
"Create frankenui and tailwind cdns"
return self._create_headers(HEADER_URLS, mode=mode, daisy=daisy)
return self._create_headers(HEADER_URLS, mode=mode, daisy=daisy, highlightjs=highlightjs)

def local_headers(self, mode='auto', static_dir='static'):
def local_headers(self, mode='auto', static_dir='static', daisy=True, highlightjs=False):
"Create headers using local files downloaded from CDNs"
Path(static_dir).mkdir(exist_ok=True)
local_urls = dict([_download_resource(url, static_dir) for url in HEADER_URLS.items()])
return self._create_headers(local_urls, mode=mode,daisy=daisy)
return self._create_headers(local_urls, mode=mode, daisy=daisy, highlightjs=highlightjs)
20 changes: 16 additions & 4 deletions monsterui/franken.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

# %% auto 0
__all__ = ['franken_class_map', 'TextT', 'TextFont', 'PParagraph', 'PLarge', 'PLead', 'PSmall', 'PMuted', 'CodeSpan',
'Blockquote', 'H1', 'H2', 'H3', 'H4', 'ButtonT', 'Button', 'ContainerT', 'Container', 'Titled', 'DividerT',
'Divider', 'DividerSplit', 'DividerLine', 'Article', 'ArticleTitle', 'ArticleMeta', 'SectionT', 'Section',
'Form', 'Fieldset', 'Legend', 'Input', 'Select', 'Radio', 'CheckboxX', 'Range', 'TextArea', 'Switch',
'FormLabel', 'LabelT', 'Label', 'UkFormSection', 'GenericLabelInput', 'LabelInput', 'LabelRange',
'Blockquote', 'CodeBlock', 'H1', 'H2', 'H3', 'H4', 'ButtonT', 'Button', 'ContainerT', 'Container', 'Titled',
'DividerT', 'Divider', 'DividerSplit', 'DividerLine', 'Article', 'ArticleTitle', 'ArticleMeta', 'SectionT',
'Section', 'Form', 'Fieldset', 'Legend', 'Input', 'Select', 'Radio', 'CheckboxX', 'Range', 'TextArea',
'Switch', 'FormLabel', 'LabelT', 'Label', 'UkFormSection', 'GenericLabelInput', 'LabelInput', 'LabelRange',
'LabelTextArea', 'LabelSwitch', 'LabelRadio', 'LabelCheckboxX', 'LabelSelect', 'Options', 'UkSelect',
'LabelUkSelect', 'AT', 'ListT', 'UkList', 'ModalContainer', 'ModalDialog', 'ModalHeader', 'ModalBody',
'ModalFooter', 'ModalTitle', 'ModalCloseButton', 'Modal', 'PaddingT', 'PositionT', 'Placeholder', 'Progress',
Expand Down Expand Up @@ -116,6 +116,18 @@ def Blockquote(*c:FT|str, # Contents of Blockquote tag (often text)
"Blockquote with Styling"
return fh.Blockquote(*c, cls=('uk-blockquote',stringify(cls)), **kwargs)

# %% ../nbs/02_franken.ipynb
def CodeBlock(*c: str, # Contents of Code tag (often text)
cls: Enum | str | tuple = (), # Classes for the outer container
code_cls: Enum | str | tuple = (), # Classes for the code tag
**kwargs # Additional args for Code tag
) -> FT: # Div(Pre(Code(..., cls='uk-codeblock), cls='multiple tailwind styles'), cls='uk-block')
"CodeBlock with Styling"
return Div(
Pre(Code(*c, cls=('uk-codeblock', stringify(code_cls)), **kwargs),
cls=('bg-gray-100 dark:bg-gray-800 dark:text-gray-200 p-0.4 rounded text-sm font-mono')),
cls=('uk-block', stringify(cls)))

# %% ../nbs/02_franken.ipynb
def H1(*c:FT|str, # Contents of H1 tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to H1 styling
Expand Down
Loading

0 comments on commit 390fada

Please sign in to comment.