@@ -1,307 +1,525 @@
{{ 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;
}
</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 -co ntainer ">
<!-- 搜索框 -- >
<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" 1 4 "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 class="ui small secondary filter menu tw-items -ce nter tw-mx-0 tw-mb-4 ">
<form class="ui form ignore-dirty tw-flex-1" >
<div class="ui small fluid action input">
<input type="text" id="searchInput" name="q" placeholder="{{ ctx .Locale .Tr "search.type_tooltip" }} ">
<button class="ui small icon button" id="searchButton" type="submit" aria-label=" {{ ctx .Locale .Tr "explore .search" }} ">
{{ svg "octicon-search" }}
</button>
</div>
</form>
<!-- Sort -->
<div class="ui small dropdown type jump item tw-mr-0" id="sortDropdown ">
<span class="text ">
{{ ctx .Locale .Tr "repo.issues.filter_sort" }}
</span >
{{ svg "octicon-triangle-down" 1 4 "dropdown icon " }}
<div class="menu" >
<a class="item" data-value="newest"> {{ ctx .Locale .Tr "repo.issues.filter_sort.latest " }} </a>
<a class="item" data-value="oldest"> {{ ctx .Locale .Tr "repo.issues.filter_sort.oldest" }} </a >
<a class="item" data-value="alphabetically"> {{ ctx .Locale .Tr "repo.issues.label.filter_sort.alphabetically" }} </a >
<a class="item" data-value="reversealphabetically"> {{ ctx .Locale .Tr "repo.issues.label.filter_sort.reverse_alphabetically" }} </a >
<a class="item" data-value="recentupdate"> {{ ctx .Locale .Tr "repo.issues.filter_sort.recentupdate" }} </a >
<a class="item" data-value="leastupdate"> {{ ctx .Locale .Tr "repo.issues.filter_sort.leastupdate" }} </a >
<a class="item" data-value="moststars"> {{ ctx .Locale .Tr "repo.issues.filter_sort.moststars" }} </a >
<a class="item" data-value="feweststars"> {{ ctx .Locale .Tr "repo.issues.filter_sort.feweststars" }} </a >
<a class="item" data-value="mostforks"> {{ ctx .Locale .Tr "repo.issues.filter_sort.mostforks" }} </a >
<a class="item" data-value="fewestforks"> {{ ctx .Locale .Tr "repo.issues.filter_sort.fewestforks" }} </a >
</div>
</div>
</div>
<!-- 标签筛选区域 -->
<div id="tag-filter-container" style="display: none; margin-bottom: 1rem;">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="flex: 1;">
<div class="ui small labels" id="selected-tags" style="margin: 0;"></div>
</div>
<a class="ui small basic button" id="clear-tags" style="display: none; margin-left: 1rem;">
{{ svg "octicon-x" }} {{ ctx .Locale .Tr "repo.create_from_template.clear_filters" }}
</a>
</div>
</div>
<!-- 所有可用标签 -->
<div class="tw-mb-4" id="all-tags-container" style="display: none;">
<div class="tw-text-12 tw-text-grey tw-mb-2"> {{ ctx .Locale .Tr "repo.create_from_template.quick_filter" }} :</div>
<div class="ui small labels tw-flex-wrap" id="all-tags"></div>
</div>
<div class="divider"></div>
<!-- 模板卡片容器 -->
<div class="ui cards migrate-entries" id="template-cards">
<div class="ui active inverted" >
<div class="ui text loader">加载模板中...</div>
</div>
<div class="ui active centered inline text loader"> {{ ctx .Locale .Tr "loading" }} </div >
</div>
</div>
</div>
</div>
<script>
// 确保 jQuery 已加载
function ensureJQuery(callback) {
if (window.jQuery) {
callback() ;
} else {
setTimeout(() => ensureJQuery(callback), 100);
(function() {
'use strict';
const appSubUrl = ' {{ .AppSubUrl }} ' ;
const i18n = {
errorOccurred: ' {{ ctx .Locale .Tr "error.occurred" }} ',
noResults: ' {{ ctx .Locale .Tr "repo.repo_no_results" }} ',
loading: ' {{ ctx .Locale .Tr "loading" }} '
};
// 标签类型配置(支持前缀约定,如 os:linux, framework:react)
const tagTypeConfig = {
'os': { color: 'blue', label: '系统' },
'platform': { color: 'purple', label: '平台' },
'arch': { color: 'purple', label: '架构' },
'framework': { color: 'teal', label: '框架' },
'lang': { color: 'orange', label: '语言' },
'type': { color: 'green', label: '类型' },
'tool': { color: 'brown', label: '工具' },
'default': { color: 'grey', label: '' }
};
// 解析标签(支持 prefix:value 格式)
function parseTag(tag) {
const match = tag.match(/^([a-z]+):(.+)$/);
if (match) {
const [, prefix, value] = match;
const config = tagTypeConfig[prefix] || tagTypeConfig.default;
return {
prefix: prefix,
value: value,
display: value,
original: tag,
type: prefix,
color: config.color,
typeLabel: config.label
};
}
// 无前缀的普通标签
return {
prefix: null,
value: tag,
display: tag,
original: tag,
type: 'topic',
color: 'grey',
typeLabel: ''
};
}
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 ';
// 根据标签内容智能选择颜色
function getSmartColor(tag ) {
const lower = tag.toLowerCase();
// 常见语言
if (['javascript', 'js', 'typescript', 'ts'].includes(lower)) return 'yellow' ;
if (['python', 'py'].includes(lower)) return 'blue' ;
if (['java'].includes(lower)) return 'red' ;
if (['go', 'golang'].includes(lower)) return 'teal' ;
if (['rust'].includes(lower)) return 'orange' ;
if (['c++', 'cpp', 'c'].includes(lower)) return 'purple' ;
// 常见框架
if (['react', 'reactjs'].includes(lower)) return 'blue ';
if (['vue', 'vuejs'].includes(lower)) return 'green';
if (['angular'].includes(lower)) return 'red';
if (['django'].includes(lower)) return 'green';
if (['spring', 'springboot'].includes(lower)) return 'green';
// 操作系统
if (['linux', 'ubuntu', 'debian'].includes(lower)) return 'orange';
if (['windows'].includes(lower)) return 'blue';
if (['macos', 'mac'].includes(lower)) return 'grey';
return 'grey';
}
// 全局设置silent模式
$.fn.dropdown.settings.silent = true ;
$.fn.transition.settings.silent = true ;
function initPage() {
const cardsContainer = document.getElementById('template-cards') ;
const searchInput = document.getElementById('searchInput') ;
const searchButton = document.getElementById('searchButton');
const allTagsContainer = document.getElementById('all-tags-container');
const allTagsElement = document.getElementById('all-tags');
const selectedTagsElement = document.getElementById('selected-tags');
const tagFilterContainer = document.getElementById('tag-filter-container');
const clearTagsButton = document.getElementById('clear-tags');
// 初始化下拉框并添加选中状态处理
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');
}
});
if (typeof window.$ === 'undefined') {
setTimeout(initPage, 100);
return;
}
// 点击触发器时切换 active 类
sortTrigger.addEventListener('click', function () {
sortContainer.classList.toggle('active') ;
$(`.ui.dropdown .menu .item[data-value="$ { currentSortValue}"]`).addClass('selected') ;
} );
const $ = window.$;
let allRepos = [];
let currentSearchTerm = '' ;
let currentSortValue = 'newest' ;
let selectedTags = new Set( );
let allAvailableTags = new Set();
// 点击页面其他位置时移除 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');
}
});
$('#sortDropdown').dropdown( {
action: 'hide',
onChange: function(value ) {
currentSortValue = value ;
applyFiltersAndRender();
}
});
// 获取模板数据
fetch('https://devstar.cn/api/v1/ repos/search?template=true' )
.then(response => response.json())
.then(data => {
allRepos = (data.data || []).map(repo => ( {
...repo,
fetch('https://devstar.cn/api/v1/repos/search?template=true')
.then( res ponse => response.json() )
.then(data => {
allRepos = (data.data || []).map(repo => {
// 提取标签:语言 + topics
const tags = [];
if (repo.language) tags.push(repo.language);
if (repo.topics && Array.isArray(repo.topics)) {
tags.push(...repo.topics);
}
// 收集所有标签
tags.forEach(tag => allAvailableTags.add(tag));
return {
name: repo.name,
description: repo.description || '',
clone_url: repo.clone_url,
full_name: "Devstar.cn/" + repo.full_name,
language: repo.language || '',
topics: repo.topics || [],
tags: tags,
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];
renderAllTags();
applyFiltersAndRender();
// 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 ' rec ently_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;
searchButton.addEventListener('click', function (e) {
e.preventDefault() ;
currentSearchTerm = searchInput.value.trim().toLowerCase();
applyFiltersAndRender();
} );
searchInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
e.preventDefault() ;
currentSearchTerm = searchInput.value.trim().toLowerCase();
applyFiltersAndRender( );
}
});
clearTagsButton.addEventListener('click', function(e) {
e.p rev entDefault();
selectedTags.clear( );
updateSelectedTagsDisplay() ;
applyFiltersAndRender();
} );
})
.catch(error => {
console.error('Error:', error );
cardsContainer.innerHTML = '<div class="ui error message">' + i18n.errorOccurred + '</div>' ;
});
function renderAllTags() {
if (allAvailableTags.size === 0) return;
allTagsContainer.style.display = 'block' ;
allTagsElement.innerHTML = '';
// 按标签类型分组
const tagsByType = { };
Array.from(allAvailableTags).forEach(tag => {
const parsed = parseTag(tag);
const type = parsed.type || 'topic';
if (!tagsByType[type]) tagsByType[type] = [];
tagsByType[type].push(parsed);
});
// 按类型渲染标签
const typeOrder = ['os', 'platform', 'arch', 'framework', 'lang', 'type', 'tool', 'topic'];
typeOrder.forEach(type => {
if (!tagsByType[type]) return;
// 添加类型标题
if (type !== 'topic' && tagsByType[type].length > 0) {
const config = tagTypeConfig[type] || tagTypeConfig.default;
const typeLabel = document.createElement('span');
typeLabel.className = 'tw-text-12 tw-text-grey tw-mr-2';
typeLabel.textContent = config.label + ':';
allTagsElement.appendChild(typeLabel);
}
// 3. 渲染结果
renderTemplates(sortedRepos);
// 渲染该类型的标签
tagsByType[type].sort((a, b) => a.display.localeCompare(b.display)).forEach(parsed => {
const label = document.createElement('a');
const color = parsed.color || getSmartColor(parsed.value);
const isSelected = selectedTags.has(parsed.original);
// 确保颜色类正确应用,选中时添加 basic 样式
label.className = 'ui ' + color + ' label' + (isSelected ? ' basic' : '');
label.textContent = parsed.display;
label.style.cursor = 'pointer';
label.style.marginBottom = '4px';
label.style.marginRight = '4px';
label.style.transition = 'all 0.2s';
// 添加类型提示
if (parsed.typeLabel) {
label.setAttribute('data-tooltip-content', parsed.typeLabel + ': ' + parsed.display);
}
// 悬停效果
label.addEventListener('mouseenter', function() {
if (!selectedTags.has(parsed.original)) {
this.style.transform = 'scale(1.05)';
this.style.boxShadow = '0 2px 4px rgba(0,0,0,0.15)';
}
});
label.addEventListener('mouseleave', function() {
this.style.transform = 'scale(1)';
this.style.boxShadow = 'none';
});
label.addEventListener('click', function(e) {
e.preventDefault();
toggleTag(parsed.original);
});
allTagsElement.appendChild(label);
});
// 添加分隔(除了最后一个类型)
if (type !== 'topic' && tagsByType[type].length > 0) {
const separator = document.createElement('div');
separator.className = 'tw-w-full tw-h-2';
allTagsElement.appendChild(separator);
}
});
}
function toggleTag(tag) {
if (selectedTags.has(tag)) {
selectedTags.delete(tag);
} else {
selectedTags.add(tag);
}
updateSelectedTagsDisplay();
applyFiltersAndRender();
}
function updateSelectedTagsDisplay() {
if (selectedTags.size === 0) {
tagFilterContainer.style.display = 'none';
clearTagsButton.style.display = 'none';
return;
}
// 渲染模板列表
function renderTemplates(repos) {
cardsContainer .innerHTML = '';
tagFilterContainer.style.display = 'flex';
clearTagsButton.style.display = 'block';
selectedTagsElement .innerHTML = '';
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'
selectedTags.forEach(tag => {
const parsed = parseTag(tag);
const color = parsed.color || getSmartColor(parsed.value );
const label = document.createElement('div');
// 确保颜色类正确应用
label.className = 'ui ' + color + ' label';
label.style.paddingRight = '2.5em';
label.style.position = 'relative';
label.style.marginRight = '0.5em';
label.style.marginBottom = '0.5em';
// 标签文本
const text = document.createElement('span');
text.textContent = parsed.display;
label.appendChild(text);
// 删除按钮(使用 × 字符)
const deleteBtn = document.createElement('span');
deleteBtn.innerHTML = '× ';
deleteBtn.style.position = 'absolute';
deleteBtn.style.right = '0.5em';
deleteBtn.style.top = '50%';
deleteBtn.style.transform = 'translateY(-50%)';
deleteBtn.style.cursor = 'pointer';
deleteBtn.style.fontSize = '1.2em';
deleteBtn.style.fontWeight = 'bold';
deleteBtn.style.opacity = '0.6';
deleteBtn.style.transition = 'all 0.2s';
deleteBtn.style.lineHeight = '1';
deleteBtn.style.width = '1em';
deleteBtn.style.height = '1em';
deleteBtn.style.display = 'flex';
deleteBtn.style.alignItems = 'center';
deleteBtn.style.justifyContent = 'center';
deleteBtn.setAttribute('title', '点击移除筛选条件');
deleteBtn.addEventListener('mouseenter', function() {
this.style.opacity = '1';
this.style.transform = 'translateY(-50%) scale(1.3)';
});
deleteBtn.addEventListener('mouseleave', function() {
this.style.opacity = '0.6';
this.style.transform = 'translateY(-50%) scale(1)';
});
deleteBtn.addEventListener('click', function(e) {
e.stopPropagation();
toggleTag(tag);
});
label.appendChild(deleteBtn);
selectedTagsElement.appendChild(label);
});
}
function applyFiltersAndRender() {
let filteredRepos = allRepos.slice();
// 按标签筛选
if (selectedTags.size > 0) {
filteredRepos = filteredRepos.filter(repo => {
return Array.from(selectedTags).every(tag => repo.tags.includes(tag));
});
}
// 按搜索词筛选
if (currentSearchTerm) {
filteredRepos = filteredRepos.filter(repo =>
repo.lowerName.includes(currentSearchTerm)
);
}
// 排序
let sortedRepos = filteredRepos.slice();
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 'alphabetically':
sortedRepos.sort((a, b) => a.name.localeCompare(b.name));
break;
case 'reversealphabetically':
sortedRepos.sort((a, b) => b.name.localeCompare(a.name));
break;
case 'recentupdate':
sortedRepos.sort((a, b) => b.updatedTimestamp - a.updatedTimestamp);
break;
case 'leastupdate':
sortedRepos.sort((a, b) => a.updatedTimestamp - b.updatedTimestamp);
break;
case 'moststars':
sortedRepos.sort((a, b) => b.starsCount - a.starsCount);
break;
case 'feweststars':
sortedRepos.sort((a, b) => a.starsCount - b.starsCount);
break;
case 'mostforks':
sortedRepos.sort((a, b) => b.forksCount - a.forksCount);
break;
case 'fewestforks':
sortedRepos.sort((a, b) => a.forksCount - b.forksCount);
break;
}
renderTemplates(sortedRepos);
}
function renderTemplates(repos) {
cardsContainer.innerHTML = '';
if (repos && repos.length > 0) {
repos.forEach(repo => {
const card = document.createElement('a');
card.className = 'ui card migrate-entry';
card.href = appSubUrl + '/repo/create?template_name=' + encodeURIComponent(repo.name) +
'&description=' + encodeURIComponent(repo.clone_url) +
'&template_full_name=' + encodeURIComponent(repo.full_name);
const imageContainer = document.createElement('div');
imageContainer.className = 'tw-flex tw-justify-center tw-p-4';
const img = document.createElement('img');
img.src = '/assets/img/favicon.svg';
img.width = 120;
img.height = 120;
img.alt = repo.name;
imageContainer.appendChild(img);
const content = document.createElement('div');
content.className = 'content';
const header = document.createElement('div');
header.className = 'header tw-text-center';
header.textContent = repo.name;
const desc = document.createElement('div');
desc.className = 'description tw-text-center tw-text-balance tw-mb-2';
desc.textContent = repo.description;
// 添加标签
if (repo.tags.length > 0) {
const tagsContainer = document.createElement('div');
tagsContainer.className = 'tw-flex tw-justify-center tw-flex-wrap tw-gap-1 tw-mt-2';
repo.tags.slice(0, 6).forEach(tag => {
const parsed = parseTag(tag);
const tagLabel = document.createElement('span');
const color = parsed.color || getSmartColor(parsed.value);
// 确保颜色类在 label 之前
tagLabel.className = 'ui mini ' + color + ' label';
// 如果有类型前缀,显示类型标签
if (parsed.typeLabel) {
tagLabel.innerHTML = '<small>' + parsed.typeLabel + '</small> ' + parsed.display;
} else {
tagLabel.textContent = parsed.display;
}
tagLabel.style.cursor = 'pointer';
tagLabel.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
toggleTag(tag);
});
tagsContainer.appendChild(tagLabel);
});
const card = `
<div class="ui card migrate-entry">
<a class="ui card migrate-entry tw-flex tw-items-center"
href=" {{ .AppSubUrl }} /repo/create?template_name=$ { repo.name}&description=$ { repo.clone_url}&template_full_name=$ { repo.full_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>
`;
cardsContainer.insertAdjacentHTML('beforeend', card);
});
} else {
cardsContainer.innerHTML = '<div class="ui warning message">未找到匹配的模板</div>';
}
if (repo.tags.length > 6) {
const moreLabel = document.createElement('span');
moreLabel.className = 'ui mini label grey';
moreLabel.textContent = '+' + (repo.tags.length - 6);
tagsContainer.appendChild(moreLabel);
}
// 确保下拉框状态正确
sortDropdown.dropdown('refresh' );
content.appendChild(header);
content.appendChild(desc );
content.appendChild(tagsContainer);
} else {
content.appendChild(header);
content.appendChild(desc);
}
card.appendChild(imageContainer);
card.appendChild(content);
cardsContainer.appendChild(card);
});
} else {
const msg = i18n.noResults;
cardsContainer.innerHTML = '<div class="ui warning message">' + msg + '</div>';
}
});
});
}
}
window.addEventListener('load', initPage);
})();
</script>
{{ template "base/footer" . }}
这里的os应该是开发环境操作系统,可以考虑叫devos开发环境OS,类似devos:ubuntu-latest
docker search ubuntu
docker search alpine
docker search centos
docker search openeuler/openeuler
docker search debian
docker search fedora
主要有这些,每种还有不同的版本,怎么形成开发环境OS的下拉列表项目?合并所有devos:类?默认植入一些主流的版本?