[TikiWiki-commits] [Git][tikiwiki/tiki][master] [ENH]: Search usability: when searching for related items to associate to a...
Benoit Grégoire (@benoitg) via TikiWiki-cvs <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <69ba900e2d1be_3bc3bedc948bf@gitlab-sidekiq-low-urgency-cpu-bound-v2-7845844f5c-9wdmd.mail> |
Benoit Grégoire pushed to branch master at Tiki Wiki CMS Groupware / Tiki
Commits:
bdc02b36 by Grace Nshokano at 2026-03-18T11:36:36+00:00
[ENH]: Search usability: when searching for related items to associate to a tiki tracker, search results should have a popover tooltip for details (such as extended description)
---
* [FIX] search: stop silently ignoring tracker popup errors
* fix(search): use Tiki popover-md and render tracker infobox as a bordered table
* refactor(search): replace hand-rolled popup HTML with Services_Object_Controller::getTrackerItemPopupContent()
* [FIX] related search popup: remove tracker 6 hardcode and use dynamic popup fields fallback
* [FIX] fix: adding the popover
See merge request tikiwiki/tiki!8851
- - - - -
7 changed files:
- lib/core/Services/Object/Controller.php
- lib/core/Services/Search/Controller.php
- lib/core/Tracker/Field/Relation.php
- lib/jquery_tiki/tiki-jquery.js
- lib/smarty_tiki/FunctionHandler/ObjectLink.php
- templates/object/infobox/trackeritem.tpl
- templates/trackeroutput/relation.tpl
Changes:
=====================================
lib/core/Services/Object/Controller.php
=====================================
@@ -181,6 +181,58 @@ class Services_Object_Controller
return [];
}
+ /**
+ * Build HTML popup content for a tracker item for use as `popup_content` in search results.
+ * Reuses the existing infobox template and trackeroutput field rendering (including REL fields).
+ * Returns null on any failure or when nothing useful can be shown.
+ */
+ public static function getTrackerItemPopupContent(int $itemId): ?string
+ {
+ $trklib = TikiLib::lib('trk');
+ $item = $trklib->get_tracker_item($itemId);
+ if (! $item) {
+ return null;
+ }
+
+ $definition = Tracker_Definition::get($item['trackerId']);
+ if (! $definition) {
+ return null;
+ }
+
+ $itemObject = Tracker_Item::fromInfo($item);
+ if (! $itemObject->canView()) {
+ return null;
+ }
+
+ $fields = [];
+ foreach ($definition->getPopupFields() as $fieldId) {
+ if ($itemObject->canViewField($fieldId) && $field = $definition->getField($fieldId)) {
+ $fields[] = $field;
+ }
+ }
+
+ if (empty($fields)) {
+ foreach ($definition->getFields() as $field) {
+ if (isset($field['fieldId']) && $itemObject->canViewField($field['fieldId'])) {
+ $fields[] = $field;
+ }
+ }
+ }
+
+ if (empty($fields)) {
+ return null;
+ }
+
+ $smarty = TikiLib::lib('smarty');
+ $smarty->assign('fields', $fields);
+ $smarty->assign('item', $item);
+ $smarty->assign('can_modify', false);
+ $smarty->assign('can_remove', false);
+ $smarty->assign('mode', 'table');
+
+ return $smarty->fetch('object/infobox/trackeritem.tpl');
+ }
+
/**
* Generic function to allow consistently formatted errors from javascript using Feedback
*
=====================================
lib/core/Services/Search/Controller.php
=====================================
@@ -253,6 +253,15 @@ class Services_Search_Controller
];
if ($item['object_type'] == 'trackeritem') {
$transformed['status_icon'] = smarty_function_tracker_item_status_icon(['item' => $item['object_id']], $smarty->getEmptyInternalTemplate());
+
+ try {
+ $popupContent = Services_Object_Controller::getTrackerItemPopupContent((int) $item['object_id']);
+ if ($popupContent !== null) {
+ $transformed['popup_content'] = $popupContent;
+ }
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ }
}
return $transformed;
});
=====================================
lib/core/Tracker/Field/Relation.php
=====================================
@@ -129,6 +129,15 @@ class Tracker_Field_Relation extends \Tracker\Field\AbstractItemField implements
'description' => tr('Key to filter objects by using the value from the Extra Filter Field above.'),
'filter' => 'text',
],
+ 'linkTarget' => [
+ 'name' => tr('Link Target'),
+ 'description' => tr('Control whether relation links open in the same window or a new tab.'),
+ 'filter' => 'word',
+ 'options' => [
+ '' => tr('Same window'),
+ '_blank' => tr('New tab'),
+ ],
+ ],
],
],
];
@@ -348,7 +357,8 @@ class Tracker_Field_Relation extends \Tracker\Field\AbstractItemField implements
[
'display' => $display,
'relations' => $relations,
- 'format' => $this->trackerField->getOption('format')
+ 'format' => $this->trackerField->getOption('format'),
+ 'linkTarget' => $this->trackerField->getOption('linkTarget')
]
);
}
=====================================
lib/jquery_tiki/tiki-jquery.js
=====================================
@@ -1867,7 +1867,7 @@ $(document).tiki('copy')(
});
}
}
- $._object_selector_add_item = function (type, $select, $results, parent_title, item, title, status_icon, selected) {
+ $._object_selector_add_item = function (type, $select, $results, parent_title, item, title, status_icon, selected, popup_content) {
var checkname = $select.closest('.object-selector, .object-selector-multi')
.find('.primary').attr('id') + '_sel';
var suffix = $results.find('.form-check').length || 0;
@@ -1879,8 +1879,43 @@ $(document).tiki('copy')(
.prop('selected', selected)
.appendTo($select);
+ // Build clickable link for the title if item identifier is available
+ var titleHtml = title;
+ if (item && typeof item === 'string' && item.indexOf(':') !== -1) {
+ var parts = item.split(':');
+ var objectType = parts[0];
+ var objectId = parts[1];
+ var url = '';
+
+ // Build URL based on object type
+ if (objectType === 'trackeritem') {
+ url = 'tiki-view_tracker_item.php?itemId=' + encodeURIComponent(objectId);
+ } else if (objectType === 'wiki page') {
+ url = 'tiki-index.php?page=' + encodeURIComponent(objectId);
+ } else if (objectType === 'article') {
+ url = 'tiki-read_article.php?articleId=' + encodeURIComponent(objectId);
+ } else if (objectType === 'blog post') {
+ url = 'tiki-view_blog_post.php?postId=' + encodeURIComponent(objectId);
+ } else if (objectType === 'file') {
+ url = 'tiki-download_file.php?fileId=' + encodeURIComponent(objectId);
+ }
+ // Add more object types as needed
+
+ if (url) {
+ var popoverAttrs = '';
+ if (popup_content) {
+ var escapedContent = popup_content.replace(/"/g, '"').replace(/'/g, ''');
+ popoverAttrs = ' data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-html="true" data-bs-content="' + escapedContent + '" data-bs-delay=\'{"show":250,"hide":500}\'';
+ }
+ titleHtml = '<a href="' + url + '" target="_blank" class="object-link"' + popoverAttrs + '>' + title + '</a>';
+ }
+ }
+
+ // Build the label content: status_icon + titleHtml
+ var labelContent = status_icon ? status_icon + ' ' + titleHtml : titleHtml;
+
$('<div class="form-check"><input type="' + type + '" class="form-check-input" ><label class="form-check-label"></label></div>')
- .find('label').append(status_icon ? status_icon + ' ' + title : title).end()
+ .find('label').append(labelContent).end()
.find(':radio, :checkbox')
.attr('name', checkname)
.prop('checked', selected)
@@ -1940,19 +1975,106 @@ $(document).tiki('copy')(
if (selection.indexOf(current) === -1) {
if (initial) {
- $._object_selector_add_item(type, $select, $([]), value.parent_title, current, value.title, value.status_icon, selected);
+ $._object_selector_add_item(type, $select, $([]), value.parent_title, current, value.title, value.status_icon, selected, value.popup_content);
} else {
- $._object_selector_add_item(type, $select, $results, value.parent_title, current, value.title, value.status_icon, selected);
+ $._object_selector_add_item(type, $select, $results, value.parent_title, current, value.title, value.status_icon, selected, value.popup_content);
}
} else {
const option = $("option[value='" + current + "']", $select);
if (!option.length) {
- $._object_selector_add_item(type, $select, $results, value.parent_title, current, value.title, value.status_icon, true);
+ $._object_selector_add_item(type, $select, $results, value.parent_title, current, value.title, value.status_icon, true, value.popup_content);
} else {
option.text(value.title);
}
}
});
+
+ if ($results.length) {
+ var $popovers = $results.find('[data-bs-toggle="popover"]');
+ if ($popovers.length > 0) {
+ setTimeout(function() {
+ $popovers.each(function() {
+ if (typeof bootstrap !== 'undefined' && bootstrap.Popover) {
+ var popoverInstance = new bootstrap.Popover(this, {
+ container: 'body',
+ html: true,
+ trigger: 'manual',
+ delay: {"show": 250, "hide": 500},
+ sanitize: false,
+ boundary: "window",
+ placement: $.tikiPopoverWhereToPlace,
+ customClass: 'popover-md'
+ });
+
+ var showTimer, hideTimer;
+ var elem = this;
+
+ var showPopover = function() {
+ clearTimeout(hideTimer);
+ showTimer = setTimeout(function() {
+ popoverInstance.show();
+ }, 250);
+ };
+
+ var hidePopover = function() {
+ clearTimeout(showTimer);
+ hideTimer = setTimeout(function() {
+ popoverInstance.hide();
+ }, 500);
+ };
+
+ elem.addEventListener('mouseenter', showPopover);
+ elem.addEventListener('mouseleave', hidePopover);
+
+ elem.addEventListener('shown.bs.popover', function() {
+ var popoverId = this.getAttribute('aria-describedby');
+ if (popoverId) {
+ var popoverEl = document.getElementById(popoverId);
+ if (popoverEl) {
+ popoverEl.addEventListener('mouseenter', function() {
+ clearTimeout(hideTimer);
+ });
+ popoverEl.addEventListener('mouseleave', hidePopover);
+ }
+ }
+ });
+
+ this.addEventListener('shown.bs.popover', function() {
+ var popoverId = this.getAttribute('aria-describedby');
+ if (popoverId) {
+ var popoverEl = document.getElementById(popoverId);
+ if (popoverEl) {
+ popoverEl.style.zIndex = '9999';
+
+ popoverEl.addEventListener('click', function(e) {
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ }, true);
+
+ var links = popoverEl.querySelectorAll('a');
+ links.forEach(function(link) {
+ link.addEventListener('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+
+ var href = this.getAttribute('href');
+ if (href) {
+ window.open(href, '_blank', 'noopener,noreferrer');
+ }
+
+ clearTimeout(hideTimer);
+ return false;
+ }, true);
+ });
+ }
+ }
+ });
+ }
+ });
+ }, 100);
+ }
+ }
};
$.fn.object_selector = function (action, value, title) {
=====================================
lib/smarty_tiki/FunctionHandler/ObjectLink.php
=====================================
@@ -159,6 +159,12 @@ class ObjectLink extends Base
$class = ' class="' . implode(' ', $classList) . '"';
+ // Add target attribute if specified
+ $targetAttribute = '';
+ if (! empty($params['target'])) {
+ $targetAttribute = ' target="' . smarty_modifier_escape($params['target']) . '"';
+ }
+
if (! str_contains($escapedHref, '://')) {
//$html = '<a href="' . $base_url . $escapedHref . '"' . $class . $titleAttribute . $metadata . '>' . $escapedText . '</a>';
// When the link is created for a tiki page, then we do NOT want the baseurl included,
@@ -166,9 +172,9 @@ class ObjectLink extends Base
// configured for teh ip adress we run our webserver.
// Eaxmple: Fqdn = tiki.mydomain.com -> port forwarding/nat to: 192.168.1.110.
// In this case links should NOT be generated as absolut urls pointing to 192.168.1.110 which would be the part of the baseUrl.
- $html = '<a href="' . $escapedHref . '"' . $class . $titleAttribute . $metadata . '>' . $escapedText . '</a>';
+ $html = '<a href="' . $escapedHref . '"' . $class . $titleAttribute . $targetAttribute . $metadata . '>' . $escapedText . '</a>';
} else {
- $html = '<a rel="external" href="' . $escapedHref . '"' . $class . $titleAttribute . $metadata . '>' . $escapedText . '</a>';
+ $html = '<a rel="external" href="' . $escapedHref . '"' . $class . $titleAttribute . $targetAttribute . $metadata . '>' . $escapedText . '</a>';
}
$attributelib = \TikiLib::lib('attribute');
=====================================
templates/object/infobox/trackeritem.tpl
=====================================
@@ -1,12 +1,14 @@
{if $mode eq 'table'}
- <table>
- {foreach from=$fields item=field}
- <tr class="field_{$field.fieldId}">
- <th>{tr}{$field.name|escape}{/tr}</th>
- <td>{trackeroutput field=$field item=$item process=y showlinks=n}</td>
- </tr>
- {/foreach}
- </table>
+ <div class="table-responsive">
+ <table class="table table-sm table-bordered mb-0">
+ {foreach from=$fields item=field}
+ <tr class="field_{$field.fieldId}">
+ <th class="text-nowrap" style="width: 1%;">{tr}{$field.name|escape}{/tr}</th>
+ <td>{trackeroutput field=$field item=$item process=y showlinks=n}</td>
+ </tr>
+ {/foreach}
+ </table>
+ </div>
{else}
{foreach from=$fields item=field}
<h6 class="field_{$field.fieldId}">{tr}{$field.name|escape}{/tr}</h6>
=====================================
templates/trackeroutput/relation.tpl
=====================================
@@ -8,7 +8,7 @@
{/if}
<ul class="relation-list">
{foreach from=$data.relations item=rel}
- <li>{object_link identifier=[$rel.target.type,$rel.target.itemId]|join:':' format=$data.format metaItemId=$rel->getMetadataItemId()}</li>
+ <li>{object_link identifier=[$rel.target.type,$rel.target.itemId]|join:':' format=$data.format metaItemId=$rel->getMetadataItemId() target='_blank'}</li>
{/foreach}
</ul>
</div>
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/bdc02b36ea02e78c6d206b7df8d8fbc099d5e01b
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/bdc02b36ea02e78c6d206b7df8d8fbc099d5e01b
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs