Просмотр исходного кода

Timeline feed: posts are the only cards, titles are headings, one-row actions (#5920)

- The home and dashboard feeds use design box 4 (title, no background or
  padding) instead of 11, so post cards no longer sit inside a block card
  with the same 16px corners and a 16px inset.
- Post titles are h3 under the block's h2; CSS keeps their size and weight.
- On cards 24rem wide or less (phones), the action bar shows Comment, Like
  and Share as icons and keeps one row; Share used to wrap. The labels stay
  visually hidden and still name the controls. Outline cards keep labels.
- A finished feed shows no disabled Load More button. The bar stays only
  for the Timeline view's "jump to" control.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Andrey Yasko 1 день назад
Родитель
Сommit
6530f35f43

+ 26 - 1
modules/boonex/artificer/data/template/bx_timeline/css/view.css

@@ -88,8 +88,11 @@ div.bx-tl-item-content div.bx-tl-title {
        the box is a flex container. */
     overflow-wrap: anywhere;
 }
-div.bx-tl-item-content div.bx-tl-title div.bx-tl-title-cnt {
+div.bx-tl-item-content div.bx-tl-title .bx-tl-title-cnt {
     min-width: 0;
+    /* The title is an h3 under the block's h2; it keeps the size and weight its box sets. */
+    margin: 0;
+    font: inherit;
 }
 div.bx-tl-item-text div.bx-tl-content {
     position: relative;
@@ -398,3 +401,25 @@ div.bx-tl-bview-image.bx-tl-bvi-single {
 .bx-tl-view-item-comments .cmt-reply.cmt-reply-min {
     margin-bottom: 0;
 }
+
+/*--- Item Actions Bar ---*/
+/* On a narrow post card (a phone, an outline column) Comment, Like and Share don't fit beside the manage
+   button and Share used to drop to a second row. There the bar shows icons only. The labels stay in the
+   page, visually hidden, so they still name the controls. */
+div.bx-tl-item-cnt {
+    container-type: inline-size;
+}
+@container (max-width: 24rem) {
+    .bx-menu-object-bx_timeline_menu_item_actions .bx-menu-item-title,
+    .bx-menu-object-bx_timeline_menu_item_actions .sys-action-do-text {
+        position: absolute;
+        width: 1px;
+        height: 1px;
+        padding: 0;
+        margin: -1px;
+        overflow: hidden;
+        clip: rect(0, 0, 0, 0);
+        white-space: nowrap;
+        border-width: 0;
+    }
+}

+ 9 - 8
modules/boonex/timeline/classes/BxTimelineTemplate.php

@@ -1308,22 +1308,23 @@ class BxTimelineTemplate extends BxBaseModNotificationsTemplate
         $iStart = $aParams['start'];
         $iPerPage = $aParams['per_page'];
 
+        $bJumpTo = $aParams['view'] == BX_TIMELINE_VIEW_TIMELINE && $this->_oConfig->isJumpTo();
+
         $aTmplVars = array(
             'style_prefix' => $sStylePrefix,
-            'visible' => ($aParams['view'] == BX_TIMELINE_VIEW_TIMELINE && $bVisible) || ($aParams['view'] == BX_TIMELINE_VIEW_OUTLINE && $bEnabled && $bVisible) ? 'block' : 'none',
-            'disabled' => !$bEnabled ? ' disabled="disabled"' : '',
-            'bx_if:is_disabled' => array(
-                'condition' => !$bEnabled,
-                'content' => array()
-            ),
-            'bx_if:show_on_click' => array(
+            /*
+             * At the end of the feed there is nothing to load, so no button: a disabled one only reads as broken.
+             * The bar stays for the Timeline view's 'jump to' control when that is on.
+             */
+            'visible' => $bVisible && ($bEnabled || $bJumpTo) ? 'block' : 'none',
+            'bx_if:show_button' => array(
                 'condition' => $bEnabled,
                 'content' => array(
                     'on_click' => 'javascript:' . $sJsObject . '.changePage(this, ' . ($iStart + $iPerPage) . ', ' . $iPerPage . ')'
                 )
             ),
             'bx_if:show_jump_to' => array(
-                'condition' => $this->_oConfig->isJumpTo(),
+                'condition' => $bJumpTo,
                 'content' => array(
                     'style_prefix' => $sStylePrefix,
                     'content' => $this->getJumpTo($aParams)

+ 2 - 2
modules/boonex/timeline/install/sql/enable.sql

@@ -48,7 +48,7 @@ SET @iPBCellDashboard = 2;
 SET @iPBOrderDashboard = 1; --(SELECT IFNULL(MAX(`order`), 0) FROM `sys_pages_blocks` WHERE `object` = 'sys_dashboard' AND `cell_id` = @iPBCellDashboard LIMIT 1);
 INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `tabs`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
 ('sys_dashboard', @iPBCellDashboard, 'bx_timeline', '_bx_timeline_page_block_title_system_post_account', '_bx_timeline_page_block_title_post_account', 11, 0, 2147483644, 'service', 'a:2:{s:6:"module";s:11:"bx_timeline";s:6:"method";s:22:"get_block_post_account";}', 0, 1, 0, @iPBOrderDashboard),
-('sys_dashboard', @iPBCellDashboard, 'bx_timeline', '_bx_timeline_page_block_title_system_views_timeline', '_bx_timeline_page_block_title_views_timeline', 11, 1, 2147483644, 'service', 'a:3:{s:6:"module";s:11:"bx_timeline";s:6:"method";s:24:"get_block_views_timeline";s:6:"params";a:1:{i:0;s:4:"feed";}}', 0, 0, 0, @iPBOrderDashboard + 1),
+('sys_dashboard', @iPBCellDashboard, 'bx_timeline', '_bx_timeline_page_block_title_system_views_timeline', '_bx_timeline_page_block_title_views_timeline', 4, 1, 2147483644, 'service', 'a:3:{s:6:"module";s:11:"bx_timeline";s:6:"method";s:24:"get_block_views_timeline";s:6:"params";a:1:{i:0;s:4:"feed";}}', 0, 0, 0, @iPBOrderDashboard + 1),
 ('sys_dashboard', @iPBCellDashboard, 'bx_timeline', '_bx_timeline_page_block_title_system_views_outline', '_bx_timeline_page_block_title_views_outline', 11, 1, 2147483644, 'service', 'a:3:{s:6:"module";s:11:"bx_timeline";s:6:"method";s:23:"get_block_views_outline";s:6:"params";a:1:{i:0;s:4:"feed";}}', 0, 0, 0, @iPBOrderDashboard + 1);
 
 -- PAGES: add page block on home
@@ -56,7 +56,7 @@ SET @iPBCellHome = 3;
 SET @iPBOrderHome = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_pages_blocks` WHERE `object` = 'sys_home' AND `cell_id` = @iPBCellHome ORDER BY `order` DESC LIMIT 1);
 INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `tabs`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
 ('sys_home', @iPBCellHome, 'bx_timeline', '_bx_timeline_page_block_title_system_post_home', '_bx_timeline_page_block_title_post_home', 11, 0, 2147483644, 'service', 'a:2:{s:6:"module";s:11:"bx_timeline";s:6:"method";s:19:"get_block_post_home";}', 0, 1, 0, @iPBOrderHome + 1),
-('sys_home', @iPBCellHome, 'bx_timeline', '_bx_timeline_page_block_title_system_views_timeline', '_bx_timeline_page_block_title_views_timeline', 11, 1, 2147483644, 'service', 'a:3:{s:6:"module";s:11:"bx_timeline";s:6:"method";s:24:"get_block_views_timeline";s:6:"params";a:1:{i:0;s:4:"feed";}}', 0, 0, 1, @iPBOrderHome + 2);
+('sys_home', @iPBCellHome, 'bx_timeline', '_bx_timeline_page_block_title_system_views_timeline', '_bx_timeline_page_block_title_views_timeline', 4, 1, 2147483644, 'service', 'a:3:{s:6:"module";s:11:"bx_timeline";s:6:"method";s:24:"get_block_views_timeline";s:6:"params";a:1:{i:0;s:4:"feed";}}', 0, 0, 1, @iPBOrderHome + 2);
 
 -- The public outline on the guests' home page comes inactive: a new site has no posts to show yet. Studio can turn it on.
 SET @iPBCellHome = 5;

+ 4 - 1
modules/boonex/timeline/template/css/view.css

@@ -455,8 +455,11 @@ div.bx-tl-item-content div.bx-tl-title {
        the box is a flex container. */
     overflow-wrap: anywhere;
 }
-div.bx-tl-item-content div.bx-tl-title div.bx-tl-title-cnt {
+div.bx-tl-item-content div.bx-tl-title .bx-tl-title-cnt {
     min-width: 0;
+    /* The title is an h3 under the block's h2; it keeps the size and weight its box sets. */
+    margin: 0;
+    font: inherit;
 }
 div.bx-tl-item-content div.bx-tl-content iframe[src*='video'] {
     width: 100%;

+ 5 - 3
modules/boonex/timeline/template/load_more.html

@@ -1,8 +1,10 @@
 <div class="__style_prefix__-load-more bx-def-padding-sec-top bx-clearfix" style="display:__visible__;">
     <div class="__style_prefix__-load-more-cnt flex items-center justify-between">
-        <button class="bx-btn<bx_if:is_disabled> bx-btn-disabled</bx_if:is_disabled>"__disabled__ <bx_if:show_on_click>onclick="__on_click__"</bx_if:show_on_click>>
-            <bx_text:_bx_timeline_txt_load_more />
-        </button>
+        <bx_if:show_button>
+            <button class="bx-btn" onclick="__on_click__">
+                <bx_text:_bx_timeline_txt_load_more />
+            </button>
+        </bx_if:show_button>
         <bx_if:show_jump_to>
             <div class="__style_prefix__-jump-to">__content__</div>
         </bx_if:show_jump_to>

+ 1 - 1
modules/boonex/timeline/template/type_post.html

@@ -25,7 +25,7 @@
 <div class="__style_prefix__-item-text mt-3 md:mt-4 mx-3 md:mx-4">
     <bx_if:show_title>
         <div class="__style_prefix__-title flex flex-wrap items-center mb-2 md:mb-4 text-lg lg:text-xl font-semibold text-gray-700 dark:text-gray-300">
-            <div class="__style_prefix__-title-cnt mr-2">__title__</div>__badges__
+            <h3 class="__style_prefix__-title-cnt mr-2">__title__</h3>__badges__
         </div>
     </bx_if:show_title>
     <bx_if:show_content>

+ 56 - 0
tests/units/modules/boonex/timeline/classes/BxTimelineFeedMarkupTest.php

@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Feed markup: post titles are headings, a finished feed shows no disabled Load More, and the action bar keeps
+ * one row on narrow cards.
+ */
+class BxTimelineFeedMarkupTest extends BxDolTestCase
+{
+    private static function read(string $sPath): string
+    {
+        $s = file_get_contents(__DIR__ . '/../../../../../../' . $sPath);
+        if ($s === false)
+            self::fail($sPath . ' is not readable.');
+
+        return str_replace("\r\n", "\n", $s);
+    }
+
+    public function testPostTitleIsAHeading(): void
+    {
+        $this->assertStringContainsString('<h3 class="__style_prefix__-title-cnt mr-2">__title__</h3>', self::read('modules/boonex/timeline/template/type_post.html'), 'Post titles are h3 under the block\'s h2, so screen reader users can jump between posts.');
+
+        foreach (['modules/boonex/timeline/template/css/view.css', 'modules/boonex/artificer/data/template/bx_timeline/css/view.css'] as $sPath)
+            $this->assertMatchesRegularExpression('/div\.bx-tl-title \.bx-tl-title-cnt \{[^}]*margin: 0;[^}]*font: inherit;/', self::read($sPath), "$sPath keeps the heading at the title's own size.");
+    }
+
+    public function testActionBarHidesLabelsOnNarrowCards(): void
+    {
+        $s = self::read('modules/boonex/artificer/data/template/bx_timeline/css/view.css');
+
+        $this->assertMatchesRegularExpression('/div\.bx-tl-item-cnt \{\s*container-type: inline-size;/', $s);
+        $this->assertMatchesRegularExpression('/@container \(max-width: 24rem\) \{\s*\.bx-menu-object-bx_timeline_menu_item_actions \.bx-menu-item-title,\s*\.bx-menu-object-bx_timeline_menu_item_actions \.sys-action-do-text \{[^}]*clip: rect\(0, 0, 0, 0\);/', $s, 'Labels are visually hidden, not removed, so they still name the controls.');
+    }
+
+    public function testFinishedFeedHasNoDisabledLoadMore(): void
+    {
+        bx_import('BxDolModule');
+        $oModule = BxDolModule::getInstance('bx_timeline');
+        if (!$oModule)
+            $this->markTestSkipped('bx_timeline module is not installed.');
+
+        $oTemplate = $oModule->_oTemplate;
+        if ($oTemplate instanceof BxDolModuleProxy)
+            $oTemplate = $this->bxGetProtected($oTemplate, '_oProxifiedObject', BxDolModuleProxy::class);
+
+        $aParams = ['view' => BX_TIMELINE_VIEW_OUTLINE, 'type' => 'public', 'start' => 0, 'per_page' => 10];
+
+        $s = $oTemplate->getLoadMore($aParams, true);
+        $this->assertStringContainsString('<button class="bx-btn" onclick=', $s);
+        $this->assertStringContainsString('display:block', $s);
+
+        $s = $oTemplate->getLoadMore($aParams, false);
+        $this->assertStringNotContainsString('<button', $s, 'Nothing more to load: no button at all.');
+        $this->assertStringNotContainsString('bx-btn-disabled', $s);
+        $this->assertStringContainsString('display:none', $s);
+    }
+}

+ 11 - 0
tests/units/modules/boonex/timeline/install/TimelineEnableSqlTest.php

@@ -31,4 +31,15 @@ class TimelineEnableSqlTest extends \PHPUnit\Framework\TestCase
         $this->assertSame(1, $bFound, 'enable.sql adds the members\' feed to the home page.');
         $this->assertSame(1, (int)$aMatch[1], 'Members keep their feed on the home page.');
     }
+
+    public function testHomeAndDashboardFeedsHaveNoCardAroundThePosts(): void
+    {
+        // Each post is a card, so the block keeps its title and tabs but drops the background and padding (design box 4).
+        foreach (['sys_dashboard' => '@iPBCellDashboard', 'sys_home' => '@iPBCellHome'] as $sObject => $sCell) {
+            $bFound = preg_match("/\\('" . $sObject . "', " . preg_quote($sCell, '/') . ", 'bx_timeline', '_bx_timeline_page_block_title_system_views_timeline', '_bx_timeline_page_block_title_views_timeline', (\\d+),/", self::enableSql(), $aMatch);
+
+            $this->assertSame(1, $bFound, "enable.sql adds the feed to $sObject.");
+            $this->assertSame(4, (int)$aMatch[1], "The $sObject feed uses design box 4 (content + title).");
+        }
+    }
 }