752 lines
21 KiB
HTML
752 lines
21 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Item Editor - Command Storage - Devanturas{% endblock %}
|
||
{% block description %}Store commands longer than 256 characters temporarily for Minecraft Item Editor plugin{% endblock %}
|
||
|
||
{% block content %}
|
||
<section class="page-header">
|
||
<div class="container">
|
||
<div class="project-breadcrumb">
|
||
<a href="{{ url_for('projects') }}">Projects</a> / Item Editor / Command Storage
|
||
</div>
|
||
<h1>Item Editor Command Storage</h1>
|
||
<p>Store long commands (>256 chars) temporarily and generate a retrievable link</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="command-storage-section">
|
||
<div class="container">
|
||
<div class="storage-grid">
|
||
<!-- Left: Command Input -->
|
||
<div class="storage-card">
|
||
<div class="card-header">
|
||
<i class="fas fa-terminal"></i>
|
||
<h2>Store Command</h2>
|
||
</div>
|
||
<p class="card-description">
|
||
Paste your Minecraft command below. Commands longer than 256 characters will be stored
|
||
for 30 minutes and a unique retrieval link will be generated.
|
||
</p>
|
||
|
||
<form id="commandForm">
|
||
<div class="form-group">
|
||
<label for="command">Minecraft Command</label>
|
||
<textarea
|
||
id="command"
|
||
name="command"
|
||
rows="6"
|
||
placeholder="Paste your command here..."
|
||
required
|
||
></textarea>
|
||
<div class="char-counter">
|
||
<span id="charCount">0</span> characters
|
||
<span id="charStatus"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary" id="submitBtn">
|
||
<i class="fas fa-save"></i> Store Command
|
||
</button>
|
||
</form>
|
||
|
||
<!-- Success Message -->
|
||
<div id="successMessage" style="display: none;" class="success-box">
|
||
<div class="success-header">
|
||
<i class="fas fa-check-circle"></i>
|
||
<h3>Command Stored Successfully!</h3>
|
||
</div>
|
||
<p class="success-description">Your command has been stored for 30 minutes.</p>
|
||
|
||
<div class="link-display">
|
||
<label>Retrieval Link (JSON):</label>
|
||
<div class="link-box">
|
||
<input type="text" id="generatedLink" readonly>
|
||
<button class="btn btn-secondary btn-copy" onclick="copyLink()">
|
||
<i class="fas fa-copy"></i> Copy
|
||
</button>
|
||
</div>
|
||
<small>Use this link in your Item Editor plugin to load the command.</small>
|
||
</div>
|
||
|
||
<div class="expiry-info">
|
||
<i class="fas fa-clock"></i>
|
||
<span>Expires in: <strong id="expiryTime">30 minutes</strong></span>
|
||
</div>
|
||
|
||
<button class="btn btn-outline" onclick="resetForm()">
|
||
<i class="fas fa-plus"></i> Store Another Command
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Recently Stored Commands Section -->
|
||
<div id="recentCommandsSection" class="recent-commands-section" style="margin-top:2.5rem;">
|
||
<h3 style="color:#00d4ff; margin-bottom:1rem;">Recently Stored Commands</h3>
|
||
<div id="recentCommandsList" class="recent-commands-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right: Info & Stats -->
|
||
<div class="info-sidebar">
|
||
<div class="info-card">
|
||
<div class="info-icon">
|
||
<i class="fas fa-info-circle"></i>
|
||
</div>
|
||
<h3>How It Works</h3>
|
||
<ol class="info-steps">
|
||
<li>Paste your long Minecraft command</li>
|
||
<li>Click "Store Command" to generate a link</li>
|
||
<li>Copy the generated JSON link</li>
|
||
<li>Use the link in your Item Editor plugin</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<div class="info-card">
|
||
<div class="info-icon">
|
||
<i class="fas fa-shield-alt"></i>
|
||
</div>
|
||
<h3>Storage Limits</h3>
|
||
<ul class="info-list">
|
||
<li><i class="fas fa-check"></i> <strong>Duration:</strong> 30 minutes</li>
|
||
<li><i class="fas fa-check"></i> <strong>Max Length:</strong> 10,000 characters</li>
|
||
<li><i class="fas fa-check"></i> <strong>Format:</strong> JSON response</li>
|
||
<li><i class="fas fa-check"></i> <strong>Access:</strong> Anyone with link</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="info-card">
|
||
<div class="info-icon">
|
||
<i class="fas fa-server"></i>
|
||
</div>
|
||
<h3>Self-Hosting</h3>
|
||
<p class="code-description">Host this system on your own webserver with this simple PHP script:</p>
|
||
<pre class="code-block" style="font-size: 0.75rem; line-height: 1.4;"><?php
|
||
header('Content-Type: application/json');
|
||
$storage_file = 'commands.json';
|
||
|
||
// Store command (POST)
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$data = json_decode(file_get_contents('php://input'), true);
|
||
$command = $data['command'] ?? '';
|
||
|
||
if (strlen($command) > 10000) {
|
||
http_response_code(400);
|
||
echo json_encode(['error' => 'Too long']);
|
||
exit;
|
||
}
|
||
|
||
$uuid = bin2hex(random_bytes(16));
|
||
$expires = time() + 1800; // 30 min
|
||
|
||
$storage = file_exists($storage_file)
|
||
? json_decode(file_get_contents($storage_file), true)
|
||
: [];
|
||
|
||
$storage[$uuid] = [
|
||
'command' => $command,
|
||
'expires_at' => $expires
|
||
];
|
||
|
||
file_put_contents($storage_file, json_encode($storage));
|
||
|
||
echo json_encode([
|
||
'success' => true,
|
||
'url' => "https://yourserver.com/storage.php?id=$uuid"
|
||
]);
|
||
exit;
|
||
}
|
||
|
||
// Retrieve command (GET)
|
||
if (isset($_GET['id'])) {
|
||
$storage = file_exists($storage_file)
|
||
? json_decode(file_get_contents($storage_file), true)
|
||
: [];
|
||
|
||
$uuid = $_GET['id'];
|
||
|
||
if (!isset($storage[$uuid])) {
|
||
http_response_code(404);
|
||
echo json_encode(['error' => 'Not found']);
|
||
exit;
|
||
}
|
||
|
||
if ($storage[$uuid]['expires_at'] < time()) {
|
||
unset($storage[$uuid]);
|
||
file_put_contents($storage_file, json_encode($storage));
|
||
http_response_code(410);
|
||
echo json_encode(['error' => 'Expired']);
|
||
exit;
|
||
}
|
||
|
||
echo json_encode($storage[$uuid]);
|
||
exit;
|
||
}
|
||
?></pre>
|
||
<p style="color: #cfcfcf; font-size: 0.9rem; margin-top: 1rem;">
|
||
<strong>Setup:</strong> Save as <code>storage.php</code> on your webserver.
|
||
Make sure the directory is writable for <code>commands.json</code>.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="info-card" style="margin-top:2.5rem;">
|
||
<div class="info-icon">
|
||
<i class="fas fa-percent"></i>
|
||
</div>
|
||
<h3>Available Placeholders</h3>
|
||
<ul class="info-list">
|
||
<li><code>%player%</code> – Name of the player using the item</li>
|
||
<li><code>%target%</code> – Name of the target player</li>
|
||
<li><code>%x%</code> – Block X coordinate of the player</li>
|
||
<li><code>%y%</code> – Block Y coordinate of the player</li>
|
||
<li><code>%z%</code> – Block Z coordinate of the player</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<style>
|
||
.page-header {
|
||
padding: 110px 0 40px;
|
||
background: #0f0f0f;
|
||
border-bottom: 1px solid #1f1f1f;
|
||
}
|
||
|
||
.command-storage-section {
|
||
padding: 60px 0;
|
||
background: #0a0a0a;
|
||
}
|
||
|
||
.storage-grid {
|
||
display: grid;
|
||
grid-template-columns: 1.5fr 1fr;
|
||
gap: 2rem;
|
||
align-items: start;
|
||
}
|
||
|
||
.storage-card, .info-card {
|
||
background: linear-gradient(145deg, #161616, #1e1e1e);
|
||
border: 1px solid #2a2a2a;
|
||
border-radius: 16px;
|
||
padding: 2rem;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.card-header i {
|
||
color: #00d4ff;
|
||
font-size: 2rem;
|
||
}
|
||
|
||
.card-header h2 {
|
||
color: #fff;
|
||
font-size: 1.8rem;
|
||
margin: 0;
|
||
}
|
||
|
||
.card-description {
|
||
color: #cfcfcf;
|
||
margin-bottom: 2rem;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
color: #00d4ff;
|
||
font-weight: 600;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.form-group textarea {
|
||
width: 100%;
|
||
background: #121212;
|
||
border: 1px solid #2a2a2a;
|
||
border-radius: 10px;
|
||
padding: 1rem;
|
||
color: #fff;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.95rem;
|
||
resize: vertical;
|
||
transition: border-color 0.3s;
|
||
}
|
||
|
||
.form-group textarea:focus {
|
||
outline: none;
|
||
border-color: #00d4ff;
|
||
}
|
||
|
||
.char-counter {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 0.5rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
#charCount {
|
||
color: #00d4ff;
|
||
font-weight: 700;
|
||
}
|
||
|
||
#charStatus {
|
||
color: #999;
|
||
}
|
||
|
||
#charStatus.warning {
|
||
color: #ffaa00;
|
||
}
|
||
|
||
#charStatus.error {
|
||
color: #ff5555;
|
||
}
|
||
|
||
.success-box {
|
||
margin-top: 2rem;
|
||
padding: 2rem;
|
||
background: rgba(0, 255, 166, 0.05);
|
||
border: 1px solid rgba(0, 255, 166, 0.2);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.success-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.success-header i {
|
||
color: #00ffa6;
|
||
font-size: 2rem;
|
||
}
|
||
|
||
.success-header h3 {
|
||
color: #00ffa6;
|
||
margin: 0;
|
||
}
|
||
|
||
.success-description {
|
||
color: #cfcfcf;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.link-display {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.link-display label {
|
||
display: block;
|
||
color: #00d4ff;
|
||
font-weight: 600;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.link-box {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.link-box input {
|
||
flex: 1;
|
||
background: #121212;
|
||
border: 1px solid #2a2a2a;
|
||
border-radius: 8px;
|
||
padding: 0.75rem;
|
||
color: #00d4ff;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.btn-copy {
|
||
padding: 0.75rem 1rem;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.link-display small {
|
||
display: block;
|
||
color: #999;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.expiry-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
color: #cfcfcf;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.expiry-info i {
|
||
color: #ffaa00;
|
||
}
|
||
|
||
.info-sidebar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1.5rem;
|
||
}
|
||
|
||
.info-icon {
|
||
width: 50px;
|
||
height: 50px;
|
||
background: rgba(0, 212, 255, 0.1);
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.info-icon i {
|
||
color: #00d4ff;
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.info-card h3 {
|
||
color: #fff;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.info-steps {
|
||
padding-left: 1.5rem;
|
||
color: #cfcfcf;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
.info-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
}
|
||
|
||
.info-list li {
|
||
color: #cfcfcf;
|
||
margin-bottom: 0.75rem;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.info-list i {
|
||
color: #00ffa6;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.code-description {
|
||
color: #cfcfcf;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.code-block {
|
||
background: #0a0a0a;
|
||
border: 1px solid #1f1f1f;
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
color: #00d4ff;
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.85rem;
|
||
overflow-x: auto;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.recent-commands-section {
|
||
background: #181818;
|
||
border:1px solid #232323;
|
||
border-radius: 14px;
|
||
padding: 1.5rem;
|
||
margin-top: 2.5rem;
|
||
}
|
||
.recent-commands-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1.2rem;
|
||
}
|
||
.recent-command-card {
|
||
background: #222;
|
||
border-radius: 10px;
|
||
padding: 1rem 1.2rem;
|
||
}
|
||
.recent-command-row {
|
||
display: flex;
|
||
gap: .7rem;
|
||
align-items: center;
|
||
margin-bottom: .3rem;
|
||
}
|
||
.recent-command-label {
|
||
color: #00d4ff;
|
||
font-size: .95rem;
|
||
min-width: 80px;
|
||
}
|
||
.recent-command-value {
|
||
color: #fff;
|
||
font-size: .97rem;
|
||
}
|
||
.recent-command-link {
|
||
color: #00ffa6;
|
||
font-size: .97rem;
|
||
word-break: break-all;
|
||
}
|
||
.recent-command-timer {
|
||
color: #ffaa00;
|
||
font-size: .97rem;
|
||
}
|
||
.recent-command-text {
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
@media (max-width: 992px) {
|
||
.storage-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
const textarea = document.getElementById('command');
|
||
const charCount = document.getElementById('charCount');
|
||
const charStatus = document.getElementById('charStatus');
|
||
const commandForm = document.getElementById('commandForm');
|
||
const submitBtn = document.getElementById('submitBtn');
|
||
const successMessage = document.getElementById('successMessage');
|
||
|
||
// Character counter
|
||
textarea.addEventListener('input', function() {
|
||
const length = this.value.length;
|
||
charCount.textContent = length;
|
||
|
||
if (length === 0) {
|
||
charStatus.textContent = '';
|
||
charStatus.className = '';
|
||
} else if (length <= 256) {
|
||
charStatus.textContent = '(within chat limit)';
|
||
charStatus.className = '';
|
||
} else if (length <= 10000) {
|
||
charStatus.textContent = '(exceeds chat limit - storage required)';
|
||
charStatus.className = 'warning';
|
||
} else {
|
||
charStatus.textContent = '(exceeds maximum length)';
|
||
charStatus.className = 'error';
|
||
}
|
||
});
|
||
|
||
// Form submission
|
||
commandForm.addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
|
||
const command = textarea.value.trim();
|
||
if (!command) return;
|
||
|
||
if (command.length > 10000) {
|
||
alert('Command exceeds maximum length of 10,000 characters');
|
||
return;
|
||
}
|
||
|
||
submitBtn.disabled = true;
|
||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Storing...';
|
||
|
||
try {
|
||
const response = await fetch('/projects/itemeditor/storage', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ command: command })
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
// Hide form, show success
|
||
commandForm.style.display = 'none';
|
||
successMessage.style.display = 'block';
|
||
|
||
// Set generated link
|
||
document.getElementById('generatedLink').value = data.url;
|
||
|
||
// Start countdown
|
||
startCountdown(data.expires_at);
|
||
} else {
|
||
alert('Error: ' + (data.error || 'Failed to store command'));
|
||
submitBtn.disabled = false;
|
||
submitBtn.innerHTML = '<i class="fas fa-save"></i> Store Command';
|
||
}
|
||
} catch (error) {
|
||
console.error('Error:', error);
|
||
alert('Failed to store command. Please try again.');
|
||
submitBtn.disabled = false;
|
||
submitBtn.innerHTML = '<i class="fas fa-save"></i> Store Command';
|
||
}
|
||
});
|
||
|
||
function copyLink() {
|
||
const linkInput = document.getElementById('generatedLink');
|
||
linkInput.select();
|
||
document.execCommand('copy');
|
||
|
||
const copyBtn = event.target.closest('.btn-copy');
|
||
const originalHTML = copyBtn.innerHTML;
|
||
copyBtn.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
||
setTimeout(() => {
|
||
copyBtn.innerHTML = originalHTML;
|
||
}, 2000);
|
||
}
|
||
|
||
function resetForm() {
|
||
commandForm.style.display = 'block';
|
||
successMessage.style.display = 'none';
|
||
textarea.value = '';
|
||
charCount.textContent = '0';
|
||
charStatus.textContent = '';
|
||
submitBtn.disabled = false;
|
||
submitBtn.innerHTML = '<i class="fas fa-save"></i> Store Command';
|
||
document.getElementById('generatedLink').value = '';
|
||
document.getElementById('expiryTime').textContent = '30 minutes';
|
||
}
|
||
|
||
function startCountdown(expiresAt) {
|
||
const expiryTime = document.getElementById('expiryTime');
|
||
const endTime = new Date(expiresAt).getTime();
|
||
|
||
// Update countdown immediately
|
||
function updateCountdown() {
|
||
const now = new Date().getTime();
|
||
const distance = endTime - now;
|
||
|
||
if (distance < 0) {
|
||
expiryTime.innerHTML = '<span style="color: #ff5555;">Expired</span>';
|
||
return false;
|
||
}
|
||
|
||
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
|
||
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
|
||
expiryTime.textContent = `${minutes}m ${seconds}s`;
|
||
return true;
|
||
}
|
||
|
||
// Update immediately
|
||
if (!updateCountdown()) return;
|
||
|
||
// Then update every second
|
||
const interval = setInterval(() => {
|
||
if (!updateCountdown()) {
|
||
clearInterval(interval);
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
// --- Recent Commands Storage (localStorage) ---
|
||
function getRecentCommands() {
|
||
let cmds = [];
|
||
try {
|
||
cmds = JSON.parse(localStorage.getItem('recentCommands') || '[]');
|
||
} catch {}
|
||
return Array.isArray(cmds) ? cmds : [];
|
||
}
|
||
function setRecentCommands(cmds) {
|
||
localStorage.setItem('recentCommands', JSON.stringify(cmds));
|
||
}
|
||
function addRecentCommand(cmd) {
|
||
let cmds = getRecentCommands();
|
||
cmds.unshift(cmd);
|
||
cmds = cmds.filter(c => c && c.expires_at && new Date(c.expires_at).getTime() > Date.now());
|
||
if (cmds.length > 5) cmds = cmds.slice(0,5);
|
||
setRecentCommands(cmds);
|
||
}
|
||
function renderRecentCommands() {
|
||
const list = document.getElementById('recentCommandsList');
|
||
const cmds = getRecentCommands();
|
||
if (!cmds.length) {
|
||
list.innerHTML = '<div style="color:#888;">No recent commands.</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = '';
|
||
cmds.forEach((cmd, idx) => {
|
||
const div = document.createElement('div');
|
||
div.className = 'recent-command-card';
|
||
div.innerHTML = `
|
||
<div class="recent-command-row">
|
||
<span class="recent-command-label">Command:</span>
|
||
<span class="recent-command-value recent-command-text">${cmd.command.length > 60 ? cmd.command.slice(0,60)+'...' : cmd.command}</span>
|
||
</div>
|
||
<div class="recent-command-row">
|
||
<span class="recent-command-label">Link:</span>
|
||
<a href="${cmd.url}" target="_blank" class="recent-command-link">${cmd.url}</a>
|
||
</div>
|
||
<div class="recent-command-row">
|
||
<span class="recent-command-label">Expires in:</span>
|
||
<span class="recent-command-timer" id="recent-timer-${idx}"></span>
|
||
</div>
|
||
`;
|
||
list.appendChild(div);
|
||
startRecentCountdown(cmd.expires_at, `recent-timer-${idx}`, cmd.url);
|
||
});
|
||
}
|
||
function startRecentCountdown(expiresAt, elemId, url) {
|
||
const el = document.getElementById(elemId);
|
||
function update() {
|
||
const now = Date.now();
|
||
const end = new Date(expiresAt).getTime();
|
||
const dist = end - now;
|
||
if (dist < 0) {
|
||
el.innerHTML = '<span style="color:#ff5555;">Expired</span>';
|
||
// Remove from localStorage
|
||
let cmds = getRecentCommands().filter(c => c.url !== url);
|
||
setRecentCommands(cmds);
|
||
renderRecentCommands();
|
||
return false;
|
||
}
|
||
const m = Math.floor((dist % (1000*60*60))/(1000*60));
|
||
const s = Math.floor((dist % (1000*60))/1000);
|
||
el.textContent = `${m}m ${s}s`;
|
||
return true;
|
||
}
|
||
if (!update()) return;
|
||
const interval = setInterval(() => {
|
||
if (!update()) clearInterval(interval);
|
||
}, 1000);
|
||
}
|
||
|
||
// On page load
|
||
renderRecentCommands();
|
||
|
||
// On successful store, add to recent
|
||
const origSuccessHandler = function(data) {
|
||
addRecentCommand({
|
||
command: textarea.value.trim(),
|
||
url: data.url,
|
||
expires_at: data.expires_at
|
||
});
|
||
renderRecentCommands();
|
||
};
|
||
|
||
// Patch the form submit handler to call origSuccessHandler
|
||
const origSubmit = commandForm.onsubmit;
|
||
commandForm.onsubmit = function(e) {
|
||
if (origSubmit) origSubmit.call(this, e);
|
||
};
|
||
|
||
// Patch the fetch success in the submit handler
|
||
const origFetch = window.fetch;
|
||
window.fetch = async function() {
|
||
const res = await origFetch.apply(this, arguments);
|
||
if (arguments[0] && arguments[0].toString().includes('/projects/itemeditor/storage')) {
|
||
try {
|
||
const clone = res.clone();
|
||
const data = await clone.json();
|
||
if (data.success && textarea && textarea.value) {
|
||
origSuccessHandler(data);
|
||
}
|
||
} catch {}
|
||
}
|
||
return res;
|
||
};
|
||
</script>
|
||
{% endblock %}
|