363 lines
9.6 KiB
Handlebars
363 lines
9.6 KiB
Handlebars
<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> |