diff --git a/src/moin/apps/frontend/views.py b/src/moin/apps/frontend/views.py index 5980c4f60..b5f0021e5 100644 --- a/src/moin/apps/frontend/views.py +++ b/src/moin/apps/frontend/views.py @@ -685,6 +685,23 @@ def content_item(item_name, rev): return render_template("content.html", item_name=item.name, data_rendered=Markup(item.content._render_data())) +@frontend.route("/+slideshow/", defaults=dict(rev=CURRENT)) +def slide_item(item_name, rev): + """same as show_item, but we only show the content""" + fqname = split_fqname(item_name) + item_displayed.send(app, fqname=fqname) + try: + item = Item.create(item_name, rev_id=rev) + except AccessDenied: + abort(403) + if isinstance(item, NonExistent): + abort(404, item_name) + data_rendered = Markup(item.content._render_data_slide()) + return render_template( + "slideshow.html", item_name=item.name, full_name=fqname.fullname, data_rendered=data_rendered + ) + + @presenter("get") def get_item(item): return item.content.do_get() diff --git a/src/moin/constants/misc.py b/src/moin/constants/misc.py index c7dfb63a5..6e6fb477d 100644 --- a/src/moin/constants/misc.py +++ b/src/moin/constants/misc.py @@ -72,7 +72,7 @@ LOCK = "lock" # Valid views allowed for itemlinks -VALID_ITEMLINK_VIEWS = ["+meta", "+history", "+download", "+highlight"] +VALID_ITEMLINK_VIEWS = ["+meta", "+history", "+download", "+highlight", "+slideshow"] # Transient attribute added/removed to/from flask session. Used when a User Settings # form creates a flash message but then redirects the page making the flash message a diff --git a/src/moin/items/content.py b/src/moin/items/content.py index f8add72ab..9bc0ebff3 100644 --- a/src/moin/items/content.py +++ b/src/moin/items/content.py @@ -336,6 +336,55 @@ def _get_data_diff_text(self, oldfile, newfile): """ return [] + def _render_data_slide(self, preview=None): + try: + from moin.converters import default_registry as reg + + doc = self.internal_representation(preview=preview) + doc = self._expand_document(doc) + + slide_pages = [] + before_first_header = True + for elem1 in doc: + single_slide = [] + for element in elem1: + if element.tag.name == "h" and element.get(moin_page("outline-level")) in ["1", "2"]: + if before_first_header: + before_first_header = False # ignore everything before + else: + slide_pages.append(single_slide) + single_slide = [] + single_slide.append(element) + slide_pages.append(single_slide) + print(f"{len(slide_pages)} slides found.") + + flaskg.clock.start("conv_dom_html") + html_conv = reg.get(type_moin_document, Type("application/x-xhtml-moin-page")) + + slide_content = [] + attrib = {moin_page.class_: "moin-slides"} + for slide in slide_pages: + slide_content.append(moin_page.div(attrib=attrib, children=slide)) + + body = moin_page.body(children=slide_content) + root = moin_page.page(children=[body]) + doc = html_conv(root) + rendered_data = conv_serialize(doc, {html.namespace: ""}) + flaskg.clock.stop("conv_dom_html") + + except Exception: + # we really want to make sure that invalid data or a malfunctioning + # converter does not crash the item view (otherwise a user might + # not be able to fix it from the UI). + error_id = uuid.uuid4() + logging.exception(f"An exception happened in _render_data (error_id = {error_id} ):") + rendered_data = [ + render_template( + "crash.html", server_time=time.strftime("%Y-%m-%d %H:%M:%S %Z"), url=request.url, error_id=error_id + ) + ] + return rendered_data + def get_templates(self, contenttype=None): """create a list of templates (for some specific contenttype)""" terms = [ diff --git a/src/moin/macros/SlideShow.py b/src/moin/macros/SlideShow.py new file mode 100644 index 000000000..7a5e2ce8e --- /dev/null +++ b/src/moin/macros/SlideShow.py @@ -0,0 +1,18 @@ +# Copyright: 2024 MoinMoin:UlrichB +# License: GNU GPL v2 (or any later version), see LICENSE.txt for details. + +""" +Create link to start a SlideShow for the current item +""" + +from moin.i18n import _ +from moin.utils.tree import moin_page, xlink +from moin.macros._base import MacroInlineBase + + +class Macro(MacroInlineBase): + def macro(self, content, arguments, page_url, alternative): + attrib = {moin_page.class_: "fa-regular fa-circle-play"} + children = [moin_page.span(attrib=attrib), _(" Start SlideShow")] + result = moin_page.a(attrib={xlink.href: f"/+slideshow{page_url.path}"}, children=children) + return result diff --git a/src/moin/static/css/projection.css b/src/moin/static/css/projection.css new file mode 100644 index 000000000..67f1d51cd --- /dev/null +++ b/src/moin/static/css/projection.css @@ -0,0 +1,88 @@ +/* projection.css - MoinMoin Slide Styles + +Copyright (c) 2003 by Juergen Hermann +Copyright (c) 2024 by MoinMoin project +*/ + +html { line-height: 1.8em; } + +body, b, em, a, span, div, p, td { font-size: 18pt; } + +h1 { font-size: 24pt; padding-top: 24px; color: #33F; text-align: left; } +h2 { font-size: 22pt; padding-top: 24px; color: #33F; } +h3 { font-size: 20pt; } +h4 { font-size: 18pt; } +h5 { font-size: 16pt; } +h6 { font-size: 14pt; } + +tt, pre { font-size: 14pt; } +sup, sub { font-size: 10pt; } + +#moin-content-data { padding-left: 50px; margin: 0 2%; } + +@media print { + h1 { padding-top: 50px; } + h2 { page-break-before: always; padding-top: 50px; } + h3, h4 { page-break-after: avoid; } + pre, blockquote { page-break-inside: avoid; } +} + +* {box-sizing:border-box} + +/* Slideshow container */ +.slideshow-container { + max-width: 1000px; + position: relative; + margin: auto; +} + +/* navigation container */ +.navi-container { + position: fixed; + bottom: 15px; + text-align: center; + margin: 0; + left: 50%; +} + +/* Next and previous slide button */ +.prev, .next { + cursor: pointer; + position: fixed; + top: 50%; + width: auto; + padding: 0 20px 40% 20px; + color: lightgray; + font-weight: bold; + font-size: 18px; + transition: 0.6s ease; + border-radius: 0 3px 3px 0; + user-select: none; +} + +/* Move the "next" button to the right */ +.next { + right: 0; + border-radius: 3px 0 0 3px; +} + +a.prev:hover, a.next:hover { + color: darkgray; + text-decoration: none; + font-size: 24px; +} + +/* navigation */ +a.slide-nav, .slide-nav { + font-size: 18px; + padding: 0 7px; + color: lightgray; + text-align: center; + cursor: pointer; + display: inline-block; + transition: 0.6s ease; +} + +.active, .slide-nav:hover { + color: darkgray; +} diff --git a/src/moin/templates/slideshow.html b/src/moin/templates/slideshow.html new file mode 100644 index 000000000..0c27d4d94 --- /dev/null +++ b/src/moin/templates/slideshow.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% block layout %} + + {% if data_rendered %} + +
+
+ {{ data_rendered }} +
+
+ {% endif %} + + + + + + + + + +{% endblock %}