Files
tamabox/templates/user/vote-form.html
T
tamakyi e3e59e7c69 chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
2026-09-10 10:56:27 +08:00

393 lines
23 KiB
HTML
Raw 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.
{{template "base/header" .}}
<div class="uk-flex uk-flex-between uk-flex-middle uk-margin-bottom">
<h3 class="uk-margin-remove">
{{ if .IsEdit }}{{t .LangCode "vote.editTitle"}}{{ else }}{{t .LangCode "vote.newTitle"}}{{ end }}
</h3>
<a class="uk-button uk-button-default uk-button-small" href="/user/questions#votes">{{t .LangCode "common.back"}}</a>
</div>
<form method="post" action="{{ if .IsEdit }}/user/votes/{{ .Vote.ID }}{{ else }}/user/votes{{ end }}" onsubmit="return validateBuilderForm(event)">
{{ .CSRFTokenHTML }}
{{template "base/alert" .}}
<!-- 基本设置 -->
<div class="uk-card uk-card-default uk-card-small uk-card-body uk-margin-bottom">
<h4 class="uk-margin-small-bottom">{{t .LangCode "vote.formBasic"}}</h4>
{{ if .LockedContent }}
<div class="uk-alert uk-alert-warning uk-margin-small-bottom">{{t .LangCode "vote.formLockedHint"}}</div>
{{ end }}
<div class="uk-margin-small">
<label class="uk-form-label">{{t .LangCode "vote.formTitle"}} *</label>
<input class="uk-input" type="text" name="title" maxlength="100" required value="{{ if .Vote }}{{ .Vote.Title }}{{ end }}" placeholder="{{t .LangCode "vote.formTitlePlaceholder"}}">
</div>
<div class="uk-margin-small">
<label class="uk-form-label">{{t .LangCode "vote.formDescription"}}</label>
<textarea class="uk-textarea" name="description" rows="3" maxlength="5000" placeholder="{{t .LangCode "vote.formDescriptionPlaceholder"}}">{{ if .Vote }}{{ .Vote.Description }}{{ end }}</textarea>
</div>
<div class="uk-grid-small uk-child-width-1-2@s uk-margin-small-top" uk-grid>
<div>
<label class="uk-form-label">{{t .LangCode "vote.formType"}}</label>
{{ if .LockedContent }}
<input class="uk-input" type="text" disabled
value="{{ if eq .Vote.Type "single" }}{{t $.LangCode "vote.typeSingle"}}{{ else if eq .Vote.Type "multiple" }}{{t $.LangCode "vote.typeMultiple"}}{{ else if eq .Vote.Type "rating" }}{{t $.LangCode "vote.typeRating"}}{{ else if eq .Vote.Type "rank" }}{{t $.LangCode "vote.typeRank"}}{{ else }}{{t $.LangCode "vote.typeForm"}}{{ end }}">
{{ else }}
<select class="uk-select" name="type" id="vote-type" onchange="toggleEditor()">
<option value="single" {{ if and .Vote (eq .Vote.Type "single") }}selected{{ end }}>{{t .LangCode "vote.typeSingle"}}</option>
<option value="multiple" {{ if and .Vote (eq .Vote.Type "multiple") }}selected{{ end }}>{{t .LangCode "vote.typeMultiple"}}</option>
<option value="rating" {{ if and .Vote (eq .Vote.Type "rating") }}selected{{ end }}>{{t .LangCode "vote.typeRating"}}</option>
<option value="rank" {{ if and .Vote (eq .Vote.Type "rank") }}selected{{ end }}>{{t .LangCode "vote.typeRank"}}</option>
<option value="form" {{ if and .Vote (eq .Vote.Type "form") }}selected{{ end }}>{{t .LangCode "vote.typeForm"}}</option>
</select>
{{ end }}
</div>
<div>
<label class="uk-form-label">{{t .LangCode "vote.formVisibility"}} *</label>
<select class="uk-select" name="visibility">
<option value="public" {{ if and .Vote (eq .Vote.Visibility "public") }}selected{{ end }}>{{t .LangCode "vote.formVisibilityPublic"}}</option>
<option value="link_only" {{ if and .Vote (eq .Vote.Visibility "link_only") }}selected{{ end }}>{{t .LangCode "vote.formVisibilityLinkOnly"}}</option>
</select>
</div>
<div>
<label class="uk-form-label">{{t .LangCode "vote.formVoterScope"}} *</label>
<select class="uk-select" name="voter_scope">
<option value="anyone" {{ if and .Vote (eq .Vote.VoterScope "anyone") }}selected{{ end }}>{{t .LangCode "vote.formVoterScopeAnyone"}}</option>
<option value="registered_only" {{ if and .Vote (eq .Vote.VoterScope "registered_only") }}selected{{ end }}>{{t .LangCode "vote.formVoterScopeRegistered"}}</option>
</select>
</div>
<div>
<label class="uk-form-label">{{t .LangCode "vote.formShowResult"}} *</label>
<select class="uk-select" name="show_result">
<option value="after_vote" {{ if and .Vote (eq .Vote.ShowResult "after_vote") }}selected{{ end }}>{{t .LangCode "vote.formShowResultAfterVote"}}</option>
<option value="after_end" {{ if and .Vote (eq .Vote.ShowResult "after_end") }}selected{{ end }}>{{t .LangCode "vote.formShowResultAfterEnd"}}</option>
<option value="owner_only" {{ if and .Vote (eq .Vote.ShowResult "owner_only") }}selected{{ end }}>{{t .LangCode "vote.formShowResultOwnerOnly"}}</option>
</select>
</div>
<div class="uk-margin-small-top">
<label class="uk-form-label">{{t .LangCode "vote.formStartAt"}}</label>
<input class="uk-input" type="datetime-local" name="start_at" value="{{ .StartAtStr }}">
</div>
<div class="uk-margin-small-top">
<label class="uk-form-label">{{t .LangCode "vote.formEndAt"}}</label>
<input class="uk-input" type="datetime-local" name="end_at" value="{{ .EndAtStr }}">
</div>
<div class="uk-margin-small-top">
<label class="uk-form-label">{{t .LangCode "vote.formQuotaLimit"}}</label>
<input class="uk-input" type="number" name="quota_limit" min="0" max="1000000" value="{{ if .Vote }}{{ .Vote.QuotaLimit }}{{ else }}0{{ end }}">
<span class="uk-text-meta uk-text-small">{{t .LangCode "vote.formQuotaHint"}}</span>
</div>
{{ if not .LockedContent }}
<div id="max-choices-field" class="uk-margin-small-top">
<label class="uk-form-label">{{t .LangCode "vote.formMaxChoices"}}</label>
<input class="uk-input" type="number" name="max_choices" min="1" max="20" value="{{ if .Vote }}{{ .Vote.MaxChoices }}{{ else }}3{{ end }}">
</div>
{{ end }}
</div>
<!-- 限投提醒:移出两列网格,桌面/移动均占满整行,避免与输入框并排显得局促 -->
<!-- registered_only 时限制维度变为"每账号",否则为"每设备",文案随之切换 -->
<div class="uk-margin-small-top">
<div id="scope-hint" class="uk-alert uk-alert-primary uk-margin-remove-bottom" role="status">
<span id="scope-hint-device"{{ if and .Vote (eq .Vote.VoterScope "registered_only") }} hidden{{ end }}>{{t .LangCode "vote.formMaxVotesPerFingerprint"}}</span><span id="scope-hint-account"{{ if and .Vote (eq .Vote.VoterScope "registered_only") }}{{ else }} hidden{{ end }}>{{t .LangCode "vote.formMaxVotesPerAccount"}}</span>
</div>
</div>
<div class="uk-margin-small-top uk-grid-small uk-child-width-1-2@s" uk-grid>
<!-- 全局"允许补充自定义选项"仅对单选/多选/排序生效;表单收集与评分类型由各自机制控制,此处隐藏避免误导 -->
<label id="global-custom-field" class="uk-form-label" {{ if and .Vote (or (eq .Vote.Type "form") (eq .Vote.Type "rating")) }}hidden{{ end }}><input class="uk-checkbox" type="checkbox" name="allow_custom" {{ if and .Vote .Vote.AllowCustom }}checked{{ end }}> {{t .LangCode "vote.formAllowCustom"}}</label>
<label class="uk-form-label"><input class="uk-checkbox" type="checkbox" name="require_captcha" checked disabled> {{t .LangCode "vote.formRequireCaptcha"}}</label>
</div>
</div>
{{ if not .LockedContent }}
<!-- 选项编辑器(单选/多选/排序;评分题无需选项) -->
<div class="uk-card uk-card-default uk-card-small uk-card-body uk-margin-bottom" id="options-editor">
<h4 class="uk-margin-small-bottom">{{t .LangCode "vote.formOptions"}}</h4>
<div id="rating-hint" class="uk-alert uk-alert-primary uk-margin-small-bottom" hidden>{{t .LangCode "vote.formRatingHint"}}</div>
<div id="options-list"></div>
<button type="button" class="uk-button uk-button-default uk-button-small" onclick="addOption()">{{t .LangCode "vote.addOption"}}</button>
<input type="hidden" name="options" id="options-payload">
</div>
<!-- 表单字段设计器(表单收集类型) -->
<div class="uk-card uk-card-default uk-card-small uk-card-body uk-margin-bottom" id="fields-editor" hidden>
<h4 class="uk-margin-small-bottom">{{t .LangCode "vote.formFields"}}</h4>
<div id="fields-list"></div>
<button type="button" class="uk-button uk-button-default uk-button-small" onclick="addField()">{{t .LangCode "vote.addField"}}</button>
<input type="hidden" name="form_fields" id="fields-payload">
</div>
{{ end }}
<div class="uk-margin">
<input type="hidden" name="action" id="vote-action" value="save">
{{ if and .IsEdit (not .LockedContent) }}<button class="uk-button uk-button-default" type="submit" onclick="setVoteAction('preview')">{{t .LangCode "vote.preview"}}</button>{{ end }}
<button class="uk-button uk-button-primary" type="submit" onclick="setVoteAction('save')">{{ if .IsEdit }}{{t .LangCode "common.save"}}{{ else }}{{t .LangCode "vote.createDraft"}}{{ end }}</button>
{{ if not .LockedContent }}<button class="uk-button uk-button-secondary" type="submit" onclick="setVoteAction('publish')">{{t .LangCode "vote.publish"}}</button>{{ end }}
<a class="uk-button uk-button-default" href="/user/questions#votes">{{t .LangCode "common.cancel"}}</a>
</div>
<div id="builder-hint" class="uk-alert uk-alert-danger uk-margin-small-top" hidden></div>
</form>
<script>
var INITIAL_OPTIONS = {{ toJSON .InitialOptions }};
var INITIAL_FIELDS = {{ toJSON .InitialFields }};
// 站长侧单个选项文本上限,与后端 conf.Limits.VoteOptionLen([limits] 配置段)同源。
// 参与者的自定义补充文本上限是另一套 MAX_CUSTOM_LEN(conf.Limits.CustomAnswerLen),二者独立。
var MAX_OPTION_LEN = {{ .MaxVoteOptionLen }};
var optionsData = [];
var fieldsData = [];
function toggleEditor() {
var t = document.getElementById('vote-type').value;
var optEditor = document.getElementById('options-editor');
var fldEditor = document.getElementById('fields-editor');
var maxField = document.getElementById('max-choices-field');
var ratingHint = document.getElementById('rating-hint');
var globalCustom = document.getElementById('global-custom-field');
optEditor.hidden = (t === 'form');
fldEditor.hidden = (t !== 'form');
maxField.hidden = (t !== 'multiple');
ratingHint.hidden = (t !== 'rating');
if (globalCustom) { globalCustom.hidden = (t === 'form' || t === 'rating'); }
updateBuilderHint();
}
function addOption() { optionsData.push(''); renderOptions(); updateBuilderHint(); }
function removeOption(i) { optionsData.splice(i, 1); renderOptions(); updateBuilderHint(); }
function renderOptions() {
var list = document.getElementById('options-list');
list.innerHTML = '';
optionsData.forEach(function (val, i) {
var row = document.createElement('div');
row.className = 'uk-flex uk-flex-middle uk-margin-small-bottom';
row.style.gap = '6px';
row.innerHTML = '<span class="uk-text-meta">' + (i + 1) + '.</span>' +
'<input class="uk-input" style="flex:1" type="text" maxlength="' + MAX_OPTION_LEN + '" placeholder="{{t .LangCode "vote.optionPlaceholder"}}" value="' + escapeAttr(val) + '" oninput="optionsData[' + i + ']=this.value">' +
'<button type="button" class="uk-button uk-button-danger uk-button-small" onclick="removeOption(' + i + ')">{{t .LangCode "common.delete"}}</button>';
list.appendChild(row);
});
}
function addField() {
fieldsData.push({ field_type: 'radio', label: '', required: false, max_length: 0, optionsArr: [], allow_custom: false });
renderFields();
updateBuilderHint();
}
function removeField(i) { fieldsData.splice(i, 1); renderFields(); updateBuilderHint(); }
// 字段上下移:交换数组元素后重渲染;保存时 payload 顺序即新顺序(后端按数组下标写 SortOrder)
function moveField(i, dir) {
var j = i + dir;
if (j < 0 || j >= fieldsData.length) { return; }
var tmp = fieldsData[i]; fieldsData[i] = fieldsData[j]; fieldsData[j] = tmp;
renderFields();
updateBuilderHint();
}
function renderFields() {
var list = document.getElementById('fields-list');
list.innerHTML = '';
fieldsData.forEach(function (f, i) {
var box = document.createElement('div');
box.className = 'uk-card uk-card-default uk-card-small uk-card-body uk-margin-small-bottom';
box.innerHTML =
'<div class="uk-flex uk-flex-between uk-flex-middle uk-margin-small-bottom">' +
'<span class="uk-text-bold">{{t .LangCode "vote.field"}} ' + (i + 1) + '</span>' +
'<div class="uk-flex uk-flex-middle" style="gap:6px">' +
'<button type="button" class="uk-button uk-button-default uk-button-small"' + (i === 0 ? ' disabled' : '') + ' onclick="moveField(' + i + ',-1)" title="{{t .LangCode "common.moveUp"}}">↑</button>' +
'<button type="button" class="uk-button uk-button-default uk-button-small"' + (i === fieldsData.length - 1 ? ' disabled' : '') + ' onclick="moveField(' + i + ',1)" title="{{t .LangCode "common.moveDown"}}">↓</button>' +
'<button type="button" class="uk-button uk-button-danger uk-button-small" onclick="removeField(' + i + ')">{{t .LangCode "common.delete"}}</button>' +
'</div>' +
'</div>' +
'<div class="uk-grid-small uk-child-width-1-2@s" uk-grid>' +
'<div><label class="uk-form-label">{{t .LangCode "vote.formFieldType"}}</label>' +
'<select class="uk-select" onchange="fieldsData[' + i + '].field_type=this.value;renderFields()">' +
'<option value="radio"' + (f.field_type === 'radio' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeRadio"}}</option>' +
'<option value="text"' + (f.field_type === 'text' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeText"}}</option>' +
'<option value="textarea"' + (f.field_type === 'textarea' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeTextarea"}}</option>' +
'<option value="select"' + (f.field_type === 'select' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeSelect"}}</option>' +
'<option value="checkbox"' + (f.field_type === 'checkbox' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeCheckbox"}}</option>' +
'<option value="number"' + (f.field_type === 'number' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeNumber"}}</option>' +
'<option value="rating"' + (f.field_type === 'rating' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeRating"}}</option>' +
'<option value="rank"' + (f.field_type === 'rank' ? ' selected' : '') + '>{{t .LangCode "vote.fieldTypeRank"}}</option>' +
'</select></div>' +
'<div><label class="uk-form-label">{{t .LangCode "vote.formFieldLabel"}}</label>' +
'<input class="uk-input" type="text" maxlength="100" value="' + escapeAttr(f.label) + '" oninput="fieldsData[' + i + '].label=this.value"></div>' +
'</div>' +
'<div class="uk-grid-small uk-child-width-1-3@s uk-margin-small-top" uk-grid>' +
'<label class="uk-form-label"><input class="uk-checkbox" type="checkbox"' + (f.required ? ' checked' : '') + ' onchange="fieldsData[' + i + '].required=this.checked"> {{t .LangCode "vote.formFieldRequired"}}</label>' +
'<div id="maxlen-' + i + '"' + (isTextType(f.field_type) ? '' : ' style="display:none"') + '><label class="uk-form-label">{{t .LangCode "vote.formFieldMaxLength"}}</label>' +
'<input class="uk-input" type="number" min="0" max="2000" value="' + (f.max_length || 0) + '" oninput="fieldsData[' + i + '].max_length=parseInt(this.value||0)"></div>' +
'</div>' +
'<div class="uk-margin-small-top" id="opts-' + i + '"' + (isChoiceType(f.field_type) ? '' : ' hidden') + '>' +
'<label class="uk-form-label">{{t .LangCode "vote.formFieldOptions"}}</label>' +
'<div class="uk-text-meta uk-margin-small-top">{{t .LangCode "vote.formFieldOptionsHint"}}</div>' +
'<div id="fopts-' + i + '" class="uk-margin-small-top"></div>' +
'<button type="button" class="uk-button uk-button-default uk-button-small" onclick="addFieldOption(' + i + ')">{{t .LangCode "vote.addOption"}}</button>' +
'</div>' +
'<div class="uk-margin-small-top" id="custom-' + i + '"' + (isCustomType(f.field_type) ? '' : ' hidden') + '>' +
'<label class="uk-form-label"><input class="uk-checkbox" type="checkbox"' + (f.allow_custom ? ' checked' : '') + ' onchange="fieldsData[' + i + '].allow_custom=this.checked"> {{t .LangCode "vote.fieldAllowCustom"}}</label>' +
'</div>';
list.appendChild(box);
if (isChoiceType(f.field_type)) { renderFieldOptions(i); }
});
if (typeof UIkit !== 'undefined') { UIkit.update(list); }
}
function isChoiceType(t) { return t === 'select' || t === 'radio' || t === 'checkbox' || t === 'rank'; }
// 支持"补充自定义选项"的字段类型(rank 排序题不开放)
function isCustomType(t) { return t === 'select' || t === 'radio' || t === 'checkbox'; }
function isTextType(t) { return t === 'text' || t === 'textarea'; }
// 字段选项兼容处理:后端可能返回数组(新)或逗号字符串(旧数据/回填),统一为数组。
function normalizeFieldOptions(v) {
if (Array.isArray(v)) return v.slice();
if (typeof v === 'string' && v !== '') return v.split(',').map(function (x) { return x.trim(); }).filter(function (x) { return x !== ''; });
return [];
}
// 渲染单个字段的选项行(逐条 add/remove),避免整段文本框 + 逗号分隔。
function renderFieldOptions(i) {
var box = document.getElementById('fopts-' + i);
if (!box) return;
box.innerHTML = '';
var arr = fieldsData[i].optionsArr || [];
arr.forEach(function (val, j) {
var row = document.createElement('div');
row.className = 'uk-flex uk-flex-middle uk-margin-small-bottom';
row.style.gap = '6px';
row.innerHTML = '<span class="uk-text-meta">' + (j + 1) + '.</span>' +
'<input class="uk-input" style="flex:1" type="text" maxlength="' + MAX_OPTION_LEN + '" placeholder="{{t .LangCode "vote.optionPlaceholder"}}" value="' + escapeAttr(val) + '" oninput="fieldsData[' + i + '].optionsArr[' + j + ']=this.value">' +
'<button type="button" class="uk-button uk-button-danger uk-button-small" onclick="removeFieldOption(' + i + ',' + j + ')">{{t .LangCode "common.delete"}}</button>';
box.appendChild(row);
});
}
function addFieldOption(i) { (fieldsData[i].optionsArr = fieldsData[i].optionsArr || []).push(''); renderFieldOptions(i); }
function removeFieldOption(i, j) { fieldsData[i].optionsArr.splice(j, 1); renderFieldOptions(i); }
// 设置提交动作:save=保存草稿(宽松),publish=发布(一次性完整校验)。
function setVoteAction(a) { document.getElementById('vote-action').value = a; }
// 前端即时校验:根据题型/字段返回需提示给用户的信息;空字符串表示通过。
// 锁定模式(已发布投票)下无选项/字段编辑器,直接返回通过。
function computeBuilderHint() {
if (!document.getElementById('options-payload') && !document.getElementById('fields-payload')) {
return '';
}
var t = document.getElementById('vote-type').value;
if (t === 'single' || t === 'multiple' || t === 'rank') {
var cnt = 0;
for (var i = 0; i < optionsData.length; i++) {
if (optionsData[i].trim() !== '') cnt++;
}
if (cnt < 2) return '{{t .LangCode "vote.builderHintOptions"}}';
} else if (t === 'form') {
if (fieldsData.length < 1) return '{{t .LangCode "vote.builderHintFields"}}';
for (var i = 0; i < fieldsData.length; i++) {
var f = fieldsData[i];
if (isChoiceType(f.field_type)) {
var opts = (f.optionsArr || []).map(function (x) { return x.trim(); }).filter(function (x) { return x !== ''; });
if (opts.length < (f.field_type === 'rank' ? 2 : 1)) {
return '{{t .LangCode "vote.builderHintFieldOptions"}}';
}
}
}
}
return '';
}
// 实时更新提示条(切换题型/增删选项或字段时调用)
function updateBuilderHint() {
var m = computeBuilderHint();
var el = document.getElementById('builder-hint');
if (m) { el.textContent = m; el.hidden = false; } else { el.hidden = true; }
}
// 提交时执行:先收集 payload,再按当前动作做校验。
// 发布动作下校验不通过则阻止提交;草稿动作仅提示,不阻止(草稿本就允许暂存)。
function validateBuilderForm(event) {
buildPayload();
var m = computeBuilderHint();
var el = document.getElementById('builder-hint');
if (m) {
el.textContent = m; el.hidden = false;
if (document.getElementById('vote-action').value === 'publish') {
resetSubmitState();
var form = event && event.target ? event.target : document.querySelector('form');
if (form) form.setAttribute('data-tb-skip-submit-feedback', '1');
if (event) event.preventDefault();
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
return false;
}
} else {
el.hidden = true;
}
return true;
}
function buildPayload() {
// 发布后锁定模式:无选项/字段编辑器,跳过 JSON 收集
var optPayload = document.getElementById('options-payload');
if (optPayload) {
var options = optionsData.map(function (v) { return v.trim(); }).filter(function (v) { return v !== ''; });
optPayload.value = JSON.stringify(options);
}
var fldPayload = document.getElementById('fields-payload');
if (fldPayload) {
var fields = fieldsData.map(function (f) {
return { field_type: f.field_type, label: f.label, required: f.required, max_length: f.max_length || 0, options: (f.optionsArr || []), allow_custom: !!f.allow_custom };
});
fldPayload.value = JSON.stringify(fields);
}
return true;
}
function escapeAttr(s) {
return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
// 限投维度提示:仅注册用户投票时切换为"每账号",否则"每设备"。
// 与提交逻辑一致——registered_only 下指纹维度不参与,约束实为每账号一票。
function updateScopeHint() {
var sel = document.querySelector('select[name="voter_scope"]');
var dev = document.getElementById('scope-hint-device');
var acc = document.getElementById('scope-hint-account');
if (!sel || !dev || !acc) return;
var isReg = sel.value === 'registered_only';
dev.hidden = isReg;
acc.hidden = !isReg;
}
(function init() {
// 发布后锁定模式:编辑器未渲染,跳过初始化
{{ if .LockedContent }}return;{{ end }}
optionsData = INITIAL_OPTIONS.slice();
fieldsData = INITIAL_FIELDS.map(function (f) { return { field_type: f.field_type || 'text', label: f.label || '', required: !!f.required, max_length: f.max_length || 0, optionsArr: normalizeFieldOptions(f.options), allow_custom: !!f.allow_custom }; });
renderOptions();
renderFields();
toggleEditor();
updateBuilderHint();
var scopeSel = document.querySelector('select[name="voter_scope"]');
if (scopeSel) { scopeSel.addEventListener('change', updateScopeHint); }
updateScopeHint();
})();
</script>
{{if .DraftSaved}}
<script>
if (window.history && window.history.replaceState) {
window.history.replaceState({}, document.title, window.location.pathname);
}
</script>
{{end}}
{{template "base/footer" .}}