Как автоматически подстраивать высоту iframe для встроенных записей WordPress

Я сейчас работаю над пользовательским шаблоном embed для записей WordPress, чтобы встраивать их через плагин wp-discourse в iframe.

В настоящее время в запись можно добавить только фиксированную высоту iframe. Запись выглядит так:
https://forum.cannabisanbauen.net/t/test-embed-fuer-ca-blog/6843

Используемый HTML:

<iframe src="https://wordpress-92041-921046.cloudwaysapps.com/growbox-dimensionieren/" width="1200" height="2000" frameborder="0"></iframe>
  1. Есть ли способ настроить высоту iframe в переменном режиме, чтобы она подстраивалась под размер встроенного контента?
  2. Поскольку это будет вставлено в шаблон wp-discourse (как это сделал @simon здесь), есть ли способ динамически задавать высоту iframe на основе каких-либо параметров записи WordPress (например, количества символов или подобных показателей)?

Я тоже пытаюсь это сделать, так как понял: если я буду встраивать свои посты из WordPress в Discourse, то смогу направлять пользователей сразу на Discourse, а не на два разных сайта.

Кто-нибудь разобрался, как это реализовать? Я уже какое-то время бьюсь над этим.

Вы используете что-то подобное для добавления iframe:

function your_namespace_publish_format_html( $output ) {
    global $post;

    if ( 'my_iframe_post_type' === $post->post_type) {
	ob_start();

	?>
    <iframe width="690" height="600" src="<?php echo esc_url( the_permalink() ); ?>" frameborder="0"></iframe>
	<?php
	$output = ob_get_clean();

	// Возвращаем iframe для этого типа записи.
	return $output;
    }

    // Возвращаем вывод по умолчанию или делаем что-то другое с ним здесь.
    return $output;
}
add_filter( 'discourse_publish_format_html', 'your_namespace_publish_format_html' );

но хотите установить свойство height в зависимости от высоты записи?

Если да, то насколько точно это должно быть? Мне интересно, нельзя ли просто посчитать количество символов в записи, затем добавить высоту любых изображений или других элементов, у которых задана высота, и добавить немного сверху для надёжности. У меня могут быть предложения, как это сделать.

Также возможно использовать JavaScript, чтобы получить точную высоту записи после её отображения при заданной ширине.

Ах, я ещё не дошёл до интеграции этого в фильтр discourse_publish_format_html. Я просто вручную добавлял это в пост на Discourse и пытался разобраться с JavaScript для определения высоты содержимого внутри iframe и последующего изменения его размера.

Я видел множество руководств по этому вопросу в интернете, но по какой-то причине не могу заставить этот JS работать на Discourse. Я пробовал использовать события изменения страницы и decorateCookedElement в API, но всё равно не могу добиться успеха.

Если возможно, я предпочёл бы реализовать это непосредственно в Discourse, чтобы иметь возможность изменять размер iframe на полную высоту даже при встраивании контента с сайтов, не использующих WordPress.

Это интересная проблема. Что произойдёт, если вручную изменить атрибут height элемента iframe в посте на Discourse? После изменения высоты и просмотра поста будет ли применена новая высота или необходимо пересобрать (rebake) пост на Discourse, чтобы изменения вступили в силу?

Редактирование: это достаточно интересно, поэтому я проверю это позже сегодня.

Если я добавлю это в тег iframe с height="400px", то после перезагрузки страницы он изменит размер. Если же указать height="100%", то, похоже, ничего не происходит.

Если я добавлю CSS-свойства для iframe, высота тоже меняется корректно, по крайней мере, на то значение, которое я ввожу вручную.

