plone / plone.app.standardtiles

Plone Standard tiles (reflecting viewlets et al) to be used with Plone Mosaic
Other
7 stars 12 forks source link

Better embed, YT #138

Open jensens opened 1 year ago

jensens commented 1 year ago

To not forget about it I create this issue. FTR:

I wrote an alternative embed tile for a customer last year and now I need it again. The current embed tile was not sufficient in both case. It is very simple and provides a tile with 100% width in its row, aspect ration kept, settings kept. You just need to paste the Youtube embed code into a text field.

This could be extended for similar video embeds (Iframe based).

Here is the code

from plone.supermodel.model import Schema
from plone.tiles import Tile
from zope import schema
from lxml import etree
from lxml import html

import logging

logger = logging.getLogger(__name__)

class IYTEmbedTile(Schema):
    """Youtube Embed tile."""

    media_embed = schema.Text(title="Youtube Media Embed code", required=True)

class YTEmbedTile(Tile):
    """A tile that embeds Youtube."""

    def __call__(self):
        # parse embed code
        embed_code = self.data.get("media_embed")
        try:
            parsed = html.fromstring(
                '<div class="youtube-responsive-wrapper">{}</div>'.format(embed_code)
            )
        except Exception:
            logger.exception(f"Embed parsing problem with:\n{embed_code}")
            return "<div>Malformated embed code</div>"
        children = parsed.getchildren()
        if not (
            children
            and len(children) == 1
            and children[0].tag == "iframe"
            and "src" in children[0].attrib
            and "youtube" in children[0].attrib["src"]
        ):
            return "<div>Invalid embed code (no iframe)</div>"
        iframe = children[0]
        if not ("height" in iframe.attrib and "width" in iframe.attrib):
            return "<div>Invalid embed code (measures missing)</div>"
        try:
            height, width = int(iframe.attrib["height"]), int(iframe.attrib["width"])
        except Exception:
            return "<div>Invalid embed code (measure no numbers)</div>"
        del iframe.attrib["height"]
        del iframe.attrib["width"]
        parsed.attrib["style"] = f"padding-bottom: {(height/width)*100}%"
        new_iframe = etree.tostring(parsed, pretty_print=True).decode("utf8")
        return f"<html><body>{new_iframe}</body></html>"

ZCML:

  <!-- ytembed tile  -->
  <plone:tile
      name="kup.tiles.ytembed"
      title="YT Embed"
      description="Paste an Youtube embed code and get an tile-width video"
      for="*"
      schema=".ytembed.IYTEmbedTile"
      class=".ytembed.YTEmbedTile"
      permission="zope2.View"
      add_permission="cmf.ModifyPortalContent"
      />

Styles:

      .youtube-responsive-wrapper {
        position: relative;
        padding-top: 0;
        height: 0;
        overflow: hidden;
        iframe {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
        }
      }

Also some registry.xml was needed:

  <record name="plone.app.tiles">
    <field type="plone.registry.field.List">
      <title>Tiles</title>
      <value_type type="plone.registry.field.TextLine" />
    </field>
    <value purge="false">
      <element>kup.tiles.ytembed</element>
    </value>
  </record>
  <records interface="plone.app.mosaic.interfaces.ITile"
           prefix="plone.app.mosaic.app_tiles.kup_tiles_ytembed"
  >
    <value key="name">kup.tiles.ytembed</value>
    <value key="label">YT Embed</value>
    <value key="category">media</value>
    <value key="tile_type">app</value>
    <value key="default_value" />
    <value key="read_only">false</value>
    <value key="settings">true</value>
    <value key="favorite">false</value>
    <value key="rich_text">false</value>
    <value key="weight">21</value>
  </records>