const XSS = {
escapeHtml: function (str) {
if (str === null || str === undefined) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
},
setTextSafe: function (element, text) {
if (element) element.textContent = text;
},
sanitizeUrl: function (url) {
if (!url) return '';
const cleaned = String(url).replace(/[\x00-\x1F\x7F]/g, '').trim();
try {
const parsed = new URL(cleaned, window.location.origin);
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') return parsed.href;
} catch (e) {
if (cleaned.startsWith('/') && !cleaned.startsWith('//')) return cleaned;
}
return '';
}
};
window.XSS = XSS;
let _pendingConfirmForm = null;
function showConfirmModal(message, onOk) {
const bodyEl = document.getElementById('confirmModalBody');
const okBtn = document.getElementById('confirmModalOk');
if (!bodyEl || !okBtn) { if (onOk) onOk(); return; }
bodyEl.textContent = message;
const handler = function () {
okBtn.removeEventListener('click', handler);
bootstrap.Modal.getInstance(document.getElementById('confirmModal')).hide();
if (onOk) onOk();
};
okBtn.addEventListener('click', handler);
new bootstrap.Modal(document.getElementById('confirmModal')).show();
}
window.showConfirmModal = showConfirmModal;
function setupFormSubmitOnce(form) {
form.addEventListener('submit', function () {
const btn = form.querySelector('[type=submit]');
if (!btn || btn.disabled) return;
btn.disabled = true;
const orig = btn.innerHTML;
btn.innerHTML = '処理中...';
window.addEventListener('pageshow', function () {
btn.disabled = false;
btn.innerHTML = orig;
}, { once: true });
});
}
function showCopyFeedback(message) {
let el = document.getElementById('globalCopyFeedback');
if (!el) {
el = document.createElement('div');
el.id = 'globalCopyFeedback';
el.className = 'copy-feedback alert alert-success shadow-sm py-2 px-3';
document.body.appendChild(el);
}
el.textContent = message;
el.classList.add('show');
clearTimeout(el._timeout);
el._timeout = setTimeout(function () { el.classList.remove('show'); }, 2000);
}
window.showCopyFeedback = showCopyFeedback;
document.addEventListener('DOMContentLoaded', function () {
const alerts = document.querySelectorAll('.alert:not(.alert-danger):not(.modal .alert)');
alerts.forEach(function (alert) {
setTimeout(function () {
alert.classList.add('fade');
setTimeout(function () { alert.remove(); }, 150);
}, 5000);
});
document.querySelectorAll('form[data-confirm]').forEach(function (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
const msg = form.dataset.confirm;
showConfirmModal(msg, function () { form.submit(); });
});
});
document.querySelectorAll('form:not([data-confirm])').forEach(setupFormSubmitOnce);
const dueDateInput = document.getElementById('due_date');
if (dueDateInput && !dueDateInput.value) {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(23, 59, 0, 0);
dueDateInput.value = tomorrow.toISOString().slice(0, 16);
}
});