Уф, наконец-то получилось. Проблема была в том, что я использовал пользовательский класс для тега iframe, а Discourse удаляет все такие теги. Мне стоило сделать выводы из ситуации несколько дней назад (Formatting posts to look more like Wordpress blog - #6 by jimkleiber). Я читал, что это ограничение связано с безопасностью, из-за чего пользовательские HTML-классы здесь не работают, но не совсем понимаю почему :confused:

Тем не менее, вот код для подстройки высоты iframe с помощью селектора .topic-body iframe (не уверен, что он подойдёт всем, но у меня работает):

<script type="text/discourse-plugin" version="0.8.18">
    api.decorateCookedElement(
      element => {
        setTimeout(function() {
          let iframes = element.querySelectorAll('.topic-body iframe');
          if (iframes) {
            iframes.forEach(function(iframe) {
              iframe.onload = function() {
                let iframeDocument = this.contentDocument || this.contentWindow.document;
                let contentHeight = Math.max(
                  iframeDocument.body.scrollHeight,
                  iframeDocument.documentElement.scrollHeight
                ) + 'px';
                this.style.height = contentHeight;
              };
            });
          }
        }, 5000); // При необходимости измените задержку                  
      },
      { id: "component-id", onlyStream: true}
    );
</script>

РЕДАКТИРОВАНИЕ: на самом деле, это не работает. Я добавил height="4000px" прямо в тег iframe, и именно поэтому всё выглядело рабочим, думаю.

Это сложная проблема. Я не думаю, что легко получить доступ к содержимому iframe изнутри Discourse. Возможно, это удастся реализовать, если источник iframe и ваш сайт Discourse находятся на разных поддоменах одного и того же домена.

Если я правильно понял проблему, вам нужно установить document.domain как на поддомене, с которого вы загружаете контент, так и в скрипте, который выполняется на Discourse, указав корневой домен.

Если источник iframe на самом деле является вашим корневым доменом, попробуйте изменить скрипт следующим образом:

<script type="text/discourse-plugin" version="0.8.18">
   document.domain = "your_root_domain.com"; // замените на ваш домен
    api.decorateCookedElement(
      element => {

Если домен iframe также является поддоменом вашего корневого домена, вам также нужно установить document.domain на корневого домена на нём.

Для стилизации iframe (или чего-либо ещё) на Discourse вы можете обернуть содержимое в div с атрибутом данных:

<div data-full-height>
<iframe src="http://wp-discourse.test/zalg_iframe/this-is-a-test-this-is-only-a-test/" height="600" width="690"></iframe>
</div>

CSS темы:

[data-full-height] > iframe {
      // при желании здесь можно стилизовать внешний iframe: высота, ширина и т.д.
     // к сожалению, height: 100% не сработает — содержащий элемент iframe не имеет заданной высоты.
}

Если подход с document.domain не сработает, могут существовать другие решения — например, использование window.postMessage для общения между родительским документом iframe и Discourse.

Я не думаю, что моя первоначальная идея вычислять высоту iframe на его исходном сайте сработает — ширина отрендеренного iframe будет варьироваться в зависимости от устройства, на котором просматривается Discourse.

Хм, мой форум является поддоменом моего корневого домена, поэтому я добавил корневой домен, как вы предложили, и получил эту ошибку:

Uncaught DOMException: Failed to read a named property 'document' from 'Window': Blocked a frame with origin

Теперь я пытаюсь использовать механизм postMessage, но у меня не получается. Думаю, мне нужно лучше понять, как работает JavaScript в Discourse. Мне кажется, я предполагаю, что он работает так же, как на других сайтах, а может быть, это не так, или, возможно, я просто недостаточно хорошо знаю JavaScript, особенно в части работы с кросс-доменными запросами, lol.

Спасибо, что уделили время и посмотрели это!

Я давно задумывался об этом. Вот доказательство концепции (обратите внимание, что это не решает проблему удаления полос прокрутки из iframe).

Добавьте тег script в пост, который вы встраиваете:

<script>
    function sendHeight() {
        const body = document.body,
            html = document.documentElement;

        const height = Math.max(body.scrollHeight, body.offsetHeight,
            html.clientHeight, html.scrollHeight, html.offsetHeight);

        window.parent.postMessage({
            'iframeHeight': height,
            'iframeId': 'zalgFrame' // Используйте уникальный идентификатор, если у вас несколько iframe
        }, '*'); // Для безопасности лучше указать домен родительского окна
    }

    // Отправить начальную высоту
    window.onload = sendHeight;

    // Опционально: обновлять высоту при изменении размера окна или других событиях
    window.onresize = sendHeight;
</script>

В скрипте я использую идентификатор "zalgFrame".

В вашей теме Discourse:

<script type="text/discourse-plugin" version="1.29.0">
let iframeHeight, iframeId;
window.addEventListener('message', (event) => {
  if (event.origin !== "http://wp-discourse.test") return; // мой тестовый домен, замените на свой или закомментируйте
  // получаем высоту iframe, переданную из `wp-discourse.test`, и проверяем, совпадает ли iframeId с установленным мной
  if (event.data.iframeHeight && event.data.iframeId === 'zalgFrame') {
      // откройте страницу Discourse с iframe и консолью разработчика
      // вы увидите, как обновляются высоты, отправляемые с родительского сайта при изменении размера окна
      console.log("мы получили событие:" + event.data.iframeHeight); 
      iframeHeight = event.data.iframeHeight;
      iframeId = event.data.iframeId;
  }
  }, false);
</script>

В посте Discourse:

<div data-iframe-test-one>
<iframe src="http://wp-discourse.test/zalg_iframe/this-is-a-test-this-is-only-a-test/" width="100%" height="1659"></iframe>
</div>

Таким образом, можно получить фактическую высоту отрендеренного iframe из родительского окна.

Однако я не знаю, как передать высоту из данных обработчика событий в вызов api.decorateCookedElement. Кроме того, я не уверен, что это вообще поможет убрать вертикальную полосу прокрутки у длинных iframe. Если я попробую жестко задать большую высоту (1600px) в элементе iframe, полоса прокрутки всё равно появится.

Редактирование: для полноты картины:

<script type="text/discourse-plugin" version="1.29.0">
api.decorateCookedElement(
  (e) => {
    let iframeHeight, iframeId;

    function handleMessage(event) {
      if (event.origin !== "http://wp-discourse.test") return;
      if (event.data.iframeHeight && event.data.iframeId === "zalgFrame") {
        iframeHeight = event.data.iframeHeight;
        iframeId = event.data.iframeId;
        // исходя из предположения, что внутри div с атрибутом data-zalgFrame будет только один iframe
        let iframe = e.querySelector("[data-zalgFrame] iframe");
        if (iframe) {
          iframe.style.height = `${iframeHeight}px`;
        }
        // после установки фактической отрендеренной высоты iframe
        // удаляем обработчик событий
        window.removeEventListener("message", handleMessage, false);
      }
    }
    window.addEventListener("message", handleMessage, false);
  },
  { id: "component-id", onlyStream: true }
);
</script>

Для любого контента высотой более ~1000px кажется, что нет способа избежать добавления полосы прокрутки со стороны Discourse, поэтому я не рекомендую этот подход.

Я думаю, что ответ на вопрос автора оригинального поста (OP) таков: это в какой-то степени возможно, но, вероятно, не приносит особой пользы. (За исключением того, что я узнал о методе window.postMessage() :))

Я ценю ваши героические усилия и не хочу их обесценивать, однако должен признаться, что я немного скептически отношусь к предпосылке этой темы, а именно:

У меня есть два (искренних) вопроса к тебе, Джим:

  1. Почему ты хочешь использовать iframe здесь вместо обычной функции встраивания тем?
  2. Мне интересно, зачем тебе вообще нужен сайт WordPress, если ты не хочешь, чтобы пользователи потребляли контент там?

Я не могу говорить за автора оригинальной темы, но могу ответить за себя:

Я написал несколько плагинов для WordPress, которые позволяют мне создать плеер подкастов с интерактивными транскриптами (слова подсвечиваются по мере воспроизведения аудио, и можно кликнуть, чтобы перейти к этой части), интерактивными главами и заметками к выпуску, а также с возможностью поиска, сортировки и фильтрации плейлиста.

Поэтому, если просто встроить их здесь без iframe, я не смогу получить доступ к JavaScript и всем стилям, которые я в них внедрил.

Кстати, мне гораздо проще собирать такие вещи на WordPress, чем на Discourse; я действительно испытываю трудности с написанием JavaScript и созданием плагинов здесь.

Для размещения эпизодов подкастов мне всё равно понадобится сайт на WordPress. Но относительно того, хочу ли я, чтобы пользователи потребляли контент там, я не уверен. Поскольку я использую Discourse для комментариев на WordPress, интерактивность снизилась. Раньше люди писали комментарии на WordPress, но для работы с Discourse им нужно пересечь границу домена, и тогда они взаимодействуют в отдельном месте. Если бы Discourse сделал проще публикацию в встроенном виде на форуме WordPress, я бы, вероятно, сосредоточился на этом.

Не уверен, что это необходимо, но у меня есть ощущение, что мне нужно одно основное место для сбора людей. Раньше я думал, что это будет WordPress с встроенными комментариями и публикациями Discourse, но теперь я считаю, что Discourse со встроенными публикациями WordPress может быть проще и с большей вероятностью вдохновит людей на взаимодействие друг с другом.

Круто!

Почему?

Понимаю! Тем не менее, в зависимости от вашего ответа на мой предыдущий вопрос («Почему?»), корректная вставка в пост Discourse была бы более стабильным решением, чем динамический iframe.

Мне жаль слышать о снижении активности, и я тоже понимаю, что вы имеете в виду. На ум приходит более общая тема Саймона на эту тему:

Я имею в виду, может, я и смогу? Раньше мне с трудом удавалось заставить аудиоплеер использовать mediaelement.js здесь, и я думаю, что плохо понимаю API плагина. Это просто кажется огромной работой, которую я, возможно, смогу выполнить в долгосрочной перспективе, но сейчас вариант с встраиванием через iframe выглядит вполне неплохо. Основная проблема — это поиск по тексту, встроенному в iframe, но я подумал опубликовать этот текст в посте и скрыть его или разместить под аккордеоном, чтобы он всё равно индексировался в поиске.

К тому же, я считаю, что более серьёзная проблема заключается в том, что при обработке контента (или как это ещё называют, lol) удаляется так много HTML-классов. Поэтому попытка просто опубликовать пост из WordPress здесь и использовать похожие стили CSS требует огромного количества переделок. Это и вдохновило меня написать следующее:

Понятно. Дайте мне немного подумать об этом. У меня нет каких-либо существенных идей по поводу динамических iframe, кроме того, что уже предложил Саймон, однако ваш случай заставляет меня немного задуматься.

Стоит отметить, что я активно работаю над этим (используя Discourse для системы комментариев на веб-сайтах). В данный момент основное внимание уделяется headless-сайтам на WordPress, но общий подход может быть полезен и для обычных сайтов на WordPress, а также для сайтов на других платформах.

Не помню, где я это видел, но есть скрытое максимальное значение высоты, думаю, 1000px, возможно, для обработанного контента?

Так что, возможно, это мешает вашему решению.

Я посмотрю завтра :folded_hands:t2:

Это касается элементов iframe:

iframe {
  max-width: 100%;
  max-height: #{"min(1000px, 200vh)"};
}

Это можно исправить в теме, указав селектор для iframe с атрибутом данных:

[data-zalgFrame] > iframe {
    max-height: 100%;
    border: none;
}

Это изменение необходимо для отображения более длинных iframe, но полосы прокрутки, которые я видел, генерировались самой страницей внутри iframe. Хороших результатов я добиваюсь только создавая специальные версии постов для встраивания на своём блоге — по сути, оставляя только содержимое поста и немного подстраивая стили. Например, используя пользовательский тип записей в WordPress:

single-zag_iframe.php
<?php
if ( have_posts() ) : while ( have_posts() ) : the_post();
?>
<style>
    body {
        overflow: hidden;
        height: 100%;
    }
    article.zalg-iframe {
        width: 100%;
        height: 100%;
        margin-left: auto;
        margin-right: auto;
        font-size: 1.25em;
        word-break: break-word;
    }
    article.zalg-iframe img {
        max-width: 100%;
        height: auto;
    }
</style>
<article class="zalg-iframe">
    <?php
    the_content();
    ?>
</article>
<script>
    function sendHeight() {
        const body = document.body,
            html = document.documentElement;
        // Немного наугад, но, думаю, `scrollHeight` — правильный выбор для этого случая
        const height = Math.max(body.scrollHeight, body.offsetHeight,
            html.clientHeight, html.scrollHeight, html.offsetHeight);

        window.parent.postMessage({
            'iframeHeight': height,
            'iframeId': 'zalgFrame'
        }, '*');
    }

    // Отправка начальной высоты
    window.onload = sendHeight;

    // Опционально: обновление высоты при изменении размера или других событиях
    window.onresize = sendHeight;
</script>
<?php
endwhile; endif;

Возможно, потребуется немного поэкспериментировать, чтобы это заработало на продакшн-сайте, но, похоже, стоит уделить этому больше внимания.

Отлично, у меня получилось. Думаю, была какая-то ошибка в другом месте JS, которая мешала. Ура! Похоже, всё отлично интегрировалось в мой сайт. Большое спасибо, @simon!