You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.3 KiB
139 lines
4.3 KiB
/* Settings flags */ |
|
const settingsFlagsKey = '?'; |
|
const settingsFlagsEnum = { |
|
allowCategoryEditing: 1, |
|
isRandomized: 2, |
|
}; |
|
const settingsDefaults = { |
|
allowCategoryEditing: true, |
|
isRandomized: false, |
|
}; |
|
|
|
function loadSettings(data) { |
|
const settings = Object.assign({}, settingsDefaults); |
|
const flags = data[settingsFlagsKey]; |
|
for (const settingName of Object.keys(settingsFlagsEnum)) { |
|
settings[settingName] = Boolean(settingsFlagsEnum[settingName] & flags); |
|
} |
|
return settings; |
|
} |
|
function saveSettings(settings) { |
|
let flags = 0; |
|
for (const [settingName, isEnabled] of Object.entries(settings)) { |
|
if (isEnabled) { |
|
flags |= settingsFlagsEnum[settingName]; |
|
} |
|
} |
|
return flags; |
|
} |
|
|
|
/* Saving and loading */ |
|
const uncategorizedKey = '#'; |
|
const reservedKeys = [ |
|
settingsFlagsKey, |
|
uncategorizedKey, |
|
]; |
|
const regexRemoveBasenameFromUrl = /\/[^\/]*?$/; |
|
|
|
async function uint8ArrayToBase64(data) { |
|
const base64url = await new Promise((resolve) => { |
|
const reader = new FileReader(); |
|
reader.onload = () => resolve(reader.result); |
|
reader.readAsDataURL(new Blob([data])); |
|
}); |
|
return base64url.split(',', 2)[1]; |
|
} |
|
async function base64ToUint8Array(base64string) { |
|
const response = await fetch('data:;base64,' + base64string); |
|
const blob = await response.blob(); |
|
return new Uint8Array(await blob.arrayBuffer()); |
|
} |
|
async function saveToString(data) { |
|
const jsonString = JSON.stringify(data); |
|
const compressedData = pako.deflate(new TextEncoder().encode(jsonString)); |
|
const base64String = await uint8ArrayToBase64(compressedData); |
|
const webSafeBase64String = base64String.replaceAll('+', '-').replaceAll('/', '_'); |
|
return webSafeBase64String; |
|
} |
|
async function loadFromString(webSafeBase64String) { |
|
const base64String = webSafeBase64String.replaceAll('-', '+').replaceAll('_', '/'); |
|
const compressedData = await base64ToUint8Array(base64String); |
|
const jsonString = new TextDecoder('utf8').decode(pako.inflate(compressedData)); |
|
const data = JSON.parse(jsonString); |
|
return data; |
|
} |
|
function loadFromQueryParameters() { |
|
const data = {}; |
|
const settings = Object.assign({}, settingsDefaults); |
|
const params = new URLSearchParams(window.location.search); |
|
const cardTexts = (params.get('cards') || '').split(',').filter(item => !!item); |
|
const categories = (params.get('categories') || '').split(',').filter(item => !!item); |
|
if (params.get('allowCategoryEditing') !== null) { |
|
settings.allowCategoryEditing = Boolean(parseInt(params.get('allowCategoryEditing'))); |
|
} |
|
data[uncategorizedKey] = cardTexts; |
|
data[settingsFlagsKey] = saveSettings(settings); |
|
for (const categoryName of categories) { |
|
data[categoryName] = []; |
|
} |
|
return data; |
|
} |
|
|
|
/* Utilities */ |
|
function debounce(func, wait, immediate) { |
|
let timeout; |
|
return function debouncedFunc() { |
|
const context = this; |
|
const args = arguments; |
|
const later = function () { |
|
timeout = null; |
|
if ( ! immediate) { |
|
func.apply(context, args); |
|
} |
|
}; |
|
const callNow = immediate && ! timeout; |
|
clearTimeout(timeout); |
|
timeout = setTimeout(later, wait); |
|
if (callNow) { |
|
func.apply(context, args); |
|
} |
|
}; |
|
} |
|
|
|
function asTextContent(input) { |
|
const tempElement = document.createElement('span'); |
|
tempElement.textContent = input; |
|
return tempElement.innerHTML; |
|
} |
|
|
|
function setContentEditablePlaintext(element) { |
|
element.setAttribute('contenteditable', 'PLAINTEXT-ONLY'); |
|
const isPlaintextSupported = element.contentEditable === 'plaintext-only'; |
|
element.addEventListener('keydown', function(event) { |
|
if (event.key === 'Enter') { |
|
element.blur(); |
|
event.preventDefault(); |
|
} |
|
}); |
|
element.addEventListener('blur', function(event) { |
|
// Combine text nodes into one text node |
|
element.textContent = element.textContent; |
|
}); |
|
if (isPlaintextSupported) { |
|
return; |
|
} |
|
// Polyfill for contenteditable="plaintext-only" |
|
element.contentEditable = true; |
|
const observer = new MutationObserver(plaintextOnlyMutationHandler); |
|
observer.observe(element, {childList: true, subtree: true}); |
|
} |
|
|
|
function plaintextOnlyMutationHandler(mutationList, observer) { |
|
for (const mutation of mutationList) { |
|
for (const addedNode of mutation.addedNodes) { |
|
if (addedNode.nodeType == Node.ELEMENT_NODE) { |
|
addedNode.replaceWith(addedNode.textContent); |
|
} |
|
} |
|
} |
|
} |