164 lines
5.3 KiB
JavaScript
164 lines
5.3 KiB
JavaScript
// Focus-Aware Keystroke Pattern Detector with Redirects
|
|
// This script detects keyboard shortcuts only when NOT focusing on input elements
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Variables to store keystroke history
|
|
let keystrokeHistory = '';
|
|
|
|
// Define patterns and their corresponding redirect URLs
|
|
const shortcuts = {
|
|
'uli': '/user/login',
|
|
'ulo': '/user/logout',
|
|
'usu': '/user/signup',
|
|
'nnn': '/page/create',
|
|
'eee': '/page/edit',
|
|
'ddd': '/page/delete',
|
|
'hhh': '/',
|
|
'aaa': '/user/account',
|
|
};
|
|
|
|
// Maximum length of patterns to track
|
|
const maxPatternLength = Object.keys(shortcuts).reduce((max, pattern) =>
|
|
Math.max(max, pattern.length), 0);
|
|
|
|
// Check if user is currently focused on an input element
|
|
function isUserTypingInFormElement() {
|
|
const activeElement = document.activeElement;
|
|
const inputTags = ['INPUT', 'TEXTAREA', 'SELECT'];
|
|
const isContentEditable = activeElement.hasAttribute('contenteditable') &&
|
|
activeElement.getAttribute('contenteditable') !== 'false';
|
|
|
|
return inputTags.includes(activeElement.tagName) || isContentEditable;
|
|
}
|
|
|
|
// Listen for keydown events on the document
|
|
document.addEventListener('keydown', function (event) {
|
|
// Skip if the user is typing in an input field or textarea
|
|
if (isUserTypingInFormElement()) {
|
|
return;
|
|
}
|
|
|
|
// Get the pressed key
|
|
const key = event.key;
|
|
|
|
// Add the key to keystroke history
|
|
keystrokeHistory += key;
|
|
|
|
// Keep only the last N characters where N is the max pattern length
|
|
if (keystrokeHistory.length > maxPatternLength) {
|
|
keystrokeHistory = keystrokeHistory.slice(-maxPatternLength);
|
|
}
|
|
|
|
// Check if any pattern matches the end of keystroke history
|
|
for (const [pattern, url] of Object.entries(shortcuts)) {
|
|
if (keystrokeHistory.endsWith(pattern)) {
|
|
if (pattern === "eee") {
|
|
const path = window.location.pathname;
|
|
const cleanPath = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
const parts = cleanPath.split('/');
|
|
window.location.href = url + "/" + parts[1]
|
|
} else {
|
|
window.location.href = url;
|
|
}
|
|
|
|
|
|
// Reset keystroke history
|
|
keystrokeHistory = '';
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
let debounceTimer;
|
|
function triggerDemo() {
|
|
clearTimeout(debounceTimer);
|
|
debounceTimer = setTimeout(async () => {
|
|
const oldHtml = formated.innerHTML
|
|
const newHtml = await previewText(textarea.value)
|
|
if (oldHtml !== newHtml) {
|
|
formated.innerHTML = ""
|
|
formated.innerHTML = newHtml;
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
// Insert text at saved cursor position (works even when not focused)
|
|
function insertMedia(slug, extension, reposition, event) {
|
|
event.preventDefault()
|
|
const value = textarea.value;
|
|
let textInsert = ""
|
|
|
|
if (extension === ".mp4") {
|
|
textInsert = `(video: ${slug} autoplay figcaption: )`
|
|
}
|
|
else if (extension === ".mp3") {
|
|
textInsert = `(audio: ${slug})`
|
|
} else {
|
|
textInsert = `(image: ${slug} alt: )`
|
|
}
|
|
|
|
textarea.value = value.slice(0, savedCursor) + textInsert + value.slice(savedCursor);
|
|
|
|
if (reposition) {
|
|
savedCursor += textInsert.length - 1;
|
|
} else {
|
|
savedCursor += textInsert.length;
|
|
}
|
|
|
|
// If textarea is focused, update the actual cursor position
|
|
if (document.activeElement === textarea) {
|
|
textarea.setSelectionRange(savedCursor, savedCursor);
|
|
}
|
|
|
|
// togglePreview()
|
|
setTimeout(() => {
|
|
triggerDemo()
|
|
}, 400)
|
|
}
|
|
|
|
function deleteMedia(filename, event) {
|
|
event.preventDefault()
|
|
if (!confirm(`Delete "${filename}"?`)) return;
|
|
const csrfToken = document.querySelector('#csrf_token')?.value;
|
|
|
|
fetch(`/files/delete/${encodeURIComponent(filename)}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: `csrf_token=${encodeURIComponent(csrfToken)}`
|
|
})
|
|
.then(async response => {
|
|
const responseText = await response.text();
|
|
|
|
if (response.ok) {
|
|
const mediaItem = document.querySelector(`#${filename.split('.').slice(0, -1)[0]}`)
|
|
if (mediaItem) {
|
|
mediaItem.remove()
|
|
}
|
|
return
|
|
}
|
|
|
|
// Handle error response
|
|
const errorMessage = responseText || `Request failed (${response.status})`;
|
|
throw new Error(errorMessage);
|
|
})
|
|
.catch(error => {
|
|
console.error('Delete failed:', error);
|
|
alert(`Failed to delete file: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
|
|
async function previewText(pageText) {
|
|
const csrfToken = document.querySelector('#csrf_token')?.value;
|
|
const res = await fetch("/page/preview", {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': csrfToken
|
|
},
|
|
body: pageText
|
|
})
|
|
const text = await res.text()
|
|
return text
|
|
}
|