feat: add new tag list page with interactive filtering and dynamic tag count display.
This commit is contained in:
parent
c7e7f9ceee
commit
a51b3b8eae
@ -1,88 +1,238 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html{if="$language !== 'auto'"} lang="{$language}"{/if}>
|
<html{if="$language !=='auto'"} lang=" {$language}"{/if}>
|
||||||
<head>
|
|
||||||
{$pageName="tag.list"}
|
|
||||||
{include="includes"}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{include="page.header"}
|
|
||||||
|
|
||||||
{include="tag.sort"}
|
<head>
|
||||||
|
{$pageName="tag.list"}
|
||||||
|
{include="includes"}
|
||||||
|
<style>
|
||||||
|
.tag-filter-container {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
<div class="container">
|
.tag-filter-input {
|
||||||
{$countTags=count($tags)}
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
border: 1px solid var(--border-light, #3a3f4b);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-secondary, #1e2128);
|
||||||
|
color: var(--text-primary, #e4e6eb);
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
|
.tag-filter-input:focus {
|
||||||
{loop="$plugin_start_zone"}
|
outline: none;
|
||||||
{$value}
|
border-color: var(--primary, #4a7dff);
|
||||||
{/loop}
|
box-shadow: 0 0 0 3px rgba(74, 125, 255, 0.15);
|
||||||
</div>
|
}
|
||||||
|
|
||||||
<div id="taglist" class="card">
|
.tag-filter-input::placeholder {
|
||||||
<div class="card-header">
|
color: var(--text-muted, #6c757d);
|
||||||
<div class="pull-right" style="font-size: 0.9rem;">
|
}
|
||||||
<a href="{$base_path}/?searchtags={$search_tags|urlencode}" title="{'List all links with those tags'|t}">{$countTags} {'tags'|t}</a>
|
|
||||||
|
.tag-filter-clear {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-muted, #6c757d);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
display: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter-clear:hover {
|
||||||
|
color: var(--text-primary, #e4e6eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter-clear.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-count-info {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-muted, #6c757d);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-count-info .count-visible {
|
||||||
|
color: var(--primary, #4a7dff);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-count-info .count-total {
|
||||||
|
color: var(--text-muted, #6c757d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-tags-message {
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted, #6c757d);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-tags-message.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item.hidden-by-filter {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{include="page.header"}
|
||||||
|
|
||||||
|
{include="tag.sort"}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{$countTags=count($tags)}
|
||||||
|
|
||||||
|
<div id="plugin_zone_start_tagcloud" class="plugin_zone">
|
||||||
|
{loop="$plugin_start_zone"}
|
||||||
|
{$value}
|
||||||
|
{/loop}
|
||||||
</div>
|
</div>
|
||||||
{'Tag list'|t}
|
|
||||||
</div>
|
<div id="taglist" class="card">
|
||||||
<div class="card-body" style="padding-bottom: 0;">
|
<div class="card-header">
|
||||||
<form class="card-search" method="get">
|
<div class="pull-right" style="font-size: 0.9rem;">
|
||||||
<input type="hidden" name="do" value="taglist">
|
<span class="tag-count-info">
|
||||||
<div class="form-group">
|
<span class="count-visible" id="visibleTagCount">{$countTags}</span> / <span class="count-total" id="totalTagCount">{$countTags}</span> {'tags'|t}
|
||||||
<input type="search" name="searchtags" class="form-control" placeholder="{'Filter by tag'|t}..."
|
</span>
|
||||||
{if="!empty($search_tags)"}
|
|
||||||
value="{$search_tags}"
|
|
||||||
{/if}
|
|
||||||
autocomplete="off" data-multiple data-autofirst data-minChars="1"
|
|
||||||
data-list="{loop="$tags"}{$key}, {/loop}"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
{loop="tags"}
|
|
||||||
<div class="list-group-item">
|
|
||||||
<div class="list-group-item-content" style="display: flex; align-items: center; justify-content: space-between;">
|
|
||||||
<div style="flex: 1;">
|
|
||||||
<a href="{$base_path}/?searchtags={$key|urlencode} {$search_tags|urlencode}" class="tag-link" style="font-weight: 500; font-size: 1rem;">{$key}</a>
|
|
||||||
{loop="$value.tag_plugin"}
|
|
||||||
{$value}
|
|
||||||
{/loop}
|
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-right: 1rem;">
|
{'Tag list'|t}
|
||||||
<span class="badge" style="background: var(--border-light); padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.8rem;">{$value}</span>
|
</div>
|
||||||
|
<div class="card-body" style="padding-bottom: 0;">
|
||||||
|
<div class="tag-filter-container">
|
||||||
|
<input type="text" id="tagFilterInput" class="tag-filter-input" placeholder="{'Filter by tag'|t}..." autocomplete="off">
|
||||||
|
<button type="button" id="tagFilterClear" class="tag-filter-clear" title="Clear filter">
|
||||||
|
<i class="mdi mdi-close"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="noTagsMessage" class="no-tags-message">
|
||||||
|
<i class="mdi mdi-tag-off-outline" style="font-size: 2rem; margin-bottom: 0.5rem;"></i>
|
||||||
|
<p>{'No tags match your filter'|t}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group-item-action">
|
<div class="list-group list-group-flush" id="tagListContainer">
|
||||||
<a href="{$base_path}/add-tag/{$key|urlencode}" title="{'Add tag'|t} {$key} {'to the filters'|t}" class="btn btn-secondary btn-sm" style="padding: 0.25rem 0.5rem;">
|
{loop="tags"}
|
||||||
<i class="mdi mdi-magnify-plus-outline"></i>
|
<div class="list-group-item" data-tag-name="{$key|strtolower}">
|
||||||
</a>
|
<div class="list-group-item-content" style="display: flex; align-items: center; justify-content: space-between;">
|
||||||
{if="$is_logged_in"}
|
<div style="flex: 1;">
|
||||||
<a href="{$base_path}/admin/tags?fromtag={$key|urlencode}" title="{'Rename tag'|t} {$key}" data-tag="{$key}" class="rename-tag btn btn-secondary btn-sm" style="padding: 0.25rem 0.5rem;">
|
<a href="{$base_path}/?searchtags={$key|urlencode}" class="tag-link" style="font-weight: 500; font-size: 1rem;">{$key}</a>
|
||||||
<i class="mdi mdi-pencil"></i>
|
</div>
|
||||||
</a>
|
<div style="margin-right: 1rem;">
|
||||||
<a href="#" class="delete-tag btn btn-danger btn-sm" data-tag="{$key}" title="{'Delete tag'|t} {$key}" style="padding: 0.25rem 0.5rem;">
|
<span class="badge" style="background: var(--border-light); padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.8rem;">{$value}</span>
|
||||||
<i class="mdi mdi-delete"></i>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
{/if}
|
<div class="list-group-item-action">
|
||||||
|
<a href="{$base_path}/add-tag/{$key|urlencode}" title="{'Add tag'|t} {$key} {'to the filters'|t}" class="btn btn-secondary btn-sm" style="padding: 0.25rem 0.5rem;">
|
||||||
|
<i class="mdi mdi-magnify-plus-outline"></i>
|
||||||
|
</a>
|
||||||
|
{if="$is_logged_in"}
|
||||||
|
<a href="{$base_path}/admin/tags?fromtag={$key|urlencode}" title="{'Rename tag'|t} {$key}" data-tag="{$key}" class="rename-tag btn btn-secondary btn-sm" style="padding: 0.25rem 0.5rem;">
|
||||||
|
<i class="mdi mdi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="delete-tag btn btn-danger btn-sm" data-tag="{$key}" title="{'Delete tag'|t} {$key}" style="padding: 0.25rem 0.5rem;">
|
||||||
|
<i class="mdi mdi-delete"></i>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/loop}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/loop}
|
|
||||||
|
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
|
||||||
|
{loop="$plugin_end_zone"}
|
||||||
|
{$value}
|
||||||
|
{/loop}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="plugin_zone_end_tagcloud" class="plugin_zone">
|
{if="$is_logged_in"}
|
||||||
{loop="$plugin_end_zone"}
|
<input type="hidden" name="taglist" id="tagListData" value="">
|
||||||
{$value}
|
{/if}
|
||||||
{/loop}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{if="$is_logged_in"}
|
{literal}
|
||||||
<input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}">
|
<script>
|
||||||
{/if}
|
(function () {
|
||||||
|
var filterInput = document.getElementById('tagFilterInput');
|
||||||
|
var clearBtn = document.getElementById('tagFilterClear');
|
||||||
|
var tagListContainer = document.getElementById('tagListContainer');
|
||||||
|
var noTagsMessage = document.getElementById('noTagsMessage');
|
||||||
|
var visibleTagCount = document.getElementById('visibleTagCount');
|
||||||
|
|
||||||
{include="page.footer"}
|
if (!filterInput || !tagListContainer) return;
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
var tagItems = tagListContainer.querySelectorAll('.list-group-item');
|
||||||
|
var totalTags = tagItems.length;
|
||||||
|
|
||||||
|
function filterTags() {
|
||||||
|
var filterValue = filterInput.value.toLowerCase().trim();
|
||||||
|
var visibleCount = 0;
|
||||||
|
|
||||||
|
// Show/hide clear button
|
||||||
|
if (filterValue.length > 0) {
|
||||||
|
clearBtn.classList.add('visible');
|
||||||
|
} else {
|
||||||
|
clearBtn.classList.remove('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < tagItems.length; i++) {
|
||||||
|
var item = tagItems[i];
|
||||||
|
var tagName = item.getAttribute('data-tag-name') || '';
|
||||||
|
|
||||||
|
if (filterValue === '' || tagName.indexOf(filterValue) !== -1) {
|
||||||
|
item.classList.remove('hidden-by-filter');
|
||||||
|
visibleCount++;
|
||||||
|
} else {
|
||||||
|
item.classList.add('hidden-by-filter');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update count
|
||||||
|
if (visibleTagCount) {
|
||||||
|
visibleTagCount.textContent = visibleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide no results message
|
||||||
|
if (visibleCount === 0 && filterValue.length > 0) {
|
||||||
|
noTagsMessage.classList.add('visible');
|
||||||
|
} else {
|
||||||
|
noTagsMessage.classList.remove('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterInput.addEventListener('input', filterTags);
|
||||||
|
filterInput.addEventListener('keyup', filterTags);
|
||||||
|
|
||||||
|
clearBtn.addEventListener('click', function () {
|
||||||
|
filterInput.value = '';
|
||||||
|
filterTags();
|
||||||
|
filterInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle escape key
|
||||||
|
filterInput.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
filterInput.value = '';
|
||||||
|
filterTags();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{/literal}
|
||||||
|
|
||||||
|
{include="page.footer"}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user