Add share button
This commit is contained in:
parent
c628d3523a
commit
80a1b2470f
2 changed files with 564 additions and 8 deletions
|
|
@ -17,6 +17,21 @@
|
|||
|
||||
<!-- Main Content Area (Board and Controls) -->
|
||||
<div class="w-full flex flex-col items-center justify-center p-4 overflow-y-auto">
|
||||
<!-- Top-right action buttons -->
|
||||
<div class="fixed top-4 right-4 z-30 flex gap-2">
|
||||
<button onclick="copyShareLink()" class="bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-full shadow-lg" title="Copy Share Link" aria-label="Copy Share Link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
|
||||
<path d="M13.5 6a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0V8.56l-6.22 6.22a.75.75 0 0 1-1.06-1.06L16.94 7.5h-1.94A.75.75 0 0 1 13.5 6Z"/>
|
||||
<path d="M6 5.25A2.25 2.25 0 0 0 3.75 7.5v12A2.25 2.25 0 0 0 6 21.75h12A2.25 2.25 0 0 0 20.25 19.5v-6a.75.75 0 0 0-1.5 0v6c0 .414-.336.75-.75.75H6a.75.75 0 0 1-.75-.75v-12c0-.414.336-.75.75-.75h6a.75.75 0 0 0 0-1.5H6Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="config-toggle-button" onclick="toggleConfig()" class="bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-full shadow-lg" title="Toggle Settings" aria-label="Toggle Settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.646.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.004.827c-.293.24-.438.613-.431.992a6.759 6.759 0 0 1 0 1.555c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 0 1-.22.128c-.333.184-.583.496-.646.87l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.063-.374-.313-.686-.646-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.293-.24.438-.613.431-.992a6.759 6.759 0 0 1 0-1.555c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.184.582-.496.646-.87l.213-1.281Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Search Bar -->
|
||||
<div class="mb-4 w-full max-w-xl">
|
||||
<label for="search-input" class="sr-only">Search Items</label>
|
||||
|
|
@ -51,12 +66,6 @@
|
|||
<button onclick="clearMarks()" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
|
||||
Clear Marks
|
||||
</button>
|
||||
<button id="config-toggle-button" onclick="toggleConfig()" class="fixed top-4 right-4 z-30 bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-full shadow-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.646.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.004.827c-.293.24-.438.613-.431.992a6.759 6.759 0 0 1 0 1.555c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 0 1-.22.128c-.333.184-.583.496-.646.87l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.063-.374-.313-.686-.646-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.293-.24.438-.613.431-.992a6.759 6.759 0 0 1 0-1.555c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.184.582-.496.646-.87l.213-1.281Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -441,7 +450,7 @@
|
|||
|
||||
<script type="module" src="js/beango.js?v=1.0.4"></script>
|
||||
<script type="module">
|
||||
import { toggleConfig, exportSettings, importSettings, resetAllSettings, explodeBeans, randomizeBoard, clearMarks, generateBoard, resetSettings } from './js/beango.js';
|
||||
import { toggleConfig, exportSettings, importSettings, resetAllSettings, explodeBeans, randomizeBoard, clearMarks, generateBoard, resetSettings, copyShareLink } from './js/beango.js';
|
||||
window.explodeBeans = explodeBeans;
|
||||
window.toggleConfig = toggleConfig;
|
||||
window.exportSettings = exportSettings;
|
||||
|
|
@ -451,6 +460,7 @@
|
|||
window.clearMarks = clearMarks;
|
||||
window.generateBoard = generateBoard;
|
||||
window.resetSettings = resetSettings;
|
||||
window.copyShareLink = copyShareLink;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1392,6 +1392,9 @@ export function explodeBeans(event) {
|
|||
|
||||
// --- Consolidated Event Listeners ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Apply shared settings from URL if present before loading
|
||||
tryApplySettingsFromUrl();
|
||||
|
||||
// Load state first thing
|
||||
loadFromLocalStorage();
|
||||
|
||||
|
|
@ -1600,7 +1603,550 @@ function checkWinConditions() {
|
|||
}
|
||||
|
||||
|
||||
// --- Settings Import/Export ---
|
||||
// --- Settings Import/Export & Sharing ---
|
||||
|
||||
// v1 fallback: base64url helpers (kept for backward compatibility)
|
||||
function base64UrlEncode(text) {
|
||||
const utf8 = new TextEncoder().encode(text);
|
||||
let binary = '';
|
||||
utf8.forEach(byte => binary += String.fromCharCode(byte));
|
||||
const base64 = btoa(binary);
|
||||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
||||
}
|
||||
|
||||
function base64UrlDecode(text) {
|
||||
let base64 = text.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (base64.length % 4) base64 += '=';
|
||||
const binary = atob(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
||||
return new TextDecoder().decode(bytes);
|
||||
}
|
||||
|
||||
// v2: Lightweight LZ-based compression for URI component
|
||||
// Adapted from LZ-String (MIT) compressToEncodedURIComponent/decompressFromEncodedURIComponent
|
||||
const LZ = (function() {
|
||||
const f = String.fromCharCode;
|
||||
const keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
|
||||
const baseReverseDic = {};
|
||||
|
||||
function getBaseValue(alphabet, character) {
|
||||
if (!baseReverseDic[alphabet]) {
|
||||
baseReverseDic[alphabet] = {};
|
||||
for (let i = 0; i < alphabet.length; i++) {
|
||||
baseReverseDic[alphabet][alphabet.charAt(i)] = i;
|
||||
}
|
||||
}
|
||||
return baseReverseDic[alphabet][character];
|
||||
}
|
||||
|
||||
function compressToEncodedURIComponent(input) {
|
||||
if (input == null) return '';
|
||||
return _compress(input, 6, function(a){ return keyStrUriSafe.charAt(a); });
|
||||
}
|
||||
|
||||
function _compress(uncompressed, bitsPerChar, getCharFromInt) {
|
||||
if (uncompressed == null) return '';
|
||||
let i, value,
|
||||
context_dictionary = {},
|
||||
context_dictionaryToCreate = {},
|
||||
context_c = '',
|
||||
context_wc = '',
|
||||
context_w = '',
|
||||
context_enlargeIn = 2, // Compensate for the first entry which should not count
|
||||
context_dictSize = 3,
|
||||
context_numBits = 2,
|
||||
context_data = [],
|
||||
context_data_val = 0,
|
||||
context_data_position = 0,
|
||||
ii;
|
||||
|
||||
for (ii = 0; ii < uncompressed.length; ii += 1) {
|
||||
context_c = uncompressed.charAt(ii);
|
||||
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
|
||||
context_dictionary[context_c] = context_dictSize++;
|
||||
context_dictionaryToCreate[context_c] = true;
|
||||
}
|
||||
|
||||
context_wc = context_w + context_c;
|
||||
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
|
||||
context_w = context_wc;
|
||||
} else {
|
||||
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
|
||||
if (context_w.charCodeAt(0) < 256) {
|
||||
for (i = 0; i < context_numBits; i++) {
|
||||
context_data_val = (context_data_val << 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
}
|
||||
value = context_w.charCodeAt(0);
|
||||
for (i = 0; i < 8; i++) {
|
||||
context_data_val = (context_data_val << 1) | (value & 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = value >> 1;
|
||||
}
|
||||
} else {
|
||||
value = 1;
|
||||
for (i = 0; i < context_numBits; i++) {
|
||||
context_data_val = (context_data_val << 1) | value;
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = 0;
|
||||
}
|
||||
value = context_w.charCodeAt(0);
|
||||
for (i = 0; i < 16; i++) {
|
||||
context_data_val = (context_data_val << 1) | (value & 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = value >> 1;
|
||||
}
|
||||
}
|
||||
context_enlargeIn--;
|
||||
if (context_enlargeIn == 0) {
|
||||
context_enlargeIn = Math.pow(2, context_numBits);
|
||||
context_numBits++;
|
||||
}
|
||||
delete context_dictionaryToCreate[context_w];
|
||||
} else {
|
||||
value = context_dictionary[context_w];
|
||||
for (i = 0; i < context_numBits; i++) {
|
||||
context_data_val = (context_data_val << 1) | (value & 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = value >> 1;
|
||||
}
|
||||
}
|
||||
context_enlargeIn--;
|
||||
if (context_enlargeIn == 0) {
|
||||
context_enlargeIn = Math.pow(2, context_numBits);
|
||||
context_numBits++;
|
||||
}
|
||||
// Add wc to the dictionary.
|
||||
context_dictionary[context_wc] = context_dictSize++;
|
||||
context_w = String(context_c);
|
||||
}
|
||||
}
|
||||
|
||||
// Output the code for w.
|
||||
if (context_w !== '') {
|
||||
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
|
||||
if (context_w.charCodeAt(0) < 256) {
|
||||
for (i = 0; i < context_numBits; i++) {
|
||||
context_data_val = (context_data_val << 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
}
|
||||
value = context_w.charCodeAt(0);
|
||||
for (i = 0; i < 8; i++) {
|
||||
context_data_val = (context_data_val << 1) | (value & 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = value >> 1;
|
||||
}
|
||||
} else {
|
||||
value = 1;
|
||||
for (i = 0; i < context_numBits; i++) {
|
||||
context_data_val = (context_data_val << 1) | value;
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = 0;
|
||||
}
|
||||
value = context_w.charCodeAt(0);
|
||||
for (i = 0; i < 16; i++) {
|
||||
context_data_val = (context_data_val << 1) | (value & 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = value >> 1;
|
||||
}
|
||||
}
|
||||
context_enlargeIn--;
|
||||
if (context_enlargeIn == 0) {
|
||||
context_enlargeIn = Math.pow(2, context_numBits);
|
||||
context_numBits++;
|
||||
}
|
||||
delete context_dictionaryToCreate[context_w];
|
||||
} else {
|
||||
value = context_dictionary[context_w];
|
||||
for (i = 0; i < context_numBits; i++) {
|
||||
context_data_val = (context_data_val << 1) | (value & 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = value >> 1;
|
||||
}
|
||||
}
|
||||
context_enlargeIn--;
|
||||
if (context_enlargeIn == 0) {
|
||||
context_enlargeIn = Math.pow(2, context_numBits);
|
||||
context_numBits++;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the end of the stream
|
||||
value = 2;
|
||||
for (i = 0; i < context_numBits; i++) {
|
||||
context_data_val = (context_data_val << 1) | (value & 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data_position = 0;
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
context_data_val = 0;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
value = value >> 1;
|
||||
}
|
||||
|
||||
// Flush the last char
|
||||
while (true) {
|
||||
context_data_val = (context_data_val << 1);
|
||||
if (context_data_position == bitsPerChar - 1) {
|
||||
context_data.push(getCharFromInt(context_data_val));
|
||||
break;
|
||||
} else {
|
||||
context_data_position++;
|
||||
}
|
||||
}
|
||||
return context_data.join('');
|
||||
}
|
||||
|
||||
function decompressFromEncodedURIComponent(input) {
|
||||
if (input == null) return '';
|
||||
if (input === '') return null;
|
||||
input = input.replace(/ /g, '+');
|
||||
return _decompress(input.length, 32, function(index) { return getBaseValue(keyStrUriSafe, input.charAt(index)); });
|
||||
}
|
||||
|
||||
function _decompress(length, resetValue, getNextValue) {
|
||||
let dictionary = []
|
||||
, next
|
||||
, enlargeIn = 4
|
||||
, dictSize = 4
|
||||
, numBits = 3
|
||||
, entry = ''
|
||||
, result = []
|
||||
, i
|
||||
, w
|
||||
, bits, resb, maxpower, power
|
||||
, c
|
||||
, data = {val:getNextValue(0), position:resetValue, index:1};
|
||||
|
||||
for (i = 0; i < 3; i += 1) {
|
||||
dictionary[i] = i;
|
||||
}
|
||||
|
||||
bits = 0;
|
||||
maxpower = Math.pow(2, 2);
|
||||
power = 1;
|
||||
while (power != maxpower) {
|
||||
resb = data.val & data.position;
|
||||
data.position >>= 1;
|
||||
if (data.position == 0) {
|
||||
data.position = resetValue;
|
||||
data.val = getNextValue(data.index++);
|
||||
}
|
||||
bits |= (resb>0 ? 1 : 0) * power;
|
||||
power <<= 1;
|
||||
}
|
||||
|
||||
switch (next = bits) {
|
||||
case 0:
|
||||
bits = 0;
|
||||
maxpower = Math.pow(2, 8);
|
||||
power = 1;
|
||||
while (power != maxpower) {
|
||||
resb = data.val & data.position;
|
||||
data.position >>= 1;
|
||||
if (data.position == 0) {
|
||||
data.position = resetValue;
|
||||
data.val = getNextValue(data.index++);
|
||||
}
|
||||
bits |= (resb>0 ? 1 : 0) * power;
|
||||
power <<= 1;
|
||||
}
|
||||
c = f(bits);
|
||||
break;
|
||||
case 1:
|
||||
bits = 0;
|
||||
maxpower = Math.pow(2, 16);
|
||||
power = 1;
|
||||
while (power != maxpower) {
|
||||
resb = data.val & data.position;
|
||||
data.position >>= 1;
|
||||
if (data.position == 0) {
|
||||
data.position = resetValue;
|
||||
data.val = getNextValue(data.index++);
|
||||
}
|
||||
bits |= (resb>0 ? 1 : 0) * power;
|
||||
power <<= 1;
|
||||
}
|
||||
c = f(bits);
|
||||
break;
|
||||
case 2:
|
||||
return '';
|
||||
}
|
||||
dictionary[3] = c;
|
||||
w = c;
|
||||
result.push(c);
|
||||
while (true) {
|
||||
if (data.index > length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
bits = 0;
|
||||
maxpower = Math.pow(2, numBits);
|
||||
power = 1;
|
||||
while (power != maxpower) {
|
||||
resb = data.val & data.position;
|
||||
data.position >>= 1;
|
||||
if (data.position == 0) {
|
||||
data.position = resetValue;
|
||||
data.val = getNextValue(data.index++);
|
||||
}
|
||||
bits |= (resb>0 ? 1 : 0) * power;
|
||||
power <<= 1;
|
||||
}
|
||||
|
||||
switch (c = bits) {
|
||||
case 0:
|
||||
bits = 0;
|
||||
maxpower = Math.pow(2, 8);
|
||||
power = 1;
|
||||
while (power != maxpower) {
|
||||
resb = data.val & data.position;
|
||||
data.position >>= 1;
|
||||
if (data.position == 0) {
|
||||
data.position = resetValue;
|
||||
data.val = getNextValue(data.index++);
|
||||
}
|
||||
bits |= (resb>0 ? 1 : 0) * power;
|
||||
power <<= 1;
|
||||
}
|
||||
|
||||
dictionary[dictSize++] = f(bits);
|
||||
c = dictSize - 1;
|
||||
enlargeIn--;
|
||||
break;
|
||||
case 1:
|
||||
bits = 0;
|
||||
maxpower = Math.pow(2, 16);
|
||||
power = 1;
|
||||
while (power != maxpower) {
|
||||
resb = data.val & data.position;
|
||||
data.position >>= 1;
|
||||
if (data.position == 0) {
|
||||
data.position = resetValue;
|
||||
data.val = getNextValue(data.index++);
|
||||
}
|
||||
bits |= (resb>0 ? 1 : 0) * power;
|
||||
power <<= 1;
|
||||
}
|
||||
dictionary[dictSize++] = f(bits);
|
||||
c = dictSize - 1;
|
||||
enlargeIn--;
|
||||
break;
|
||||
case 2:
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
if (enlargeIn == 0) {
|
||||
enlargeIn = Math.pow(2, numBits);
|
||||
numBits++;
|
||||
}
|
||||
|
||||
if (dictionary[c]) {
|
||||
entry = dictionary[c];
|
||||
} else {
|
||||
if (c === dictSize) {
|
||||
entry = w + w.charAt(0);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
result.push(entry);
|
||||
|
||||
// Add w+entry[0]
|
||||
dictionary[dictSize++] = w + entry.charAt(0);
|
||||
enlargeIn--;
|
||||
w = entry;
|
||||
|
||||
if (enlargeIn == 0) {
|
||||
enlargeIn = Math.pow(2, numBits);
|
||||
numBits++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
compressToEncodedURIComponent,
|
||||
decompressFromEncodedURIComponent,
|
||||
};
|
||||
})();
|
||||
|
||||
function collectAllBeangoSettings() {
|
||||
const settings = {};
|
||||
const prefix = 'beango_';
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && key.startsWith(prefix)) {
|
||||
settings[key] = localStorage.getItem(key);
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
export async function copyShareLink() {
|
||||
try {
|
||||
const data = collectAllBeangoSettings();
|
||||
const json = JSON.stringify(data);
|
||||
// v2 compressed, URL-safe
|
||||
const encoded = LZ.compressToEncodedURIComponent(json);
|
||||
const shareUrl = `${location.origin}${location.pathname}#s=v2:${encoded}`;
|
||||
|
||||
let copySucceeded = false;
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(shareUrl);
|
||||
copySucceeded = true;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to legacy copy
|
||||
}
|
||||
|
||||
if (!copySucceeded) {
|
||||
const tempInput = document.createElement('input');
|
||||
tempInput.value = shareUrl;
|
||||
tempInput.style.position = 'fixed';
|
||||
tempInput.style.top = '-1000px';
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.focus();
|
||||
tempInput.select();
|
||||
try {
|
||||
copySucceeded = document.execCommand('copy');
|
||||
} catch {
|
||||
copySucceeded = false;
|
||||
}
|
||||
document.body.removeChild(tempInput);
|
||||
}
|
||||
|
||||
if (copySucceeded) {
|
||||
showNotification('Share link copied to clipboard!', 'success');
|
||||
} else {
|
||||
try { window.prompt('Copy this share link:', shareUrl); } catch { /* ignore */ }
|
||||
showNotification('Could not copy automatically. Link shown for manual copy.', 'warning');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to create share link', e);
|
||||
showNotification('Failed to create share link.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
let _sharedSettingsAppliedOnce = false;
|
||||
|
||||
function tryApplySettingsFromUrl() {
|
||||
try {
|
||||
const hash = window.location.hash || '';
|
||||
const match = hash.match(/^#(?:s|settings)=(.+)$/);
|
||||
if (!match) return;
|
||||
|
||||
const payload = match[1];
|
||||
let json = '';
|
||||
if (payload.startsWith('v2:')) {
|
||||
const body = payload.slice(3);
|
||||
json = LZ.decompressFromEncodedURIComponent(body) || '';
|
||||
} else {
|
||||
// v1 fallback (base64url)
|
||||
json = base64UrlDecode(payload);
|
||||
}
|
||||
const parsed = JSON.parse(json);
|
||||
if (typeof parsed !== 'object' || parsed === null) return;
|
||||
|
||||
// Clear existing beango_ keys to avoid stale settings
|
||||
const prefix = 'beango_';
|
||||
const toRemove = [];
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (key && key.startsWith(prefix)) toRemove.push(key);
|
||||
}
|
||||
toRemove.forEach(k => localStorage.removeItem(k));
|
||||
|
||||
let imported = 0;
|
||||
Object.keys(parsed).forEach(key => {
|
||||
if (key.startsWith(prefix)) {
|
||||
const value = parsed[key];
|
||||
if (value !== undefined && value !== null) {
|
||||
localStorage.setItem(key, String(value));
|
||||
imported++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (imported > 0) {
|
||||
_sharedSettingsAppliedOnce = true;
|
||||
// Clean the URL hash and reload once to ensure all UI reflects new settings
|
||||
if (history && history.replaceState) {
|
||||
history.replaceState(null, '', `${location.pathname}${location.search}`);
|
||||
} else {
|
||||
window.location.hash = '';
|
||||
}
|
||||
// Reload synchronously; after reload the hash is clean, so this runs only once
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to apply settings from URL', e);
|
||||
showNotification('Invalid share link.', 'warning');
|
||||
}
|
||||
}
|
||||
export function exportSettings() {
|
||||
const settingsToExport = {};
|
||||
const prefix = 'beango_';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue