搜索模板和排序功能实现
This commit is contained in:
@@ -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>
|
||||
|
||||
|
Reference in New Issue
Block a user