搜索模板和排序功能实现

This commit is contained in:
2025-09-16 07:51:06 +00:00
parent e851832d58
commit 1c7d53bd1c

View File

@@ -1,9 +1,116 @@
{{template "base/head" .}}
<!-- 强制加载 jQuery 和 Semantic UI -->
<script>
if (typeof jQuery === 'undefined') {
document.write('<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js"><\/script>');
document.write('<script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.5.0/dist/semantic.min.js"><\/script>');
}
</script>
<style>
.search-sort-container {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.search-input-container {
flex: 1;
min-width: 0;
}
.sort-dropdown-container {
white-space: nowrap;
flex-shrink: 0;
}
.sort-dropdown-container:hover {
cursor: pointer;
background: var(--color-hover) !important;
}
.sort-dropdown-container.active {
background: var(--color-active) !important;
}
.sort-dropdown-trigger {
padding: 10px 12px;
}
.ui.dropdown .menu {
position: absolute;
top: 110%;
}
.ui.dropdown .text {
display: inline-block;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
/* 新增样式:选中项高亮 */
.ui.dropdown .menu .active.item,
.ui.dropdown .menu .item:hover {
background: var(--color-hover) !important;
}
/* 当前选中项样式 */
.ui.dropdown .menu .selected.item {
color: var(--color-text) !important;
background: var(--color-active) !important;
font-weight: var(--font-weight-medium) !important;
}
/* 隐藏元素 */
.hidden {
display: none !important;
}
</style>
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
<div class="ui middle very relaxed page grid">
<div class="column">
<!-- 搜索和排序控制栏 -->
<div class="search-sort-container">
<!-- 搜索框 -->
<div class="ui action input search-input-container">
<input type="text" id="searchInput" placeholder="搜索模板...">
<button class="ui small icon button" id="searchButton" aria-label="{{ctx.Locale.Tr " search.search"}}" {{with
.Tooltip}}data-tooltip-content="{{.}}" {{end}}{{if .Disabled}} disabled{{end}}>{{svg
"octicon-search"}}</button>
</div>
<!-- 排序下拉框 -->
<div class="sort-dropdown-container">
<div class="ui small dropdown type jump item" id="sortDropdown">
<div class="sort-dropdown-trigger">
<span class="text">
{{ctx.Locale.Tr "repo.issues.filter_sort"}}
</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
<div class="menu">
<div class="item " data-value="newest">最新创建</div>
<div class="item" data-value="oldest">最早创建</div>
<div class="item" data-value="name_asc">按字母顺序排序</div>
<div class="item" data-value="name_desc">按字母逆序排序</div>
<div class="item" data-value="recently_updated">最近更新</div>
<div class="item" data-value="least_recently_updated">最早更新</div>
<div class="item" data-value="most_likes">点赞由多到少</div>
<div class="item" data-value="least_likes">点赞由少到多</div>
<div class="item" data-value="most_forks">派生由多到少</div>
<div class="item" data-value="least_forks">派生由少到多</div>
</div>
</div>
</div>
</div>
<!-- 模板卡片容器 -->
<div class="ui cards migrate-entries" id="template-cards">
<div class="ui active inverted dimmer">
<div class="ui active inverted">
<div class="ui text loader">加载模板中...</div>
</div>
</div>
@@ -12,35 +119,265 @@
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const cardsContainer = document.getElementById('template-cards');
// 确保 jQuery 已加载
function ensureJQuery(callback) {
if (window.jQuery) {
callback();
} else {
setTimeout(() => ensureJQuery(callback), 100);
}
}
fetch('https://devstar.cn/api/v1/repos/search?template=true')
.then(response => response.json())
.then(data => {
// 模板选择处理函数
const checkDevStarTemplate = function () {
if ($devstarTemplate.val() !== '' && $devstarTemplate.val() !== '0') {
if ($('#repo_name').val() == '') {
$('#repo_name').val(getRepoNameFromGitUrl($devstarTemplate.val()));
}
if ($('#description').val() == '') {
$('#description').val("init repo from " + $devstarTemplate.val());
}
$repoTemplate.val('');
hideElem($gitURLTemplateArea);
hideElem($repoTemplateArea);
hideElem($nonTemplate);
} else {
showElem($repoTemplateArea);
showElem($gitURLTemplateArea);
hideElem($templateUnits);
showElem($nonTemplate);
}
};
// 辅助函数
function hideElem($elem) {
$elem.addClass('hidden');
}
function showElem($elem) {
$elem.removeClass('hidden');
}
function getRepoNameFromGitUrl(url) {
return url.split('/').pop().replace('.git', '');
}
document.addEventListener('DOMContentLoaded', function () {
ensureJQuery(function () {
// 初始化变量
const cardsContainer = document.getElementById('template-cards');
const searchInput = document.getElementById('searchInput');
const searchButton = document.getElementById('searchButton');
const sortContainer = document.querySelector('.sort-dropdown-container');
const sortTrigger = document.querySelector('.sort-dropdown-trigger');
const sortDropdown = $('#sortDropdown');
let allRepos = [];
let currentSearchTerm = '';
let currentSortValue = 'newest';
// 假设这些元素在目标页面中存在
$devstarTemplate = $('#devstar_template');
$repoTemplate = $('#repo_template');
$gitURLTemplateArea = $('#git_url_template_area');
$repoTemplateArea = $('#repo_template_area');
$nonTemplate = $('#non_template');
$templateUnits = $('#template_units');
// 全局设置silent模式
$.fn.dropdown.settings.silent = true;
$.fn.transition.settings.silent = true;
// 初始化下拉框并添加选中状态处理
sortDropdown.dropdown({
action: 'hide',
onShow: function () {
$(`.ui.dropdown .menu .item[data-value="${currentSortValue}"]`).addClass('selected');
return document.body.contains(this);
},
onHide: function () {
return document.body.contains(this);
},
onChange: function (value) {
currentSortValue = value;
$('.ui.dropdown .menu .item').removeClass('selected');
$(`.ui.dropdown .menu .item[data-value="${value}"]`).addClass('selected');
sortContainer.classList.remove('active');
applyFiltersAndRender();
},
onInitialize: function () {
$('.ui.dropdown .menu .item').removeClass('selected');
$(`.ui.dropdown .menu .item[data-value="newest"]`).addClass('selected');
}
});
// 点击触发器时切换 active 类
sortTrigger.addEventListener('click', function () {
sortContainer.classList.toggle('active');
$(`.ui.dropdown .menu .item[data-value="${currentSortValue}"]`).addClass('selected');
});
// 点击页面其他位置时移除 active 类
document.addEventListener('click', function (event) {
if (!sortContainer.contains(event.target)) {
sortContainer.classList.remove('active');
}
});
// 鼠标右键时移除active类
document.addEventListener('contextmenu', function (event) {
if (!sortContainer.contains(event.target)) {
sortContainer.classList.remove('active');
}
});
// 获取模板数据
fetch('https://devstar.cn/api/v1/repos/search?template=true')
.then(response => response.json())
.then(data => {
allRepos = (data.data || []).map(repo => ({
...repo,
createdTimestamp: new Date(repo.created_at).getTime(),
updatedTimestamp: new Date(repo.updated_at).getTime(),
lowerName: repo.name.toLowerCase(),
lowerDescription: (repo.description || '').toLowerCase(),
starsCount: repo.stars_count || 0,
forksCount: repo.forks_count || 0
}));
applyFiltersAndRender();
// 搜索按钮点击事件
searchButton.addEventListener('click', function () {
currentSearchTerm = searchInput.value.trim().toLowerCase();
applyFiltersAndRender();
});
// 输入框回车事件
searchInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
currentSearchTerm = searchInput.value.trim().toLowerCase();
applyFiltersAndRender();
}
});
})
.catch(error => {
console.error('获取模板数据失败:', error);
cardsContainer.innerHTML = '<div class="ui error message">加载模板失败</div>';
});
// 应用搜索和排序筛选
function applyFiltersAndRender() {
// 1. 应用搜索筛选
let filteredRepos = currentSearchTerm ?
allRepos.filter(repo =>
repo.lowerName.includes(currentSearchTerm)
) :
[...allRepos];
// 2. 应用排序
let sortedRepos = [...filteredRepos];
switch (currentSortValue) {
case 'newest':
sortedRepos.sort((a, b) => b.createdTimestamp - a.createdTimestamp);
break;
case 'oldest':
sortedRepos.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
break;
case 'name_asc':
sortedRepos.sort((a, b) => a.name.localeCompare(b.name));
break;
case 'name_desc':
sortedRepos.sort((a, b) => b.name.localeCompare(a.name));
break;
case 'recently_updated':
sortedRepos.sort((a, b) => b.updatedTimestamp - a.updatedTimestamp);
break;
case 'least_recently_updated':
sortedRepos.sort((a, b) => a.updatedTimestamp - b.updatedTimestamp);
break;
case 'most_likes':
sortedRepos.sort((a, b) => b.starsCount - a.starsCount);
break;
case 'least_likes':
sortedRepos.sort((a, b) => a.starsCount - b.starsCount);
break;
case 'most_forks':
sortedRepos.sort((a, b) => b.forksCount - a.forksCount);
break;
case 'least_forks':
sortedRepos.sort((a, b) => a.forksCount - b.forksCount);
break;
}
// 3. 渲染结果
renderTemplates(sortedRepos);
}
// 渲染模板列表
function renderTemplates(repos) {
cardsContainer.innerHTML = '';
if (data.data && data.data.length > 0) {
data.data.forEach(repo => {
if (repos && repos.length > 0) {
repos.forEach(repo => {
const createdDate = new Date(repo.owner?.created || repo.created_at);
const formattedDate = createdDate.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
const card = `
<a class="ui card migrate-entry tw-flex tw-items-center"
href="{{.AppSubUrl}}/repo/create?template_owner=${repo.owner.login}&template_name=${repo.name}">
<img src="/assets/img/favicon.svg" width="184" height="184" class="tw-p-4" alt="${repo.name}" />
<div class="content">
<div class="header tw-text-center">${repo.name}</div>
<div class="description tw-text-center tw-text-balance">${repo.description}</div>
</div>
</a>
`;
<div class="ui card migrate-entry">
<a class="ui card migrate-entry tw-flex tw-items-center template-card"
href="#"
data-name="${repo.name}"
data-description="${repo.description || ''}">
<img src="/assets/img/favicon.svg" width="184" height="184" class="tw-p-4" alt="${repo.name}" />
<div class="content">
<div class="header tw-text-center">${repo.name}</div>
<div class="description tw-text-center tw-text-balance">${repo.description || ''}</div>
</div>
</a>
</div>
`;
cardsContainer.insertAdjacentHTML('beforeend', card);
});
// 添加点击事件处理
document.querySelectorAll('.template-card').forEach(card => {
card.addEventListener('click', function (e) {
e.preventDefault();
const repoName = this.getAttribute('data-name');
const repoDescription = this.getAttribute('data-description');
// 填充表单值
if ($('#repo_name').length && $('#repo_name').val() === '') {
$('#repo_name').val(repoName);
}
if ($('#description').length && $('#description').val() === '') {
$('#description').val(repoDescription || `init repo from ${repoName}`);
}
// 模拟 $devstarTemplate 有值的情况
if ($devstarTemplate.length) {
$devstarTemplate.val('1');
checkDevStarTemplate();
}
// 如果需要跳转,可以在这里添加
window.location.href = "{{.AppSubUrl}}/repo/create";
});
});
} else {
cardsContainer.innerHTML = '<div class="ui warning message">未找到任何模板</div>';
cardsContainer.innerHTML = '<div class="ui warning message">未找到匹配的模板</div>';
}
})
.catch(error => {
console.error('获取模板数据失败:', error);
cardsContainer.innerHTML = '<div class="ui error message">加载模板失败</div>';
});
// 确保下拉框状态正确
sortDropdown.dropdown('refresh');
}
});
});
</script>