Files
devstar/templates/shared/devcontainer/variable_list.tmpl
xinitx 9071a754f4 !108 给devcontainer增加变量和脚本功能
给devcontainer增加变量和脚本功能

- 能从devstar.cn上获取预定义的DEVSTAR_开头的变量或脚本
- 添加到脚本管理中的变量名,在devcontainer启动时会自动执行,然后才执行devcontainer.json中用户自定义脚本,其中可以调用设置的变量或脚本
- 变量或脚本在用户设置、项目设置和后台管理中都可以添加,如有重名优先级为:用户设置 > 项目设置 > 后台管理
2025-10-18 08:53:50 +00:00

363 lines
9.6 KiB
Handlebars
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<h4 class="ui top attached header">
{{ctx.Locale.Tr "devcontainer.scripts"}}
</h4>
<div class="ui attached segment">
{{ctx.Locale.Tr "devcontainer.scripts.description"}}
{{if or .Variables .DevstarVariables}}
<div class="dynamic-tags" data-tags='{{.Tags}}'></div>
{{end}}
</div>
<h4 class="ui top attached header">
{{ctx.Locale.Tr "devcontainer.variables.management"}}
<div class="ui right">
<button class="ui primary tiny button show-modal"
data-modal="#edit-variable-modal"
data-modal-form.action="{{.Link}}/new"
data-modal-header="{{ctx.Locale.Tr "devcontainer.variables.creation"}}"
data-modal-dialog-variable-name=""
data-modal-dialog-variable-data=""
data-modal-dialog-variable-description=""
>
{{ctx.Locale.Tr "devcontainer.variables.creation"}}
</button>
</div>
</h4>
<div class="ui attached segment">
{{if or .Variables .DevstarVariables}}
<div class="flex-list">
{{range .Variables}}
<div class="flex-item tw-items-center">
<div class="flex-item-leading">
{{svg "octicon-pencil" 32}}
</div>
<div class="flex-item-main">
<div class="flex-item-title">
{{.Name}}
</div>
<div class="flex-item-body">
{{if .Description}}{{.Description}}{{else}}-{{end}}
</div>
<div class="flex-item-body">
{{.Data}}
</div>
</div>
<div class="flex-item-trailing">
<span class="color-text-light-2">
{{ctx.Locale.Tr "settings.added_on" (DateUtils.AbsoluteShort .CreatedUnix)}}
</span>
<button class="btn interact-bg tw-p-2 show-modal"
data-tooltip-content="{{ctx.Locale.Tr "devcontainer.variables.edit"}}"
data-modal="#edit-variable-modal"
data-modal-form.action="{{$.Link}}/{{.ID}}/edit"
data-modal-header="{{ctx.Locale.Tr "devcontainer.variables.edit"}}"
data-modal-dialog-variable-name="{{.Name}}"
data-modal-dialog-variable-data="{{.Data}}"
data-modal-dialog-variable-description="{{.Description}}"
>
{{svg "octicon-pencil"}}
</button>
<button class="btn interact-bg tw-p-2 link-action"
data-tooltip-content="{{ctx.Locale.Tr "devcontainer.variables.deletion"}}"
data-url="{{$.Link}}/{{.ID}}/delete"
data-modal-confirm="{{ctx.Locale.Tr "devcontainer.variables.deletion.description"}}"
>
{{svg "octicon-trash"}}
</button>
</div>
</div>
{{end}}
{{range .DevstarVariables}}
<div class="flex-item tw-items-center">
<div class="flex-item-leading">
{{svg "octicon-pencil" 32}}
</div>
<div class="flex-item-main">
<div class="flex-item-title">
{{.Name}}
</div>
<div class="flex-item-body">
{{if .Description}}{{.Description}}{{else}}-{{end}}
</div>
<div class="flex-item-body">
{{.Data}}
</div>
</div>
</div>
{{end}}
</div>
{{else}}
{{ctx.Locale.Tr "devcontainer.variables.none"}}
{{end}}
</div>
{{/** Edit variable dialog */}}
<div class="ui small modal" id="edit-variable-modal">
<div class="header"></div>
<form class="ui form form-fetch-action" method="post">
<div class="content">
{{.CsrfTokenHtml}}
<div class="field">
{{ctx.Locale.Tr "devcontainer.variables.description"}}
</div>
<div class="field">
<label for="dialog-variable-name">{{ctx.Locale.Tr "name"}}</label>
<input autofocus required
name="name"
id="dialog-variable-name"
value="{{.name}}"
pattern="^(?!GITEA_|GITHUB_)[a-zA-Z_][a-zA-Z0-9_]*$"
placeholder="{{ctx.Locale.Tr "secrets.creation.name_placeholder"}}"
>
</div>
<div class="field">
<label for="dialog-variable-data">{{ctx.Locale.Tr "value"}}</label>
<textarea required
name="data"
id="dialog-variable-data"
placeholder="{{ctx.Locale.Tr "secrets.creation.value_placeholder"}}"
></textarea>
</div>
<div class="field">
<label for="dialog-variable-description">{{ctx.Locale.Tr "secrets.creation.description"}}</label>
<textarea
name="description"
id="dialog-variable-description"
rows="2"
maxlength="{{.DescriptionMaxLength}}"
placeholder="{{ctx.Locale.Tr "secrets.creation.description_placeholder"}}"
></textarea>
</div>
</div>
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
</form>
</div>
<script>
function initDynamicTags() {
// 查找所有具有 dynamic-tags 类的元素
const elements = document.querySelectorAll('.dynamic-tags');
elements.forEach(el => {
// 获取标签数据
let tags = [];
try {
tags = JSON.parse(el.getAttribute('data-tags') || '[]');
} catch (e) {
console.error('Invalid tags data:', el.getAttribute('data-tags'));
}
// 创建容器
const container = document.createElement('div');
container.className = 'dynamic-tags-container';
// 创建标签列表容器
const tagList = document.createElement('div');
tagList.className = 'dynamic-tags-list';
// 渲染标签
function renderTags() {
// 清空标签列表
tagList.innerHTML = '';
// 添加每个标签
tags.forEach(tag => {
const tagElement = document.createElement('span');
tagElement.className = 'tag-item';
tagElement.innerHTML = `
${tag}
<button class="tag-close" data-tag="${tag}">×</button>
`;
tagList.appendChild(tagElement);
});
// 添加"新增标签"按钮或输入框
const inputContainer = document.createElement('span');
inputContainer.className = 'tag-input-container';
inputContainer.innerHTML = `
<button class="tag-add-button">+ New Script</button>
<input type="text" class="tag-input" style="display: none;" placeholder="Enter tag">
`;
tagList.appendChild(inputContainer);
// 绑定事件
bindEvents();
}
// 绑定事件
function bindEvents() {
// 删除标签事件
tagList.querySelectorAll('.tag-close').forEach(button => {
button.addEventListener('click', (e) => {
const tag = e.target.getAttribute('data-tag');
// 删除标签时访问 /script/delete
fetch('{{.Link}}/script/delete?name=' + encodeURIComponent(tag), {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}).then(response => {
if (response.ok) {
tags = tags.filter(t => t !== tag);
renderTags();
console.log('Successfully deleted script for variable: ' + tag);
} else {
console.error('Failed to delete script for variable: ' + tag);
}
}).catch(error => {
console.error('Error deleting script:', error);
});
});
});
// 显示输入框事件
const addButton = tagList.querySelector('.tag-add-button');
const tagInput = tagList.querySelector('.tag-input');
addButton.addEventListener('click', () => {
addButton.style.display = 'none';
tagInput.style.display = 'inline-block';
tagInput.focus();
});
// 添加标签事件
tagInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
const value = e.target.value.trim();
if (value && !tags.includes(value)) {
// 当按下 Enter 键时,发送请求到 /script/new
fetch('{{.Link}}/script/new?name=' + encodeURIComponent(value), {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}).then(response => {
if (response.ok) {
console.log('Successfully created script for variable: ' + value);
tags.push(value);
renderTags();
} else {
console.error('Failed to create script for variable: ' + value);
}
}).catch(error => {
console.error('Error creating script:', error);
});
}
}
});
// 失去焦点时隐藏输入框
tagInput.addEventListener('blur', () => {
const value = tagInput.value.trim();
if (value && !tags.includes(value)) {
fetch('{{.Link}}/script/new?name=' + encodeURIComponent(value), {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}).then(response => {
if (response.ok) {
console.log('Successfully created script for variable: ' + value);
tags.push(value);
renderTags();
} else {
console.error('Failed to create script for variable: ' + value);
}
}).catch(error => {
console.error('Error creating script:', error);
});
}
addButton.style.display = 'inline-flex';
tagInput.style.display = 'none';
tagInput.value = '';
renderTags();
});
}
// 初始渲染
renderTags();
container.appendChild(tagList);
el.innerHTML = '';
el.appendChild(container);
});
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initDynamicTags();
});
</script>
<style>
.dynamic-tags-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.tag-item {
display: inline-flex;
align-items: center;
background-color: #007bff;
color: white;
border-radius: 4px;
padding: 4px 8px;
font-size: 12px;
margin: 2px;
}
.tag-close {
margin-left: 6px;
cursor: pointer;
opacity: 0.8;
background: none;
border: none;
color: white;
font-weight: bold;
}
.tag-close:hover {
opacity: 1;
}
.tag-add-button {
display: inline-flex;
align-items: center;
gap: 4px;
background: transparent;
border: 1px dashed #6c757d;
color: #6c757d;
border-radius: 4px;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
}
.tag-add-button:hover {
border-color: #007bff;
color: #007bff;
}
.tag-input-container {
display: inline-block;
width: 90px;
}
.tag-input {
width: 100%;
padding: 4px 8px;
font-size: 12px;
border: 1px solid #6c757d;
border-radius: 4px;
background: #fff;
color: #333;
}
.tag-input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
</style>