yeet
This commit is contained in:
317
static/js/script.js
Normal file
317
static/js/script.js
Normal file
@@ -0,0 +1,317 @@
|
||||
// ObsWiki Frontend JavaScript
|
||||
class ObsWiki {
|
||||
constructor() {
|
||||
this.searchInput = document.getElementById("search");
|
||||
this.currentUser = null;
|
||||
this.searchTimeout = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupSearch();
|
||||
this.setupAuth();
|
||||
this.setupWikiLinks();
|
||||
this.setupTags();
|
||||
}
|
||||
|
||||
setupSearch() {
|
||||
if (this.searchInput) {
|
||||
this.searchInput.addEventListener("input", (e) => {
|
||||
clearTimeout(this.searchTimeout);
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.performSearch(e.target.value);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
this.searchInput.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
this.performSearch(e.target.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async performSearch(query) {
|
||||
if (query.length < 2) {
|
||||
this.hideSearchResults();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/search?q=${encodeURIComponent(query)}&limit=5`,
|
||||
);
|
||||
if (response.ok) {
|
||||
const results = await response.json();
|
||||
this.showSearchResults(results);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Search error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
showSearchResults(results) {
|
||||
let resultsContainer = document.getElementById("search-results");
|
||||
|
||||
if (!resultsContainer) {
|
||||
resultsContainer = document.createElement("div");
|
||||
resultsContainer.id = "search-results";
|
||||
this.searchInput.parentNode.appendChild(resultsContainer);
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
resultsContainer.innerHTML =
|
||||
'<div>No results found</div>';
|
||||
} else {
|
||||
resultsContainer.innerHTML = results
|
||||
.map(
|
||||
(page) => `
|
||||
<a href="/wiki/${this.encodePath(page.path)}">
|
||||
<div>${this.escapeHtml(page.title)}</div>
|
||||
<div>${this.escapeHtml(page.path)}</div>
|
||||
</a>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
resultsContainer.style.display = "block";
|
||||
}
|
||||
|
||||
hideSearchResults() {
|
||||
const resultsContainer = document.getElementById("search-results");
|
||||
if (resultsContainer) {
|
||||
resultsContainer.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
setupAuth() {
|
||||
const token = localStorage.getItem("obswiki_token");
|
||||
if (token) {
|
||||
this.verifyToken(token);
|
||||
}
|
||||
|
||||
// Handle login form
|
||||
const loginForm = document.getElementById("login-form");
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
this.handleLogin(new FormData(loginForm));
|
||||
});
|
||||
}
|
||||
|
||||
// Handle register form
|
||||
const registerForm = document.getElementById("register-form");
|
||||
if (registerForm) {
|
||||
registerForm.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
this.handleRegister(new FormData(registerForm));
|
||||
});
|
||||
}
|
||||
|
||||
// Handle logout
|
||||
const logoutBtn = document.getElementById("logout-btn");
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async handleLogin(formData) {
|
||||
try {
|
||||
const response = await fetch("/auth/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: formData.get("username"),
|
||||
password: formData.get("password"),
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.setAuth(data.token, data.user);
|
||||
window.location.href = "/";
|
||||
} else {
|
||||
this.showError("Invalid username or password");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
this.showError("Login failed");
|
||||
}
|
||||
}
|
||||
|
||||
async handleRegister(formData) {
|
||||
try {
|
||||
const response = await fetch("/auth/register", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: formData.get("username"),
|
||||
email: formData.get("email"),
|
||||
password: formData.get("password"),
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.setAuth(data.token, data.user);
|
||||
window.location.href = "/";
|
||||
} else {
|
||||
this.showError("Registration failed");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Register error:", error);
|
||||
this.showError("Registration failed");
|
||||
}
|
||||
}
|
||||
|
||||
async verifyToken(token) {
|
||||
try {
|
||||
const response = await fetch("/api/me", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const user = await response.json();
|
||||
this.setAuth(token, user, false);
|
||||
} else {
|
||||
localStorage.removeItem("obswiki_token");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Token verification error:", error);
|
||||
localStorage.removeItem("obswiki_token");
|
||||
}
|
||||
}
|
||||
|
||||
setAuth(token, user, store = true) {
|
||||
if (store) {
|
||||
localStorage.setItem("obswiki_token", token);
|
||||
}
|
||||
this.currentUser = user;
|
||||
this.updateAuthUI();
|
||||
}
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem("obswiki_token");
|
||||
this.currentUser = null;
|
||||
this.updateAuthUI();
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
updateAuthUI() {
|
||||
const authContainer = document.querySelector(".auth");
|
||||
if (!authContainer) return;
|
||||
|
||||
if (this.currentUser) {
|
||||
authContainer.innerHTML = `
|
||||
<span>Welcome, ${this.escapeHtml(this.currentUser.username)}</span>
|
||||
<a href="#" id="logout-btn">Logout</a>
|
||||
`;
|
||||
|
||||
const logoutBtn = document.getElementById("logout-btn");
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
authContainer.innerHTML = `
|
||||
<a href="/auth/login">Login</a>
|
||||
<a href="/auth/register">Register</a>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
setupWikiLinks() {
|
||||
// Handle wiki link clicks for better navigation
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("wiki-link")) {
|
||||
// Add loading state or other UX improvements here
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupTags() {
|
||||
// Handle tag clicks
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("tag")) {
|
||||
const tag = e.target.getAttribute("data-tag");
|
||||
if (tag) {
|
||||
this.searchByTag(tag);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
searchByTag(tag) {
|
||||
if (this.searchInput) {
|
||||
this.searchInput.value = `#${tag}`;
|
||||
this.performSearch(`#${tag}`);
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
// Create or update error message
|
||||
let errorDiv = document.getElementById("error-message");
|
||||
if (!errorDiv) {
|
||||
errorDiv = document.createElement("div");
|
||||
errorDiv.id = "error-message";
|
||||
errorDiv.id = "error-message";
|
||||
document.body.insertBefore(errorDiv, document.body.firstChild);
|
||||
}
|
||||
|
||||
errorDiv.textContent = message;
|
||||
errorDiv.style.display = "block";
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
errorDiv.style.display = "none";
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
encodePath(path) {
|
||||
// Split path by '/' and encode each component separately, then rejoin with '/'
|
||||
return path
|
||||
.split("/")
|
||||
.map((component) => encodeURIComponent(component))
|
||||
.join("/");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Utility method for making authenticated requests
|
||||
async authenticatedFetch(url, options = {}) {
|
||||
const token = localStorage.getItem("obswiki_token");
|
||||
if (token) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${token}`,
|
||||
};
|
||||
}
|
||||
return fetch(url, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
window.obswiki = new ObsWiki();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user