Add real notifications to copy buttons
This commit is contained in:
@ -292,7 +292,8 @@
|
|||||||
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
|
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-xl font-semibold text-white">Holesail Keys</h2>
|
<h2 class="text-xl font-semibold text-white">Holesail Keys</h2>
|
||||||
<button id="toggleTutorial" class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">Tutorial</button>
|
<button id="toggleTutorial"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">Tutorial</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
<div class="flex items-center bg-gray-700 p-3 rounded-md">
|
<div class="flex items-center bg-gray-700 p-3 rounded-md">
|
||||||
@ -301,9 +302,13 @@
|
|||||||
<p class="text-gray-100 break-all" id="holesailHash">Not Loaded</p>
|
<p class="text-gray-100 break-all" id="holesailHash">Not Loaded</p>
|
||||||
<p class="text-sm text-gray-400">Port: 127.0.0.1:25565</p>
|
<p class="text-sm text-gray-400">Port: 127.0.0.1:25565</p>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="navigator.clipboard.writeText(document.getElementById('holesailHash').textContent)" class="ml-2 text-gray-300 hover:text-white" title="Copy">
|
<button class="copy-key-btn ml-2 text-gray-300 hover:text-white" data-key-id="holesailHash"
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
data-key-type="Minecraft" title="Copy">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
||||||
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -313,9 +318,13 @@
|
|||||||
<p class="text-gray-100 break-all" id="geyserHash">Not Loaded</p>
|
<p class="text-gray-100 break-all" id="geyserHash">Not Loaded</p>
|
||||||
<p class="text-sm text-gray-400">Port: 127.0.0.1:19132</p>
|
<p class="text-sm text-gray-400">Port: 127.0.0.1:19132</p>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="navigator.clipboard.writeText(document.getElementById('geyserHash').textContent)" class="ml-2 text-gray-300 hover:text-white" title="Copy">
|
<button class="copy-key-btn ml-2 text-gray-300 hover:text-white" data-key-id="geyserHash"
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
data-key-type="Geyser" title="Copy">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
||||||
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -325,19 +334,27 @@
|
|||||||
<p class="text-gray-100 break-all" id="sftpHash">Not Loaded</p>
|
<p class="text-gray-100 break-all" id="sftpHash">Not Loaded</p>
|
||||||
<p class="text-sm text-gray-400">Port: 127.0.0.1:22</p>
|
<p class="text-sm text-gray-400">Port: 127.0.0.1:22</p>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="navigator.clipboard.writeText(document.getElementById('sftpHash').textContent)" class="ml-2 text-gray-300 hover:text-white" title="Copy">
|
<button class="copy-key-btn ml-2 text-gray-300 hover:text-white" data-key-id="sftpHash" data-key-type="SFTP"
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
title="Copy">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z">
|
||||||
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tutorialSection" class="hidden">
|
<div id="tutorialSection" class="hidden">
|
||||||
<div class="bg-gray-700 p-6 rounded-xl mb-6 border border-gray-600">
|
<div class="bg-gray-700 p-6 rounded-xl mb-6 border border-gray-600">
|
||||||
<h3 class="text-xl font-bold mb-4 text-white border-b border-gray-500 pb-2">What is Holesail.io?</h3>
|
<h3 class="text-xl font-bold mb-4 text-white border-b border-gray-500 pb-2">What is Holesail.io?</h3>
|
||||||
<p class="text-gray-200 text-base leading-relaxed mb-4">
|
<p class="text-gray-200 text-base leading-relaxed mb-4">
|
||||||
<a href="https://holesail.io" target="_blank" class="text-blue-400 hover:underline font-medium">Holesail.io</a> is an open-source, peer-to-peer networking tool that creates secure, encrypted tunnels that bypass network restrictions, firewalls, and NAT.<BR>It exposes your local network to the internet without needing port forwarding, static IPs, or Dynamic DNS, acting as a versatile tunneling and reverse proxy solution.
|
<a href="https://holesail.io" target="_blank"
|
||||||
|
class="text-blue-400 hover:underline font-medium">Holesail.io</a> is an open-source, peer-to-peer
|
||||||
|
networking tool that creates secure, encrypted tunnels that bypass network restrictions, firewalls, and
|
||||||
|
NAT.<BR>It exposes your local network to the internet without needing port forwarding, static IPs, or
|
||||||
|
Dynamic DNS, acting as a versatile tunneling and reverse proxy solution.
|
||||||
</p>
|
</p>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<p class="text-gray-200 font-semibold mb-2">With Holesail, you can:</p>
|
<p class="text-gray-200 font-semibold mb-2">With Holesail, you can:</p>
|
||||||
@ -348,39 +365,51 @@
|
|||||||
<li>* Play LAN games like Minecraft with friends remotely.</li>
|
<li>* Play LAN games like Minecraft with friends remotely.</li>
|
||||||
<li>* Secure SSH servers by blocking IP access and using Holesail for connections.</li>
|
<li>* Secure SSH servers by blocking IP access and using Holesail for connections.</li>
|
||||||
<li>* Built for ANY application, Holesail supports both the TCP and UDP Protocols nativly.</li>
|
<li>* Built for ANY application, Holesail supports both the TCP and UDP Protocols nativly.</li>
|
||||||
<li>* Expose single ports to the peer-to-peer network, unlike VPNs you never expose your entire local network.</li>
|
<li>* Expose single ports to the peer-to-peer network, unlike VPNs you never expose your entire local
|
||||||
|
network.</li>
|
||||||
<li><BR>And so much more! With Holesail, the possiblities are endless!</li>
|
<li><BR>And so much more! With Holesail, the possiblities are endless!</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-gray-200 text-base leading-relaxed">
|
<p class="text-gray-200 text-base leading-relaxed">
|
||||||
Built with security in mind, Holesail ensures all data is encrypted and never touches third-party servers.<BR>Connections are truly peer-to-peer, accessible only to those with whom you share your private key, providing both ease of use and robust security.<BR>Other peers cannot detect your activity or services.<BR>As an open-source tool, Holesail enables third-party services to integrate it, enhancing their security and connectivity.
|
Built with security in mind, Holesail ensures all data is encrypted and never touches third-party
|
||||||
|
servers.<BR>Connections are truly peer-to-peer, accessible only to those with whom you share your private
|
||||||
|
key, providing both ease of use and robust security.<BR>Other peers cannot detect your activity or
|
||||||
|
services.<BR>As an open-source tool, Holesail enables third-party services to integrate it, enhancing
|
||||||
|
their security and connectivity.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-200 text-base leading-relaxed">
|
<p class="text-gray-200 text-base leading-relaxed">
|
||||||
<BR>Your Public My-MC Ports are Holesail connections hosted on a separate server, not our Minecraft host.<BR>They use the same keys from the tutorial below. We hook 'em up at our jump host to go public. Pretty dope, right?
|
<BR>Your Public My-MC Ports are Holesail connections hosted on a separate server, not our Minecraft
|
||||||
|
host.<BR>They use the same keys from the tutorial below. We hook 'em up at our jump host to go public.
|
||||||
|
Pretty dope, right?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-700 p-4 rounded-md mb-6">
|
<div class="bg-gray-700 p-4 rounded-md mb-6">
|
||||||
<h3 class="text-lg font-semibold mb-3 text-white">How to Use Holesail Keys</h3>
|
<h3 class="text-lg font-semibold mb-3 text-white">How to Use Holesail Keys</h3>
|
||||||
<ol class="list-decimal list-inside space-y-2 text-gray-300">
|
<ol class="list-decimal list-inside space-y-2 text-gray-300">
|
||||||
<li>Ensure <a href="https://nodejs.org/" target="_blank" class="text-blue-400 hover:underline">Node.js</a> is installed on your system.</li>
|
<li>Ensure <a href="https://nodejs.org/" target="_blank" class="text-blue-400 hover:underline">Node.js</a>
|
||||||
|
is installed on your system.</li>
|
||||||
<li>Install Holesail by running:
|
<li>Install Holesail by running:
|
||||||
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">npm i holesail@2.1.0</pre>
|
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">npm i holesail@2.1.0</pre>
|
||||||
</li>
|
</li>
|
||||||
<li>Connect to a key using the appropriate command:
|
<li>Connect to a key using the appropriate command:
|
||||||
<ul class="list-disc list-inside ml-4 mt-1">
|
<ul class="list-disc list-inside ml-4 mt-1">
|
||||||
<li>For Minecraft Key (e.g., Minecraft server):
|
<li>For Minecraft Key (e.g., Minecraft server):
|
||||||
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialHolesailHash">Not Loaded</span></pre>
|
<pre
|
||||||
|
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialHolesailHash">Not Loaded</span></pre>
|
||||||
</li>
|
</li>
|
||||||
<li>For Geyser key (e.g., cross-platform Minecraft):
|
<li>For Geyser key (e.g., cross-platform Minecraft):
|
||||||
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialGeyserHash">Not Loaded</span></pre>
|
<pre
|
||||||
|
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialGeyserHash">Not Loaded</span></pre>
|
||||||
</li>
|
</li>
|
||||||
<li>For SFTP key (e.g., file transfer):
|
<li>For SFTP key (e.g., file transfer):
|
||||||
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialSftpHash">Not Loaded</span></pre>
|
<pre
|
||||||
|
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialSftpHash">Not Loaded</span></pre>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="holesail-output-mobile-hidden">Holesail will confirm the connection. Example output for your Minecraft server:
|
<li class="holesail-output-mobile-hidden">Holesail will confirm the connection. Example output for your
|
||||||
|
Minecraft server:
|
||||||
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">
|
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">
|
||||||
~ ❯ holesail <span id="tutorialHolesailHashOutput">Not Loaded</span>
|
~ ❯ holesail <span id="tutorialHolesailHashOutput">Not Loaded</span>
|
||||||
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||||
@ -397,22 +426,28 @@
|
|||||||
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||||
</pre>
|
</pre>
|
||||||
</li>
|
</li>
|
||||||
<li>To share the port over your internet IP as an open port, add the <code class="bg-gray-800 px-1 rounded">--host 0.0.0.0</code> switch:
|
<li>To share the port over your internet IP as an open port, add the <code
|
||||||
<pre class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialHolesailHashHost">Not Loaded</span> --host 0.0.0.0</pre>
|
class="bg-gray-800 px-1 rounded">--host 0.0.0.0</code> switch:
|
||||||
|
<pre
|
||||||
|
class="bg-gray-800 p-2 rounded mt-1 text-sm">holesail <span id="tutorialHolesailHashHost">Not Loaded</span> --host 0.0.0.0</pre>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-gray-700 p-4 rounded-md">
|
<div class="bg-gray-700 p-4 rounded-md">
|
||||||
<h3 class="text-lg font-semibold mb-3 text-white">Share with Friends</h3>
|
<h3 class="text-lg font-semibold mb-3 text-white">Share with Friends</h3>
|
||||||
<p class="text-gray-300 mb-3">
|
<p class="text-gray-300 mb-3">
|
||||||
Share this tutorial with your friends so they can connect to your Minecraft server or other services on their own localhosts!<BR>With Holesail, no public IPs are needed! Everything stays secure and peer-to-peer!
|
Share this tutorial with your friends so they can connect to your Minecraft server or other services on
|
||||||
|
their own localhosts!<BR>With Holesail, no public IPs are needed! Everything stays secure and
|
||||||
|
peer-to-peer!
|
||||||
</p>
|
</p>
|
||||||
<button id="copyTutorial" class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">Copy Tutorial</button>
|
<button id="copyTutorial"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded-md transition duration-200">Copy
|
||||||
|
Tutorial</button>
|
||||||
<p id="copyNotification" class="hidden text-green-400 mt-2 text-sm">Tutorial copied to clipboard!</p>
|
<p id="copyNotification" class="hidden text-green-400 mt-2 text-sm">Tutorial copied to clipboard!</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Debounce function to prevent multiple triggers on mobile
|
// Debounce function to prevent multiple triggers on mobile
|
||||||
function debounce(func, wait) {
|
function debounce(func, wait) {
|
||||||
@ -432,7 +467,7 @@
|
|||||||
const holesailHash = document.getElementById('holesailHash').textContent;
|
const holesailHash = document.getElementById('holesailHash').textContent;
|
||||||
const geyserHash = document.getElementById('geyserHash').textContent;
|
const geyserHash = document.getElementById('geyserHash').textContent;
|
||||||
const sftpHash = document.getElementById('sftpHash').textContent;
|
const sftpHash = document.getElementById('sftpHash').textContent;
|
||||||
|
|
||||||
document.getElementById('tutorialHolesailHash').textContent = holesailHash;
|
document.getElementById('tutorialHolesailHash').textContent = holesailHash;
|
||||||
document.getElementById('tutorialGeyserHash').textContent = geyserHash;
|
document.getElementById('tutorialGeyserHash').textContent = geyserHash;
|
||||||
document.getElementById('tutorialSftpHash').textContent = sftpHash;
|
document.getElementById('tutorialSftpHash').textContent = sftpHash;
|
||||||
@ -440,66 +475,84 @@
|
|||||||
document.getElementById('tutorialHolesailHashOutput2').textContent = holesailHash;
|
document.getElementById('tutorialHolesailHashOutput2').textContent = holesailHash;
|
||||||
document.getElementById('tutorialHolesailHashHost').textContent = holesailHash;
|
document.getElementById('tutorialHolesailHashHost').textContent = holesailHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run initially
|
// Run initially
|
||||||
updateTutorialSpans();
|
updateTutorialSpans();
|
||||||
|
|
||||||
// Observe changes to key display spans
|
// Observe changes to key display spans
|
||||||
const observer = new MutationObserver(updateTutorialSpans);
|
const observer = new MutationObserver(updateTutorialSpans);
|
||||||
document.querySelectorAll('#holesailHash, #geyserHash, #sftpHash').forEach(span => {
|
document.querySelectorAll('#holesailHash, #geyserHash, #sftpHash').forEach(span => {
|
||||||
observer.observe(span, { childList: true, characterData: true, subtree: true });
|
observer.observe(span, { childList: true, characterData: true, subtree: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure notification is hidden on page load
|
// Ensure notification is hidden on page load
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const notification = document.getElementById('copyNotification');
|
const notification = document.getElementById('copyNotification');
|
||||||
notification.classList.add('hidden');
|
notification.classList.add('hidden');
|
||||||
|
|
||||||
|
// Add event listeners to copy buttons
|
||||||
|
document.querySelectorAll('.copy-key-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', debounce((event) => {
|
||||||
|
event.preventDefault(); // Prevent default touch behavior
|
||||||
|
if (!event.isTrusted) return; // Ensure user-initiated action
|
||||||
|
|
||||||
|
const keyElementId = button.getAttribute('data-key-id');
|
||||||
|
const keyText = document.getElementById(keyElementId).textContent;
|
||||||
|
const keyType = button.getAttribute('data-key-type');
|
||||||
|
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(keyText).then(() => {
|
||||||
|
// Use showNotification from app.js
|
||||||
|
window.showNotification(`${keyType} key copied to clipboard!`, 'success', `copy-${keyElementId}`);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(`Failed to copy ${keyType} key: `, err);
|
||||||
|
window.showNotification(`Failed to copy ${keyType} key.`, 'error', `copy-${keyElementId}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.showNotification('Clipboard API not supported. Please copy manually.', 'error', `copy-${keyElementId}`);
|
||||||
|
}
|
||||||
|
}, 300));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle tutorial section visibility
|
// Toggle tutorial section visibility
|
||||||
document.getElementById('toggleTutorial').addEventListener('click', () => {
|
document.getElementById('toggleTutorial').addEventListener('click', () => {
|
||||||
const tutorialSection = document.getElementById('tutorialSection');
|
const tutorialSection = document.getElementById('tutorialSection');
|
||||||
tutorialSection.classList.toggle('hidden');
|
tutorialSection.classList.toggle('hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy summarized tutorial in Markdown with debounced event listener
|
// Copy summarized tutorial in Markdown with debounced event listener
|
||||||
document.getElementById('copyTutorial').addEventListener('click', debounce((event) => {
|
document.getElementById('copyTutorial').addEventListener('click', debounce((event) => {
|
||||||
event.preventDefault(); // Prevent default touch behavior
|
event.preventDefault(); // Prevent default touch behavior
|
||||||
if (!event.isTrusted) return; // Ensure user-initiated action
|
if (!event.isTrusted) return; // Ensure user-initiated action
|
||||||
|
|
||||||
const holesailHash = document.getElementById('holesailHash').textContent;
|
const holesailHash = document.getElementById('holesailHash').textContent;
|
||||||
const markdownTutorial = `# Join My Minecraft Server with Holesail!
|
const markdownTutorial = `# Join My Minecraft Server with Holesail!
|
||||||
|
|
||||||
Holesail is a secure, peer-to-peer tool that lets you connect to my server without public IPs.
|
Holesail is a secure, peer-to-peer tool that lets you connect to my server without public IPs.
|
||||||
|
|
||||||
1. **Install Node.js**: Download and install from [nodejs.org](https://nodejs.org/).
|
1. **Install Node.js**: Download and install from [nodejs.org](https://nodejs.org/).
|
||||||
2. **Install Holesail**: Open a terminal and run:
|
2. **Install Holesail**: Open a terminal and run:
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
npm i holesail@2.1.0
|
npm i holesail@2.1.0
|
||||||
\`\`\`
|
\`\`\`
|
||||||
3. **Connect to the server**: Use this command with the key:
|
3. **Connect to the server**: Use this command with the key:
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
holesail ${holesailHash}
|
holesail ${holesailHash}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
4. **Join in Minecraft**: Once Holesail confirms the connection, connect to \`127.0.0.1:25565\` in Minecraft.
|
4. **Join in Minecraft**: Once Holesail confirms the connection, connect to \`127.0.0.1:25565\` in Minecraft.
|
||||||
|
|
||||||
**Note**: Keep the key private, like an SSH key. No public IPs needed—it's all peer-to-peer!`;
|
**Note**: Keep the key private, like an SSH key. No public IPs needed—it's all peer-to-peer!`;
|
||||||
|
|
||||||
// Check if Clipboard API is supported
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
navigator.clipboard.writeText(markdownTutorial).then(() => {
|
navigator.clipboard.writeText(markdownTutorial).then(() => {
|
||||||
const notification = document.getElementById('copyNotification');
|
window.showNotification('Tutorial copied to clipboard!', 'success', 'copy-tutorial');
|
||||||
notification.classList.remove('hidden');
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.classList.add('hidden');
|
|
||||||
}, 3000);
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('Failed to copy tutorial: ', err);
|
console.error('Failed to copy tutorial: ', err);
|
||||||
alert('Failed to copy tutorial to clipboard. Please copy it manually.');
|
window.showNotification('Failed to copy tutorial. Please copy manually.', 'error', 'copy-tutorial');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Fallback for unsupported browsers
|
window.showNotification('Clipboard API not supported. Please copy manually.', 'error', 'copy-tutorial');
|
||||||
alert('Clipboard API not supported. Please copy the tutorial manually.');
|
|
||||||
}
|
}
|
||||||
}, 300)); // 300ms debounce
|
}, 300)); // 300ms debounce
|
||||||
</script>
|
</script>
|
||||||
|
@ -2176,4 +2176,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
} else {
|
} else {
|
||||||
showLoginPage();
|
showLoginPage();
|
||||||
}
|
}
|
||||||
|
window.showNotification = showNotification;
|
||||||
});
|
});
|
Reference in New Issue
Block a user