OPML logic
This commit is contained in:
parent
2041e39ab8
commit
98ff8be9d8
52
src/import.rs
Normal file
52
src/import.rs
Normal file
@ -0,0 +1,52 @@
|
||||
// Module for handling OPML feed list imports
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
use rocket::data::ToByteUnit;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::serde::{Deserialize, Serialize};
|
||||
use rocket::{post, Data};
|
||||
use rocket_db_pools::Connection;
|
||||
use tracing::error;
|
||||
|
||||
use crate::user::AuthenticatedUser;
|
||||
use crate::Db;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct ImportResponse {
|
||||
success: bool,
|
||||
message: String,
|
||||
feeds_imported: Option<usize>,
|
||||
}
|
||||
|
||||
/// Import feeds from an OPML file
|
||||
#[post("/import/opml", data = "<file>")]
|
||||
pub async fn import_opml(
|
||||
mut _db: Connection<Db>,
|
||||
_user: AuthenticatedUser,
|
||||
file: Data<'_>,
|
||||
) -> Result<Json<ImportResponse>, Status> {
|
||||
// Limit file size to 1MB
|
||||
let file_data = file.open(1.mebibytes()).into_bytes().await.map_err(|e| {
|
||||
error!("Failed to read OPML file: {}", e);
|
||||
Status::BadRequest
|
||||
})?;
|
||||
|
||||
if !file_data.is_complete() {
|
||||
error!("OPML file too large");
|
||||
return Err(Status::PayloadTooLarge);
|
||||
}
|
||||
|
||||
let bytes = file_data.value;
|
||||
let _cursor = Cursor::new(bytes);
|
||||
|
||||
// TODO: Parse OPML and import feeds
|
||||
// For now just return a placeholder response
|
||||
Ok(Json(ImportResponse {
|
||||
success: true,
|
||||
message: "OPML file received successfully. Import not yet implemented.".to_string(),
|
||||
feeds_imported: None,
|
||||
}))
|
||||
}
|
@ -8,6 +8,7 @@ use tracing_subscriber::FmtSubscriber;
|
||||
mod demo;
|
||||
mod feed_utils;
|
||||
mod feeds;
|
||||
mod import;
|
||||
mod poll;
|
||||
mod poll_utils;
|
||||
mod session_store;
|
||||
@ -162,6 +163,7 @@ fn rocket() -> _ {
|
||||
feeds::delete_feed,
|
||||
poll::poll_feed,
|
||||
poll::update_entry_state,
|
||||
import::import_opml,
|
||||
],
|
||||
)
|
||||
.mount("/static", FileServer::from("static"))
|
||||
|
129
static/js/app.js
129
static/js/app.js
@ -1,20 +1,23 @@
|
||||
// Add at the beginning of the file
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Mobile menu functionality
|
||||
const hamburgerMenu = document.getElementById('hamburgerMenu');
|
||||
const sidebarClose = document.getElementById('sidebarClose');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const userMenuButton = document.getElementById('userMenuButton');
|
||||
const userMenuDropdown = document.getElementById('userMenuDropdown');
|
||||
|
||||
// Add OPML import button handler
|
||||
const importOpmlButton = document.getElementById('importOpmlButton');
|
||||
importOpmlButton.addEventListener('click', function() {
|
||||
console.log('OPML import button clicked - functionality coming soon!');
|
||||
});
|
||||
// Create a hidden file input for OPML upload
|
||||
const opmlFileInput = document.createElement('input');
|
||||
opmlFileInput.type = 'file';
|
||||
opmlFileInput.accept = '.opml,.xml';
|
||||
opmlFileInput.style.display = 'none';
|
||||
document.body.appendChild(opmlFileInput);
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebar.classList.toggle('active');
|
||||
}
|
||||
|
||||
// Mobile menu handlers
|
||||
hamburgerMenu.addEventListener('click', toggleSidebar);
|
||||
sidebarClose.addEventListener('click', toggleSidebar);
|
||||
|
||||
@ -28,6 +31,80 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
sidebar.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// OPML import handlers
|
||||
opmlFileInput.addEventListener('change', async (e) => {
|
||||
if (e.target.files.length > 0) {
|
||||
const file = e.target.files[0];
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('/import/opml', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('OPML import successful: ' + result.message);
|
||||
// Refresh the feed list if feeds were imported
|
||||
if (result.feeds_imported) {
|
||||
handleFeeds();
|
||||
}
|
||||
} else {
|
||||
alert('OPML import failed: ' + result.message);
|
||||
}
|
||||
} else {
|
||||
alert('Failed to import OPML file. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('OPML import failed:', error);
|
||||
alert('Failed to import OPML file. Please try again.');
|
||||
}
|
||||
}
|
||||
// Clear the input so the same file can be selected again
|
||||
e.target.value = '';
|
||||
});
|
||||
|
||||
// User menu handlers
|
||||
userMenuButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
userMenuDropdown.classList.toggle('show');
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.user-menu')) {
|
||||
userMenuDropdown.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
userMenuDropdown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
document.getElementById('importOpmlButton').addEventListener('click', () => {
|
||||
opmlFileInput.click();
|
||||
userMenuDropdown.classList.remove('show');
|
||||
});
|
||||
|
||||
document.getElementById('logoutButton').addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await fetch('/logout', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize feeds
|
||||
handleFeeds();
|
||||
});
|
||||
|
||||
// Fetch and display feeds
|
||||
@ -348,46 +425,6 @@ async function handleFeeds() {
|
||||
}
|
||||
}
|
||||
|
||||
// Load feeds when page loads
|
||||
document.addEventListener('DOMContentLoaded', handleFeeds);
|
||||
|
||||
// User menu functionality
|
||||
const userMenuButton = document.getElementById('userMenuButton');
|
||||
const userMenuDropdown = document.getElementById('userMenuDropdown');
|
||||
|
||||
// Toggle menu on button click
|
||||
userMenuButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
userMenuDropdown.classList.toggle('show');
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.user-menu')) {
|
||||
userMenuDropdown.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent menu from closing when clicking inside it
|
||||
userMenuDropdown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Logout functionality
|
||||
document.getElementById('logoutButton').addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await fetch('/logout', {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Modal functionality
|
||||
const modal = document.getElementById('addFeedModal');
|
||||
const addFeedButton = document.getElementById('addFeedButton');
|
||||
|
Loading…
Reference in New Issue
Block a user